sigma.require.js 347 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184
  1. ;(function(undefined) {
  2. 'use strict';
  3. var __instances = {};
  4. /**
  5. * This is the sigma instances constructor. One instance of sigma represent
  6. * one graph. It is possible to represent this grapĥ with several renderers
  7. * at the same time. By default, the default renderer (WebGL + Canvas
  8. * polyfill) will be used as the only renderer, with the container specified
  9. * in the configuration.
  10. *
  11. * @param {?*} conf The configuration of the instance. There are a lot of
  12. * different recognized forms to instantiate sigma, check
  13. * example files, documentation in this file and unit
  14. * tests to know more.
  15. * @return {sigma} The fresh new sigma instance.
  16. *
  17. * Instanciating sigma:
  18. * ********************
  19. * If no parameter is given to the constructor, the instance will be created
  20. * without any renderer or camera. It will just instantiate the graph, and
  21. * other modules will have to be instantiated through the public methods,
  22. * like "addRenderer" etc:
  23. *
  24. * > s0 = new sigma();
  25. * > s0.addRenderer({
  26. * > type: 'canvas',
  27. * > container: 'my-container-id'
  28. * > });
  29. *
  30. * In most of the cases, sigma will simply be used with the default renderer.
  31. * Then, since the only required parameter is the DOM container, there are
  32. * some simpler way to call the constructor. The four following calls do the
  33. * exact same things:
  34. *
  35. * > s1 = new sigma('my-container-id');
  36. * > s2 = new sigma(document.getElementById('my-container-id'));
  37. * > s3 = new sigma({
  38. * > container: document.getElementById('my-container-id')
  39. * > });
  40. * > s4 = new sigma({
  41. * > renderers: [{
  42. * > container: document.getElementById('my-container-id')
  43. * > }]
  44. * > });
  45. *
  46. * Recognized parameters:
  47. * **********************
  48. * Here is the exhaustive list of every accepted parameters, when calling the
  49. * constructor with to top level configuration object (fourth case in the
  50. * previous examples):
  51. *
  52. * {?string} id The id of the instance. It will be generated
  53. * automatically if not specified.
  54. * {?array} renderers An array containing objects describing renderers.
  55. * {?object} graph An object containing an array of nodes and an array
  56. * of edges, to avoid having to add them by hand later.
  57. * {?object} settings An object containing instance specific settings that
  58. * will override the default ones defined in the object
  59. * sigma.settings.
  60. */
  61. var sigma = function(conf) {
  62. // Local variables:
  63. // ****************
  64. var i,
  65. l,
  66. a,
  67. c,
  68. o,
  69. id;
  70. sigma.classes.dispatcher.extend(this);
  71. // Private attributes:
  72. // *******************
  73. var _self = this,
  74. _conf = conf || {};
  75. // Little shortcut:
  76. // ****************
  77. // The configuration is supposed to have a list of the configuration
  78. // objects for each renderer.
  79. // - If there are no configuration at all, then nothing is done.
  80. // - If there are no renderer list, the given configuration object will be
  81. // considered as describing the first and only renderer.
  82. // - If there are no renderer list nor "container" object, it will be
  83. // considered as the container itself (a DOM element).
  84. // - If the argument passed to sigma() is a string, it will be considered
  85. // as the ID of the DOM container.
  86. if (
  87. typeof _conf === 'string' ||
  88. _conf instanceof HTMLElement
  89. )
  90. _conf = {
  91. renderers: [_conf]
  92. };
  93. else if (Object.prototype.toString.call(_conf) === '[object Array]')
  94. _conf = {
  95. renderers: _conf
  96. };
  97. // Also check "renderer" and "container" keys:
  98. o = _conf.renderers || _conf.renderer || _conf.container;
  99. if (!_conf.renderers || _conf.renderers.length === 0)
  100. if (
  101. typeof o === 'string' ||
  102. o instanceof HTMLElement ||
  103. (typeof o === 'object' && 'container' in o)
  104. )
  105. _conf.renderers = [o];
  106. // Recense the instance:
  107. if (_conf.id) {
  108. if (__instances[_conf.id])
  109. throw 'sigma: Instance "' + _conf.id + '" already exists.';
  110. Object.defineProperty(this, 'id', {
  111. value: _conf.id
  112. });
  113. } else {
  114. id = 0;
  115. while (__instances[id])
  116. id++;
  117. Object.defineProperty(this, 'id', {
  118. value: '' + id
  119. });
  120. }
  121. __instances[this.id] = this;
  122. // Initialize settings function:
  123. this.settings = new sigma.classes.configurable(
  124. sigma.settings,
  125. _conf.settings || {}
  126. );
  127. // Initialize locked attributes:
  128. Object.defineProperty(this, 'graph', {
  129. value: new sigma.classes.graph(this.settings),
  130. configurable: true
  131. });
  132. Object.defineProperty(this, 'middlewares', {
  133. value: [],
  134. configurable: true
  135. });
  136. Object.defineProperty(this, 'cameras', {
  137. value: {},
  138. configurable: true
  139. });
  140. Object.defineProperty(this, 'renderers', {
  141. value: {},
  142. configurable: true
  143. });
  144. Object.defineProperty(this, 'renderersPerCamera', {
  145. value: {},
  146. configurable: true
  147. });
  148. Object.defineProperty(this, 'cameraFrames', {
  149. value: {},
  150. configurable: true
  151. });
  152. Object.defineProperty(this, 'camera', {
  153. get: function() {
  154. return this.cameras[0];
  155. }
  156. });
  157. Object.defineProperty(this, 'events', {
  158. value: [
  159. 'click',
  160. 'rightClick',
  161. 'clickStage',
  162. 'doubleClickStage',
  163. 'rightClickStage',
  164. 'clickNode',
  165. 'clickNodes',
  166. 'doubleClickNode',
  167. 'doubleClickNodes',
  168. 'rightClickNode',
  169. 'rightClickNodes',
  170. 'overNode',
  171. 'overNodes',
  172. 'outNode',
  173. 'outNodes',
  174. 'downNode',
  175. 'downNodes',
  176. 'upNode',
  177. 'upNodes'
  178. ],
  179. configurable: true
  180. });
  181. // Add a custom handler, to redispatch events from renderers:
  182. this._handler = (function(e) {
  183. var k,
  184. data = {};
  185. for (k in e.data)
  186. data[k] = e.data[k];
  187. data.renderer = e.target;
  188. this.dispatchEvent(e.type, data);
  189. }).bind(this);
  190. // Initialize renderers:
  191. a = _conf.renderers || [];
  192. for (i = 0, l = a.length; i < l; i++)
  193. this.addRenderer(a[i]);
  194. // Initialize middlewares:
  195. a = _conf.middlewares || [];
  196. for (i = 0, l = a.length; i < l; i++)
  197. this.middlewares.push(
  198. typeof a[i] === 'string' ?
  199. sigma.middlewares[a[i]] :
  200. a[i]
  201. );
  202. // Check if there is already a graph to fill in:
  203. if (typeof _conf.graph === 'object' && _conf.graph) {
  204. this.graph.read(_conf.graph);
  205. // If a graph is given to the to the instance, the "refresh" method is
  206. // directly called:
  207. this.refresh();
  208. }
  209. // Deal with resize:
  210. window.addEventListener('resize', function() {
  211. if (_self.settings)
  212. _self.refresh();
  213. });
  214. };
  215. /**
  216. * This methods will instantiate and reference a new camera. If no id is
  217. * specified, then an automatic id will be generated.
  218. *
  219. * @param {?string} id Eventually the camera id.
  220. * @return {sigma.classes.camera} The fresh new camera instance.
  221. */
  222. sigma.prototype.addCamera = function(id) {
  223. var self = this,
  224. camera;
  225. if (!arguments.length) {
  226. id = 0;
  227. while (this.cameras['' + id])
  228. id++;
  229. id = '' + id;
  230. }
  231. if (this.cameras[id])
  232. throw 'sigma.addCamera: The camera "' + id + '" already exists.';
  233. camera = new sigma.classes.camera(id, this.graph, this.settings);
  234. this.cameras[id] = camera;
  235. // Add a quadtree to the camera:
  236. camera.quadtree = new sigma.classes.quad();
  237. // Add an edgequadtree to the camera:
  238. if (sigma.classes.edgequad !== undefined) {
  239. camera.edgequadtree = new sigma.classes.edgequad();
  240. }
  241. camera.bind('coordinatesUpdated', function(e) {
  242. self.renderCamera(camera, camera.isAnimated);
  243. });
  244. this.renderersPerCamera[id] = [];
  245. return camera;
  246. };
  247. /**
  248. * This method kills a camera, and every renderer attached to it.
  249. *
  250. * @param {string|camera} v The camera to kill or its ID.
  251. * @return {sigma} Returns the instance.
  252. */
  253. sigma.prototype.killCamera = function(v) {
  254. v = typeof v === 'string' ? this.cameras[v] : v;
  255. if (!v)
  256. throw 'sigma.killCamera: The camera is undefined.';
  257. var i,
  258. l,
  259. a = this.renderersPerCamera[v.id];
  260. for (l = a.length, i = l - 1; i >= 0; i--)
  261. this.killRenderer(a[i]);
  262. delete this.renderersPerCamera[v.id];
  263. delete this.cameraFrames[v.id];
  264. delete this.cameras[v.id];
  265. if (v.kill)
  266. v.kill();
  267. return this;
  268. };
  269. /**
  270. * This methods will instantiate and reference a new renderer. The "type"
  271. * argument can be the constructor or its name in the "sigma.renderers"
  272. * package. If no type is specified, then "sigma.renderers.def" will be used.
  273. * If no id is specified, then an automatic id will be generated.
  274. *
  275. * @param {?object} options Eventually some options to give to the renderer
  276. * constructor.
  277. * @return {renderer} The fresh new renderer instance.
  278. *
  279. * Recognized parameters:
  280. * **********************
  281. * Here is the exhaustive list of every accepted parameters in the "options"
  282. * object:
  283. *
  284. * {?string} id Eventually the renderer id.
  285. * {?(function|string)} type Eventually the renderer constructor or its
  286. * name in the "sigma.renderers" package.
  287. * {?(camera|string)} camera Eventually the renderer camera or its
  288. * id.
  289. */
  290. sigma.prototype.addRenderer = function(options) {
  291. var id,
  292. fn,
  293. camera,
  294. renderer,
  295. o = options || {};
  296. // Polymorphism:
  297. if (typeof o === 'string')
  298. o = {
  299. container: document.getElementById(o)
  300. };
  301. else if (o instanceof HTMLElement)
  302. o = {
  303. container: o
  304. };
  305. // If the container still is a string, we get it by id
  306. if (typeof o.container === 'string')
  307. o.container = document.getElementById(o.container);
  308. // Reference the new renderer:
  309. if (!('id' in o)) {
  310. id = 0;
  311. while (this.renderers['' + id])
  312. id++;
  313. id = '' + id;
  314. } else
  315. id = o.id;
  316. if (this.renderers[id])
  317. throw 'sigma.addRenderer: The renderer "' + id + '" already exists.';
  318. // Find the good constructor:
  319. fn = typeof o.type === 'function' ? o.type : sigma.renderers[o.type];
  320. fn = fn || sigma.renderers.def;
  321. // Find the good camera:
  322. camera = 'camera' in o ?
  323. (
  324. o.camera instanceof sigma.classes.camera ?
  325. o.camera :
  326. this.cameras[o.camera] || this.addCamera(o.camera)
  327. ) :
  328. this.addCamera();
  329. if (this.cameras[camera.id] !== camera)
  330. throw 'sigma.addRenderer: The camera is not properly referenced.';
  331. // Instantiate:
  332. renderer = new fn(this.graph, camera, this.settings, o);
  333. this.renderers[id] = renderer;
  334. Object.defineProperty(renderer, 'id', {
  335. value: id
  336. });
  337. // Bind events:
  338. if (renderer.bind)
  339. renderer.bind(
  340. [
  341. 'click',
  342. 'rightClick',
  343. 'clickStage',
  344. 'doubleClickStage',
  345. 'rightClickStage',
  346. 'clickNode',
  347. 'clickNodes',
  348. 'clickEdge',
  349. 'clickEdges',
  350. 'doubleClickNode',
  351. 'doubleClickNodes',
  352. 'doubleClickEdge',
  353. 'doubleClickEdges',
  354. 'rightClickNode',
  355. 'rightClickNodes',
  356. 'rightClickEdge',
  357. 'rightClickEdges',
  358. 'overNode',
  359. 'overNodes',
  360. 'overEdge',
  361. 'overEdges',
  362. 'outNode',
  363. 'outNodes',
  364. 'outEdge',
  365. 'outEdges',
  366. 'downNode',
  367. 'downNodes',
  368. 'downEdge',
  369. 'downEdges',
  370. 'upNode',
  371. 'upNodes',
  372. 'upEdge',
  373. 'upEdges'
  374. ],
  375. this._handler
  376. );
  377. // Reference the renderer by its camera:
  378. this.renderersPerCamera[camera.id].push(renderer);
  379. return renderer;
  380. };
  381. /**
  382. * This method kills a renderer.
  383. *
  384. * @param {string|renderer} v The renderer to kill or its ID.
  385. * @return {sigma} Returns the instance.
  386. */
  387. sigma.prototype.killRenderer = function(v) {
  388. v = typeof v === 'string' ? this.renderers[v] : v;
  389. if (!v)
  390. throw 'sigma.killRenderer: The renderer is undefined.';
  391. var a = this.renderersPerCamera[v.camera.id],
  392. i = a.indexOf(v);
  393. if (i >= 0)
  394. a.splice(i, 1);
  395. if (v.kill)
  396. v.kill();
  397. delete this.renderers[v.id];
  398. return this;
  399. };
  400. /**
  401. * This method calls the "render" method of each renderer, with the same
  402. * arguments than the "render" method, but will also check if the renderer
  403. * has a "process" method, and call it if it exists.
  404. *
  405. * It is useful for quadtrees or WebGL processing, for instance.
  406. *
  407. * @param {?object} options Eventually some options to give to the refresh
  408. * method.
  409. * @return {sigma} Returns the instance itself.
  410. *
  411. * Recognized parameters:
  412. * **********************
  413. * Here is the exhaustive list of every accepted parameters in the "options"
  414. * object:
  415. *
  416. * {?boolean} skipIndexation A flag specifying wether or not the refresh
  417. * function should reindex the graph in the
  418. * quadtrees or not (default: false).
  419. */
  420. sigma.prototype.refresh = function(options) {
  421. var i,
  422. l,
  423. k,
  424. a,
  425. c,
  426. bounds,
  427. prefix = 0;
  428. options = options || {};
  429. // Call each middleware:
  430. a = this.middlewares || [];
  431. for (i = 0, l = a.length; i < l; i++)
  432. a[i].call(
  433. this,
  434. (i === 0) ? '' : 'tmp' + prefix + ':',
  435. (i === l - 1) ? 'ready:' : ('tmp' + (++prefix) + ':')
  436. );
  437. // Then, for each camera, call the "rescale" middleware, unless the
  438. // settings specify not to:
  439. for (k in this.cameras) {
  440. c = this.cameras[k];
  441. if (
  442. c.settings('autoRescale') &&
  443. this.renderersPerCamera[c.id] &&
  444. this.renderersPerCamera[c.id].length
  445. )
  446. sigma.middlewares.rescale.call(
  447. this,
  448. a.length ? 'ready:' : '',
  449. c.readPrefix,
  450. {
  451. width: this.renderersPerCamera[c.id][0].width,
  452. height: this.renderersPerCamera[c.id][0].height
  453. }
  454. );
  455. else
  456. sigma.middlewares.copy.call(
  457. this,
  458. a.length ? 'ready:' : '',
  459. c.readPrefix
  460. );
  461. if (!options.skipIndexation) {
  462. // Find graph boundaries:
  463. bounds = sigma.utils.getBoundaries(
  464. this.graph,
  465. c.readPrefix
  466. );
  467. // Refresh quadtree:
  468. c.quadtree.index(this.graph.nodes(), {
  469. prefix: c.readPrefix,
  470. bounds: {
  471. x: bounds.minX,
  472. y: bounds.minY,
  473. width: bounds.maxX - bounds.minX,
  474. height: bounds.maxY - bounds.minY
  475. }
  476. });
  477. // Refresh edgequadtree:
  478. if (
  479. c.edgequadtree !== undefined &&
  480. c.settings('drawEdges') &&
  481. c.settings('enableEdgeHovering')
  482. ) {
  483. c.edgequadtree.index(this.graph, {
  484. prefix: c.readPrefix,
  485. bounds: {
  486. x: bounds.minX,
  487. y: bounds.minY,
  488. width: bounds.maxX - bounds.minX,
  489. height: bounds.maxY - bounds.minY
  490. }
  491. });
  492. }
  493. }
  494. }
  495. // Call each renderer:
  496. a = Object.keys(this.renderers);
  497. for (i = 0, l = a.length; i < l; i++)
  498. if (this.renderers[a[i]].process) {
  499. if (this.settings('skipErrors'))
  500. try {
  501. this.renderers[a[i]].process();
  502. } catch (e) {
  503. console.log(
  504. 'Warning: The renderer "' + a[i] + '" crashed on ".process()"'
  505. );
  506. }
  507. else
  508. this.renderers[a[i]].process();
  509. }
  510. this.render();
  511. return this;
  512. };
  513. /**
  514. * This method calls the "render" method of each renderer.
  515. *
  516. * @return {sigma} Returns the instance itself.
  517. */
  518. sigma.prototype.render = function() {
  519. var i,
  520. l,
  521. a,
  522. prefix = 0;
  523. // Call each renderer:
  524. a = Object.keys(this.renderers);
  525. for (i = 0, l = a.length; i < l; i++)
  526. if (this.settings('skipErrors'))
  527. try {
  528. this.renderers[a[i]].render();
  529. } catch (e) {
  530. if (this.settings('verbose'))
  531. console.log(
  532. 'Warning: The renderer "' + a[i] + '" crashed on ".render()"'
  533. );
  534. }
  535. else
  536. this.renderers[a[i]].render();
  537. return this;
  538. };
  539. /**
  540. * This method calls the "render" method of each renderer that is bound to
  541. * the specified camera. To improve the performances, if this method is
  542. * called too often, the number of effective renderings is limitated to one
  543. * per frame, unless you are using the "force" flag.
  544. *
  545. * @param {sigma.classes.camera} camera The camera to render.
  546. * @param {?boolean} force If true, will render the camera
  547. * directly.
  548. * @return {sigma} Returns the instance itself.
  549. */
  550. sigma.prototype.renderCamera = function(camera, force) {
  551. var i,
  552. l,
  553. a,
  554. self = this;
  555. if (force) {
  556. a = this.renderersPerCamera[camera.id];
  557. for (i = 0, l = a.length; i < l; i++)
  558. if (this.settings('skipErrors'))
  559. try {
  560. a[i].render();
  561. } catch (e) {
  562. if (this.settings('verbose'))
  563. console.log(
  564. 'Warning: The renderer "' + a[i].id + '" crashed on ".render()"'
  565. );
  566. }
  567. else
  568. a[i].render();
  569. } else {
  570. if (!this.cameraFrames[camera.id]) {
  571. a = this.renderersPerCamera[camera.id];
  572. for (i = 0, l = a.length; i < l; i++)
  573. if (this.settings('skipErrors'))
  574. try {
  575. a[i].render();
  576. } catch (e) {
  577. if (this.settings('verbose'))
  578. console.log(
  579. 'Warning: The renderer "' +
  580. a[i].id +
  581. '" crashed on ".render()"'
  582. );
  583. }
  584. else
  585. a[i].render();
  586. this.cameraFrames[camera.id] = requestAnimationFrame(function() {
  587. delete self.cameraFrames[camera.id];
  588. });
  589. }
  590. }
  591. return this;
  592. };
  593. /**
  594. * This method calls the "kill" method of each module and destroys any
  595. * reference from the instance.
  596. */
  597. sigma.prototype.kill = function() {
  598. var k;
  599. // Dispatching event
  600. this.dispatchEvent('kill');
  601. // Kill graph:
  602. this.graph.kill();
  603. // Kill middlewares:
  604. delete this.middlewares;
  605. // Kill each renderer:
  606. for (k in this.renderers)
  607. this.killRenderer(this.renderers[k]);
  608. // Kill each camera:
  609. for (k in this.cameras)
  610. this.killCamera(this.cameras[k]);
  611. delete this.renderers;
  612. delete this.cameras;
  613. // Kill everything else:
  614. for (k in this)
  615. if (this.hasOwnProperty(k))
  616. delete this[k];
  617. delete __instances[this.id];
  618. };
  619. /**
  620. * Returns a clone of the instances object or a specific running instance.
  621. *
  622. * @param {?string} id Eventually an instance ID.
  623. * @return {object} The related instance or a clone of the instances
  624. * object.
  625. */
  626. sigma.instances = function(id) {
  627. return arguments.length ?
  628. __instances[id] :
  629. sigma.utils.extend({}, __instances);
  630. };
  631. /**
  632. * The current version of sigma:
  633. */
  634. sigma.version = '1.0.3';
  635. /**
  636. * EXPORT:
  637. * *******
  638. */
  639. if (typeof this.sigma !== 'undefined')
  640. throw 'An object called sigma is already in the global scope.';
  641. this.sigma = sigma;
  642. }).call(this);
  643. /**
  644. * conrad.js is a tiny JavaScript jobs scheduler,
  645. *
  646. * Version: 0.1.0
  647. * Sources: http://github.com/jacomyal/conrad.js
  648. * Doc: http://github.com/jacomyal/conrad.js#readme
  649. *
  650. * License:
  651. * --------
  652. * Copyright © 2013 Alexis Jacomy, Sciences-Po médialab
  653. *
  654. * Permission is hereby granted, free of charge, to any person obtaining a copy
  655. * of this software and associated documentation files (the "Software"), to
  656. * deal in the Software without restriction, including without limitation the
  657. * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  658. * sell copies of the Software, and to permit persons to whom the Software is
  659. * furnished to do so, subject to the following conditions:
  660. *
  661. * The above copyright notice and this permission notice shall be included in
  662. * all copies or substantial portions of the Software.
  663. *
  664. * The Software is provided "as is", without warranty of any kind, express or
  665. * implied, including but not limited to the warranties of merchantability,
  666. * fitness for a particular purpose and noninfringement. In no event shall the
  667. * authors or copyright holders be liable for any claim, damages or other
  668. * liability, whether in an action of contract, tort or otherwise, arising
  669. * from, out of or in connection with the software or the use or other dealings
  670. * in the Software.
  671. */
  672. (function(global) {
  673. 'use strict';
  674. // Check that conrad.js has not been loaded yet:
  675. if (global.conrad)
  676. throw new Error('conrad already exists');
  677. /**
  678. * PRIVATE VARIABLES:
  679. * ******************
  680. */
  681. /**
  682. * A flag indicating whether conrad is running or not.
  683. *
  684. * @type {Number}
  685. */
  686. var _lastFrameTime;
  687. /**
  688. * A flag indicating whether conrad is running or not.
  689. *
  690. * @type {Boolean}
  691. */
  692. var _isRunning = false;
  693. /**
  694. * The hash of registered jobs. Each job must at least have a unique ID
  695. * under the key "id" and a function under the key "job". This hash
  696. * contains each running job and each waiting job.
  697. *
  698. * @type {Object}
  699. */
  700. var _jobs = {};
  701. /**
  702. * The hash of currently running jobs.
  703. *
  704. * @type {Object}
  705. */
  706. var _runningJobs = {};
  707. /**
  708. * The array of currently running jobs, sorted by priority.
  709. *
  710. * @type {Array}
  711. */
  712. var _sortedByPriorityJobs = [];
  713. /**
  714. * The array of currently waiting jobs.
  715. *
  716. * @type {Object}
  717. */
  718. var _waitingJobs = {};
  719. /**
  720. * The array of finished jobs. They are stored in an array, since two jobs
  721. * with the same "id" can happen at two different times.
  722. *
  723. * @type {Array}
  724. */
  725. var _doneJobs = [];
  726. /**
  727. * A dirty flag to keep conrad from starting: Indeed, when addJob() is called
  728. * with several jobs, conrad must be started only at the end. This flag keeps
  729. * me from duplicating the code that effectively adds a job.
  730. *
  731. * @type {Boolean}
  732. */
  733. var _noStart = false;
  734. /**
  735. * An hash containing some global settings about how conrad.js should
  736. * behave.
  737. *
  738. * @type {Object}
  739. */
  740. var _parameters = {
  741. frameDuration: 20,
  742. history: true
  743. };
  744. /**
  745. * This object contains every handlers bound to conrad events. It does not
  746. * requirea any DOM implementation, since the events are all JavaScript.
  747. *
  748. * @type {Object}
  749. */
  750. var _handlers = Object.create(null);
  751. /**
  752. * PRIVATE FUNCTIONS:
  753. * ******************
  754. */
  755. /**
  756. * Will execute the handler everytime that the indicated event (or the
  757. * indicated events) will be triggered.
  758. *
  759. * @param {string|array|object} events The name of the event (or the events
  760. * separated by spaces).
  761. * @param {function(Object)} handler The handler to bind.
  762. * @return {Object} Returns conrad.
  763. */
  764. function _bind(events, handler) {
  765. var i,
  766. i_end,
  767. event,
  768. eArray;
  769. if (!arguments.length)
  770. return;
  771. else if (
  772. arguments.length === 1 &&
  773. Object(arguments[0]) === arguments[0]
  774. )
  775. for (events in arguments[0])
  776. _bind(events, arguments[0][events]);
  777. else if (arguments.length > 1) {
  778. eArray =
  779. Array.isArray(events) ?
  780. events :
  781. events.split(/ /);
  782. for (i = 0, i_end = eArray.length; i !== i_end; i += 1) {
  783. event = eArray[i];
  784. if (!_handlers[event])
  785. _handlers[event] = [];
  786. // Using an object instead of directly the handler will make possible
  787. // later to add flags
  788. _handlers[event].push({
  789. handler: handler
  790. });
  791. }
  792. }
  793. }
  794. /**
  795. * Removes the handler from a specified event (or specified events).
  796. *
  797. * @param {?string} events The name of the event (or the events
  798. * separated by spaces). If undefined,
  799. * then all handlers are removed.
  800. * @param {?function(Object)} handler The handler to unbind. If undefined,
  801. * each handler bound to the event or the
  802. * events will be removed.
  803. * @return {Object} Returns conrad.
  804. */
  805. function _unbind(events, handler) {
  806. var i,
  807. i_end,
  808. j,
  809. j_end,
  810. a,
  811. event,
  812. eArray = Array.isArray(events) ?
  813. events :
  814. events.split(/ /);
  815. if (!arguments.length)
  816. _handlers = Object.create(null);
  817. else if (handler) {
  818. for (i = 0, i_end = eArray.length; i !== i_end; i += 1) {
  819. event = eArray[i];
  820. if (_handlers[event]) {
  821. a = [];
  822. for (j = 0, j_end = _handlers[event].length; j !== j_end; j += 1)
  823. if (_handlers[event][j].handler !== handler)
  824. a.push(_handlers[event][j]);
  825. _handlers[event] = a;
  826. }
  827. if (_handlers[event] && _handlers[event].length === 0)
  828. delete _handlers[event];
  829. }
  830. } else
  831. for (i = 0, i_end = eArray.length; i !== i_end; i += 1)
  832. delete _handlers[eArray[i]];
  833. }
  834. /**
  835. * Executes each handler bound to the event.
  836. *
  837. * @param {string} events The name of the event (or the events separated
  838. * by spaces).
  839. * @param {?Object} data The content of the event (optional).
  840. * @return {Object} Returns conrad.
  841. */
  842. function _dispatch(events, data) {
  843. var i,
  844. j,
  845. i_end,
  846. j_end,
  847. event,
  848. eventName,
  849. eArray = Array.isArray(events) ?
  850. events :
  851. events.split(/ /);
  852. data = data === undefined ? {} : data;
  853. for (i = 0, i_end = eArray.length; i !== i_end; i += 1) {
  854. eventName = eArray[i];
  855. if (_handlers[eventName]) {
  856. event = {
  857. type: eventName,
  858. data: data || {}
  859. };
  860. for (j = 0, j_end = _handlers[eventName].length; j !== j_end; j += 1)
  861. try {
  862. _handlers[eventName][j].handler(event);
  863. } catch (e) {}
  864. }
  865. }
  866. }
  867. /**
  868. * Executes the most prioritary job once, and deals with filling the stats
  869. * (done, time, averageTime, currentTime, etc...).
  870. *
  871. * @return {?Object} Returns the job object if it has to be killed, null else.
  872. */
  873. function _executeFirstJob() {
  874. var i,
  875. l,
  876. test,
  877. kill,
  878. pushed = false,
  879. time = __dateNow(),
  880. job = _sortedByPriorityJobs.shift();
  881. // Execute the job and look at the result:
  882. test = job.job();
  883. // Deal with stats:
  884. time = __dateNow() - time;
  885. job.done++;
  886. job.time += time;
  887. job.currentTime += time;
  888. job.weightTime = job.currentTime / (job.weight || 1);
  889. job.averageTime = job.time / job.done;
  890. // Check if the job has to be killed:
  891. kill = job.count ? (job.count <= job.done) : !test;
  892. // Reset priorities:
  893. if (!kill) {
  894. for (i = 0, l = _sortedByPriorityJobs.length; i < l; i++)
  895. if (_sortedByPriorityJobs[i].weightTime > job.weightTime) {
  896. _sortedByPriorityJobs.splice(i, 0, job);
  897. pushed = true;
  898. break;
  899. }
  900. if (!pushed)
  901. _sortedByPriorityJobs.push(job);
  902. }
  903. return kill ? job : null;
  904. }
  905. /**
  906. * Activates a job, by adding it to the _runningJobs object and the
  907. * _sortedByPriorityJobs array. It also initializes its currentTime value.
  908. *
  909. * @param {Object} job The job to activate.
  910. */
  911. function _activateJob(job) {
  912. var l = _sortedByPriorityJobs.length;
  913. // Add the job to the running jobs:
  914. _runningJobs[job.id] = job;
  915. job.status = 'running';
  916. // Add the job to the priorities:
  917. if (l) {
  918. job.weightTime = _sortedByPriorityJobs[l - 1].weightTime;
  919. job.currentTime = job.weightTime * (job.weight || 1);
  920. }
  921. // Initialize the job and dispatch:
  922. job.startTime = __dateNow();
  923. _dispatch('jobStarted', __clone(job));
  924. _sortedByPriorityJobs.push(job);
  925. }
  926. /**
  927. * The main loop of conrad.js:
  928. * . It executes job such that they all occupate the same processing time.
  929. * . It stops jobs that do not need to be executed anymore.
  930. * . It triggers callbacks when it is relevant.
  931. * . It starts waiting jobs when they need to be started.
  932. * . It injects frames to keep a constant frapes per second ratio.
  933. * . It stops itself when there are no more jobs to execute.
  934. */
  935. function _loop() {
  936. var k,
  937. o,
  938. l,
  939. job,
  940. time,
  941. deadJob;
  942. // Deal with the newly added jobs (the _jobs object):
  943. for (k in _jobs) {
  944. job = _jobs[k];
  945. if (job.after)
  946. _waitingJobs[k] = job;
  947. else
  948. _activateJob(job);
  949. delete _jobs[k];
  950. }
  951. // Set the _isRunning flag to false if there are no running job:
  952. _isRunning = !!_sortedByPriorityJobs.length;
  953. // Deal with the running jobs (the _runningJobs object):
  954. while (
  955. _sortedByPriorityJobs.length &&
  956. __dateNow() - _lastFrameTime < _parameters.frameDuration
  957. ) {
  958. deadJob = _executeFirstJob();
  959. // Deal with the case where the job has ended:
  960. if (deadJob) {
  961. _killJob(deadJob.id);
  962. // Check for waiting jobs:
  963. for (k in _waitingJobs)
  964. if (_waitingJobs[k].after === deadJob.id) {
  965. _activateJob(_waitingJobs[k]);
  966. delete _waitingJobs[k];
  967. }
  968. }
  969. }
  970. // Check if conrad still has jobs to deal with, and kill it if not:
  971. if (_isRunning) {
  972. // Update the _lastFrameTime:
  973. _lastFrameTime = __dateNow();
  974. _dispatch('enterFrame');
  975. setTimeout(_loop, 0);
  976. } else
  977. _dispatch('stop');
  978. }
  979. /**
  980. * Adds one or more jobs, and starts the loop if no job was running before. A
  981. * job is at least a unique string "id" and a function, and there are some
  982. * parameters that you can specify for each job to modify the way conrad will
  983. * execute it. If a job is added with the "id" of another job that is waiting
  984. * or still running, an error will be thrown.
  985. *
  986. * When a job is added, it is referenced in the _jobs object, by its id.
  987. * Then, if it has to be executed right now, it will be also referenced in
  988. * the _runningJobs object. If it has to wait, then it will be added into the
  989. * _waitingJobs object, until it can start.
  990. *
  991. * Keep reading this documentation to see how to call this method.
  992. *
  993. * @return {Object} Returns conrad.
  994. *
  995. * Adding one job:
  996. * ***************
  997. * Basically, a job is defined by its string id and a function (the job). It
  998. * is also possible to add some parameters:
  999. *
  1000. * > conrad.addJob('myJobId', myJobFunction);
  1001. * > conrad.addJob('myJobId', {
  1002. * > job: myJobFunction,
  1003. * > someParameter: someValue
  1004. * > });
  1005. * > conrad.addJob({
  1006. * > id: 'myJobId',
  1007. * > job: myJobFunction,
  1008. * > someParameter: someValue
  1009. * > });
  1010. *
  1011. * Adding several jobs:
  1012. * ********************
  1013. * When adding several jobs at the same time, it is possible to specify
  1014. * parameters for each one individually or for all:
  1015. *
  1016. * > conrad.addJob([
  1017. * > {
  1018. * > id: 'myJobId1',
  1019. * > job: myJobFunction1,
  1020. * > someParameter1: someValue1
  1021. * > },
  1022. * > {
  1023. * > id: 'myJobId2',
  1024. * > job: myJobFunction2,
  1025. * > someParameter2: someValue2
  1026. * > }
  1027. * > ], {
  1028. * > someCommonParameter: someCommonValue
  1029. * > });
  1030. * > conrad.addJob({
  1031. * > myJobId1: {,
  1032. * > job: myJobFunction1,
  1033. * > someParameter1: someValue1
  1034. * > },
  1035. * > myJobId2: {,
  1036. * > job: myJobFunction2,
  1037. * > someParameter2: someValue2
  1038. * > }
  1039. * > }, {
  1040. * > someCommonParameter: someCommonValue
  1041. * > });
  1042. * > conrad.addJob({
  1043. * > myJobId1: myJobFunction1,
  1044. * > myJobId2: myJobFunction2
  1045. * > }, {
  1046. * > someCommonParameter: someCommonValue
  1047. * > });
  1048. *
  1049. * Recognized parameters:
  1050. * **********************
  1051. * Here is the exhaustive list of every accepted parameters:
  1052. *
  1053. * {?Function} end A callback to execute when the job is ended. It is
  1054. * not executed if the job is killed instead of ended
  1055. * "naturally".
  1056. * {?Integer} count The number of time the job has to be executed.
  1057. * {?Number} weight If specified, the job will be executed as it was
  1058. * added "weight" times.
  1059. * {?String} after The id of another job (eventually not added yet).
  1060. * If specified, this job will start only when the
  1061. * specified "after" job is ended.
  1062. */
  1063. function _addJob(v1, v2) {
  1064. var i,
  1065. l,
  1066. o;
  1067. // Array of jobs:
  1068. if (Array.isArray(v1)) {
  1069. // Keep conrad to start until the last job is added:
  1070. _noStart = true;
  1071. for (i = 0, l = v1.length; i < l; i++)
  1072. _addJob(v1[i].id, __extend(v1[i], v2));
  1073. _noStart = false;
  1074. if (!_isRunning) {
  1075. // Update the _lastFrameTime:
  1076. _lastFrameTime = __dateNow();
  1077. _dispatch('start');
  1078. _loop();
  1079. }
  1080. } else if (typeof v1 === 'object') {
  1081. // One job (object):
  1082. if (typeof v1.id === 'string')
  1083. _addJob(v1.id, v1);
  1084. // Hash of jobs:
  1085. else {
  1086. // Keep conrad to start until the last job is added:
  1087. _noStart = true;
  1088. for (i in v1)
  1089. if (typeof v1[i] === 'function')
  1090. _addJob(i, __extend({
  1091. job: v1[i]
  1092. }, v2));
  1093. else
  1094. _addJob(i, __extend(v1[i], v2));
  1095. _noStart = false;
  1096. if (!_isRunning) {
  1097. // Update the _lastFrameTime:
  1098. _lastFrameTime = __dateNow();
  1099. _dispatch('start');
  1100. _loop();
  1101. }
  1102. }
  1103. // One job (string, *):
  1104. } else if (typeof v1 === 'string') {
  1105. if (_hasJob(v1))
  1106. throw new Error(
  1107. '[conrad.addJob] Job with id "' + v1 + '" already exists.'
  1108. );
  1109. // One job (string, function):
  1110. if (typeof v2 === 'function') {
  1111. o = {
  1112. id: v1,
  1113. done: 0,
  1114. time: 0,
  1115. status: 'waiting',
  1116. currentTime: 0,
  1117. averageTime: 0,
  1118. weightTime: 0,
  1119. job: v2
  1120. };
  1121. // One job (string, object):
  1122. } else if (typeof v2 === 'object') {
  1123. o = __extend(
  1124. {
  1125. id: v1,
  1126. done: 0,
  1127. time: 0,
  1128. status: 'waiting',
  1129. currentTime: 0,
  1130. averageTime: 0,
  1131. weightTime: 0
  1132. },
  1133. v2
  1134. );
  1135. // If none of those cases, throw an error:
  1136. } else
  1137. throw new Error('[conrad.addJob] Wrong arguments.');
  1138. // Effectively add the job:
  1139. _jobs[v1] = o;
  1140. _dispatch('jobAdded', __clone(o));
  1141. // Check if the loop has to be started:
  1142. if (!_isRunning && !_noStart) {
  1143. // Update the _lastFrameTime:
  1144. _lastFrameTime = __dateNow();
  1145. _dispatch('start');
  1146. _loop();
  1147. }
  1148. // If none of those cases, throw an error:
  1149. } else
  1150. throw new Error('[conrad.addJob] Wrong arguments.');
  1151. return this;
  1152. }
  1153. /**
  1154. * Kills one or more jobs, indicated by their ids. It is only possible to
  1155. * kill running jobs or waiting jobs. If you try to kill a job that does not
  1156. * exists or that is already killed, a warning will be thrown.
  1157. *
  1158. * @param {Array|String} v1 A string job id or an array of job ids.
  1159. * @return {Object} Returns conrad.
  1160. */
  1161. function _killJob(v1) {
  1162. var i,
  1163. l,
  1164. k,
  1165. a,
  1166. job,
  1167. found = false;
  1168. // Array of job ids:
  1169. if (Array.isArray(v1))
  1170. for (i = 0, l = v1.length; i < l; i++)
  1171. _killJob(v1[i]);
  1172. // One job's id:
  1173. else if (typeof v1 === 'string') {
  1174. a = [_runningJobs, _waitingJobs, _jobs];
  1175. // Remove the job from the hashes:
  1176. for (i = 0, l = a.length; i < l; i++)
  1177. if (v1 in a[i]) {
  1178. job = a[i][v1];
  1179. if (_parameters.history) {
  1180. job.status = 'done';
  1181. _doneJobs.push(job);
  1182. }
  1183. _dispatch('jobEnded', __clone(job));
  1184. delete a[i][v1];
  1185. if (typeof job.end === 'function')
  1186. job.end();
  1187. found = true;
  1188. }
  1189. // Remove the priorities array:
  1190. a = _sortedByPriorityJobs;
  1191. for (i = 0, l = a.length; i < l; i++)
  1192. if (a[i].id === v1) {
  1193. a.splice(i, 1);
  1194. break;
  1195. }
  1196. if (!found)
  1197. throw new Error('[conrad.killJob] Job "' + v1 + '" not found.');
  1198. // If none of those cases, throw an error:
  1199. } else
  1200. throw new Error('[conrad.killJob] Wrong arguments.');
  1201. return this;
  1202. }
  1203. /**
  1204. * Kills every running, waiting, and just added jobs.
  1205. *
  1206. * @return {Object} Returns conrad.
  1207. */
  1208. function _killAll() {
  1209. var k,
  1210. jobs = __extend(_jobs, _runningJobs, _waitingJobs);
  1211. // Take every jobs and push them into the _doneJobs object:
  1212. if (_parameters.history)
  1213. for (k in jobs) {
  1214. jobs[k].status = 'done';
  1215. _doneJobs.push(jobs[k]);
  1216. if (typeof jobs[k].end === 'function')
  1217. jobs[k].end();
  1218. }
  1219. // Reinitialize the different jobs lists:
  1220. _jobs = {};
  1221. _waitingJobs = {};
  1222. _runningJobs = {};
  1223. _sortedByPriorityJobs = [];
  1224. // In case some jobs are added right after the kill:
  1225. _isRunning = false;
  1226. return this;
  1227. }
  1228. /**
  1229. * Returns true if a job with the specified id is currently running or
  1230. * waiting, and false else.
  1231. *
  1232. * @param {String} id The id of the job.
  1233. * @return {?Object} Returns the job object if it exists.
  1234. */
  1235. function _hasJob(id) {
  1236. var job = _jobs[id] || _runningJobs[id] || _waitingJobs[id];
  1237. return job ? __extend(job) : null;
  1238. }
  1239. /**
  1240. * This method will set the setting specified by "v1" to the value specified
  1241. * by "v2" if both are given, and else return the current value of the
  1242. * settings "v1".
  1243. *
  1244. * @param {String} v1 The name of the property.
  1245. * @param {?*} v2 Eventually, a value to set to the specified
  1246. * property.
  1247. * @return {Object|*} Returns the specified settings value if "v2" is not
  1248. * given, and conrad else.
  1249. */
  1250. function _settings(v1, v2) {
  1251. var o;
  1252. if (typeof a1 === 'string' && arguments.length === 1)
  1253. return _parameters[a1];
  1254. else {
  1255. o = (typeof a1 === 'object' && arguments.length === 1) ?
  1256. a1 || {} :
  1257. {};
  1258. if (typeof a1 === 'string')
  1259. o[a1] = a2;
  1260. for (var k in o)
  1261. if (o[k] !== undefined)
  1262. _parameters[k] = o[k];
  1263. else
  1264. delete _parameters[k];
  1265. return this;
  1266. }
  1267. }
  1268. /**
  1269. * Returns true if conrad is currently running, and false else.
  1270. *
  1271. * @return {Boolean} Returns _isRunning.
  1272. */
  1273. function _getIsRunning() {
  1274. return _isRunning;
  1275. }
  1276. /**
  1277. * Unreference every jobs that are stored in the _doneJobs object. It will
  1278. * not be possible anymore to get stats about these jobs, but it will release
  1279. * the memory.
  1280. *
  1281. * @return {Object} Returns conrad.
  1282. */
  1283. function _clearHistory() {
  1284. _doneJobs = [];
  1285. return this;
  1286. }
  1287. /**
  1288. * Returns a snapshot of every data about jobs that wait to be started, are
  1289. * currently running or are done.
  1290. *
  1291. * It is possible to get only running, waiting or done jobs by giving
  1292. * "running", "waiting" or "done" as fist argument.
  1293. *
  1294. * It is also possible to get every job with a specified id by giving it as
  1295. * first argument. Also, using a RegExp instead of an id will return every
  1296. * jobs whose ids match the RegExp. And these two last use cases work as well
  1297. * by giving before "running", "waiting" or "done".
  1298. *
  1299. * @return {Array} The array of the matching jobs.
  1300. *
  1301. * Some call examples:
  1302. * *******************
  1303. * > conrad.getStats('running')
  1304. * > conrad.getStats('waiting')
  1305. * > conrad.getStats('done')
  1306. * > conrad.getStats('myJob')
  1307. * > conrad.getStats(/test/)
  1308. * > conrad.getStats('running', 'myRunningJob')
  1309. * > conrad.getStats('running', /test/)
  1310. */
  1311. function _getStats(v1, v2) {
  1312. var a,
  1313. k,
  1314. i,
  1315. l,
  1316. stats,
  1317. pattern,
  1318. isPatternString;
  1319. if (!arguments.length) {
  1320. stats = [];
  1321. for (k in _jobs)
  1322. stats.push(_jobs[k]);
  1323. for (k in _waitingJobs)
  1324. stats.push(_waitingJobs[k]);
  1325. for (k in _runningJobs)
  1326. stats.push(_runningJobs[k]);
  1327. stats = stats.concat(_doneJobs);
  1328. }
  1329. if (typeof v1 === 'string')
  1330. switch (v1) {
  1331. case 'waiting':
  1332. stats = __objectValues(_waitingJobs);
  1333. break;
  1334. case 'running':
  1335. stats = __objectValues(_runningJobs);
  1336. break;
  1337. case 'done':
  1338. stats = _doneJobs;
  1339. break;
  1340. default:
  1341. pattern = v1;
  1342. }
  1343. if (v1 instanceof RegExp)
  1344. pattern = v1;
  1345. if (!pattern && (typeof v2 === 'string' || v2 instanceof RegExp))
  1346. pattern = v2;
  1347. // Filter jobs if a pattern is given:
  1348. if (pattern) {
  1349. isPatternString = typeof pattern === 'string';
  1350. if (stats instanceof Array) {
  1351. a = stats;
  1352. } else if (typeof stats === 'object') {
  1353. a = [];
  1354. for (k in stats)
  1355. a = a.concat(stats[k]);
  1356. } else {
  1357. a = [];
  1358. for (k in _jobs)
  1359. a.push(_jobs[k]);
  1360. for (k in _waitingJobs)
  1361. a.push(_waitingJobs[k]);
  1362. for (k in _runningJobs)
  1363. a.push(_runningJobs[k]);
  1364. a = a.concat(_doneJobs);
  1365. }
  1366. stats = [];
  1367. for (i = 0, l = a.length; i < l; i++)
  1368. if (isPatternString ? a[i].id === pattern : a[i].id.match(pattern))
  1369. stats.push(a[i]);
  1370. }
  1371. return __clone(stats);
  1372. }
  1373. /**
  1374. * TOOLS FUNCTIONS:
  1375. * ****************
  1376. */
  1377. /**
  1378. * This function takes any number of objects as arguments, copies from each
  1379. * of these objects each pair key/value into a new object, and finally
  1380. * returns this object.
  1381. *
  1382. * The arguments are parsed from the last one to the first one, such that
  1383. * when two objects have keys in common, the "earliest" object wins.
  1384. *
  1385. * Example:
  1386. * ********
  1387. * > var o1 = {
  1388. * > a: 1,
  1389. * > b: 2,
  1390. * > c: '3'
  1391. * > },
  1392. * > o2 = {
  1393. * > c: '4',
  1394. * > d: [ 5 ]
  1395. * > };
  1396. * > __extend(o1, o2);
  1397. * > // Returns: {
  1398. * > // a: 1,
  1399. * > // b: 2,
  1400. * > // c: '3',
  1401. * > // d: [ 5 ]
  1402. * > // };
  1403. *
  1404. * @param {Object+} Any number of objects.
  1405. * @return {Object} The merged object.
  1406. */
  1407. function __extend() {
  1408. var i,
  1409. k,
  1410. res = {},
  1411. l = arguments.length;
  1412. for (i = l - 1; i >= 0; i--)
  1413. for (k in arguments[i])
  1414. res[k] = arguments[i][k];
  1415. return res;
  1416. }
  1417. /**
  1418. * This function simply clones an object. This object must contain only
  1419. * objects, arrays and immutable values. Since it is not public, it does not
  1420. * deal with cyclic references, DOM elements and instantiated objects - so
  1421. * use it carefully.
  1422. *
  1423. * @param {Object} The object to clone.
  1424. * @return {Object} The clone.
  1425. */
  1426. function __clone(item) {
  1427. var result, i, k, l;
  1428. if (!item)
  1429. return item;
  1430. if (Array.isArray(item)) {
  1431. result = [];
  1432. for (i = 0, l = item.length; i < l; i++)
  1433. result.push(__clone(item[i]));
  1434. } else if (typeof item === 'object') {
  1435. result = {};
  1436. for (i in item)
  1437. result[i] = __clone(item[i]);
  1438. } else
  1439. result = item;
  1440. return result;
  1441. }
  1442. /**
  1443. * Returns an array containing the values of an object.
  1444. *
  1445. * @param {Object} The object.
  1446. * @return {Array} The array of values.
  1447. */
  1448. function __objectValues(o) {
  1449. var k,
  1450. a = [];
  1451. for (k in o)
  1452. a.push(o[k]);
  1453. return a;
  1454. }
  1455. /**
  1456. * A short "Date.now()" polyfill.
  1457. *
  1458. * @return {Number} The current time (in ms).
  1459. */
  1460. function __dateNow() {
  1461. return Date.now ? Date.now() : new Date().getTime();
  1462. }
  1463. /**
  1464. * Polyfill for the Array.isArray function:
  1465. */
  1466. if (!Array.isArray)
  1467. Array.isArray = function(v) {
  1468. return Object.prototype.toString.call(v) === '[object Array]';
  1469. };
  1470. /**
  1471. * EXPORT PUBLIC API:
  1472. * ******************
  1473. */
  1474. var conrad = {
  1475. hasJob: _hasJob,
  1476. addJob: _addJob,
  1477. killJob: _killJob,
  1478. killAll: _killAll,
  1479. settings: _settings,
  1480. getStats: _getStats,
  1481. isRunning: _getIsRunning,
  1482. clearHistory: _clearHistory,
  1483. // Events management:
  1484. bind: _bind,
  1485. unbind: _unbind,
  1486. // Version:
  1487. version: '0.1.0'
  1488. };
  1489. if (typeof exports !== 'undefined') {
  1490. if (typeof module !== 'undefined' && module.exports)
  1491. exports = module.exports = conrad;
  1492. exports.conrad = conrad;
  1493. }
  1494. global.conrad = conrad;
  1495. })(this);
  1496. // Hardcoded export for the node.js version:
  1497. var sigma = this.sigma,
  1498. conrad = this.conrad;
  1499. sigma.conrad = conrad;
  1500. // Dirty polyfills to permit sigma usage in node
  1501. if (HTMLElement === undefined)
  1502. var HTMLElement = function() {};
  1503. if (window === undefined)
  1504. var window = {
  1505. addEventListener: function() {}
  1506. };
  1507. if (typeof exports !== 'undefined') {
  1508. if (typeof module !== 'undefined' && module.exports)
  1509. exports = module.exports = sigma;
  1510. exports.sigma = sigma;
  1511. }
  1512. ;(function(undefined) {
  1513. 'use strict';
  1514. if (typeof sigma === 'undefined')
  1515. throw 'sigma is not declared';
  1516. var _root = this;
  1517. // Initialize packages:
  1518. sigma.utils = sigma.utils || {};
  1519. /**
  1520. * MISC UTILS:
  1521. */
  1522. /**
  1523. * This function takes any number of objects as arguments, copies from each
  1524. * of these objects each pair key/value into a new object, and finally
  1525. * returns this object.
  1526. *
  1527. * The arguments are parsed from the last one to the first one, such that
  1528. * when several objects have keys in common, the "earliest" object wins.
  1529. *
  1530. * Example:
  1531. * ********
  1532. * > var o1 = {
  1533. * > a: 1,
  1534. * > b: 2,
  1535. * > c: '3'
  1536. * > },
  1537. * > o2 = {
  1538. * > c: '4',
  1539. * > d: [ 5 ]
  1540. * > };
  1541. * > sigma.utils.extend(o1, o2);
  1542. * > // Returns: {
  1543. * > // a: 1,
  1544. * > // b: 2,
  1545. * > // c: '3',
  1546. * > // d: [ 5 ]
  1547. * > // };
  1548. *
  1549. * @param {object+} Any number of objects.
  1550. * @return {object} The merged object.
  1551. */
  1552. sigma.utils.extend = function() {
  1553. var i,
  1554. k,
  1555. res = {},
  1556. l = arguments.length;
  1557. for (i = l - 1; i >= 0; i--)
  1558. for (k in arguments[i])
  1559. res[k] = arguments[i][k];
  1560. return res;
  1561. };
  1562. /**
  1563. * A short "Date.now()" polyfill.
  1564. *
  1565. * @return {Number} The current time (in ms).
  1566. */
  1567. sigma.utils.dateNow = function() {
  1568. return Date.now ? Date.now() : new Date().getTime();
  1569. };
  1570. /**
  1571. * Takes a package name as parameter and checks at each lebel if it exists,
  1572. * and if it does not, creates it.
  1573. *
  1574. * Example:
  1575. * ********
  1576. * > sigma.utils.pkg('a.b.c');
  1577. * > a.b.c;
  1578. * > // Object {};
  1579. * >
  1580. * > sigma.utils.pkg('a.b.d');
  1581. * > a.b;
  1582. * > // Object { c: {}, d: {} };
  1583. *
  1584. * @param {string} pkgName The name of the package to create/find.
  1585. * @return {object} The related package.
  1586. */
  1587. sigma.utils.pkg = function(pkgName) {
  1588. return (pkgName || '').split('.').reduce(function(context, objName) {
  1589. return (objName in context) ?
  1590. context[objName] :
  1591. (context[objName] = {});
  1592. }, _root);
  1593. };
  1594. /**
  1595. * Returns a unique incremental number ID.
  1596. *
  1597. * Example:
  1598. * ********
  1599. * > sigma.utils.id();
  1600. * > // 1;
  1601. * >
  1602. * > sigma.utils.id();
  1603. * > // 2;
  1604. * >
  1605. * > sigma.utils.id();
  1606. * > // 3;
  1607. *
  1608. * @param {string} pkgName The name of the package to create/find.
  1609. * @return {object} The related package.
  1610. */
  1611. sigma.utils.id = (function() {
  1612. var i = 0;
  1613. return function() {
  1614. return ++i;
  1615. };
  1616. })();
  1617. /**
  1618. * This function takes an hexa color (for instance "#ffcc00" or "#fc0") or a
  1619. * rgb / rgba color (like "rgb(255,255,12)" or "rgba(255,255,12,1)") and
  1620. * returns an integer equal to "r * 255 * 255 + g * 255 + b", to gain some
  1621. * memory in the data given to WebGL shaders.
  1622. *
  1623. * @param {string} val The hexa or rgba color.
  1624. * @return {number} The number value.
  1625. */
  1626. sigma.utils.floatColor = function(val) {
  1627. var result = [0, 0, 0];
  1628. if (val.match(/^#/)) {
  1629. val = (val || '').replace(/^#/, '');
  1630. result = (val.length === 3) ?
  1631. [
  1632. parseInt(val.charAt(0) + val.charAt(0), 16),
  1633. parseInt(val.charAt(1) + val.charAt(1), 16),
  1634. parseInt(val.charAt(2) + val.charAt(2), 16)
  1635. ] :
  1636. [
  1637. parseInt(val.charAt(0) + val.charAt(1), 16),
  1638. parseInt(val.charAt(2) + val.charAt(3), 16),
  1639. parseInt(val.charAt(4) + val.charAt(5), 16)
  1640. ];
  1641. } else if (val.match(/^ *rgba? *\(/)) {
  1642. val = val.match(
  1643. /^ *rgba? *\( *([0-9]*) *, *([0-9]*) *, *([0-9]*) *(,.*)?\) *$/
  1644. );
  1645. result = [
  1646. +val[1],
  1647. +val[2],
  1648. +val[3]
  1649. ];
  1650. }
  1651. return (
  1652. result[0] * 256 * 256 +
  1653. result[1] * 256 +
  1654. result[2]
  1655. );
  1656. };
  1657. /**
  1658. * Perform a zoom into a camera, with or without animation, to the
  1659. * coordinates indicated using a specified ratio.
  1660. *
  1661. * Recognized parameters:
  1662. * **********************
  1663. * Here is the exhaustive list of every accepted parameters in the animation
  1664. * object:
  1665. *
  1666. * {?number} duration An amount of time that means the duration of the
  1667. * animation. If this parameter doesn't exist the
  1668. * zoom will be performed without animation.
  1669. * {?function} onComplete A function to perform it after the animation. It
  1670. * will be performed even if there is no duration.
  1671. *
  1672. * @param {camera} The camera where perform the zoom.
  1673. * @param {x} The X coordiantion where the zoom goes.
  1674. * @param {y} The Y coordiantion where the zoom goes.
  1675. * @param {ratio} The ratio to apply it to the current camera ratio.
  1676. * @param {?animation} A dictionary with options for a possible animation.
  1677. */
  1678. sigma.utils.zoomTo = function(camera, x, y, ratio, animation) {
  1679. var settings = camera.settings,
  1680. count,
  1681. newRatio,
  1682. animationSettings,
  1683. coordinates;
  1684. // Create the newRatio dealing with min / max:
  1685. newRatio = Math.max(
  1686. settings('zoomMin'),
  1687. Math.min(
  1688. settings('zoomMax'),
  1689. camera.ratio * ratio
  1690. )
  1691. );
  1692. // Check that the new ratio is different from the initial one:
  1693. if (newRatio !== camera.ratio) {
  1694. // Create the coordinates variable:
  1695. ratio = newRatio / camera.ratio;
  1696. coordinates = {
  1697. x: x * (1 - ratio) + camera.x,
  1698. y: y * (1 - ratio) + camera.y,
  1699. ratio: newRatio
  1700. };
  1701. if (animation && animation.duration) {
  1702. // Complete the animation setings:
  1703. count = sigma.misc.animation.killAll(camera);
  1704. animation = sigma.utils.extend(
  1705. animation,
  1706. {
  1707. easing: count ? 'quadraticOut' : 'quadraticInOut'
  1708. }
  1709. );
  1710. sigma.misc.animation.camera(camera, coordinates, animation);
  1711. } else {
  1712. camera.goTo(coordinates);
  1713. if (animation && animation.onComplete)
  1714. animation.onComplete();
  1715. }
  1716. }
  1717. };
  1718. /**
  1719. * Return the control point coordinates for a quadratic bezier curve.
  1720. *
  1721. * @param {number} x1 The X coordinate of the start point.
  1722. * @param {number} y1 The Y coordinate of the start point.
  1723. * @param {number} x2 The X coordinate of the end point.
  1724. * @param {number} y2 The Y coordinate of the end point.
  1725. * @return {x,y} The control point coordinates.
  1726. */
  1727. sigma.utils.getQuadraticControlPoint = function(x1, y1, x2, y2) {
  1728. return {
  1729. x: (x1 + x2) / 2 + (y2 - y1) / 4,
  1730. y: (y1 + y2) / 2 + (x1 - x2) / 4
  1731. };
  1732. };
  1733. /**
  1734. * Compute the coordinates of the point positioned
  1735. * at length t in the quadratic bezier curve.
  1736. *
  1737. * @param {number} t In [0,1] the step percentage to reach
  1738. * the point in the curve from the context point.
  1739. * @param {number} x1 The X coordinate of the context point.
  1740. * @param {number} y1 The Y coordinate of the context point.
  1741. * @param {number} x2 The X coordinate of the ending point.
  1742. * @param {number} y2 The Y coordinate of the ending point.
  1743. * @param {number} xi The X coordinate of the control point.
  1744. * @param {number} yi The Y coordinate of the control point.
  1745. * @return {object} {x,y}.
  1746. */
  1747. sigma.utils.getPointOnQuadraticCurve = function(t, x1, y1, x2, y2, xi, yi) {
  1748. // http://stackoverflow.com/a/5634528
  1749. return {
  1750. x: Math.pow(1 - t, 2) * x1 + 2 * (1 - t) * t * xi + Math.pow(t, 2) * x2,
  1751. y: Math.pow(1 - t, 2) * y1 + 2 * (1 - t) * t * yi + Math.pow(t, 2) * y2
  1752. };
  1753. };
  1754. /**
  1755. * Compute the coordinates of the point positioned
  1756. * at length t in the cubic bezier curve.
  1757. *
  1758. * @param {number} t In [0,1] the step percentage to reach
  1759. * the point in the curve from the context point.
  1760. * @param {number} x1 The X coordinate of the context point.
  1761. * @param {number} y1 The Y coordinate of the context point.
  1762. * @param {number} x2 The X coordinate of the end point.
  1763. * @param {number} y2 The Y coordinate of the end point.
  1764. * @param {number} cx The X coordinate of the first control point.
  1765. * @param {number} cy The Y coordinate of the first control point.
  1766. * @param {number} dx The X coordinate of the second control point.
  1767. * @param {number} dy The Y coordinate of the second control point.
  1768. * @return {object} {x,y} The point at t.
  1769. */
  1770. sigma.utils.getPointOnBezierCurve =
  1771. function(t, x1, y1, x2, y2, cx, cy, dx, dy) {
  1772. // http://stackoverflow.com/a/15397596
  1773. // Blending functions:
  1774. var B0_t = Math.pow(1 - t, 3),
  1775. B1_t = 3 * t * Math.pow(1 - t, 2),
  1776. B2_t = 3 * Math.pow(t, 2) * (1 - t),
  1777. B3_t = Math.pow(t, 3);
  1778. return {
  1779. x: (B0_t * x1) + (B1_t * cx) + (B2_t * dx) + (B3_t * x2),
  1780. y: (B0_t * y1) + (B1_t * cy) + (B2_t * dy) + (B3_t * y2)
  1781. };
  1782. };
  1783. /**
  1784. * Return the coordinates of the two control points for a self loop (i.e.
  1785. * where the start point is also the end point) computed as a cubic bezier
  1786. * curve.
  1787. *
  1788. * @param {number} x The X coordinate of the node.
  1789. * @param {number} y The Y coordinate of the node.
  1790. * @param {number} size The node size.
  1791. * @return {x1,y1,x2,y2} The coordinates of the two control points.
  1792. */
  1793. sigma.utils.getSelfLoopControlPoints = function(x , y, size) {
  1794. return {
  1795. x1: x - size * 7,
  1796. y1: y,
  1797. x2: x,
  1798. y2: y + size * 7
  1799. };
  1800. };
  1801. /**
  1802. * Return the euclidian distance between two points of a plane
  1803. * with an orthonormal basis.
  1804. *
  1805. * @param {number} x1 The X coordinate of the first point.
  1806. * @param {number} y1 The Y coordinate of the first point.
  1807. * @param {number} x2 The X coordinate of the second point.
  1808. * @param {number} y2 The Y coordinate of the second point.
  1809. * @return {number} The euclidian distance.
  1810. */
  1811. sigma.utils.getDistance = function(x0, y0, x1, y1) {
  1812. return Math.sqrt(Math.pow(x1 - x0, 2) + Math.pow(y1 - y0, 2));
  1813. };
  1814. /**
  1815. * Return the coordinates of the intersection points of two circles.
  1816. *
  1817. * @param {number} x0 The X coordinate of center location of the first
  1818. * circle.
  1819. * @param {number} y0 The Y coordinate of center location of the first
  1820. * circle.
  1821. * @param {number} r0 The radius of the first circle.
  1822. * @param {number} x1 The X coordinate of center location of the second
  1823. * circle.
  1824. * @param {number} y1 The Y coordinate of center location of the second
  1825. * circle.
  1826. * @param {number} r1 The radius of the second circle.
  1827. * @return {xi,yi} The coordinates of the intersection points.
  1828. */
  1829. sigma.utils.getCircleIntersection = function(x0, y0, r0, x1, y1, r1) {
  1830. // http://stackoverflow.com/a/12219802
  1831. var a, dx, dy, d, h, rx, ry, x2, y2;
  1832. // dx and dy are the vertical and horizontal distances between the circle
  1833. // centers:
  1834. dx = x1 - x0;
  1835. dy = y1 - y0;
  1836. // Determine the straight-line distance between the centers:
  1837. d = Math.sqrt((dy * dy) + (dx * dx));
  1838. // Check for solvability:
  1839. if (d > (r0 + r1)) {
  1840. // No solution. circles do not intersect.
  1841. return false;
  1842. }
  1843. if (d < Math.abs(r0 - r1)) {
  1844. // No solution. one circle is contained in the other.
  1845. return false;
  1846. }
  1847. //'point 2' is the point where the line through the circle intersection
  1848. // points crosses the line between the circle centers.
  1849. // Determine the distance from point 0 to point 2:
  1850. a = ((r0 * r0) - (r1 * r1) + (d * d)) / (2.0 * d);
  1851. // Determine the coordinates of point 2:
  1852. x2 = x0 + (dx * a / d);
  1853. y2 = y0 + (dy * a / d);
  1854. // Determine the distance from point 2 to either of the intersection
  1855. // points:
  1856. h = Math.sqrt((r0 * r0) - (a * a));
  1857. // Determine the offsets of the intersection points from point 2:
  1858. rx = -dy * (h / d);
  1859. ry = dx * (h / d);
  1860. // Determine the absolute intersection points:
  1861. var xi = x2 + rx;
  1862. var xi_prime = x2 - rx;
  1863. var yi = y2 + ry;
  1864. var yi_prime = y2 - ry;
  1865. return {xi: xi, xi_prime: xi_prime, yi: yi, yi_prime: yi_prime};
  1866. };
  1867. /**
  1868. * Check if a point is on a line segment.
  1869. *
  1870. * @param {number} x The X coordinate of the point to check.
  1871. * @param {number} y The Y coordinate of the point to check.
  1872. * @param {number} x1 The X coordinate of the line start point.
  1873. * @param {number} y1 The Y coordinate of the line start point.
  1874. * @param {number} x2 The X coordinate of the line end point.
  1875. * @param {number} y2 The Y coordinate of the line end point.
  1876. * @param {number} epsilon The precision (consider the line thickness).
  1877. * @return {boolean} True if point is "close to" the line
  1878. * segment, false otherwise.
  1879. */
  1880. sigma.utils.isPointOnSegment = function(x, y, x1, y1, x2, y2, epsilon) {
  1881. // http://stackoverflow.com/a/328122
  1882. var crossProduct = Math.abs((y - y1) * (x2 - x1) - (x - x1) * (y2 - y1)),
  1883. d = sigma.utils.getDistance(x1, y1, x2, y2),
  1884. nCrossProduct = crossProduct / d; // normalized cross product
  1885. return (nCrossProduct < epsilon &&
  1886. Math.min(x1, x2) <= x && x <= Math.max(x1, x2) &&
  1887. Math.min(y1, y2) <= y && y <= Math.max(y1, y2));
  1888. };
  1889. /**
  1890. * Check if a point is on a quadratic bezier curve segment with a thickness.
  1891. *
  1892. * @param {number} x The X coordinate of the point to check.
  1893. * @param {number} y The Y coordinate of the point to check.
  1894. * @param {number} x1 The X coordinate of the curve start point.
  1895. * @param {number} y1 The Y coordinate of the curve start point.
  1896. * @param {number} x2 The X coordinate of the curve end point.
  1897. * @param {number} y2 The Y coordinate of the curve end point.
  1898. * @param {number} cpx The X coordinate of the curve control point.
  1899. * @param {number} cpy The Y coordinate of the curve control point.
  1900. * @param {number} epsilon The precision (consider the line thickness).
  1901. * @return {boolean} True if (x,y) is on the curve segment,
  1902. * false otherwise.
  1903. */
  1904. sigma.utils.isPointOnQuadraticCurve =
  1905. function(x, y, x1, y1, x2, y2, cpx, cpy, epsilon) {
  1906. // Fails if the point is too far from the extremities of the segment,
  1907. // preventing for more costly computation:
  1908. var dP1P2 = sigma.utils.getDistance(x1, y1, x2, y2);
  1909. if (Math.abs(x - x1) > dP1P2 || Math.abs(y - y1) > dP1P2) {
  1910. return false;
  1911. }
  1912. var dP1 = sigma.utils.getDistance(x, y, x1, y1),
  1913. dP2 = sigma.utils.getDistance(x, y, x2, y2),
  1914. t = 0.5,
  1915. r = (dP1 < dP2) ? -0.01 : 0.01,
  1916. rThreshold = 0.001,
  1917. i = 100,
  1918. pt = sigma.utils.getPointOnQuadraticCurve(t, x1, y1, x2, y2, cpx, cpy),
  1919. dt = sigma.utils.getDistance(x, y, pt.x, pt.y),
  1920. old_dt;
  1921. // This algorithm minimizes the distance from the point to the curve. It
  1922. // find the optimal t value where t=0 is the start point and t=1 is the end
  1923. // point of the curve, starting from t=0.5.
  1924. // It terminates because it runs a maximum of i interations.
  1925. while (i-- > 0 &&
  1926. t >= 0 && t <= 1 &&
  1927. (dt > epsilon) &&
  1928. (r > rThreshold || r < -rThreshold)) {
  1929. old_dt = dt;
  1930. pt = sigma.utils.getPointOnQuadraticCurve(t, x1, y1, x2, y2, cpx, cpy);
  1931. dt = sigma.utils.getDistance(x, y, pt.x, pt.y);
  1932. if (dt > old_dt) {
  1933. // not the right direction:
  1934. // halfstep in the opposite direction
  1935. r = -r / 2;
  1936. t += r;
  1937. }
  1938. else if (t + r < 0 || t + r > 1) {
  1939. // oops, we've gone too far:
  1940. // revert with a halfstep
  1941. r = r / 2;
  1942. dt = old_dt;
  1943. }
  1944. else {
  1945. // progress:
  1946. t += r;
  1947. }
  1948. }
  1949. return dt < epsilon;
  1950. };
  1951. /**
  1952. * Check if a point is on a cubic bezier curve segment with a thickness.
  1953. *
  1954. * @param {number} x The X coordinate of the point to check.
  1955. * @param {number} y The Y coordinate of the point to check.
  1956. * @param {number} x1 The X coordinate of the curve start point.
  1957. * @param {number} y1 The Y coordinate of the curve start point.
  1958. * @param {number} x2 The X coordinate of the curve end point.
  1959. * @param {number} y2 The Y coordinate of the curve end point.
  1960. * @param {number} cpx1 The X coordinate of the 1st curve control point.
  1961. * @param {number} cpy1 The Y coordinate of the 1st curve control point.
  1962. * @param {number} cpx2 The X coordinate of the 2nd curve control point.
  1963. * @param {number} cpy2 The Y coordinate of the 2nd curve control point.
  1964. * @param {number} epsilon The precision (consider the line thickness).
  1965. * @return {boolean} True if (x,y) is on the curve segment,
  1966. * false otherwise.
  1967. */
  1968. sigma.utils.isPointOnBezierCurve =
  1969. function(x, y, x1, y1, x2, y2, cpx1, cpy1, cpx2, cpy2, epsilon) {
  1970. // Fails if the point is too far from the extremities of the segment,
  1971. // preventing for more costly computation:
  1972. var dP1CP1 = sigma.utils.getDistance(x1, y1, cpx1, cpy1);
  1973. if (Math.abs(x - x1) > dP1CP1 || Math.abs(y - y1) > dP1CP1) {
  1974. return false;
  1975. }
  1976. var dP1 = sigma.utils.getDistance(x, y, x1, y1),
  1977. dP2 = sigma.utils.getDistance(x, y, x2, y2),
  1978. t = 0.5,
  1979. r = (dP1 < dP2) ? -0.01 : 0.01,
  1980. rThreshold = 0.001,
  1981. i = 100,
  1982. pt = sigma.utils.getPointOnBezierCurve(
  1983. t, x1, y1, x2, y2, cpx1, cpy1, cpx2, cpy2),
  1984. dt = sigma.utils.getDistance(x, y, pt.x, pt.y),
  1985. old_dt;
  1986. // This algorithm minimizes the distance from the point to the curve. It
  1987. // find the optimal t value where t=0 is the start point and t=1 is the end
  1988. // point of the curve, starting from t=0.5.
  1989. // It terminates because it runs a maximum of i interations.
  1990. while (i-- > 0 &&
  1991. t >= 0 && t <= 1 &&
  1992. (dt > epsilon) &&
  1993. (r > rThreshold || r < -rThreshold)) {
  1994. old_dt = dt;
  1995. pt = sigma.utils.getPointOnBezierCurve(
  1996. t, x1, y1, x2, y2, cpx1, cpy1, cpx2, cpy2);
  1997. dt = sigma.utils.getDistance(x, y, pt.x, pt.y);
  1998. if (dt > old_dt) {
  1999. // not the right direction:
  2000. // halfstep in the opposite direction
  2001. r = -r / 2;
  2002. t += r;
  2003. }
  2004. else if (t + r < 0 || t + r > 1) {
  2005. // oops, we've gone too far:
  2006. // revert with a halfstep
  2007. r = r / 2;
  2008. dt = old_dt;
  2009. }
  2010. else {
  2011. // progress:
  2012. t += r;
  2013. }
  2014. }
  2015. return dt < epsilon;
  2016. };
  2017. /**
  2018. * ************
  2019. * EVENTS UTILS:
  2020. * ************
  2021. */
  2022. /**
  2023. * Here are some useful functions to unify extraction of the information we
  2024. * need with mouse events and touch events, from different browsers:
  2025. */
  2026. /**
  2027. * Extract the local X position from a mouse or touch event.
  2028. *
  2029. * @param {event} e A mouse or touch event.
  2030. * @return {number} The local X value of the mouse.
  2031. */
  2032. sigma.utils.getX = function(e) {
  2033. return (
  2034. (e.offsetX !== undefined && e.offsetX) ||
  2035. (e.layerX !== undefined && e.layerX) ||
  2036. (e.clientX !== undefined && e.clientX)
  2037. );
  2038. };
  2039. /**
  2040. * Extract the local Y position from a mouse or touch event.
  2041. *
  2042. * @param {event} e A mouse or touch event.
  2043. * @return {number} The local Y value of the mouse.
  2044. */
  2045. sigma.utils.getY = function(e) {
  2046. return (
  2047. (e.offsetY !== undefined && e.offsetY) ||
  2048. (e.layerY !== undefined && e.layerY) ||
  2049. (e.clientY !== undefined && e.clientY)
  2050. );
  2051. };
  2052. /**
  2053. * Extract the width from a mouse or touch event.
  2054. *
  2055. * @param {event} e A mouse or touch event.
  2056. * @return {number} The width of the event's target.
  2057. */
  2058. sigma.utils.getWidth = function(e) {
  2059. var w = (!e.target.ownerSVGElement) ?
  2060. e.target.width :
  2061. e.target.ownerSVGElement.width;
  2062. return (
  2063. (typeof w === 'number' && w) ||
  2064. (w !== undefined && w.baseVal !== undefined && w.baseVal.value)
  2065. );
  2066. };
  2067. /**
  2068. * Extract the height from a mouse or touch event.
  2069. *
  2070. * @param {event} e A mouse or touch event.
  2071. * @return {number} The height of the event's target.
  2072. */
  2073. sigma.utils.getHeight = function(e) {
  2074. var h = (!e.target.ownerSVGElement) ?
  2075. e.target.height :
  2076. e.target.ownerSVGElement.height;
  2077. return (
  2078. (typeof h === 'number' && h) ||
  2079. (h !== undefined && h.baseVal !== undefined && h.baseVal.value)
  2080. );
  2081. };
  2082. /**
  2083. * Extract the wheel delta from a mouse or touch event.
  2084. *
  2085. * @param {event} e A mouse or touch event.
  2086. * @return {number} The wheel delta of the mouse.
  2087. */
  2088. sigma.utils.getDelta = function(e) {
  2089. return (
  2090. (e.wheelDelta !== undefined && e.wheelDelta) ||
  2091. (e.detail !== undefined && -e.detail)
  2092. );
  2093. };
  2094. /**
  2095. * Returns the offset of a DOM element.
  2096. *
  2097. * @param {DOMElement} dom The element to retrieve the position.
  2098. * @return {object} The offset of the DOM element (top, left).
  2099. */
  2100. sigma.utils.getOffset = function(dom) {
  2101. var left = 0,
  2102. top = 0;
  2103. while (dom) {
  2104. top = top + parseInt(dom.offsetTop);
  2105. left = left + parseInt(dom.offsetLeft);
  2106. dom = dom.offsetParent;
  2107. }
  2108. return {
  2109. top: top,
  2110. left: left
  2111. };
  2112. };
  2113. /**
  2114. * Simulates a "double click" event.
  2115. *
  2116. * @param {HTMLElement} target The event target.
  2117. * @param {string} type The event type.
  2118. * @param {function} callback The callback to execute.
  2119. */
  2120. sigma.utils.doubleClick = function(target, type, callback) {
  2121. var clicks = 0,
  2122. self = this,
  2123. handlers;
  2124. target._doubleClickHandler = target._doubleClickHandler || {};
  2125. target._doubleClickHandler[type] = target._doubleClickHandler[type] || [];
  2126. handlers = target._doubleClickHandler[type];
  2127. handlers.push(function(e) {
  2128. clicks++;
  2129. if (clicks === 2) {
  2130. clicks = 0;
  2131. return callback(e);
  2132. } else if (clicks === 1) {
  2133. setTimeout(function() {
  2134. clicks = 0;
  2135. }, sigma.settings.doubleClickTimeout);
  2136. }
  2137. });
  2138. target.addEventListener(type, handlers[handlers.length - 1], false);
  2139. };
  2140. /**
  2141. * Unbind simulated "double click" events.
  2142. *
  2143. * @param {HTMLElement} target The event target.
  2144. * @param {string} type The event type.
  2145. */
  2146. sigma.utils.unbindDoubleClick = function(target, type) {
  2147. var handler,
  2148. handlers = (target._doubleClickHandler || {})[type] || [];
  2149. while ((handler = handlers.pop())) {
  2150. target.removeEventListener(type, handler);
  2151. }
  2152. delete (target._doubleClickHandler || {})[type];
  2153. };
  2154. /**
  2155. * Here are just some of the most basic easing functions, used for the
  2156. * animated camera "goTo" calls.
  2157. *
  2158. * If you need some more easings functions, don't hesitate to add them to
  2159. * sigma.utils.easings. But I will not add some more here or merge PRs
  2160. * containing, because I do not want sigma sources full of overkill and never
  2161. * used stuff...
  2162. */
  2163. sigma.utils.easings = sigma.utils.easings || {};
  2164. sigma.utils.easings.linearNone = function(k) {
  2165. return k;
  2166. };
  2167. sigma.utils.easings.quadraticIn = function(k) {
  2168. return k * k;
  2169. };
  2170. sigma.utils.easings.quadraticOut = function(k) {
  2171. return k * (2 - k);
  2172. };
  2173. sigma.utils.easings.quadraticInOut = function(k) {
  2174. if ((k *= 2) < 1)
  2175. return 0.5 * k * k;
  2176. return - 0.5 * (--k * (k - 2) - 1);
  2177. };
  2178. sigma.utils.easings.cubicIn = function(k) {
  2179. return k * k * k;
  2180. };
  2181. sigma.utils.easings.cubicOut = function(k) {
  2182. return --k * k * k + 1;
  2183. };
  2184. sigma.utils.easings.cubicInOut = function(k) {
  2185. if ((k *= 2) < 1)
  2186. return 0.5 * k * k * k;
  2187. return 0.5 * ((k -= 2) * k * k + 2);
  2188. };
  2189. /**
  2190. * ************
  2191. * WEBGL UTILS:
  2192. * ************
  2193. */
  2194. /**
  2195. * Loads a WebGL shader and returns it.
  2196. *
  2197. * @param {WebGLContext} gl The WebGLContext to use.
  2198. * @param {string} shaderSource The shader source.
  2199. * @param {number} shaderType The type of shader.
  2200. * @param {function(string): void} error Callback for errors.
  2201. * @return {WebGLShader} The created shader.
  2202. */
  2203. sigma.utils.loadShader = function(gl, shaderSource, shaderType, error) {
  2204. var compiled,
  2205. shader = gl.createShader(shaderType);
  2206. // Load the shader source
  2207. gl.shaderSource(shader, shaderSource);
  2208. // Compile the shader
  2209. gl.compileShader(shader);
  2210. // Check the compile status
  2211. compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  2212. // If something went wrong:
  2213. if (!compiled) {
  2214. if (error) {
  2215. error(
  2216. 'Error compiling shader "' + shader + '":' +
  2217. gl.getShaderInfoLog(shader)
  2218. );
  2219. }
  2220. gl.deleteShader(shader);
  2221. return null;
  2222. }
  2223. return shader;
  2224. };
  2225. /**
  2226. * Creates a program, attaches shaders, binds attrib locations, links the
  2227. * program and calls useProgram.
  2228. *
  2229. * @param {Array.<WebGLShader>} shaders The shaders to attach.
  2230. * @param {Array.<string>} attribs The attribs names.
  2231. * @param {Array.<number>} locations The locations for the attribs.
  2232. * @param {function(string): void} error Callback for errors.
  2233. * @return {WebGLProgram} The created program.
  2234. */
  2235. sigma.utils.loadProgram = function(gl, shaders, attribs, loc, error) {
  2236. var i,
  2237. linked,
  2238. program = gl.createProgram();
  2239. for (i = 0; i < shaders.length; ++i)
  2240. gl.attachShader(program, shaders[i]);
  2241. if (attribs)
  2242. for (i = 0; i < attribs.length; ++i)
  2243. gl.bindAttribLocation(
  2244. program,
  2245. locations ? locations[i] : i,
  2246. opt_attribs[i]
  2247. );
  2248. gl.linkProgram(program);
  2249. // Check the link status
  2250. linked = gl.getProgramParameter(program, gl.LINK_STATUS);
  2251. if (!linked) {
  2252. if (error)
  2253. error('Error in program linking: ' + gl.getProgramInfoLog(program));
  2254. gl.deleteProgram(program);
  2255. return null;
  2256. }
  2257. return program;
  2258. };
  2259. /**
  2260. * *********
  2261. * MATRICES:
  2262. * *********
  2263. * The following utils are just here to help generating the transformation
  2264. * matrices for the WebGL renderers.
  2265. */
  2266. sigma.utils.pkg('sigma.utils.matrices');
  2267. /**
  2268. * The returns a 3x3 translation matrix.
  2269. *
  2270. * @param {number} dx The X translation.
  2271. * @param {number} dy The Y translation.
  2272. * @return {array} Returns the matrix.
  2273. */
  2274. sigma.utils.matrices.translation = function(dx, dy) {
  2275. return [
  2276. 1, 0, 0,
  2277. 0, 1, 0,
  2278. dx, dy, 1
  2279. ];
  2280. };
  2281. /**
  2282. * The returns a 3x3 or 2x2 rotation matrix.
  2283. *
  2284. * @param {number} angle The rotation angle.
  2285. * @param {boolean} m2 If true, the function will return a 2x2 matrix.
  2286. * @return {array} Returns the matrix.
  2287. */
  2288. sigma.utils.matrices.rotation = function(angle, m2) {
  2289. var cos = Math.cos(angle),
  2290. sin = Math.sin(angle);
  2291. return m2 ? [
  2292. cos, -sin,
  2293. sin, cos
  2294. ] : [
  2295. cos, -sin, 0,
  2296. sin, cos, 0,
  2297. 0, 0, 1
  2298. ];
  2299. };
  2300. /**
  2301. * The returns a 3x3 or 2x2 homothetic transformation matrix.
  2302. *
  2303. * @param {number} ratio The scaling ratio.
  2304. * @param {boolean} m2 If true, the function will return a 2x2 matrix.
  2305. * @return {array} Returns the matrix.
  2306. */
  2307. sigma.utils.matrices.scale = function(ratio, m2) {
  2308. return m2 ? [
  2309. ratio, 0,
  2310. 0, ratio
  2311. ] : [
  2312. ratio, 0, 0,
  2313. 0, ratio, 0,
  2314. 0, 0, 1
  2315. ];
  2316. };
  2317. /**
  2318. * The returns a 3x3 or 2x2 homothetic transformation matrix.
  2319. *
  2320. * @param {array} a The first matrix.
  2321. * @param {array} b The second matrix.
  2322. * @param {boolean} m2 If true, the function will assume both matrices are
  2323. * 2x2.
  2324. * @return {array} Returns the matrix.
  2325. */
  2326. sigma.utils.matrices.multiply = function(a, b, m2) {
  2327. var l = m2 ? 2 : 3,
  2328. a00 = a[0 * l + 0],
  2329. a01 = a[0 * l + 1],
  2330. a02 = a[0 * l + 2],
  2331. a10 = a[1 * l + 0],
  2332. a11 = a[1 * l + 1],
  2333. a12 = a[1 * l + 2],
  2334. a20 = a[2 * l + 0],
  2335. a21 = a[2 * l + 1],
  2336. a22 = a[2 * l + 2],
  2337. b00 = b[0 * l + 0],
  2338. b01 = b[0 * l + 1],
  2339. b02 = b[0 * l + 2],
  2340. b10 = b[1 * l + 0],
  2341. b11 = b[1 * l + 1],
  2342. b12 = b[1 * l + 2],
  2343. b20 = b[2 * l + 0],
  2344. b21 = b[2 * l + 1],
  2345. b22 = b[2 * l + 2];
  2346. return m2 ? [
  2347. a00 * b00 + a01 * b10,
  2348. a00 * b01 + a01 * b11,
  2349. a10 * b00 + a11 * b10,
  2350. a10 * b01 + a11 * b11
  2351. ] : [
  2352. a00 * b00 + a01 * b10 + a02 * b20,
  2353. a00 * b01 + a01 * b11 + a02 * b21,
  2354. a00 * b02 + a01 * b12 + a02 * b22,
  2355. a10 * b00 + a11 * b10 + a12 * b20,
  2356. a10 * b01 + a11 * b11 + a12 * b21,
  2357. a10 * b02 + a11 * b12 + a12 * b22,
  2358. a20 * b00 + a21 * b10 + a22 * b20,
  2359. a20 * b01 + a21 * b11 + a22 * b21,
  2360. a20 * b02 + a21 * b12 + a22 * b22
  2361. ];
  2362. };
  2363. }).call(this);
  2364. ;(function(global) {
  2365. 'use strict';
  2366. /**
  2367. * http://paulirish.com/2011/requestanimationframe-for-smart-animating/
  2368. * http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
  2369. * requestAnimationFrame polyfill by Erik Möller.
  2370. * fixes from Paul Irish and Tino Zijdel
  2371. * MIT license
  2372. */
  2373. var x,
  2374. lastTime = 0,
  2375. vendors = ['ms', 'moz', 'webkit', 'o'];
  2376. for (x = 0; x < vendors.length && !global.requestAnimationFrame; x++) {
  2377. global.requestAnimationFrame =
  2378. global[vendors[x] + 'RequestAnimationFrame'];
  2379. global.cancelAnimationFrame =
  2380. global[vendors[x] + 'CancelAnimationFrame'] ||
  2381. global[vendors[x] + 'CancelRequestAnimationFrame'];
  2382. }
  2383. if (!global.requestAnimationFrame)
  2384. global.requestAnimationFrame = function(callback, element) {
  2385. var currTime = new Date().getTime(),
  2386. timeToCall = Math.max(0, 16 - (currTime - lastTime)),
  2387. id = global.setTimeout(
  2388. function() {
  2389. callback(currTime + timeToCall);
  2390. },
  2391. timeToCall
  2392. );
  2393. lastTime = currTime + timeToCall;
  2394. return id;
  2395. };
  2396. if (!global.cancelAnimationFrame)
  2397. global.cancelAnimationFrame = function(id) {
  2398. clearTimeout(id);
  2399. };
  2400. /**
  2401. * Function.prototype.bind polyfill found on MDN.
  2402. * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Compatibility
  2403. * Public domain
  2404. */
  2405. if (!Function.prototype.bind)
  2406. Function.prototype.bind = function(oThis) {
  2407. if (typeof this !== 'function')
  2408. // Closest thing possible to the ECMAScript 5 internal IsCallable
  2409. // function:
  2410. throw new TypeError(
  2411. 'Function.prototype.bind - what is trying to be bound is not callable'
  2412. );
  2413. var aArgs = Array.prototype.slice.call(arguments, 1),
  2414. fToBind = this,
  2415. fNOP,
  2416. fBound;
  2417. fNOP = function() {};
  2418. fBound = function() {
  2419. return fToBind.apply(
  2420. this instanceof fNOP && oThis ?
  2421. this :
  2422. oThis,
  2423. aArgs.concat(Array.prototype.slice.call(arguments))
  2424. );
  2425. };
  2426. fNOP.prototype = this.prototype;
  2427. fBound.prototype = new fNOP();
  2428. return fBound;
  2429. };
  2430. })(this);
  2431. ;(function(undefined) {
  2432. 'use strict';
  2433. if (typeof sigma === 'undefined')
  2434. throw 'sigma is not declared';
  2435. // Packages initialization:
  2436. sigma.utils.pkg('sigma.settings');
  2437. var settings = {
  2438. /**
  2439. * GRAPH SETTINGS:
  2440. * ***************
  2441. */
  2442. // {boolean} Indicates if the data have to be cloned in methods to add
  2443. // nodes or edges.
  2444. clone: true,
  2445. // {boolean} Indicates if nodes "id" values and edges "id", "source" and
  2446. // "target" values must be set as immutable.
  2447. immutable: true,
  2448. // {boolean} Indicates if sigma can log its errors and warnings.
  2449. verbose: false,
  2450. /**
  2451. * RENDERERS SETTINGS:
  2452. * *******************
  2453. */
  2454. // {string}
  2455. classPrefix: 'sigma',
  2456. // {string}
  2457. defaultNodeType: 'def',
  2458. // {string}
  2459. defaultEdgeType: 'def',
  2460. // {string}
  2461. defaultLabelColor: '#000',
  2462. // {string}
  2463. defaultEdgeColor: '#000',
  2464. // {string}
  2465. defaultNodeColor: '#000',
  2466. // {string}
  2467. defaultLabelSize: 14,
  2468. // {string} Indicates how to choose the edges color. Available values:
  2469. // "source", "target", "default"
  2470. edgeColor: 'source',
  2471. // {number} Defines the minimal edge's arrow display size.
  2472. minArrowSize: 0,
  2473. // {string}
  2474. font: 'arial',
  2475. // {string} Example: 'bold'
  2476. fontStyle: '',
  2477. // {string} Indicates how to choose the labels color. Available values:
  2478. // "node", "default"
  2479. labelColor: 'default',
  2480. // {string} Indicates how to choose the labels size. Available values:
  2481. // "fixed", "proportional"
  2482. labelSize: 'fixed',
  2483. // {string} The ratio between the font size of the label and the node size.
  2484. labelSizeRatio: 1,
  2485. // {number} The minimum size a node must have to see its label displayed.
  2486. labelThreshold: 8,
  2487. // {number} The oversampling factor used in WebGL renderer.
  2488. webglOversamplingRatio: 2,
  2489. // {number} The size of the border of hovered nodes.
  2490. borderSize: 0,
  2491. // {number} The default hovered node border's color.
  2492. defaultNodeBorderColor: '#000',
  2493. // {number} The hovered node's label font. If not specified, will heritate
  2494. // the "font" value.
  2495. hoverFont: '',
  2496. // {boolean} If true, then only one node can be hovered at a time.
  2497. singleHover: true,
  2498. // {string} Example: 'bold'
  2499. hoverFontStyle: '',
  2500. // {string} Indicates how to choose the hovered nodes shadow color.
  2501. // Available values: "node", "default"
  2502. labelHoverShadow: 'default',
  2503. // {string}
  2504. labelHoverShadowColor: '#000',
  2505. // {string} Indicates how to choose the hovered nodes color.
  2506. // Available values: "node", "default"
  2507. nodeHoverColor: 'node',
  2508. // {string}
  2509. defaultNodeHoverColor: '#000',
  2510. // {string} Indicates how to choose the hovered nodes background color.
  2511. // Available values: "node", "default"
  2512. labelHoverBGColor: 'default',
  2513. // {string}
  2514. defaultHoverLabelBGColor: '#fff',
  2515. // {string} Indicates how to choose the hovered labels color.
  2516. // Available values: "node", "default"
  2517. labelHoverColor: 'default',
  2518. // {string}
  2519. defaultLabelHoverColor: '#000',
  2520. // {string} Indicates how to choose the edges hover color. Available values:
  2521. // "edge", "default"
  2522. edgeHoverColor: 'edge',
  2523. // {number} The size multiplicator of hovered edges.
  2524. edgeHoverSizeRatio: 1,
  2525. // {string}
  2526. defaultEdgeHoverColor: '#000',
  2527. // {boolean} Indicates if the edge extremities must be hovered when the
  2528. // edge is hovered.
  2529. edgeHoverExtremities: false,
  2530. // {booleans} The different drawing modes:
  2531. // false: Layered not displayed.
  2532. // true: Layered displayed.
  2533. drawEdges: true,
  2534. drawNodes: true,
  2535. drawLabels: true,
  2536. drawEdgeLabels: false,
  2537. // {boolean} Indicates if the edges must be drawn in several frames or in
  2538. // one frame, as the nodes and labels are drawn.
  2539. batchEdgesDrawing: false,
  2540. // {boolean} Indicates if the edges must be hidden during dragging and
  2541. // animations.
  2542. hideEdgesOnMove: false,
  2543. // {numbers} The different batch sizes, when elements are displayed in
  2544. // several frames.
  2545. canvasEdgesBatchSize: 500,
  2546. webglEdgesBatchSize: 1000,
  2547. /**
  2548. * RESCALE SETTINGS:
  2549. * *****************
  2550. */
  2551. // {string} Indicates of to scale the graph relatively to its container.
  2552. // Available values: "inside", "outside"
  2553. scalingMode: 'inside',
  2554. // {number} The margin to keep around the graph.
  2555. sideMargin: 0,
  2556. // {number} Determine the size of the smallest and the biggest node / edges
  2557. // on the screen. This mapping makes easier to display the graph,
  2558. // avoiding too big nodes that take half of the screen, or too
  2559. // small ones that are not readable. If the two parameters are
  2560. // equals, then the minimal display size will be 0. And if they
  2561. // are both equal to 0, then there is no mapping, and the radius
  2562. // of the nodes will be their size.
  2563. minEdgeSize: 0.5,
  2564. maxEdgeSize: 1,
  2565. minNodeSize: 1,
  2566. maxNodeSize: 8,
  2567. /**
  2568. * CAPTORS SETTINGS:
  2569. * *****************
  2570. */
  2571. // {boolean}
  2572. touchEnabled: true,
  2573. // {boolean}
  2574. mouseEnabled: true,
  2575. // {boolean}
  2576. mouseWheelEnabled: true,
  2577. // {boolean}
  2578. doubleClickEnabled: true,
  2579. // {boolean} Defines whether the custom events such as "clickNode" can be
  2580. // used.
  2581. eventsEnabled: true,
  2582. // {number} Defines by how much multiplicating the zooming level when the
  2583. // user zooms with the mouse-wheel.
  2584. zoomingRatio: 1.7,
  2585. // {number} Defines by how much multiplicating the zooming level when the
  2586. // user zooms by double clicking.
  2587. doubleClickZoomingRatio: 2.2,
  2588. // {number} The minimum zooming level.
  2589. zoomMin: 0.0625,
  2590. // {number} The maximum zooming level.
  2591. zoomMax: 2,
  2592. // {number} The duration of animations following a mouse scrolling.
  2593. mouseZoomDuration: 200,
  2594. // {number} The duration of animations following a mouse double click.
  2595. doubleClickZoomDuration: 200,
  2596. // {number} The duration of animations following a mouse dropping.
  2597. mouseInertiaDuration: 200,
  2598. // {number} The inertia power (mouse captor).
  2599. mouseInertiaRatio: 3,
  2600. // {number} The duration of animations following a touch dropping.
  2601. touchInertiaDuration: 200,
  2602. // {number} The inertia power (touch captor).
  2603. touchInertiaRatio: 3,
  2604. // {number} The maximum time between two clicks to make it a double click.
  2605. doubleClickTimeout: 300,
  2606. // {number} The maximum time between two taps to make it a double tap.
  2607. doubleTapTimeout: 300,
  2608. // {number} The maximum time of dragging to trigger intertia.
  2609. dragTimeout: 200,
  2610. /**
  2611. * GLOBAL SETTINGS:
  2612. * ****************
  2613. */
  2614. // {boolean} Determines whether the instance has to refresh itself
  2615. // automatically when a "resize" event is dispatched from the
  2616. // window object.
  2617. autoResize: true,
  2618. // {boolean} Determines whether the "rescale" middleware has to be called
  2619. // automatically for each camera on refresh.
  2620. autoRescale: true,
  2621. // {boolean} If set to false, the camera method "goTo" will basically do
  2622. // nothing.
  2623. enableCamera: true,
  2624. // {boolean} If set to false, the nodes cannot be hovered.
  2625. enableHovering: true,
  2626. // {boolean} If set to true, the edges can be hovered.
  2627. enableEdgeHovering: false,
  2628. // {number} The size of the area around the edges to activate hovering.
  2629. edgeHoverPrecision: 5,
  2630. // {boolean} If set to true, the rescale middleware will ignore node sizes
  2631. // to determine the graphs boundings.
  2632. rescaleIgnoreSize: false,
  2633. // {boolean} Determines if the core has to try to catch errors on
  2634. // rendering.
  2635. skipErrors: false,
  2636. /**
  2637. * CAMERA SETTINGS:
  2638. * ****************
  2639. */
  2640. // {number} The power degrees applied to the nodes/edges size relatively to
  2641. // the zooming level. Basically:
  2642. // > onScreenR = Math.pow(zoom, nodesPowRatio) * R
  2643. // > onScreenT = Math.pow(zoom, edgesPowRatio) * T
  2644. nodesPowRatio: 0.5,
  2645. edgesPowRatio: 0.5,
  2646. /**
  2647. * ANIMATIONS SETTINGS:
  2648. * ********************
  2649. */
  2650. // {number} The default animation time.
  2651. animationsTime: 200
  2652. };
  2653. // Export the previously designed settings:
  2654. sigma.settings = sigma.utils.extend(sigma.settings || {}, settings);
  2655. }).call(this);
  2656. ;(function() {
  2657. 'use strict';
  2658. /**
  2659. * Dispatcher constructor.
  2660. *
  2661. * @return {dispatcher} The new dispatcher instance.
  2662. */
  2663. var dispatcher = function() {
  2664. Object.defineProperty(this, '_handlers', {
  2665. value: {}
  2666. });
  2667. };
  2668. /**
  2669. * Will execute the handler everytime that the indicated event (or the
  2670. * indicated events) will be triggered.
  2671. *
  2672. * @param {string} events The name of the event (or the events
  2673. * separated by spaces).
  2674. * @param {function(Object)} handler The handler to bind.
  2675. * @return {dispatcher} Returns the instance itself.
  2676. */
  2677. dispatcher.prototype.bind = function(events, handler) {
  2678. var i,
  2679. l,
  2680. event,
  2681. eArray;
  2682. if (
  2683. arguments.length === 1 &&
  2684. typeof arguments[0] === 'object'
  2685. )
  2686. for (events in arguments[0])
  2687. this.bind(events, arguments[0][events]);
  2688. else if (
  2689. arguments.length === 2 &&
  2690. typeof arguments[1] === 'function'
  2691. ) {
  2692. eArray = typeof events === 'string' ? events.split(' ') : events;
  2693. for (i = 0, l = eArray.length; i !== l; i += 1) {
  2694. event = eArray[i];
  2695. // Check that event is not '':
  2696. if (!event)
  2697. continue;
  2698. if (!this._handlers[event])
  2699. this._handlers[event] = [];
  2700. // Using an object instead of directly the handler will make possible
  2701. // later to add flags
  2702. this._handlers[event].push({
  2703. handler: handler
  2704. });
  2705. }
  2706. } else
  2707. throw 'bind: Wrong arguments.';
  2708. return this;
  2709. };
  2710. /**
  2711. * Removes the handler from a specified event (or specified events).
  2712. *
  2713. * @param {?string} events The name of the event (or the events
  2714. * separated by spaces). If undefined,
  2715. * then all handlers are removed.
  2716. * @param {?function(object)} handler The handler to unbind. If undefined,
  2717. * each handler bound to the event or the
  2718. * events will be removed.
  2719. * @return {dispatcher} Returns the instance itself.
  2720. */
  2721. dispatcher.prototype.unbind = function(events, handler) {
  2722. var i,
  2723. n,
  2724. j,
  2725. m,
  2726. k,
  2727. a,
  2728. event,
  2729. eArray = typeof events === 'string' ? events.split(' ') : events;
  2730. if (!arguments.length) {
  2731. for (k in this._handlers)
  2732. delete this._handlers[k];
  2733. return this;
  2734. }
  2735. if (handler) {
  2736. for (i = 0, n = eArray.length; i !== n; i += 1) {
  2737. event = eArray[i];
  2738. if (this._handlers[event]) {
  2739. a = [];
  2740. for (j = 0, m = this._handlers[event].length; j !== m; j += 1)
  2741. if (this._handlers[event][j].handler !== handler)
  2742. a.push(this._handlers[event][j]);
  2743. this._handlers[event] = a;
  2744. }
  2745. if (this._handlers[event] && this._handlers[event].length === 0)
  2746. delete this._handlers[event];
  2747. }
  2748. } else
  2749. for (i = 0, n = eArray.length; i !== n; i += 1)
  2750. delete this._handlers[eArray[i]];
  2751. return this;
  2752. };
  2753. /**
  2754. * Executes each handler bound to the event
  2755. *
  2756. * @param {string} events The name of the event (or the events separated
  2757. * by spaces).
  2758. * @param {?object} data The content of the event (optional).
  2759. * @return {dispatcher} Returns the instance itself.
  2760. */
  2761. dispatcher.prototype.dispatchEvent = function(events, data) {
  2762. var i,
  2763. n,
  2764. j,
  2765. m,
  2766. a,
  2767. event,
  2768. eventName,
  2769. self = this,
  2770. eArray = typeof events === 'string' ? events.split(' ') : events;
  2771. data = data === undefined ? {} : data;
  2772. for (i = 0, n = eArray.length; i !== n; i += 1) {
  2773. eventName = eArray[i];
  2774. if (this._handlers[eventName]) {
  2775. event = self.getEvent(eventName, data);
  2776. a = [];
  2777. for (j = 0, m = this._handlers[eventName].length; j !== m; j += 1) {
  2778. this._handlers[eventName][j].handler(event);
  2779. if (!this._handlers[eventName][j].one)
  2780. a.push(this._handlers[eventName][j]);
  2781. }
  2782. this._handlers[eventName] = a;
  2783. }
  2784. }
  2785. return this;
  2786. };
  2787. /**
  2788. * Return an event object.
  2789. *
  2790. * @param {string} events The name of the event.
  2791. * @param {?object} data The content of the event (optional).
  2792. * @return {object} Returns the instance itself.
  2793. */
  2794. dispatcher.prototype.getEvent = function(event, data) {
  2795. return {
  2796. type: event,
  2797. data: data || {},
  2798. target: this
  2799. };
  2800. };
  2801. /**
  2802. * A useful function to deal with inheritance. It will make the target
  2803. * inherit the prototype of the class dispatcher as well as its constructor.
  2804. *
  2805. * @param {object} target The target.
  2806. */
  2807. dispatcher.extend = function(target, args) {
  2808. var k;
  2809. for (k in dispatcher.prototype)
  2810. if (dispatcher.prototype.hasOwnProperty(k))
  2811. target[k] = dispatcher.prototype[k];
  2812. dispatcher.apply(target, args);
  2813. };
  2814. /**
  2815. * EXPORT:
  2816. * *******
  2817. */
  2818. if (typeof this.sigma !== 'undefined') {
  2819. this.sigma.classes = this.sigma.classes || {};
  2820. this.sigma.classes.dispatcher = dispatcher;
  2821. } else if (typeof exports !== 'undefined') {
  2822. if (typeof module !== 'undefined' && module.exports)
  2823. exports = module.exports = dispatcher;
  2824. exports.dispatcher = dispatcher;
  2825. } else
  2826. this.dispatcher = dispatcher;
  2827. }).call(this);
  2828. ;(function() {
  2829. 'use strict';
  2830. /**
  2831. * This utils aims to facilitate the manipulation of each instance setting.
  2832. * Using a function instead of an object brings two main advantages: First,
  2833. * it will be easier in the future to catch settings updates through a
  2834. * function than an object. Second, giving it a full object will "merge" it
  2835. * to the settings object properly, keeping us to have to always add a loop.
  2836. *
  2837. * @return {configurable} The "settings" function.
  2838. */
  2839. var configurable = function() {
  2840. var i,
  2841. l,
  2842. data = {},
  2843. datas = Array.prototype.slice.call(arguments, 0);
  2844. /**
  2845. * The method to use to set or get any property of this instance.
  2846. *
  2847. * @param {string|object} a1 If it is a string and if a2 is undefined,
  2848. * then it will return the corresponding
  2849. * property. If it is a string and if a2 is
  2850. * set, then it will set a2 as the property
  2851. * corresponding to a1, and return this. If
  2852. * it is an object, then each pair string +
  2853. * object(or any other type) will be set as a
  2854. * property.
  2855. * @param {*?} a2 The new property corresponding to a1 if a1
  2856. * is a string.
  2857. * @return {*|configurable} Returns itself or the corresponding
  2858. * property.
  2859. *
  2860. * Polymorphism:
  2861. * *************
  2862. * Here are some basic use examples:
  2863. *
  2864. * > settings = new configurable();
  2865. * > settings('mySetting', 42);
  2866. * > settings('mySetting'); // Logs: 42
  2867. * > settings('mySetting', 123);
  2868. * > settings('mySetting'); // Logs: 123
  2869. * > settings({mySetting: 456});
  2870. * > settings('mySetting'); // Logs: 456
  2871. *
  2872. * Also, it is possible to use the function as a fallback:
  2873. * > settings({mySetting: 'abc'}, 'mySetting'); // Logs: 'abc'
  2874. * > settings({hisSetting: 'abc'}, 'mySetting'); // Logs: 456
  2875. */
  2876. var settings = function(a1, a2) {
  2877. var o,
  2878. i,
  2879. l,
  2880. k;
  2881. if (arguments.length === 1 && typeof a1 === 'string') {
  2882. if (data[a1] !== undefined)
  2883. return data[a1];
  2884. for (i = 0, l = datas.length; i < l; i++)
  2885. if (datas[i][a1] !== undefined)
  2886. return datas[i][a1];
  2887. return undefined;
  2888. } else if (typeof a1 === 'object' && typeof a2 === 'string') {
  2889. return (a1 || {})[a2] !== undefined ? a1[a2] : settings(a2);
  2890. } else {
  2891. o = (typeof a1 === 'object' && a2 === undefined) ? a1 : {};
  2892. if (typeof a1 === 'string')
  2893. o[a1] = a2;
  2894. for (i = 0, k = Object.keys(o), l = k.length; i < l; i++)
  2895. data[k[i]] = o[k[i]];
  2896. return this;
  2897. }
  2898. };
  2899. /**
  2900. * This method returns a new configurable function, with new objects
  2901. *
  2902. * @param {object*} Any number of objects to search in.
  2903. * @return {function} Returns the function. Check its documentation to know
  2904. * more about how it works.
  2905. */
  2906. settings.embedObjects = function() {
  2907. var args = datas.concat(
  2908. data
  2909. ).concat(
  2910. Array.prototype.splice.call(arguments, 0)
  2911. );
  2912. return configurable.apply({}, args);
  2913. };
  2914. // Initialize
  2915. for (i = 0, l = arguments.length; i < l; i++)
  2916. settings(arguments[i]);
  2917. return settings;
  2918. };
  2919. /**
  2920. * EXPORT:
  2921. * *******
  2922. */
  2923. if (typeof this.sigma !== 'undefined') {
  2924. this.sigma.classes = this.sigma.classes || {};
  2925. this.sigma.classes.configurable = configurable;
  2926. } else if (typeof exports !== 'undefined') {
  2927. if (typeof module !== 'undefined' && module.exports)
  2928. exports = module.exports = configurable;
  2929. exports.configurable = configurable;
  2930. } else
  2931. this.configurable = configurable;
  2932. }).call(this);
  2933. ;(function(undefined) {
  2934. 'use strict';
  2935. var _methods = Object.create(null),
  2936. _indexes = Object.create(null),
  2937. _initBindings = Object.create(null),
  2938. _methodBindings = Object.create(null),
  2939. _methodBeforeBindings = Object.create(null),
  2940. _defaultSettings = {
  2941. immutable: true,
  2942. clone: true
  2943. },
  2944. _defaultSettingsFunction = function(key) {
  2945. return _defaultSettings[key];
  2946. };
  2947. /**
  2948. * The graph constructor. It initializes the data and the indexes, and binds
  2949. * the custom indexes and methods to its own scope.
  2950. *
  2951. * Recognized parameters:
  2952. * **********************
  2953. * Here is the exhaustive list of every accepted parameters in the settings
  2954. * object:
  2955. *
  2956. * {boolean} clone Indicates if the data have to be cloned in methods
  2957. * to add nodes or edges.
  2958. * {boolean} immutable Indicates if nodes "id" values and edges "id",
  2959. * "source" and "target" values must be set as
  2960. * immutable.
  2961. *
  2962. * @param {?configurable} settings Eventually a settings function.
  2963. * @return {graph} The new graph instance.
  2964. */
  2965. var graph = function(settings) {
  2966. var k,
  2967. fn,
  2968. data;
  2969. /**
  2970. * DATA:
  2971. * *****
  2972. * Every data that is callable from graph methods are stored in this "data"
  2973. * object. This object will be served as context for all these methods,
  2974. * and it is possible to add other type of data in it.
  2975. */
  2976. data = {
  2977. /**
  2978. * SETTINGS FUNCTION:
  2979. * ******************
  2980. */
  2981. settings: settings || _defaultSettingsFunction,
  2982. /**
  2983. * MAIN DATA:
  2984. * **********
  2985. */
  2986. nodesArray: [],
  2987. edgesArray: [],
  2988. /**
  2989. * GLOBAL INDEXES:
  2990. * ***************
  2991. * These indexes just index data by ids.
  2992. */
  2993. nodesIndex: Object.create(null),
  2994. edgesIndex: Object.create(null),
  2995. /**
  2996. * LOCAL INDEXES:
  2997. * **************
  2998. * These indexes refer from node to nodes. Each key is an id, and each
  2999. * value is the array of the ids of related nodes.
  3000. */
  3001. inNeighborsIndex: Object.create(null),
  3002. outNeighborsIndex: Object.create(null),
  3003. allNeighborsIndex: Object.create(null),
  3004. inNeighborsCount: Object.create(null),
  3005. outNeighborsCount: Object.create(null),
  3006. allNeighborsCount: Object.create(null)
  3007. };
  3008. // Execute bindings:
  3009. for (k in _initBindings)
  3010. _initBindings[k].call(data);
  3011. // Add methods to both the scope and the data objects:
  3012. for (k in _methods) {
  3013. fn = __bindGraphMethod(k, data, _methods[k]);
  3014. this[k] = fn;
  3015. data[k] = fn;
  3016. }
  3017. };
  3018. /**
  3019. * A custom tool to bind methods such that function that are bound to it will
  3020. * be executed anytime the method is called.
  3021. *
  3022. * @param {string} methodName The name of the method to bind.
  3023. * @param {object} scope The scope where the method must be executed.
  3024. * @param {function} fn The method itself.
  3025. * @return {function} The new method.
  3026. */
  3027. function __bindGraphMethod(methodName, scope, fn) {
  3028. var result = function() {
  3029. var k,
  3030. res;
  3031. // Execute "before" bound functions:
  3032. for (k in _methodBeforeBindings[methodName])
  3033. _methodBeforeBindings[methodName][k].apply(scope, arguments);
  3034. // Apply the method:
  3035. res = fn.apply(scope, arguments);
  3036. // Execute bound functions:
  3037. for (k in _methodBindings[methodName])
  3038. _methodBindings[methodName][k].apply(scope, arguments);
  3039. // Return res:
  3040. return res;
  3041. };
  3042. return result;
  3043. }
  3044. /**
  3045. * This custom tool function removes every pair key/value from an hash. The
  3046. * goal is to avoid creating a new object while some other references are
  3047. * still hanging in some scopes...
  3048. *
  3049. * @param {object} obj The object to empty.
  3050. * @return {object} The empty object.
  3051. */
  3052. function __emptyObject(obj) {
  3053. var k;
  3054. for (k in obj)
  3055. if (!('hasOwnProperty' in obj) || obj.hasOwnProperty(k))
  3056. delete obj[k];
  3057. return obj;
  3058. }
  3059. /**
  3060. * This global method adds a method that will be bound to the futurly created
  3061. * graph instances.
  3062. *
  3063. * Since these methods will be bound to their scope when the instances are
  3064. * created, it does not use the prototype. Because of that, methods have to
  3065. * be added before instances are created to make them available.
  3066. *
  3067. * Here is an example:
  3068. *
  3069. * > graph.addMethod('getNodesCount', function() {
  3070. * > return this.nodesArray.length;
  3071. * > });
  3072. * >
  3073. * > var myGraph = new graph();
  3074. * > console.log(myGraph.getNodesCount()); // outputs 0
  3075. *
  3076. * @param {string} methodName The name of the method.
  3077. * @param {function} fn The method itself.
  3078. * @return {object} The global graph constructor.
  3079. */
  3080. graph.addMethod = function(methodName, fn) {
  3081. if (
  3082. typeof methodName !== 'string' ||
  3083. typeof fn !== 'function' ||
  3084. arguments.length !== 2
  3085. )
  3086. throw 'addMethod: Wrong arguments.';
  3087. if (_methods[methodName] || graph[methodName])
  3088. throw 'The method "' + methodName + '" already exists.';
  3089. _methods[methodName] = fn;
  3090. _methodBindings[methodName] = Object.create(null);
  3091. _methodBeforeBindings[methodName] = Object.create(null);
  3092. return this;
  3093. };
  3094. /**
  3095. * This global method returns true if the method has already been added, and
  3096. * false else.
  3097. *
  3098. * Here are some examples:
  3099. *
  3100. * > graph.hasMethod('addNode'); // returns true
  3101. * > graph.hasMethod('hasMethod'); // returns true
  3102. * > graph.hasMethod('unexistingMethod'); // returns false
  3103. *
  3104. * @param {string} methodName The name of the method.
  3105. * @return {boolean} The result.
  3106. */
  3107. graph.hasMethod = function(methodName) {
  3108. return !!(_methods[methodName] || graph[methodName]);
  3109. };
  3110. /**
  3111. * This global methods attaches a function to a method. Anytime the specified
  3112. * method is called, the attached function is called right after, with the
  3113. * same arguments and in the same scope. The attached function is called
  3114. * right before if the last argument is true, unless the method is the graph
  3115. * constructor.
  3116. *
  3117. * To attach a function to the graph constructor, use 'constructor' as the
  3118. * method name (first argument).
  3119. *
  3120. * The main idea is to have a clean way to keep custom indexes up to date,
  3121. * for instance:
  3122. *
  3123. * > var timesAddNodeCalled = 0;
  3124. * > graph.attach('addNode', 'timesAddNodeCalledInc', function() {
  3125. * > timesAddNodeCalled++;
  3126. * > });
  3127. * >
  3128. * > var myGraph = new graph();
  3129. * > console.log(timesAddNodeCalled); // outputs 0
  3130. * >
  3131. * > myGraph.addNode({ id: '1' }).addNode({ id: '2' });
  3132. * > console.log(timesAddNodeCalled); // outputs 2
  3133. *
  3134. * The idea for calling a function before is to provide pre-processors, for
  3135. * instance:
  3136. *
  3137. * > var colorPalette = { Person: '#C3CBE1', Place: '#9BDEBD' };
  3138. * > graph.attach('addNode', 'applyNodeColorPalette', function(n) {
  3139. * > n.color = colorPalette[n.category];
  3140. * > }, true);
  3141. * >
  3142. * > var myGraph = new graph();
  3143. * > myGraph.addNode({ id: 'n0', category: 'Person' });
  3144. * > console.log(myGraph.nodes('n0').color); // outputs '#C3CBE1'
  3145. *
  3146. * @param {string} methodName The name of the related method or
  3147. * "constructor".
  3148. * @param {string} key The key to identify the function to attach.
  3149. * @param {function} fn The function to bind.
  3150. * @param {boolean} before If true the function is called right before.
  3151. * @return {object} The global graph constructor.
  3152. */
  3153. graph.attach = function(methodName, key, fn, before) {
  3154. if (
  3155. typeof methodName !== 'string' ||
  3156. typeof key !== 'string' ||
  3157. typeof fn !== 'function' ||
  3158. arguments.length < 3 ||
  3159. arguments.length > 4
  3160. )
  3161. throw 'attach: Wrong arguments.';
  3162. var bindings;
  3163. if (methodName === 'constructor')
  3164. bindings = _initBindings;
  3165. else {
  3166. if (before) {
  3167. if (!_methodBeforeBindings[methodName])
  3168. throw 'The method "' + methodName + '" does not exist.';
  3169. bindings = _methodBeforeBindings[methodName];
  3170. }
  3171. else {
  3172. if (!_methodBindings[methodName])
  3173. throw 'The method "' + methodName + '" does not exist.';
  3174. bindings = _methodBindings[methodName];
  3175. }
  3176. }
  3177. if (bindings[key])
  3178. throw 'A function "' + key + '" is already attached ' +
  3179. 'to the method "' + methodName + '".';
  3180. bindings[key] = fn;
  3181. return this;
  3182. };
  3183. /**
  3184. * Alias of attach(methodName, key, fn, true).
  3185. */
  3186. graph.attachBefore = function(methodName, key, fn) {
  3187. return this.attach(methodName, key, fn, true);
  3188. };
  3189. /**
  3190. * This methods is just an helper to deal with custom indexes. It takes as
  3191. * arguments the name of the index and an object containing all the different
  3192. * functions to bind to the methods.
  3193. *
  3194. * Here is a basic example, that creates an index to keep the number of nodes
  3195. * in the current graph. It also adds a method to provide a getter on that
  3196. * new index:
  3197. *
  3198. * > sigma.classes.graph.addIndex('nodesCount', {
  3199. * > constructor: function() {
  3200. * > this.nodesCount = 0;
  3201. * > },
  3202. * > addNode: function() {
  3203. * > this.nodesCount++;
  3204. * > },
  3205. * > dropNode: function() {
  3206. * > this.nodesCount--;
  3207. * > }
  3208. * > });
  3209. * >
  3210. * > sigma.classes.graph.addMethod('getNodesCount', function() {
  3211. * > return this.nodesCount;
  3212. * > });
  3213. * >
  3214. * > var myGraph = new sigma.classes.graph();
  3215. * > console.log(myGraph.getNodesCount()); // outputs 0
  3216. * >
  3217. * > myGraph.addNode({ id: '1' }).addNode({ id: '2' });
  3218. * > console.log(myGraph.getNodesCount()); // outputs 2
  3219. *
  3220. * @param {string} name The name of the index.
  3221. * @param {object} bindings The object containing the functions to bind.
  3222. * @return {object} The global graph constructor.
  3223. */
  3224. graph.addIndex = function(name, bindings) {
  3225. if (
  3226. typeof name !== 'string' ||
  3227. Object(bindings) !== bindings ||
  3228. arguments.length !== 2
  3229. )
  3230. throw 'addIndex: Wrong arguments.';
  3231. if (_indexes[name])
  3232. throw 'The index "' + name + '" already exists.';
  3233. var k;
  3234. // Store the bindings:
  3235. _indexes[name] = bindings;
  3236. // Attach the bindings:
  3237. for (k in bindings)
  3238. if (typeof bindings[k] !== 'function')
  3239. throw 'The bindings must be functions.';
  3240. else
  3241. graph.attach(k, name, bindings[k]);
  3242. return this;
  3243. };
  3244. /**
  3245. * This method adds a node to the graph. The node must be an object, with a
  3246. * string under the key "id". Except for this, it is possible to add any
  3247. * other attribute, that will be preserved all along the manipulations.
  3248. *
  3249. * If the graph option "clone" has a truthy value, the node will be cloned
  3250. * when added to the graph. Also, if the graph option "immutable" has a
  3251. * truthy value, its id will be defined as immutable.
  3252. *
  3253. * @param {object} node The node to add.
  3254. * @return {object} The graph instance.
  3255. */
  3256. graph.addMethod('addNode', function(node) {
  3257. // Check that the node is an object and has an id:
  3258. if (Object(node) !== node || arguments.length !== 1)
  3259. throw 'addNode: Wrong arguments.';
  3260. if (typeof node.id !== 'string' && typeof node.id !== 'number')
  3261. throw 'The node must have a string or number id.';
  3262. if (this.nodesIndex[node.id])
  3263. throw 'The node "' + node.id + '" already exists.';
  3264. var k,
  3265. id = node.id,
  3266. validNode = Object.create(null);
  3267. // Check the "clone" option:
  3268. if (this.settings('clone')) {
  3269. for (k in node)
  3270. if (k !== 'id')
  3271. validNode[k] = node[k];
  3272. } else
  3273. validNode = node;
  3274. // Check the "immutable" option:
  3275. if (this.settings('immutable'))
  3276. Object.defineProperty(validNode, 'id', {
  3277. value: id,
  3278. enumerable: true
  3279. });
  3280. else
  3281. validNode.id = id;
  3282. // Add empty containers for edges indexes:
  3283. this.inNeighborsIndex[id] = Object.create(null);
  3284. this.outNeighborsIndex[id] = Object.create(null);
  3285. this.allNeighborsIndex[id] = Object.create(null);
  3286. this.inNeighborsCount[id] = 0;
  3287. this.outNeighborsCount[id] = 0;
  3288. this.allNeighborsCount[id] = 0;
  3289. // Add the node to indexes:
  3290. this.nodesArray.push(validNode);
  3291. this.nodesIndex[validNode.id] = validNode;
  3292. // Return the current instance:
  3293. return this;
  3294. });
  3295. /**
  3296. * This method adds an edge to the graph. The edge must be an object, with a
  3297. * string under the key "id", and strings under the keys "source" and
  3298. * "target" that design existing nodes. Except for this, it is possible to
  3299. * add any other attribute, that will be preserved all along the
  3300. * manipulations.
  3301. *
  3302. * If the graph option "clone" has a truthy value, the edge will be cloned
  3303. * when added to the graph. Also, if the graph option "immutable" has a
  3304. * truthy value, its id, source and target will be defined as immutable.
  3305. *
  3306. * @param {object} edge The edge to add.
  3307. * @return {object} The graph instance.
  3308. */
  3309. graph.addMethod('addEdge', function(edge) {
  3310. // Check that the edge is an object and has an id:
  3311. if (Object(edge) !== edge || arguments.length !== 1)
  3312. throw 'addEdge: Wrong arguments.';
  3313. if (typeof edge.id !== 'string' && typeof edge.id !== 'number')
  3314. throw 'The edge must have a string or number id.';
  3315. if ((typeof edge.source !== 'string' && typeof edge.source !== 'number') ||
  3316. !this.nodesIndex[edge.source])
  3317. throw 'The edge source must have an existing node id.';
  3318. if ((typeof edge.target !== 'string' && typeof edge.target !== 'number') ||
  3319. !this.nodesIndex[edge.target])
  3320. throw 'The edge target must have an existing node id.';
  3321. if (this.edgesIndex[edge.id])
  3322. throw 'The edge "' + edge.id + '" already exists.';
  3323. var k,
  3324. validEdge = Object.create(null);
  3325. // Check the "clone" option:
  3326. if (this.settings('clone')) {
  3327. for (k in edge)
  3328. if (k !== 'id' && k !== 'source' && k !== 'target')
  3329. validEdge[k] = edge[k];
  3330. } else
  3331. validEdge = edge;
  3332. // Check the "immutable" option:
  3333. if (this.settings('immutable')) {
  3334. Object.defineProperty(validEdge, 'id', {
  3335. value: edge.id,
  3336. enumerable: true
  3337. });
  3338. Object.defineProperty(validEdge, 'source', {
  3339. value: edge.source,
  3340. enumerable: true
  3341. });
  3342. Object.defineProperty(validEdge, 'target', {
  3343. value: edge.target,
  3344. enumerable: true
  3345. });
  3346. } else {
  3347. validEdge.id = edge.id;
  3348. validEdge.source = edge.source;
  3349. validEdge.target = edge.target;
  3350. }
  3351. // Add the edge to indexes:
  3352. this.edgesArray.push(validEdge);
  3353. this.edgesIndex[validEdge.id] = validEdge;
  3354. if (!this.inNeighborsIndex[validEdge.target][validEdge.source])
  3355. this.inNeighborsIndex[validEdge.target][validEdge.source] =
  3356. Object.create(null);
  3357. this.inNeighborsIndex[validEdge.target][validEdge.source][validEdge.id] =
  3358. validEdge;
  3359. if (!this.outNeighborsIndex[validEdge.source][validEdge.target])
  3360. this.outNeighborsIndex[validEdge.source][validEdge.target] =
  3361. Object.create(null);
  3362. this.outNeighborsIndex[validEdge.source][validEdge.target][validEdge.id] =
  3363. validEdge;
  3364. if (!this.allNeighborsIndex[validEdge.source][validEdge.target])
  3365. this.allNeighborsIndex[validEdge.source][validEdge.target] =
  3366. Object.create(null);
  3367. this.allNeighborsIndex[validEdge.source][validEdge.target][validEdge.id] =
  3368. validEdge;
  3369. if (validEdge.target !== validEdge.source) {
  3370. if (!this.allNeighborsIndex[validEdge.target][validEdge.source])
  3371. this.allNeighborsIndex[validEdge.target][validEdge.source] =
  3372. Object.create(null);
  3373. this.allNeighborsIndex[validEdge.target][validEdge.source][validEdge.id] =
  3374. validEdge;
  3375. }
  3376. // Keep counts up to date:
  3377. this.inNeighborsCount[validEdge.target]++;
  3378. this.outNeighborsCount[validEdge.source]++;
  3379. this.allNeighborsCount[validEdge.target]++;
  3380. this.allNeighborsCount[validEdge.source]++;
  3381. return this;
  3382. });
  3383. /**
  3384. * This method drops a node from the graph. It also removes each edge that is
  3385. * bound to it, through the dropEdge method. An error is thrown if the node
  3386. * does not exist.
  3387. *
  3388. * @param {string} id The node id.
  3389. * @return {object} The graph instance.
  3390. */
  3391. graph.addMethod('dropNode', function(id) {
  3392. // Check that the arguments are valid:
  3393. if ((typeof id !== 'string' && typeof id !== 'number') ||
  3394. arguments.length !== 1)
  3395. throw 'dropNode: Wrong arguments.';
  3396. if (!this.nodesIndex[id])
  3397. throw 'The node "' + id + '" does not exist.';
  3398. var i, k, l;
  3399. // Remove the node from indexes:
  3400. delete this.nodesIndex[id];
  3401. for (i = 0, l = this.nodesArray.length; i < l; i++)
  3402. if (this.nodesArray[i].id === id) {
  3403. this.nodesArray.splice(i, 1);
  3404. break;
  3405. }
  3406. // Remove related edges:
  3407. for (i = this.edgesArray.length - 1; i >= 0; i--)
  3408. if (this.edgesArray[i].source === id || this.edgesArray[i].target === id)
  3409. this.dropEdge(this.edgesArray[i].id);
  3410. // Remove related edge indexes:
  3411. delete this.inNeighborsIndex[id];
  3412. delete this.outNeighborsIndex[id];
  3413. delete this.allNeighborsIndex[id];
  3414. delete this.inNeighborsCount[id];
  3415. delete this.outNeighborsCount[id];
  3416. delete this.allNeighborsCount[id];
  3417. for (k in this.nodesIndex) {
  3418. delete this.inNeighborsIndex[k][id];
  3419. delete this.outNeighborsIndex[k][id];
  3420. delete this.allNeighborsIndex[k][id];
  3421. }
  3422. return this;
  3423. });
  3424. /**
  3425. * This method drops an edge from the graph. An error is thrown if the edge
  3426. * does not exist.
  3427. *
  3428. * @param {string} id The edge id.
  3429. * @return {object} The graph instance.
  3430. */
  3431. graph.addMethod('dropEdge', function(id) {
  3432. // Check that the arguments are valid:
  3433. if ((typeof id !== 'string' && typeof id !== 'number') ||
  3434. arguments.length !== 1)
  3435. throw 'dropEdge: Wrong arguments.';
  3436. if (!this.edgesIndex[id])
  3437. throw 'The edge "' + id + '" does not exist.';
  3438. var i, l, edge;
  3439. // Remove the edge from indexes:
  3440. edge = this.edgesIndex[id];
  3441. delete this.edgesIndex[id];
  3442. for (i = 0, l = this.edgesArray.length; i < l; i++)
  3443. if (this.edgesArray[i].id === id) {
  3444. this.edgesArray.splice(i, 1);
  3445. break;
  3446. }
  3447. delete this.inNeighborsIndex[edge.target][edge.source][edge.id];
  3448. if (!Object.keys(this.inNeighborsIndex[edge.target][edge.source]).length)
  3449. delete this.inNeighborsIndex[edge.target][edge.source];
  3450. delete this.outNeighborsIndex[edge.source][edge.target][edge.id];
  3451. if (!Object.keys(this.outNeighborsIndex[edge.source][edge.target]).length)
  3452. delete this.outNeighborsIndex[edge.source][edge.target];
  3453. delete this.allNeighborsIndex[edge.source][edge.target][edge.id];
  3454. if (!Object.keys(this.allNeighborsIndex[edge.source][edge.target]).length)
  3455. delete this.allNeighborsIndex[edge.source][edge.target];
  3456. if (edge.target !== edge.source) {
  3457. delete this.allNeighborsIndex[edge.target][edge.source][edge.id];
  3458. if (!Object.keys(this.allNeighborsIndex[edge.target][edge.source]).length)
  3459. delete this.allNeighborsIndex[edge.target][edge.source];
  3460. }
  3461. this.inNeighborsCount[edge.target]--;
  3462. this.outNeighborsCount[edge.source]--;
  3463. this.allNeighborsCount[edge.source]--;
  3464. this.allNeighborsCount[edge.target]--;
  3465. return this;
  3466. });
  3467. /**
  3468. * This method destroys the current instance. It basically empties each index
  3469. * and methods attached to the graph.
  3470. */
  3471. graph.addMethod('kill', function() {
  3472. // Delete arrays:
  3473. this.nodesArray.length = 0;
  3474. this.edgesArray.length = 0;
  3475. delete this.nodesArray;
  3476. delete this.edgesArray;
  3477. // Delete indexes:
  3478. delete this.nodesIndex;
  3479. delete this.edgesIndex;
  3480. delete this.inNeighborsIndex;
  3481. delete this.outNeighborsIndex;
  3482. delete this.allNeighborsIndex;
  3483. delete this.inNeighborsCount;
  3484. delete this.outNeighborsCount;
  3485. delete this.allNeighborsCount;
  3486. });
  3487. /**
  3488. * This method empties the nodes and edges arrays, as well as the different
  3489. * indexes.
  3490. *
  3491. * @return {object} The graph instance.
  3492. */
  3493. graph.addMethod('clear', function() {
  3494. this.nodesArray.length = 0;
  3495. this.edgesArray.length = 0;
  3496. // Due to GC issues, I prefer not to create new object. These objects are
  3497. // only available from the methods and attached functions, but still, it is
  3498. // better to prevent ghost references to unrelevant data...
  3499. __emptyObject(this.nodesIndex);
  3500. __emptyObject(this.edgesIndex);
  3501. __emptyObject(this.nodesIndex);
  3502. __emptyObject(this.inNeighborsIndex);
  3503. __emptyObject(this.outNeighborsIndex);
  3504. __emptyObject(this.allNeighborsIndex);
  3505. __emptyObject(this.inNeighborsCount);
  3506. __emptyObject(this.outNeighborsCount);
  3507. __emptyObject(this.allNeighborsCount);
  3508. return this;
  3509. });
  3510. /**
  3511. * This method reads an object and adds the nodes and edges, through the
  3512. * proper methods "addNode" and "addEdge".
  3513. *
  3514. * Here is an example:
  3515. *
  3516. * > var myGraph = new graph();
  3517. * > myGraph.read({
  3518. * > nodes: [
  3519. * > { id: 'n0' },
  3520. * > { id: 'n1' }
  3521. * > ],
  3522. * > edges: [
  3523. * > {
  3524. * > id: 'e0',
  3525. * > source: 'n0',
  3526. * > target: 'n1'
  3527. * > }
  3528. * > ]
  3529. * > });
  3530. * >
  3531. * > console.log(
  3532. * > myGraph.nodes().length,
  3533. * > myGraph.edges().length
  3534. * > ); // outputs 2 1
  3535. *
  3536. * @param {object} g The graph object.
  3537. * @return {object} The graph instance.
  3538. */
  3539. graph.addMethod('read', function(g) {
  3540. var i,
  3541. a,
  3542. l;
  3543. a = g.nodes || [];
  3544. for (i = 0, l = a.length; i < l; i++)
  3545. this.addNode(a[i]);
  3546. a = g.edges || [];
  3547. for (i = 0, l = a.length; i < l; i++)
  3548. this.addEdge(a[i]);
  3549. return this;
  3550. });
  3551. /**
  3552. * This methods returns one or several nodes, depending on how it is called.
  3553. *
  3554. * To get the array of nodes, call "nodes" without argument. To get a
  3555. * specific node, call it with the id of the node. The get multiple node,
  3556. * call it with an array of ids, and it will return the array of nodes, in
  3557. * the same order.
  3558. *
  3559. * @param {?(string|array)} v Eventually one id, an array of ids.
  3560. * @return {object|array} The related node or array of nodes.
  3561. */
  3562. graph.addMethod('nodes', function(v) {
  3563. // Clone the array of nodes and return it:
  3564. if (!arguments.length)
  3565. return this.nodesArray.slice(0);
  3566. // Return the related node:
  3567. if (arguments.length === 1 &&
  3568. (typeof v === 'string' || typeof v === 'number'))
  3569. return this.nodesIndex[v];
  3570. // Return an array of the related node:
  3571. if (
  3572. arguments.length === 1 &&
  3573. Object.prototype.toString.call(v) === '[object Array]'
  3574. ) {
  3575. var i,
  3576. l,
  3577. a = [];
  3578. for (i = 0, l = v.length; i < l; i++)
  3579. if (typeof v[i] === 'string' || typeof v[i] === 'number')
  3580. a.push(this.nodesIndex[v[i]]);
  3581. else
  3582. throw 'nodes: Wrong arguments.';
  3583. return a;
  3584. }
  3585. throw 'nodes: Wrong arguments.';
  3586. });
  3587. /**
  3588. * This methods returns the degree of one or several nodes, depending on how
  3589. * it is called. It is also possible to get incoming or outcoming degrees
  3590. * instead by specifying 'in' or 'out' as a second argument.
  3591. *
  3592. * @param {string|array} v One id, an array of ids.
  3593. * @param {?string} which Which degree is required. Values are 'in',
  3594. * 'out', and by default the normal degree.
  3595. * @return {number|array} The related degree or array of degrees.
  3596. */
  3597. graph.addMethod('degree', function(v, which) {
  3598. // Check which degree is required:
  3599. which = {
  3600. 'in': this.inNeighborsCount,
  3601. 'out': this.outNeighborsCount
  3602. }[which || ''] || this.allNeighborsCount;
  3603. // Return the related node:
  3604. if (typeof v === 'string' || typeof v === 'number')
  3605. return which[v];
  3606. // Return an array of the related node:
  3607. if (Object.prototype.toString.call(v) === '[object Array]') {
  3608. var i,
  3609. l,
  3610. a = [];
  3611. for (i = 0, l = v.length; i < l; i++)
  3612. if (typeof v[i] === 'string' || typeof v[i] === 'number')
  3613. a.push(which[v[i]]);
  3614. else
  3615. throw 'degree: Wrong arguments.';
  3616. return a;
  3617. }
  3618. throw 'degree: Wrong arguments.';
  3619. });
  3620. /**
  3621. * This methods returns one or several edges, depending on how it is called.
  3622. *
  3623. * To get the array of edges, call "edges" without argument. To get a
  3624. * specific edge, call it with the id of the edge. The get multiple edge,
  3625. * call it with an array of ids, and it will return the array of edges, in
  3626. * the same order.
  3627. *
  3628. * @param {?(string|array)} v Eventually one id, an array of ids.
  3629. * @return {object|array} The related edge or array of edges.
  3630. */
  3631. graph.addMethod('edges', function(v) {
  3632. // Clone the array of edges and return it:
  3633. if (!arguments.length)
  3634. return this.edgesArray.slice(0);
  3635. // Return the related edge:
  3636. if (arguments.length === 1 &&
  3637. (typeof v === 'string' || typeof v === 'number'))
  3638. return this.edgesIndex[v];
  3639. // Return an array of the related edge:
  3640. if (
  3641. arguments.length === 1 &&
  3642. Object.prototype.toString.call(v) === '[object Array]'
  3643. ) {
  3644. var i,
  3645. l,
  3646. a = [];
  3647. for (i = 0, l = v.length; i < l; i++)
  3648. if (typeof v[i] === 'string' || typeof v[i] === 'number')
  3649. a.push(this.edgesIndex[v[i]]);
  3650. else
  3651. throw 'edges: Wrong arguments.';
  3652. return a;
  3653. }
  3654. throw 'edges: Wrong arguments.';
  3655. });
  3656. /**
  3657. * EXPORT:
  3658. * *******
  3659. */
  3660. if (typeof sigma !== 'undefined') {
  3661. sigma.classes = sigma.classes || Object.create(null);
  3662. sigma.classes.graph = graph;
  3663. } else if (typeof exports !== 'undefined') {
  3664. if (typeof module !== 'undefined' && module.exports)
  3665. exports = module.exports = graph;
  3666. exports.graph = graph;
  3667. } else
  3668. this.graph = graph;
  3669. }).call(this);
  3670. ;(function(undefined) {
  3671. 'use strict';
  3672. if (typeof sigma === 'undefined')
  3673. throw 'sigma is not declared';
  3674. sigma.utils.pkg('sigma.classes');
  3675. /**
  3676. * The camera constructor. It just initializes its attributes and methods.
  3677. *
  3678. * @param {string} id The id.
  3679. * @param {sigma.classes.graph} graph The graph.
  3680. * @param {configurable} settings The settings function.
  3681. * @param {?object} options Eventually some overriding options.
  3682. * @return {camera} Returns the fresh new camera instance.
  3683. */
  3684. sigma.classes.camera = function(id, graph, settings, options) {
  3685. sigma.classes.dispatcher.extend(this);
  3686. Object.defineProperty(this, 'graph', {
  3687. value: graph
  3688. });
  3689. Object.defineProperty(this, 'id', {
  3690. value: id
  3691. });
  3692. Object.defineProperty(this, 'readPrefix', {
  3693. value: 'read_cam' + id + ':'
  3694. });
  3695. Object.defineProperty(this, 'prefix', {
  3696. value: 'cam' + id + ':'
  3697. });
  3698. this.x = 0;
  3699. this.y = 0;
  3700. this.ratio = 1;
  3701. this.angle = 0;
  3702. this.isAnimated = false;
  3703. this.settings = (typeof options === 'object' && options) ?
  3704. settings.embedObject(options) :
  3705. settings;
  3706. };
  3707. /**
  3708. * Updates the camera position.
  3709. *
  3710. * @param {object} coordinates The new coordinates object.
  3711. * @return {camera} Returns the camera.
  3712. */
  3713. sigma.classes.camera.prototype.goTo = function(coordinates) {
  3714. if (!this.settings('enableCamera'))
  3715. return this;
  3716. var i,
  3717. l,
  3718. c = coordinates || {},
  3719. keys = ['x', 'y', 'ratio', 'angle'];
  3720. for (i = 0, l = keys.length; i < l; i++)
  3721. if (c[keys[i]] !== undefined) {
  3722. if (typeof c[keys[i]] === 'number' && !isNaN(c[keys[i]]))
  3723. this[keys[i]] = c[keys[i]];
  3724. else
  3725. throw 'Value for "' + keys[i] + '" is not a number.';
  3726. }
  3727. this.dispatchEvent('coordinatesUpdated');
  3728. return this;
  3729. };
  3730. /**
  3731. * This method takes a graph and computes for each node and edges its
  3732. * coordinates relatively to the center of the camera. Basically, it will
  3733. * compute the coordinates that will be used by the graphic renderers.
  3734. *
  3735. * Since it should be possible to use different cameras and different
  3736. * renderers, it is possible to specify a prefix to put before the new
  3737. * coordinates (to get something like "node.camera1_x")
  3738. *
  3739. * @param {?string} read The prefix of the coordinates to read.
  3740. * @param {?string} write The prefix of the coordinates to write.
  3741. * @param {?object} options Eventually an object of options. Those can be:
  3742. * - A restricted nodes array.
  3743. * - A restricted edges array.
  3744. * - A width.
  3745. * - A height.
  3746. * @return {camera} Returns the camera.
  3747. */
  3748. sigma.classes.camera.prototype.applyView = function(read, write, options) {
  3749. options = options || {};
  3750. write = write !== undefined ? write : this.prefix;
  3751. read = read !== undefined ? read : this.readPrefix;
  3752. var nodes = options.nodes || this.graph.nodes(),
  3753. edges = options.edges || this.graph.edges();
  3754. var i,
  3755. l,
  3756. node,
  3757. cos = Math.cos(this.angle),
  3758. sin = Math.sin(this.angle);
  3759. for (i = 0, l = nodes.length; i < l; i++) {
  3760. node = nodes[i];
  3761. node[write + 'x'] =
  3762. (
  3763. ((node[read + 'x'] || 0) - this.x) * cos +
  3764. ((node[read + 'y'] || 0) - this.y) * sin
  3765. ) / this.ratio + (options.width || 0) / 2;
  3766. node[write + 'y'] =
  3767. (
  3768. ((node[read + 'y'] || 0) - this.y) * cos -
  3769. ((node[read + 'x'] || 0) - this.x) * sin
  3770. ) / this.ratio + (options.height || 0) / 2;
  3771. node[write + 'size'] =
  3772. (node[read + 'size'] || 0) /
  3773. Math.pow(this.ratio, this.settings('nodesPowRatio'));
  3774. }
  3775. for (i = 0, l = edges.length; i < l; i++) {
  3776. edges[i][write + 'size'] =
  3777. (edges[i][read + 'size'] || 0) /
  3778. Math.pow(this.ratio, this.settings('edgesPowRatio'));
  3779. }
  3780. return this;
  3781. };
  3782. /**
  3783. * This function converts the coordinates of a point from the frame of the
  3784. * camera to the frame of the graph.
  3785. *
  3786. * @param {number} x The X coordinate of the point in the frame of the
  3787. * camera.
  3788. * @param {number} y The Y coordinate of the point in the frame of the
  3789. * camera.
  3790. * @return {object} The point coordinates in the frame of the graph.
  3791. */
  3792. sigma.classes.camera.prototype.graphPosition = function(x, y, vector) {
  3793. var X = 0,
  3794. Y = 0,
  3795. cos = Math.cos(this.angle),
  3796. sin = Math.sin(this.angle);
  3797. // Revert the origin differential vector:
  3798. if (!vector) {
  3799. X = - (this.x * cos + this.y * sin) / this.ratio;
  3800. Y = - (this.y * cos - this.x * sin) / this.ratio;
  3801. }
  3802. return {
  3803. x: (x * cos + y * sin) / this.ratio + X,
  3804. y: (y * cos - x * sin) / this.ratio + Y
  3805. };
  3806. };
  3807. /**
  3808. * This function converts the coordinates of a point from the frame of the
  3809. * graph to the frame of the camera.
  3810. *
  3811. * @param {number} x The X coordinate of the point in the frame of the
  3812. * graph.
  3813. * @param {number} y The Y coordinate of the point in the frame of the
  3814. * graph.
  3815. * @return {object} The point coordinates in the frame of the camera.
  3816. */
  3817. sigma.classes.camera.prototype.cameraPosition = function(x, y, vector) {
  3818. var X = 0,
  3819. Y = 0,
  3820. cos = Math.cos(this.angle),
  3821. sin = Math.sin(this.angle);
  3822. // Revert the origin differential vector:
  3823. if (!vector) {
  3824. X = - (this.x * cos + this.y * sin) / this.ratio;
  3825. Y = - (this.y * cos - this.x * sin) / this.ratio;
  3826. }
  3827. return {
  3828. x: ((x - X) * cos - (y - Y) * sin) * this.ratio,
  3829. y: ((y - Y) * cos + (x - X) * sin) * this.ratio
  3830. };
  3831. };
  3832. /**
  3833. * This method returns the transformation matrix of the camera. This is
  3834. * especially useful to apply the camera view directly in shaders, in case of
  3835. * WebGL rendering.
  3836. *
  3837. * @return {array} The transformation matrix.
  3838. */
  3839. sigma.classes.camera.prototype.getMatrix = function() {
  3840. var scale = sigma.utils.matrices.scale(1 / this.ratio),
  3841. rotation = sigma.utils.matrices.rotation(this.angle),
  3842. translation = sigma.utils.matrices.translation(-this.x, -this.y),
  3843. matrix = sigma.utils.matrices.multiply(
  3844. translation,
  3845. sigma.utils.matrices.multiply(
  3846. rotation,
  3847. scale
  3848. )
  3849. );
  3850. return matrix;
  3851. };
  3852. /**
  3853. * Taking a width and a height as parameters, this method returns the
  3854. * coordinates of the rectangle representing the camera on screen, in the
  3855. * graph's referentiel.
  3856. *
  3857. * To keep displaying labels of nodes going out of the screen, the method
  3858. * keeps a margin around the screen in the returned rectangle.
  3859. *
  3860. * @param {number} width The width of the screen.
  3861. * @param {number} height The height of the screen.
  3862. * @return {object} The rectangle as x1, y1, x2 and y2, representing
  3863. * two opposite points.
  3864. */
  3865. sigma.classes.camera.prototype.getRectangle = function(width, height) {
  3866. var widthVect = this.cameraPosition(width, 0, true),
  3867. heightVect = this.cameraPosition(0, height, true),
  3868. centerVect = this.cameraPosition(width / 2, height / 2, true),
  3869. marginX = this.cameraPosition(width / 4, 0, true).x,
  3870. marginY = this.cameraPosition(0, height / 4, true).y;
  3871. return {
  3872. x1: this.x - centerVect.x - marginX,
  3873. y1: this.y - centerVect.y - marginY,
  3874. x2: this.x - centerVect.x + marginX + widthVect.x,
  3875. y2: this.y - centerVect.y - marginY + widthVect.y,
  3876. height: Math.sqrt(
  3877. Math.pow(heightVect.x, 2) +
  3878. Math.pow(heightVect.y + 2 * marginY, 2)
  3879. )
  3880. };
  3881. };
  3882. }).call(this);
  3883. ;(function(undefined) {
  3884. 'use strict';
  3885. /**
  3886. * Sigma Quadtree Module
  3887. * =====================
  3888. *
  3889. * Author: Guillaume Plique (Yomguithereal)
  3890. * Version: 0.2
  3891. */
  3892. /**
  3893. * Quad Geometric Operations
  3894. * -------------------------
  3895. *
  3896. * A useful batch of geometric operations used by the quadtree.
  3897. */
  3898. var _geom = {
  3899. /**
  3900. * Transforms a graph node with x, y and size into an
  3901. * axis-aligned square.
  3902. *
  3903. * @param {object} A graph node with at least a point (x, y) and a size.
  3904. * @return {object} A square: two points (x1, y1), (x2, y2) and height.
  3905. */
  3906. pointToSquare: function(n) {
  3907. return {
  3908. x1: n.x - n.size,
  3909. y1: n.y - n.size,
  3910. x2: n.x + n.size,
  3911. y2: n.y - n.size,
  3912. height: n.size * 2
  3913. };
  3914. },
  3915. /**
  3916. * Checks whether a rectangle is axis-aligned.
  3917. *
  3918. * @param {object} A rectangle defined by two points
  3919. * (x1, y1) and (x2, y2).
  3920. * @return {boolean} True if the rectangle is axis-aligned.
  3921. */
  3922. isAxisAligned: function(r) {
  3923. return r.x1 === r.x2 || r.y1 === r.y2;
  3924. },
  3925. /**
  3926. * Compute top points of an axis-aligned rectangle. This is useful in
  3927. * cases when the rectangle has been rotated (left, right or bottom up) and
  3928. * later operations need to know the top points.
  3929. *
  3930. * @param {object} An axis-aligned rectangle defined by two points
  3931. * (x1, y1), (x2, y2) and height.
  3932. * @return {object} A rectangle: two points (x1, y1), (x2, y2) and height.
  3933. */
  3934. axisAlignedTopPoints: function(r) {
  3935. // Basic
  3936. if (r.y1 === r.y2 && r.x1 < r.x2)
  3937. return r;
  3938. // Rotated to right
  3939. if (r.x1 === r.x2 && r.y2 > r.y1)
  3940. return {
  3941. x1: r.x1 - r.height, y1: r.y1,
  3942. x2: r.x1, y2: r.y1,
  3943. height: r.height
  3944. };
  3945. // Rotated to left
  3946. if (r.x1 === r.x2 && r.y2 < r.y1)
  3947. return {
  3948. x1: r.x1, y1: r.y2,
  3949. x2: r.x2 + r.height, y2: r.y2,
  3950. height: r.height
  3951. };
  3952. // Bottom's up
  3953. return {
  3954. x1: r.x2, y1: r.y1 - r.height,
  3955. x2: r.x1, y2: r.y1 - r.height,
  3956. height: r.height
  3957. };
  3958. },
  3959. /**
  3960. * Get coordinates of a rectangle's lower left corner from its top points.
  3961. *
  3962. * @param {object} A rectangle defined by two points (x1, y1) and (x2, y2).
  3963. * @return {object} Coordinates of the corner (x, y).
  3964. */
  3965. lowerLeftCoor: function(r) {
  3966. var width = (
  3967. Math.sqrt(
  3968. Math.pow(r.x2 - r.x1, 2) +
  3969. Math.pow(r.y2 - r.y1, 2)
  3970. )
  3971. );
  3972. return {
  3973. x: r.x1 - (r.y2 - r.y1) * r.height / width,
  3974. y: r.y1 + (r.x2 - r.x1) * r.height / width
  3975. };
  3976. },
  3977. /**
  3978. * Get coordinates of a rectangle's lower right corner from its top points
  3979. * and its lower left corner.
  3980. *
  3981. * @param {object} A rectangle defined by two points (x1, y1) and (x2, y2).
  3982. * @param {object} A corner's coordinates (x, y).
  3983. * @return {object} Coordinates of the corner (x, y).
  3984. */
  3985. lowerRightCoor: function(r, llc) {
  3986. return {
  3987. x: llc.x - r.x1 + r.x2,
  3988. y: llc.y - r.y1 + r.y2
  3989. };
  3990. },
  3991. /**
  3992. * Get the coordinates of all the corners of a rectangle from its top point.
  3993. *
  3994. * @param {object} A rectangle defined by two points (x1, y1) and (x2, y2).
  3995. * @return {array} An array of the four corners' coordinates (x, y).
  3996. */
  3997. rectangleCorners: function(r) {
  3998. var llc = this.lowerLeftCoor(r),
  3999. lrc = this.lowerRightCoor(r, llc);
  4000. return [
  4001. {x: r.x1, y: r.y1},
  4002. {x: r.x2, y: r.y2},
  4003. {x: llc.x, y: llc.y},
  4004. {x: lrc.x, y: lrc.y}
  4005. ];
  4006. },
  4007. /**
  4008. * Split a square defined by its boundaries into four.
  4009. *
  4010. * @param {object} Boundaries of the square (x, y, width, height).
  4011. * @return {array} An array containing the four new squares, themselves
  4012. * defined by an array of their four corners (x, y).
  4013. */
  4014. splitSquare: function(b) {
  4015. return [
  4016. [
  4017. {x: b.x, y: b.y},
  4018. {x: b.x + b.width / 2, y: b.y},
  4019. {x: b.x, y: b.y + b.height / 2},
  4020. {x: b.x + b.width / 2, y: b.y + b.height / 2}
  4021. ],
  4022. [
  4023. {x: b.x + b.width / 2, y: b.y},
  4024. {x: b.x + b.width, y: b.y},
  4025. {x: b.x + b.width / 2, y: b.y + b.height / 2},
  4026. {x: b.x + b.width, y: b.y + b.height / 2}
  4027. ],
  4028. [
  4029. {x: b.x, y: b.y + b.height / 2},
  4030. {x: b.x + b.width / 2, y: b.y + b.height / 2},
  4031. {x: b.x, y: b.y + b.height},
  4032. {x: b.x + b.width / 2, y: b.y + b.height}
  4033. ],
  4034. [
  4035. {x: b.x + b.width / 2, y: b.y + b.height / 2},
  4036. {x: b.x + b.width, y: b.y + b.height / 2},
  4037. {x: b.x + b.width / 2, y: b.y + b.height},
  4038. {x: b.x + b.width, y: b.y + b.height}
  4039. ]
  4040. ];
  4041. },
  4042. /**
  4043. * Compute the four axis between corners of rectangle A and corners of
  4044. * rectangle B. This is needed later to check an eventual collision.
  4045. *
  4046. * @param {array} An array of rectangle A's four corners (x, y).
  4047. * @param {array} An array of rectangle B's four corners (x, y).
  4048. * @return {array} An array of four axis defined by their coordinates (x,y).
  4049. */
  4050. axis: function(c1, c2) {
  4051. return [
  4052. {x: c1[1].x - c1[0].x, y: c1[1].y - c1[0].y},
  4053. {x: c1[1].x - c1[3].x, y: c1[1].y - c1[3].y},
  4054. {x: c2[0].x - c2[2].x, y: c2[0].y - c2[2].y},
  4055. {x: c2[0].x - c2[1].x, y: c2[0].y - c2[1].y}
  4056. ];
  4057. },
  4058. /**
  4059. * Project a rectangle's corner on an axis.
  4060. *
  4061. * @param {object} Coordinates of a corner (x, y).
  4062. * @param {object} Coordinates of an axis (x, y).
  4063. * @return {object} The projection defined by coordinates (x, y).
  4064. */
  4065. projection: function(c, a) {
  4066. var l = (
  4067. (c.x * a.x + c.y * a.y) /
  4068. (Math.pow(a.x, 2) + Math.pow(a.y, 2))
  4069. );
  4070. return {
  4071. x: l * a.x,
  4072. y: l * a.y
  4073. };
  4074. },
  4075. /**
  4076. * Check whether two rectangles collide on one particular axis.
  4077. *
  4078. * @param {object} An axis' coordinates (x, y).
  4079. * @param {array} Rectangle A's corners.
  4080. * @param {array} Rectangle B's corners.
  4081. * @return {boolean} True if the rectangles collide on the axis.
  4082. */
  4083. axisCollision: function(a, c1, c2) {
  4084. var sc1 = [],
  4085. sc2 = [];
  4086. for (var ci = 0; ci < 4; ci++) {
  4087. var p1 = this.projection(c1[ci], a),
  4088. p2 = this.projection(c2[ci], a);
  4089. sc1.push(p1.x * a.x + p1.y * a.y);
  4090. sc2.push(p2.x * a.x + p2.y * a.y);
  4091. }
  4092. var maxc1 = Math.max.apply(Math, sc1),
  4093. maxc2 = Math.max.apply(Math, sc2),
  4094. minc1 = Math.min.apply(Math, sc1),
  4095. minc2 = Math.min.apply(Math, sc2);
  4096. return (minc2 <= maxc1 && maxc2 >= minc1);
  4097. },
  4098. /**
  4099. * Check whether two rectangles collide on each one of their four axis. If
  4100. * all axis collide, then the two rectangles do collide on the plane.
  4101. *
  4102. * @param {array} Rectangle A's corners.
  4103. * @param {array} Rectangle B's corners.
  4104. * @return {boolean} True if the rectangles collide.
  4105. */
  4106. collision: function(c1, c2) {
  4107. var axis = this.axis(c1, c2),
  4108. col = true;
  4109. for (var i = 0; i < 4; i++)
  4110. col = col && this.axisCollision(axis[i], c1, c2);
  4111. return col;
  4112. }
  4113. };
  4114. /**
  4115. * Quad Functions
  4116. * ------------
  4117. *
  4118. * The Quadtree functions themselves.
  4119. * For each of those functions, we consider that in a splitted quad, the
  4120. * index of each node is the following:
  4121. * 0: top left
  4122. * 1: top right
  4123. * 2: bottom left
  4124. * 3: bottom right
  4125. *
  4126. * Moreover, the hereafter quad's philosophy is to consider that if an element
  4127. * collides with more than one nodes, this element belongs to each of the
  4128. * nodes it collides with where other would let it lie on a higher node.
  4129. */
  4130. /**
  4131. * Get the index of the node containing the point in the quad
  4132. *
  4133. * @param {object} point A point defined by coordinates (x, y).
  4134. * @param {object} quadBounds Boundaries of the quad (x, y, width, heigth).
  4135. * @return {integer} The index of the node containing the point.
  4136. */
  4137. function _quadIndex(point, quadBounds) {
  4138. var xmp = quadBounds.x + quadBounds.width / 2,
  4139. ymp = quadBounds.y + quadBounds.height / 2,
  4140. top = (point.y < ymp),
  4141. left = (point.x < xmp);
  4142. if (top) {
  4143. if (left)
  4144. return 0;
  4145. else
  4146. return 1;
  4147. }
  4148. else {
  4149. if (left)
  4150. return 2;
  4151. else
  4152. return 3;
  4153. }
  4154. }
  4155. /**
  4156. * Get a list of indexes of nodes containing an axis-aligned rectangle
  4157. *
  4158. * @param {object} rectangle A rectangle defined by two points (x1, y1),
  4159. * (x2, y2) and height.
  4160. * @param {array} quadCorners An array of the quad nodes' corners.
  4161. * @return {array} An array of indexes containing one to
  4162. * four integers.
  4163. */
  4164. function _quadIndexes(rectangle, quadCorners) {
  4165. var indexes = [];
  4166. // Iterating through quads
  4167. for (var i = 0; i < 4; i++)
  4168. if ((rectangle.x2 >= quadCorners[i][0].x) &&
  4169. (rectangle.x1 <= quadCorners[i][1].x) &&
  4170. (rectangle.y1 + rectangle.height >= quadCorners[i][0].y) &&
  4171. (rectangle.y1 <= quadCorners[i][2].y))
  4172. indexes.push(i);
  4173. return indexes;
  4174. }
  4175. /**
  4176. * Get a list of indexes of nodes containing a non-axis-aligned rectangle
  4177. *
  4178. * @param {array} corners An array containing each corner of the
  4179. * rectangle defined by its coordinates (x, y).
  4180. * @param {array} quadCorners An array of the quad nodes' corners.
  4181. * @return {array} An array of indexes containing one to
  4182. * four integers.
  4183. */
  4184. function _quadCollision(corners, quadCorners) {
  4185. var indexes = [];
  4186. // Iterating through quads
  4187. for (var i = 0; i < 4; i++)
  4188. if (_geom.collision(corners, quadCorners[i]))
  4189. indexes.push(i);
  4190. return indexes;
  4191. }
  4192. /**
  4193. * Subdivide a quad by creating a node at a precise index. The function does
  4194. * not generate all four nodes not to potentially create unused nodes.
  4195. *
  4196. * @param {integer} index The index of the node to create.
  4197. * @param {object} quad The quad object to subdivide.
  4198. * @return {object} A new quad representing the node created.
  4199. */
  4200. function _quadSubdivide(index, quad) {
  4201. var next = quad.level + 1,
  4202. subw = Math.round(quad.bounds.width / 2),
  4203. subh = Math.round(quad.bounds.height / 2),
  4204. qx = Math.round(quad.bounds.x),
  4205. qy = Math.round(quad.bounds.y),
  4206. x,
  4207. y;
  4208. switch (index) {
  4209. case 0:
  4210. x = qx;
  4211. y = qy;
  4212. break;
  4213. case 1:
  4214. x = qx + subw;
  4215. y = qy;
  4216. break;
  4217. case 2:
  4218. x = qx;
  4219. y = qy + subh;
  4220. break;
  4221. case 3:
  4222. x = qx + subw;
  4223. y = qy + subh;
  4224. break;
  4225. }
  4226. return _quadTree(
  4227. {x: x, y: y, width: subw, height: subh},
  4228. next,
  4229. quad.maxElements,
  4230. quad.maxLevel
  4231. );
  4232. }
  4233. /**
  4234. * Recursively insert an element into the quadtree. Only points
  4235. * with size, i.e. axis-aligned squares, may be inserted with this
  4236. * method.
  4237. *
  4238. * @param {object} el The element to insert in the quadtree.
  4239. * @param {object} sizedPoint A sized point defined by two top points
  4240. * (x1, y1), (x2, y2) and height.
  4241. * @param {object} quad The quad in which to insert the element.
  4242. * @return {undefined} The function does not return anything.
  4243. */
  4244. function _quadInsert(el, sizedPoint, quad) {
  4245. if (quad.level < quad.maxLevel) {
  4246. // Searching appropriate quads
  4247. var indexes = _quadIndexes(sizedPoint, quad.corners);
  4248. // Iterating
  4249. for (var i = 0, l = indexes.length; i < l; i++) {
  4250. // Subdividing if necessary
  4251. if (quad.nodes[indexes[i]] === undefined)
  4252. quad.nodes[indexes[i]] = _quadSubdivide(indexes[i], quad);
  4253. // Recursion
  4254. _quadInsert(el, sizedPoint, quad.nodes[indexes[i]]);
  4255. }
  4256. }
  4257. else {
  4258. // Pushing the element in a leaf node
  4259. quad.elements.push(el);
  4260. }
  4261. }
  4262. /**
  4263. * Recursively retrieve every elements held by the node containing the
  4264. * searched point.
  4265. *
  4266. * @param {object} point The searched point (x, y).
  4267. * @param {object} quad The searched quad.
  4268. * @return {array} An array of elements contained in the relevant
  4269. * node.
  4270. */
  4271. function _quadRetrievePoint(point, quad) {
  4272. if (quad.level < quad.maxLevel) {
  4273. var index = _quadIndex(point, quad.bounds);
  4274. // If node does not exist we return an empty list
  4275. if (quad.nodes[index] !== undefined) {
  4276. return _quadRetrievePoint(point, quad.nodes[index]);
  4277. }
  4278. else {
  4279. return [];
  4280. }
  4281. }
  4282. else {
  4283. return quad.elements;
  4284. }
  4285. }
  4286. /**
  4287. * Recursively retrieve every elements contained within an rectangular area
  4288. * that may or may not be axis-aligned.
  4289. *
  4290. * @param {object|array} rectData The searched area defined either by
  4291. * an array of four corners (x, y) in
  4292. * the case of a non-axis-aligned
  4293. * rectangle or an object with two top
  4294. * points (x1, y1), (x2, y2) and height.
  4295. * @param {object} quad The searched quad.
  4296. * @param {function} collisionFunc The collision function used to search
  4297. * for node indexes.
  4298. * @param {array?} els The retrieved elements.
  4299. * @return {array} An array of elements contained in the
  4300. * area.
  4301. */
  4302. function _quadRetrieveArea(rectData, quad, collisionFunc, els) {
  4303. els = els || {};
  4304. if (quad.level < quad.maxLevel) {
  4305. var indexes = collisionFunc(rectData, quad.corners);
  4306. for (var i = 0, l = indexes.length; i < l; i++)
  4307. if (quad.nodes[indexes[i]] !== undefined)
  4308. _quadRetrieveArea(
  4309. rectData,
  4310. quad.nodes[indexes[i]],
  4311. collisionFunc,
  4312. els
  4313. );
  4314. } else
  4315. for (var j = 0, m = quad.elements.length; j < m; j++)
  4316. if (els[quad.elements[j].id] === undefined)
  4317. els[quad.elements[j].id] = quad.elements[j];
  4318. return els;
  4319. }
  4320. /**
  4321. * Creates the quadtree object itself.
  4322. *
  4323. * @param {object} bounds The boundaries of the quad defined by an
  4324. * origin (x, y), width and heigth.
  4325. * @param {integer} level The level of the quad in the tree.
  4326. * @param {integer} maxElements The max number of element in a leaf node.
  4327. * @param {integer} maxLevel The max recursion level of the tree.
  4328. * @return {object} The quadtree object.
  4329. */
  4330. function _quadTree(bounds, level, maxElements, maxLevel) {
  4331. return {
  4332. level: level || 0,
  4333. bounds: bounds,
  4334. corners: _geom.splitSquare(bounds),
  4335. maxElements: maxElements || 20,
  4336. maxLevel: maxLevel || 4,
  4337. elements: [],
  4338. nodes: []
  4339. };
  4340. }
  4341. /**
  4342. * Sigma Quad Constructor
  4343. * ----------------------
  4344. *
  4345. * The quad API as exposed to sigma.
  4346. */
  4347. /**
  4348. * The quad core that will become the sigma interface with the quadtree.
  4349. *
  4350. * property {object} _tree Property holding the quadtree object.
  4351. * property {object} _geom Exposition of the _geom namespace for testing.
  4352. * property {object} _cache Cache for the area method.
  4353. */
  4354. var quad = function() {
  4355. this._geom = _geom;
  4356. this._tree = null;
  4357. this._cache = {
  4358. query: false,
  4359. result: false
  4360. };
  4361. };
  4362. /**
  4363. * Index a graph by inserting its nodes into the quadtree.
  4364. *
  4365. * @param {array} nodes An array of nodes to index.
  4366. * @param {object} params An object of parameters with at least the quad
  4367. * bounds.
  4368. * @return {object} The quadtree object.
  4369. *
  4370. * Parameters:
  4371. * ----------
  4372. * bounds: {object} boundaries of the quad defined by its origin (x, y)
  4373. * width and heigth.
  4374. * prefix: {string?} a prefix for node geometric attributes.
  4375. * maxElements: {integer?} the max number of elements in a leaf node.
  4376. * maxLevel: {integer?} the max recursion level of the tree.
  4377. */
  4378. quad.prototype.index = function(nodes, params) {
  4379. // Enforcing presence of boundaries
  4380. if (!params.bounds)
  4381. throw 'sigma.classes.quad.index: bounds information not given.';
  4382. // Prefix
  4383. var prefix = params.prefix || '';
  4384. // Building the tree
  4385. this._tree = _quadTree(
  4386. params.bounds,
  4387. 0,
  4388. params.maxElements,
  4389. params.maxLevel
  4390. );
  4391. // Inserting graph nodes into the tree
  4392. for (var i = 0, l = nodes.length; i < l; i++) {
  4393. // Inserting node
  4394. _quadInsert(
  4395. nodes[i],
  4396. _geom.pointToSquare({
  4397. x: nodes[i][prefix + 'x'],
  4398. y: nodes[i][prefix + 'y'],
  4399. size: nodes[i][prefix + 'size']
  4400. }),
  4401. this._tree
  4402. );
  4403. }
  4404. // Reset cache:
  4405. this._cache = {
  4406. query: false,
  4407. result: false
  4408. };
  4409. // remove?
  4410. return this._tree;
  4411. };
  4412. /**
  4413. * Retrieve every graph nodes held by the quadtree node containing the
  4414. * searched point.
  4415. *
  4416. * @param {number} x of the point.
  4417. * @param {number} y of the point.
  4418. * @return {array} An array of nodes retrieved.
  4419. */
  4420. quad.prototype.point = function(x, y) {
  4421. return this._tree ?
  4422. _quadRetrievePoint({x: x, y: y}, this._tree) || [] :
  4423. [];
  4424. };
  4425. /**
  4426. * Retrieve every graph nodes within a rectangular area. The methods keep the
  4427. * last area queried in cache for optimization reason and will act differently
  4428. * for the same reason if the area is axis-aligned or not.
  4429. *
  4430. * @param {object} A rectangle defined by two top points (x1, y1), (x2, y2)
  4431. * and height.
  4432. * @return {array} An array of nodes retrieved.
  4433. */
  4434. quad.prototype.area = function(rect) {
  4435. var serialized = JSON.stringify(rect),
  4436. collisionFunc,
  4437. rectData;
  4438. // Returning cache?
  4439. if (this._cache.query === serialized)
  4440. return this._cache.result;
  4441. // Axis aligned ?
  4442. if (_geom.isAxisAligned(rect)) {
  4443. collisionFunc = _quadIndexes;
  4444. rectData = _geom.axisAlignedTopPoints(rect);
  4445. }
  4446. else {
  4447. collisionFunc = _quadCollision;
  4448. rectData = _geom.rectangleCorners(rect);
  4449. }
  4450. // Retrieving nodes
  4451. var nodes = this._tree ?
  4452. _quadRetrieveArea(
  4453. rectData,
  4454. this._tree,
  4455. collisionFunc
  4456. ) :
  4457. [];
  4458. // Object to array
  4459. var nodesArray = [];
  4460. for (var i in nodes)
  4461. nodesArray.push(nodes[i]);
  4462. // Caching
  4463. this._cache.query = serialized;
  4464. this._cache.result = nodesArray;
  4465. return nodesArray;
  4466. };
  4467. /**
  4468. * EXPORT:
  4469. * *******
  4470. */
  4471. if (typeof this.sigma !== 'undefined') {
  4472. this.sigma.classes = this.sigma.classes || {};
  4473. this.sigma.classes.quad = quad;
  4474. } else if (typeof exports !== 'undefined') {
  4475. if (typeof module !== 'undefined' && module.exports)
  4476. exports = module.exports = quad;
  4477. exports.quad = quad;
  4478. } else
  4479. this.quad = quad;
  4480. }).call(this);
  4481. ;(function(undefined) {
  4482. 'use strict';
  4483. /**
  4484. * Sigma Quadtree Module for edges
  4485. * ===============================
  4486. *
  4487. * Author: Sébastien Heymann,
  4488. * from the quad of Guillaume Plique (Yomguithereal)
  4489. * Version: 0.2
  4490. */
  4491. /**
  4492. * Quad Geometric Operations
  4493. * -------------------------
  4494. *
  4495. * A useful batch of geometric operations used by the quadtree.
  4496. */
  4497. var _geom = {
  4498. /**
  4499. * Transforms a graph node with x, y and size into an
  4500. * axis-aligned square.
  4501. *
  4502. * @param {object} A graph node with at least a point (x, y) and a size.
  4503. * @return {object} A square: two points (x1, y1), (x2, y2) and height.
  4504. */
  4505. pointToSquare: function(n) {
  4506. return {
  4507. x1: n.x - n.size,
  4508. y1: n.y - n.size,
  4509. x2: n.x + n.size,
  4510. y2: n.y - n.size,
  4511. height: n.size * 2
  4512. };
  4513. },
  4514. /**
  4515. * Transforms a graph edge with x1, y1, x2, y2 and size into an
  4516. * axis-aligned square.
  4517. *
  4518. * @param {object} A graph edge with at least two points
  4519. * (x1, y1), (x2, y2) and a size.
  4520. * @return {object} A square: two points (x1, y1), (x2, y2) and height.
  4521. */
  4522. lineToSquare: function(e) {
  4523. if (e.y1 < e.y2) {
  4524. // (e.x1, e.y1) on top
  4525. if (e.x1 < e.x2) {
  4526. // (e.x1, e.y1) on left
  4527. return {
  4528. x1: e.x1 - e.size,
  4529. y1: e.y1 - e.size,
  4530. x2: e.x2 + e.size,
  4531. y2: e.y1 - e.size,
  4532. height: e.y2 - e.y1 + e.size * 2
  4533. };
  4534. }
  4535. // (e.x1, e.y1) on right
  4536. return {
  4537. x1: e.x2 - e.size,
  4538. y1: e.y1 - e.size,
  4539. x2: e.x1 + e.size,
  4540. y2: e.y1 - e.size,
  4541. height: e.y2 - e.y1 + e.size * 2
  4542. };
  4543. }
  4544. // (e.x2, e.y2) on top
  4545. if (e.x1 < e.x2) {
  4546. // (e.x1, e.y1) on left
  4547. return {
  4548. x1: e.x1 - e.size,
  4549. y1: e.y2 - e.size,
  4550. x2: e.x2 + e.size,
  4551. y2: e.y2 - e.size,
  4552. height: e.y1 - e.y2 + e.size * 2
  4553. };
  4554. }
  4555. // (e.x2, e.y2) on right
  4556. return {
  4557. x1: e.x2 - e.size,
  4558. y1: e.y2 - e.size,
  4559. x2: e.x1 + e.size,
  4560. y2: e.y2 - e.size,
  4561. height: e.y1 - e.y2 + e.size * 2
  4562. };
  4563. },
  4564. /**
  4565. * Transforms a graph edge of type 'curve' with x1, y1, x2, y2,
  4566. * control point and size into an axis-aligned square.
  4567. *
  4568. * @param {object} e A graph edge with at least two points
  4569. * (x1, y1), (x2, y2) and a size.
  4570. * @param {object} cp A control point (x,y).
  4571. * @return {object} A square: two points (x1, y1), (x2, y2) and height.
  4572. */
  4573. quadraticCurveToSquare: function(e, cp) {
  4574. var pt = sigma.utils.getPointOnQuadraticCurve(
  4575. 0.5,
  4576. e.x1,
  4577. e.y1,
  4578. e.x2,
  4579. e.y2,
  4580. cp.x,
  4581. cp.y
  4582. );
  4583. // Bounding box of the two points and the point at the middle of the
  4584. // curve:
  4585. var minX = Math.min(e.x1, e.x2, pt.x),
  4586. maxX = Math.max(e.x1, e.x2, pt.x),
  4587. minY = Math.min(e.y1, e.y2, pt.y),
  4588. maxY = Math.max(e.y1, e.y2, pt.y);
  4589. return {
  4590. x1: minX - e.size,
  4591. y1: minY - e.size,
  4592. x2: maxX + e.size,
  4593. y2: minY - e.size,
  4594. height: maxY - minY + e.size * 2
  4595. };
  4596. },
  4597. /**
  4598. * Transforms a graph self loop into an axis-aligned square.
  4599. *
  4600. * @param {object} n A graph node with a point (x, y) and a size.
  4601. * @return {object} A square: two points (x1, y1), (x2, y2) and height.
  4602. */
  4603. selfLoopToSquare: function(n) {
  4604. // Fitting to the curve is too costly, we compute a larger bounding box
  4605. // using the control points:
  4606. var cp = sigma.utils.getSelfLoopControlPoints(n.x, n.y, n.size);
  4607. // Bounding box of the point and the two control points:
  4608. var minX = Math.min(n.x, cp.x1, cp.x2),
  4609. maxX = Math.max(n.x, cp.x1, cp.x2),
  4610. minY = Math.min(n.y, cp.y1, cp.y2),
  4611. maxY = Math.max(n.y, cp.y1, cp.y2);
  4612. return {
  4613. x1: minX - n.size,
  4614. y1: minY - n.size,
  4615. x2: maxX + n.size,
  4616. y2: minY - n.size,
  4617. height: maxY - minY + n.size * 2
  4618. };
  4619. },
  4620. /**
  4621. * Checks whether a rectangle is axis-aligned.
  4622. *
  4623. * @param {object} A rectangle defined by two points
  4624. * (x1, y1) and (x2, y2).
  4625. * @return {boolean} True if the rectangle is axis-aligned.
  4626. */
  4627. isAxisAligned: function(r) {
  4628. return r.x1 === r.x2 || r.y1 === r.y2;
  4629. },
  4630. /**
  4631. * Compute top points of an axis-aligned rectangle. This is useful in
  4632. * cases when the rectangle has been rotated (left, right or bottom up) and
  4633. * later operations need to know the top points.
  4634. *
  4635. * @param {object} An axis-aligned rectangle defined by two points
  4636. * (x1, y1), (x2, y2) and height.
  4637. * @return {object} A rectangle: two points (x1, y1), (x2, y2) and height.
  4638. */
  4639. axisAlignedTopPoints: function(r) {
  4640. // Basic
  4641. if (r.y1 === r.y2 && r.x1 < r.x2)
  4642. return r;
  4643. // Rotated to right
  4644. if (r.x1 === r.x2 && r.y2 > r.y1)
  4645. return {
  4646. x1: r.x1 - r.height, y1: r.y1,
  4647. x2: r.x1, y2: r.y1,
  4648. height: r.height
  4649. };
  4650. // Rotated to left
  4651. if (r.x1 === r.x2 && r.y2 < r.y1)
  4652. return {
  4653. x1: r.x1, y1: r.y2,
  4654. x2: r.x2 + r.height, y2: r.y2,
  4655. height: r.height
  4656. };
  4657. // Bottom's up
  4658. return {
  4659. x1: r.x2, y1: r.y1 - r.height,
  4660. x2: r.x1, y2: r.y1 - r.height,
  4661. height: r.height
  4662. };
  4663. },
  4664. /**
  4665. * Get coordinates of a rectangle's lower left corner from its top points.
  4666. *
  4667. * @param {object} A rectangle defined by two points (x1, y1) and (x2, y2).
  4668. * @return {object} Coordinates of the corner (x, y).
  4669. */
  4670. lowerLeftCoor: function(r) {
  4671. var width = (
  4672. Math.sqrt(
  4673. Math.pow(r.x2 - r.x1, 2) +
  4674. Math.pow(r.y2 - r.y1, 2)
  4675. )
  4676. );
  4677. return {
  4678. x: r.x1 - (r.y2 - r.y1) * r.height / width,
  4679. y: r.y1 + (r.x2 - r.x1) * r.height / width
  4680. };
  4681. },
  4682. /**
  4683. * Get coordinates of a rectangle's lower right corner from its top points
  4684. * and its lower left corner.
  4685. *
  4686. * @param {object} A rectangle defined by two points (x1, y1) and (x2, y2).
  4687. * @param {object} A corner's coordinates (x, y).
  4688. * @return {object} Coordinates of the corner (x, y).
  4689. */
  4690. lowerRightCoor: function(r, llc) {
  4691. return {
  4692. x: llc.x - r.x1 + r.x2,
  4693. y: llc.y - r.y1 + r.y2
  4694. };
  4695. },
  4696. /**
  4697. * Get the coordinates of all the corners of a rectangle from its top point.
  4698. *
  4699. * @param {object} A rectangle defined by two points (x1, y1) and (x2, y2).
  4700. * @return {array} An array of the four corners' coordinates (x, y).
  4701. */
  4702. rectangleCorners: function(r) {
  4703. var llc = this.lowerLeftCoor(r),
  4704. lrc = this.lowerRightCoor(r, llc);
  4705. return [
  4706. {x: r.x1, y: r.y1},
  4707. {x: r.x2, y: r.y2},
  4708. {x: llc.x, y: llc.y},
  4709. {x: lrc.x, y: lrc.y}
  4710. ];
  4711. },
  4712. /**
  4713. * Split a square defined by its boundaries into four.
  4714. *
  4715. * @param {object} Boundaries of the square (x, y, width, height).
  4716. * @return {array} An array containing the four new squares, themselves
  4717. * defined by an array of their four corners (x, y).
  4718. */
  4719. splitSquare: function(b) {
  4720. return [
  4721. [
  4722. {x: b.x, y: b.y},
  4723. {x: b.x + b.width / 2, y: b.y},
  4724. {x: b.x, y: b.y + b.height / 2},
  4725. {x: b.x + b.width / 2, y: b.y + b.height / 2}
  4726. ],
  4727. [
  4728. {x: b.x + b.width / 2, y: b.y},
  4729. {x: b.x + b.width, y: b.y},
  4730. {x: b.x + b.width / 2, y: b.y + b.height / 2},
  4731. {x: b.x + b.width, y: b.y + b.height / 2}
  4732. ],
  4733. [
  4734. {x: b.x, y: b.y + b.height / 2},
  4735. {x: b.x + b.width / 2, y: b.y + b.height / 2},
  4736. {x: b.x, y: b.y + b.height},
  4737. {x: b.x + b.width / 2, y: b.y + b.height}
  4738. ],
  4739. [
  4740. {x: b.x + b.width / 2, y: b.y + b.height / 2},
  4741. {x: b.x + b.width, y: b.y + b.height / 2},
  4742. {x: b.x + b.width / 2, y: b.y + b.height},
  4743. {x: b.x + b.width, y: b.y + b.height}
  4744. ]
  4745. ];
  4746. },
  4747. /**
  4748. * Compute the four axis between corners of rectangle A and corners of
  4749. * rectangle B. This is needed later to check an eventual collision.
  4750. *
  4751. * @param {array} An array of rectangle A's four corners (x, y).
  4752. * @param {array} An array of rectangle B's four corners (x, y).
  4753. * @return {array} An array of four axis defined by their coordinates (x,y).
  4754. */
  4755. axis: function(c1, c2) {
  4756. return [
  4757. {x: c1[1].x - c1[0].x, y: c1[1].y - c1[0].y},
  4758. {x: c1[1].x - c1[3].x, y: c1[1].y - c1[3].y},
  4759. {x: c2[0].x - c2[2].x, y: c2[0].y - c2[2].y},
  4760. {x: c2[0].x - c2[1].x, y: c2[0].y - c2[1].y}
  4761. ];
  4762. },
  4763. /**
  4764. * Project a rectangle's corner on an axis.
  4765. *
  4766. * @param {object} Coordinates of a corner (x, y).
  4767. * @param {object} Coordinates of an axis (x, y).
  4768. * @return {object} The projection defined by coordinates (x, y).
  4769. */
  4770. projection: function(c, a) {
  4771. var l = (
  4772. (c.x * a.x + c.y * a.y) /
  4773. (Math.pow(a.x, 2) + Math.pow(a.y, 2))
  4774. );
  4775. return {
  4776. x: l * a.x,
  4777. y: l * a.y
  4778. };
  4779. },
  4780. /**
  4781. * Check whether two rectangles collide on one particular axis.
  4782. *
  4783. * @param {object} An axis' coordinates (x, y).
  4784. * @param {array} Rectangle A's corners.
  4785. * @param {array} Rectangle B's corners.
  4786. * @return {boolean} True if the rectangles collide on the axis.
  4787. */
  4788. axisCollision: function(a, c1, c2) {
  4789. var sc1 = [],
  4790. sc2 = [];
  4791. for (var ci = 0; ci < 4; ci++) {
  4792. var p1 = this.projection(c1[ci], a),
  4793. p2 = this.projection(c2[ci], a);
  4794. sc1.push(p1.x * a.x + p1.y * a.y);
  4795. sc2.push(p2.x * a.x + p2.y * a.y);
  4796. }
  4797. var maxc1 = Math.max.apply(Math, sc1),
  4798. maxc2 = Math.max.apply(Math, sc2),
  4799. minc1 = Math.min.apply(Math, sc1),
  4800. minc2 = Math.min.apply(Math, sc2);
  4801. return (minc2 <= maxc1 && maxc2 >= minc1);
  4802. },
  4803. /**
  4804. * Check whether two rectangles collide on each one of their four axis. If
  4805. * all axis collide, then the two rectangles do collide on the plane.
  4806. *
  4807. * @param {array} Rectangle A's corners.
  4808. * @param {array} Rectangle B's corners.
  4809. * @return {boolean} True if the rectangles collide.
  4810. */
  4811. collision: function(c1, c2) {
  4812. var axis = this.axis(c1, c2),
  4813. col = true;
  4814. for (var i = 0; i < 4; i++)
  4815. col = col && this.axisCollision(axis[i], c1, c2);
  4816. return col;
  4817. }
  4818. };
  4819. /**
  4820. * Quad Functions
  4821. * ------------
  4822. *
  4823. * The Quadtree functions themselves.
  4824. * For each of those functions, we consider that in a splitted quad, the
  4825. * index of each node is the following:
  4826. * 0: top left
  4827. * 1: top right
  4828. * 2: bottom left
  4829. * 3: bottom right
  4830. *
  4831. * Moreover, the hereafter quad's philosophy is to consider that if an element
  4832. * collides with more than one nodes, this element belongs to each of the
  4833. * nodes it collides with where other would let it lie on a higher node.
  4834. */
  4835. /**
  4836. * Get the index of the node containing the point in the quad
  4837. *
  4838. * @param {object} point A point defined by coordinates (x, y).
  4839. * @param {object} quadBounds Boundaries of the quad (x, y, width, heigth).
  4840. * @return {integer} The index of the node containing the point.
  4841. */
  4842. function _quadIndex(point, quadBounds) {
  4843. var xmp = quadBounds.x + quadBounds.width / 2,
  4844. ymp = quadBounds.y + quadBounds.height / 2,
  4845. top = (point.y < ymp),
  4846. left = (point.x < xmp);
  4847. if (top) {
  4848. if (left)
  4849. return 0;
  4850. else
  4851. return 1;
  4852. }
  4853. else {
  4854. if (left)
  4855. return 2;
  4856. else
  4857. return 3;
  4858. }
  4859. }
  4860. /**
  4861. * Get a list of indexes of nodes containing an axis-aligned rectangle
  4862. *
  4863. * @param {object} rectangle A rectangle defined by two points (x1, y1),
  4864. * (x2, y2) and height.
  4865. * @param {array} quadCorners An array of the quad nodes' corners.
  4866. * @return {array} An array of indexes containing one to
  4867. * four integers.
  4868. */
  4869. function _quadIndexes(rectangle, quadCorners) {
  4870. var indexes = [];
  4871. // Iterating through quads
  4872. for (var i = 0; i < 4; i++)
  4873. if ((rectangle.x2 >= quadCorners[i][0].x) &&
  4874. (rectangle.x1 <= quadCorners[i][1].x) &&
  4875. (rectangle.y1 + rectangle.height >= quadCorners[i][0].y) &&
  4876. (rectangle.y1 <= quadCorners[i][2].y))
  4877. indexes.push(i);
  4878. return indexes;
  4879. }
  4880. /**
  4881. * Get a list of indexes of nodes containing a non-axis-aligned rectangle
  4882. *
  4883. * @param {array} corners An array containing each corner of the
  4884. * rectangle defined by its coordinates (x, y).
  4885. * @param {array} quadCorners An array of the quad nodes' corners.
  4886. * @return {array} An array of indexes containing one to
  4887. * four integers.
  4888. */
  4889. function _quadCollision(corners, quadCorners) {
  4890. var indexes = [];
  4891. // Iterating through quads
  4892. for (var i = 0; i < 4; i++)
  4893. if (_geom.collision(corners, quadCorners[i]))
  4894. indexes.push(i);
  4895. return indexes;
  4896. }
  4897. /**
  4898. * Subdivide a quad by creating a node at a precise index. The function does
  4899. * not generate all four nodes not to potentially create unused nodes.
  4900. *
  4901. * @param {integer} index The index of the node to create.
  4902. * @param {object} quad The quad object to subdivide.
  4903. * @return {object} A new quad representing the node created.
  4904. */
  4905. function _quadSubdivide(index, quad) {
  4906. var next = quad.level + 1,
  4907. subw = Math.round(quad.bounds.width / 2),
  4908. subh = Math.round(quad.bounds.height / 2),
  4909. qx = Math.round(quad.bounds.x),
  4910. qy = Math.round(quad.bounds.y),
  4911. x,
  4912. y;
  4913. switch (index) {
  4914. case 0:
  4915. x = qx;
  4916. y = qy;
  4917. break;
  4918. case 1:
  4919. x = qx + subw;
  4920. y = qy;
  4921. break;
  4922. case 2:
  4923. x = qx;
  4924. y = qy + subh;
  4925. break;
  4926. case 3:
  4927. x = qx + subw;
  4928. y = qy + subh;
  4929. break;
  4930. }
  4931. return _quadTree(
  4932. {x: x, y: y, width: subw, height: subh},
  4933. next,
  4934. quad.maxElements,
  4935. quad.maxLevel
  4936. );
  4937. }
  4938. /**
  4939. * Recursively insert an element into the quadtree. Only points
  4940. * with size, i.e. axis-aligned squares, may be inserted with this
  4941. * method.
  4942. *
  4943. * @param {object} el The element to insert in the quadtree.
  4944. * @param {object} sizedPoint A sized point defined by two top points
  4945. * (x1, y1), (x2, y2) and height.
  4946. * @param {object} quad The quad in which to insert the element.
  4947. * @return {undefined} The function does not return anything.
  4948. */
  4949. function _quadInsert(el, sizedPoint, quad) {
  4950. if (quad.level < quad.maxLevel) {
  4951. // Searching appropriate quads
  4952. var indexes = _quadIndexes(sizedPoint, quad.corners);
  4953. // Iterating
  4954. for (var i = 0, l = indexes.length; i < l; i++) {
  4955. // Subdividing if necessary
  4956. if (quad.nodes[indexes[i]] === undefined)
  4957. quad.nodes[indexes[i]] = _quadSubdivide(indexes[i], quad);
  4958. // Recursion
  4959. _quadInsert(el, sizedPoint, quad.nodes[indexes[i]]);
  4960. }
  4961. }
  4962. else {
  4963. // Pushing the element in a leaf node
  4964. quad.elements.push(el);
  4965. }
  4966. }
  4967. /**
  4968. * Recursively retrieve every elements held by the node containing the
  4969. * searched point.
  4970. *
  4971. * @param {object} point The searched point (x, y).
  4972. * @param {object} quad The searched quad.
  4973. * @return {array} An array of elements contained in the relevant
  4974. * node.
  4975. */
  4976. function _quadRetrievePoint(point, quad) {
  4977. if (quad.level < quad.maxLevel) {
  4978. var index = _quadIndex(point, quad.bounds);
  4979. // If node does not exist we return an empty list
  4980. if (quad.nodes[index] !== undefined) {
  4981. return _quadRetrievePoint(point, quad.nodes[index]);
  4982. }
  4983. else {
  4984. return [];
  4985. }
  4986. }
  4987. else {
  4988. return quad.elements;
  4989. }
  4990. }
  4991. /**
  4992. * Recursively retrieve every elements contained within an rectangular area
  4993. * that may or may not be axis-aligned.
  4994. *
  4995. * @param {object|array} rectData The searched area defined either by
  4996. * an array of four corners (x, y) in
  4997. * the case of a non-axis-aligned
  4998. * rectangle or an object with two top
  4999. * points (x1, y1), (x2, y2) and height.
  5000. * @param {object} quad The searched quad.
  5001. * @param {function} collisionFunc The collision function used to search
  5002. * for node indexes.
  5003. * @param {array?} els The retrieved elements.
  5004. * @return {array} An array of elements contained in the
  5005. * area.
  5006. */
  5007. function _quadRetrieveArea(rectData, quad, collisionFunc, els) {
  5008. els = els || {};
  5009. if (quad.level < quad.maxLevel) {
  5010. var indexes = collisionFunc(rectData, quad.corners);
  5011. for (var i = 0, l = indexes.length; i < l; i++)
  5012. if (quad.nodes[indexes[i]] !== undefined)
  5013. _quadRetrieveArea(
  5014. rectData,
  5015. quad.nodes[indexes[i]],
  5016. collisionFunc,
  5017. els
  5018. );
  5019. } else
  5020. for (var j = 0, m = quad.elements.length; j < m; j++)
  5021. if (els[quad.elements[j].id] === undefined)
  5022. els[quad.elements[j].id] = quad.elements[j];
  5023. return els;
  5024. }
  5025. /**
  5026. * Creates the quadtree object itself.
  5027. *
  5028. * @param {object} bounds The boundaries of the quad defined by an
  5029. * origin (x, y), width and heigth.
  5030. * @param {integer} level The level of the quad in the tree.
  5031. * @param {integer} maxElements The max number of element in a leaf node.
  5032. * @param {integer} maxLevel The max recursion level of the tree.
  5033. * @return {object} The quadtree object.
  5034. */
  5035. function _quadTree(bounds, level, maxElements, maxLevel) {
  5036. return {
  5037. level: level || 0,
  5038. bounds: bounds,
  5039. corners: _geom.splitSquare(bounds),
  5040. maxElements: maxElements || 40,
  5041. maxLevel: maxLevel || 8,
  5042. elements: [],
  5043. nodes: []
  5044. };
  5045. }
  5046. /**
  5047. * Sigma Quad Constructor
  5048. * ----------------------
  5049. *
  5050. * The edgequad API as exposed to sigma.
  5051. */
  5052. /**
  5053. * The edgequad core that will become the sigma interface with the quadtree.
  5054. *
  5055. * property {object} _tree Property holding the quadtree object.
  5056. * property {object} _geom Exposition of the _geom namespace for testing.
  5057. * property {object} _cache Cache for the area method.
  5058. * property {boolean} _enabled Can index and retreive elements.
  5059. */
  5060. var edgequad = function() {
  5061. this._geom = _geom;
  5062. this._tree = null;
  5063. this._cache = {
  5064. query: false,
  5065. result: false
  5066. };
  5067. this._enabled = true;
  5068. };
  5069. /**
  5070. * Index a graph by inserting its edges into the quadtree.
  5071. *
  5072. * @param {object} graph A graph instance.
  5073. * @param {object} params An object of parameters with at least the quad
  5074. * bounds.
  5075. * @return {object} The quadtree object.
  5076. *
  5077. * Parameters:
  5078. * ----------
  5079. * bounds: {object} boundaries of the quad defined by its origin (x, y)
  5080. * width and heigth.
  5081. * prefix: {string?} a prefix for edge geometric attributes.
  5082. * maxElements: {integer?} the max number of elements in a leaf node.
  5083. * maxLevel: {integer?} the max recursion level of the tree.
  5084. */
  5085. edgequad.prototype.index = function(graph, params) {
  5086. if (!this._enabled)
  5087. return this._tree;
  5088. // Enforcing presence of boundaries
  5089. if (!params.bounds)
  5090. throw 'sigma.classes.edgequad.index: bounds information not given.';
  5091. // Prefix
  5092. var prefix = params.prefix || '',
  5093. cp,
  5094. source,
  5095. target,
  5096. n,
  5097. e;
  5098. // Building the tree
  5099. this._tree = _quadTree(
  5100. params.bounds,
  5101. 0,
  5102. params.maxElements,
  5103. params.maxLevel
  5104. );
  5105. var edges = graph.edges();
  5106. // Inserting graph edges into the tree
  5107. for (var i = 0, l = edges.length; i < l; i++) {
  5108. source = graph.nodes(edges[i].source);
  5109. target = graph.nodes(edges[i].target);
  5110. e = {
  5111. x1: source[prefix + 'x'],
  5112. y1: source[prefix + 'y'],
  5113. x2: target[prefix + 'x'],
  5114. y2: target[prefix + 'y'],
  5115. size: edges[i][prefix + 'size'] || 0
  5116. };
  5117. // Inserting edge
  5118. if (edges[i].type === 'curve' || edges[i].type === 'curvedArrow') {
  5119. if (source.id === target.id) {
  5120. n = {
  5121. x: source[prefix + 'x'],
  5122. y: source[prefix + 'y'],
  5123. size: source[prefix + 'size'] || 0
  5124. };
  5125. _quadInsert(
  5126. edges[i],
  5127. _geom.selfLoopToSquare(n),
  5128. this._tree);
  5129. }
  5130. else {
  5131. cp = sigma.utils.getQuadraticControlPoint(e.x1, e.y1, e.x2, e.y2);
  5132. _quadInsert(
  5133. edges[i],
  5134. _geom.quadraticCurveToSquare(e, cp),
  5135. this._tree);
  5136. }
  5137. }
  5138. else {
  5139. _quadInsert(
  5140. edges[i],
  5141. _geom.lineToSquare(e),
  5142. this._tree);
  5143. }
  5144. }
  5145. // Reset cache:
  5146. this._cache = {
  5147. query: false,
  5148. result: false
  5149. };
  5150. // remove?
  5151. return this._tree;
  5152. };
  5153. /**
  5154. * Retrieve every graph edges held by the quadtree node containing the
  5155. * searched point.
  5156. *
  5157. * @param {number} x of the point.
  5158. * @param {number} y of the point.
  5159. * @return {array} An array of edges retrieved.
  5160. */
  5161. edgequad.prototype.point = function(x, y) {
  5162. if (!this._enabled)
  5163. return [];
  5164. return this._tree ?
  5165. _quadRetrievePoint({x: x, y: y}, this._tree) || [] :
  5166. [];
  5167. };
  5168. /**
  5169. * Retrieve every graph edges within a rectangular area. The methods keep the
  5170. * last area queried in cache for optimization reason and will act differently
  5171. * for the same reason if the area is axis-aligned or not.
  5172. *
  5173. * @param {object} A rectangle defined by two top points (x1, y1), (x2, y2)
  5174. * and height.
  5175. * @return {array} An array of edges retrieved.
  5176. */
  5177. edgequad.prototype.area = function(rect) {
  5178. if (!this._enabled)
  5179. return [];
  5180. var serialized = JSON.stringify(rect),
  5181. collisionFunc,
  5182. rectData;
  5183. // Returning cache?
  5184. if (this._cache.query === serialized)
  5185. return this._cache.result;
  5186. // Axis aligned ?
  5187. if (_geom.isAxisAligned(rect)) {
  5188. collisionFunc = _quadIndexes;
  5189. rectData = _geom.axisAlignedTopPoints(rect);
  5190. }
  5191. else {
  5192. collisionFunc = _quadCollision;
  5193. rectData = _geom.rectangleCorners(rect);
  5194. }
  5195. // Retrieving edges
  5196. var edges = this._tree ?
  5197. _quadRetrieveArea(
  5198. rectData,
  5199. this._tree,
  5200. collisionFunc
  5201. ) :
  5202. [];
  5203. // Object to array
  5204. var edgesArray = [];
  5205. for (var i in edges)
  5206. edgesArray.push(edges[i]);
  5207. // Caching
  5208. this._cache.query = serialized;
  5209. this._cache.result = edgesArray;
  5210. return edgesArray;
  5211. };
  5212. /**
  5213. * EXPORT:
  5214. * *******
  5215. */
  5216. if (typeof this.sigma !== 'undefined') {
  5217. this.sigma.classes = this.sigma.classes || {};
  5218. this.sigma.classes.edgequad = edgequad;
  5219. } else if (typeof exports !== 'undefined') {
  5220. if (typeof module !== 'undefined' && module.exports)
  5221. exports = module.exports = edgequad;
  5222. exports.edgequad = edgequad;
  5223. } else
  5224. this.edgequad = edgequad;
  5225. }).call(this);
  5226. ;(function(undefined) {
  5227. 'use strict';
  5228. if (typeof sigma === 'undefined')
  5229. throw 'sigma is not declared';
  5230. // Initialize packages:
  5231. sigma.utils.pkg('sigma.captors');
  5232. /**
  5233. * The user inputs default captor. It deals with mouse events, keyboards
  5234. * events and touch events.
  5235. *
  5236. * @param {DOMElement} target The DOM element where the listeners will be
  5237. * bound.
  5238. * @param {camera} camera The camera related to the target.
  5239. * @param {configurable} settings The settings function.
  5240. * @return {sigma.captor} The fresh new captor instance.
  5241. */
  5242. sigma.captors.mouse = function(target, camera, settings) {
  5243. var _self = this,
  5244. _target = target,
  5245. _camera = camera,
  5246. _settings = settings,
  5247. // CAMERA MANAGEMENT:
  5248. // ******************
  5249. // The camera position when the user starts dragging:
  5250. _startCameraX,
  5251. _startCameraY,
  5252. _startCameraAngle,
  5253. // The latest stage position:
  5254. _lastCameraX,
  5255. _lastCameraY,
  5256. _lastCameraAngle,
  5257. _lastCameraRatio,
  5258. // MOUSE MANAGEMENT:
  5259. // *****************
  5260. // The mouse position when the user starts dragging:
  5261. _startMouseX,
  5262. _startMouseY,
  5263. _isMouseDown,
  5264. _isMoving,
  5265. _hasDragged,
  5266. _downStartTime,
  5267. _movingTimeoutId;
  5268. sigma.classes.dispatcher.extend(this);
  5269. sigma.utils.doubleClick(_target, 'click', _doubleClickHandler);
  5270. _target.addEventListener('DOMMouseScroll', _wheelHandler, false);
  5271. _target.addEventListener('mousewheel', _wheelHandler, false);
  5272. _target.addEventListener('mousemove', _moveHandler, false);
  5273. _target.addEventListener('mousedown', _downHandler, false);
  5274. _target.addEventListener('click', _clickHandler, false);
  5275. _target.addEventListener('mouseout', _outHandler, false);
  5276. document.addEventListener('mouseup', _upHandler, false);
  5277. /**
  5278. * This method unbinds every handlers that makes the captor work.
  5279. */
  5280. this.kill = function() {
  5281. sigma.utils.unbindDoubleClick(_target, 'click');
  5282. _target.removeEventListener('DOMMouseScroll', _wheelHandler);
  5283. _target.removeEventListener('mousewheel', _wheelHandler);
  5284. _target.removeEventListener('mousemove', _moveHandler);
  5285. _target.removeEventListener('mousedown', _downHandler);
  5286. _target.removeEventListener('click', _clickHandler);
  5287. _target.removeEventListener('mouseout', _outHandler);
  5288. document.removeEventListener('mouseup', _upHandler);
  5289. };
  5290. // MOUSE EVENTS:
  5291. // *************
  5292. /**
  5293. * The handler listening to the 'move' mouse event. It will effectively
  5294. * drag the graph.
  5295. *
  5296. * @param {event} e A mouse event.
  5297. */
  5298. function _moveHandler(e) {
  5299. var x,
  5300. y,
  5301. pos;
  5302. // Dispatch event:
  5303. if (_settings('mouseEnabled'))
  5304. _self.dispatchEvent('mousemove', {
  5305. x: sigma.utils.getX(e) - sigma.utils.getWidth(e) / 2,
  5306. y: sigma.utils.getY(e) - sigma.utils.getHeight(e) / 2,
  5307. clientX: e.clientX,
  5308. clientY: e.clientY,
  5309. ctrlKey: e.ctrlKey,
  5310. metaKey: e.metaKey,
  5311. altKey: e.altKey,
  5312. shiftKey: e.shiftKey
  5313. });
  5314. if (_settings('mouseEnabled') && _isMouseDown) {
  5315. _isMoving = true;
  5316. _hasDragged = true;
  5317. if (_movingTimeoutId)
  5318. clearTimeout(_movingTimeoutId);
  5319. _movingTimeoutId = setTimeout(function() {
  5320. _isMoving = false;
  5321. }, _settings('dragTimeout'));
  5322. sigma.misc.animation.killAll(_camera);
  5323. _camera.isMoving = true;
  5324. pos = _camera.cameraPosition(
  5325. sigma.utils.getX(e) - _startMouseX,
  5326. sigma.utils.getY(e) - _startMouseY,
  5327. true
  5328. );
  5329. x = _startCameraX - pos.x;
  5330. y = _startCameraY - pos.y;
  5331. if (x !== _camera.x || y !== _camera.y) {
  5332. _lastCameraX = _camera.x;
  5333. _lastCameraY = _camera.y;
  5334. _camera.goTo({
  5335. x: x,
  5336. y: y
  5337. });
  5338. }
  5339. if (e.preventDefault)
  5340. e.preventDefault();
  5341. else
  5342. e.returnValue = false;
  5343. e.stopPropagation();
  5344. return false;
  5345. }
  5346. }
  5347. /**
  5348. * The handler listening to the 'up' mouse event. It will stop dragging the
  5349. * graph.
  5350. *
  5351. * @param {event} e A mouse event.
  5352. */
  5353. function _upHandler(e) {
  5354. if (_settings('mouseEnabled') && _isMouseDown) {
  5355. _isMouseDown = false;
  5356. if (_movingTimeoutId)
  5357. clearTimeout(_movingTimeoutId);
  5358. _camera.isMoving = false;
  5359. var x = sigma.utils.getX(e),
  5360. y = sigma.utils.getY(e);
  5361. if (_isMoving) {
  5362. sigma.misc.animation.killAll(_camera);
  5363. sigma.misc.animation.camera(
  5364. _camera,
  5365. {
  5366. x: _camera.x +
  5367. _settings('mouseInertiaRatio') * (_camera.x - _lastCameraX),
  5368. y: _camera.y +
  5369. _settings('mouseInertiaRatio') * (_camera.y - _lastCameraY)
  5370. },
  5371. {
  5372. easing: 'quadraticOut',
  5373. duration: _settings('mouseInertiaDuration')
  5374. }
  5375. );
  5376. } else if (
  5377. _startMouseX !== x ||
  5378. _startMouseY !== y
  5379. )
  5380. _camera.goTo({
  5381. x: _camera.x,
  5382. y: _camera.y
  5383. });
  5384. _self.dispatchEvent('mouseup', {
  5385. x: x - sigma.utils.getWidth(e) / 2,
  5386. y: y - sigma.utils.getHeight(e) / 2,
  5387. clientX: e.clientX,
  5388. clientY: e.clientY,
  5389. ctrlKey: e.ctrlKey,
  5390. metaKey: e.metaKey,
  5391. altKey: e.altKey,
  5392. shiftKey: e.shiftKey
  5393. });
  5394. // Update _isMoving flag:
  5395. _isMoving = false;
  5396. }
  5397. }
  5398. /**
  5399. * The handler listening to the 'down' mouse event. It will start observing
  5400. * the mouse position for dragging the graph.
  5401. *
  5402. * @param {event} e A mouse event.
  5403. */
  5404. function _downHandler(e) {
  5405. if (_settings('mouseEnabled')) {
  5406. _startCameraX = _camera.x;
  5407. _startCameraY = _camera.y;
  5408. _lastCameraX = _camera.x;
  5409. _lastCameraY = _camera.y;
  5410. _startMouseX = sigma.utils.getX(e);
  5411. _startMouseY = sigma.utils.getY(e);
  5412. _hasDragged = false;
  5413. _downStartTime = (new Date()).getTime();
  5414. switch (e.which) {
  5415. case 2:
  5416. // Middle mouse button pressed
  5417. // Do nothing.
  5418. break;
  5419. case 3:
  5420. // Right mouse button pressed
  5421. _self.dispatchEvent('rightclick', {
  5422. x: _startMouseX - sigma.utils.getWidth(e) / 2,
  5423. y: _startMouseY - sigma.utils.getHeight(e) / 2,
  5424. clientX: e.clientX,
  5425. clientY: e.clientY,
  5426. ctrlKey: e.ctrlKey,
  5427. metaKey: e.metaKey,
  5428. altKey: e.altKey,
  5429. shiftKey: e.shiftKey
  5430. });
  5431. break;
  5432. // case 1:
  5433. default:
  5434. // Left mouse button pressed
  5435. _isMouseDown = true;
  5436. _self.dispatchEvent('mousedown', {
  5437. x: _startMouseX - sigma.utils.getWidth(e) / 2,
  5438. y: _startMouseY - sigma.utils.getHeight(e) / 2,
  5439. clientX: e.clientX,
  5440. clientY: e.clientY,
  5441. ctrlKey: e.ctrlKey,
  5442. metaKey: e.metaKey,
  5443. altKey: e.altKey,
  5444. shiftKey: e.shiftKey
  5445. });
  5446. }
  5447. }
  5448. }
  5449. /**
  5450. * The handler listening to the 'out' mouse event. It will just redispatch
  5451. * the event.
  5452. *
  5453. * @param {event} e A mouse event.
  5454. */
  5455. function _outHandler(e) {
  5456. if (_settings('mouseEnabled'))
  5457. _self.dispatchEvent('mouseout');
  5458. }
  5459. /**
  5460. * The handler listening to the 'click' mouse event. It will redispatch the
  5461. * click event, but with normalized X and Y coordinates.
  5462. *
  5463. * @param {event} e A mouse event.
  5464. */
  5465. function _clickHandler(e) {
  5466. if (_settings('mouseEnabled'))
  5467. _self.dispatchEvent('click', {
  5468. x: sigma.utils.getX(e) - sigma.utils.getWidth(e) / 2,
  5469. y: sigma.utils.getY(e) - sigma.utils.getHeight(e) / 2,
  5470. clientX: e.clientX,
  5471. clientY: e.clientY,
  5472. ctrlKey: e.ctrlKey,
  5473. metaKey: e.metaKey,
  5474. altKey: e.altKey,
  5475. shiftKey: e.shiftKey,
  5476. isDragging:
  5477. (((new Date()).getTime() - _downStartTime) > 100) &&
  5478. _hasDragged
  5479. });
  5480. if (e.preventDefault)
  5481. e.preventDefault();
  5482. else
  5483. e.returnValue = false;
  5484. e.stopPropagation();
  5485. return false;
  5486. }
  5487. /**
  5488. * The handler listening to the double click custom event. It will
  5489. * basically zoom into the graph.
  5490. *
  5491. * @param {event} e A mouse event.
  5492. */
  5493. function _doubleClickHandler(e) {
  5494. var pos,
  5495. ratio,
  5496. animation;
  5497. if (_settings('mouseEnabled')) {
  5498. ratio = 1 / _settings('doubleClickZoomingRatio');
  5499. _self.dispatchEvent('doubleclick', {
  5500. x: _startMouseX - sigma.utils.getWidth(e) / 2,
  5501. y: _startMouseY - sigma.utils.getHeight(e) / 2,
  5502. clientX: e.clientX,
  5503. clientY: e.clientY,
  5504. ctrlKey: e.ctrlKey,
  5505. metaKey: e.metaKey,
  5506. altKey: e.altKey,
  5507. shiftKey: e.shiftKey
  5508. });
  5509. if (_settings('doubleClickEnabled')) {
  5510. pos = _camera.cameraPosition(
  5511. sigma.utils.getX(e) - sigma.utils.getWidth(e) / 2,
  5512. sigma.utils.getY(e) - sigma.utils.getHeight(e) / 2,
  5513. true
  5514. );
  5515. animation = {
  5516. duration: _settings('doubleClickZoomDuration')
  5517. };
  5518. sigma.utils.zoomTo(_camera, pos.x, pos.y, ratio, animation);
  5519. }
  5520. if (e.preventDefault)
  5521. e.preventDefault();
  5522. else
  5523. e.returnValue = false;
  5524. e.stopPropagation();
  5525. return false;
  5526. }
  5527. }
  5528. /**
  5529. * The handler listening to the 'wheel' mouse event. It will basically zoom
  5530. * in or not into the graph.
  5531. *
  5532. * @param {event} e A mouse event.
  5533. */
  5534. function _wheelHandler(e) {
  5535. var pos,
  5536. ratio,
  5537. animation;
  5538. if (_settings('mouseEnabled') && _settings('mouseWheelEnabled')) {
  5539. ratio = sigma.utils.getDelta(e) > 0 ?
  5540. 1 / _settings('zoomingRatio') :
  5541. _settings('zoomingRatio');
  5542. pos = _camera.cameraPosition(
  5543. sigma.utils.getX(e) - sigma.utils.getWidth(e) / 2,
  5544. sigma.utils.getY(e) - sigma.utils.getHeight(e) / 2,
  5545. true
  5546. );
  5547. animation = {
  5548. duration: _settings('mouseZoomDuration')
  5549. };
  5550. sigma.utils.zoomTo(_camera, pos.x, pos.y, ratio, animation);
  5551. if (e.preventDefault)
  5552. e.preventDefault();
  5553. else
  5554. e.returnValue = false;
  5555. e.stopPropagation();
  5556. return false;
  5557. }
  5558. }
  5559. };
  5560. }).call(this);
  5561. ;(function(undefined) {
  5562. 'use strict';
  5563. if (typeof sigma === 'undefined')
  5564. throw 'sigma is not declared';
  5565. // Initialize packages:
  5566. sigma.utils.pkg('sigma.captors');
  5567. /**
  5568. * The user inputs default captor. It deals with mouse events, keyboards
  5569. * events and touch events.
  5570. *
  5571. * @param {DOMElement} target The DOM element where the listeners will be
  5572. * bound.
  5573. * @param {camera} camera The camera related to the target.
  5574. * @param {configurable} settings The settings function.
  5575. * @return {sigma.captor} The fresh new captor instance.
  5576. */
  5577. sigma.captors.touch = function(target, camera, settings) {
  5578. var _self = this,
  5579. _target = target,
  5580. _camera = camera,
  5581. _settings = settings,
  5582. // CAMERA MANAGEMENT:
  5583. // ******************
  5584. // The camera position when the user starts dragging:
  5585. _startCameraX,
  5586. _startCameraY,
  5587. _startCameraAngle,
  5588. _startCameraRatio,
  5589. // The latest stage position:
  5590. _lastCameraX,
  5591. _lastCameraY,
  5592. _lastCameraAngle,
  5593. _lastCameraRatio,
  5594. // TOUCH MANAGEMENT:
  5595. // *****************
  5596. // Touches that are down:
  5597. _downTouches = [],
  5598. _startTouchX0,
  5599. _startTouchY0,
  5600. _startTouchX1,
  5601. _startTouchY1,
  5602. _startTouchAngle,
  5603. _startTouchDistance,
  5604. _touchMode,
  5605. _isMoving,
  5606. _doubleTap,
  5607. _movingTimeoutId;
  5608. sigma.classes.dispatcher.extend(this);
  5609. sigma.utils.doubleClick(_target, 'touchstart', _doubleTapHandler);
  5610. _target.addEventListener('touchstart', _handleStart, false);
  5611. _target.addEventListener('touchend', _handleLeave, false);
  5612. _target.addEventListener('touchcancel', _handleLeave, false);
  5613. _target.addEventListener('touchleave', _handleLeave, false);
  5614. _target.addEventListener('touchmove', _handleMove, false);
  5615. function position(e) {
  5616. var offset = sigma.utils.getOffset(_target);
  5617. return {
  5618. x: e.pageX - offset.left,
  5619. y: e.pageY - offset.top
  5620. };
  5621. }
  5622. /**
  5623. * This method unbinds every handlers that makes the captor work.
  5624. */
  5625. this.kill = function() {
  5626. sigma.utils.unbindDoubleClick(_target, 'touchstart');
  5627. _target.addEventListener('touchstart', _handleStart);
  5628. _target.addEventListener('touchend', _handleLeave);
  5629. _target.addEventListener('touchcancel', _handleLeave);
  5630. _target.addEventListener('touchleave', _handleLeave);
  5631. _target.addEventListener('touchmove', _handleMove);
  5632. };
  5633. // TOUCH EVENTS:
  5634. // *************
  5635. /**
  5636. * The handler listening to the 'touchstart' event. It will set the touch
  5637. * mode ("_touchMode") and start observing the user touch moves.
  5638. *
  5639. * @param {event} e A touch event.
  5640. */
  5641. function _handleStart(e) {
  5642. if (_settings('touchEnabled')) {
  5643. var x0,
  5644. x1,
  5645. y0,
  5646. y1,
  5647. pos0,
  5648. pos1;
  5649. _downTouches = e.touches;
  5650. switch (_downTouches.length) {
  5651. case 1:
  5652. _camera.isMoving = true;
  5653. _touchMode = 1;
  5654. _startCameraX = _camera.x;
  5655. _startCameraY = _camera.y;
  5656. _lastCameraX = _camera.x;
  5657. _lastCameraY = _camera.y;
  5658. pos0 = position(_downTouches[0]);
  5659. _startTouchX0 = pos0.x;
  5660. _startTouchY0 = pos0.y;
  5661. break;
  5662. case 2:
  5663. _camera.isMoving = true;
  5664. _touchMode = 2;
  5665. pos0 = position(_downTouches[0]);
  5666. pos1 = position(_downTouches[1]);
  5667. x0 = pos0.x;
  5668. y0 = pos0.y;
  5669. x1 = pos1.x;
  5670. y1 = pos1.y;
  5671. _lastCameraX = _camera.x;
  5672. _lastCameraY = _camera.y;
  5673. _startCameraAngle = _camera.angle;
  5674. _startCameraRatio = _camera.ratio;
  5675. _startCameraX = _camera.x;
  5676. _startCameraY = _camera.y;
  5677. _startTouchX0 = x0;
  5678. _startTouchY0 = y0;
  5679. _startTouchX1 = x1;
  5680. _startTouchY1 = y1;
  5681. _startTouchAngle = Math.atan2(
  5682. _startTouchY1 - _startTouchY0,
  5683. _startTouchX1 - _startTouchX0
  5684. );
  5685. _startTouchDistance = Math.sqrt(
  5686. Math.pow(_startTouchY1 - _startTouchY0, 2) +
  5687. Math.pow(_startTouchX1 - _startTouchX0, 2)
  5688. );
  5689. e.preventDefault();
  5690. return false;
  5691. }
  5692. }
  5693. }
  5694. /**
  5695. * The handler listening to the 'touchend', 'touchcancel' and 'touchleave'
  5696. * event. It will update the touch mode if there are still at least one
  5697. * finger, and stop dragging else.
  5698. *
  5699. * @param {event} e A touch event.
  5700. */
  5701. function _handleLeave(e) {
  5702. if (_settings('touchEnabled')) {
  5703. _downTouches = e.touches;
  5704. var inertiaRatio = _settings('touchInertiaRatio');
  5705. if (_movingTimeoutId) {
  5706. _isMoving = false;
  5707. clearTimeout(_movingTimeoutId);
  5708. }
  5709. switch (_touchMode) {
  5710. case 2:
  5711. if (e.touches.length === 1) {
  5712. _handleStart(e);
  5713. e.preventDefault();
  5714. break;
  5715. }
  5716. /* falls through */
  5717. case 1:
  5718. _camera.isMoving = false;
  5719. _self.dispatchEvent('stopDrag');
  5720. if (_isMoving) {
  5721. _doubleTap = false;
  5722. sigma.misc.animation.camera(
  5723. _camera,
  5724. {
  5725. x: _camera.x +
  5726. inertiaRatio * (_camera.x - _lastCameraX),
  5727. y: _camera.y +
  5728. inertiaRatio * (_camera.y - _lastCameraY)
  5729. },
  5730. {
  5731. easing: 'quadraticOut',
  5732. duration: _settings('touchInertiaDuration')
  5733. }
  5734. );
  5735. }
  5736. _isMoving = false;
  5737. _touchMode = 0;
  5738. break;
  5739. }
  5740. }
  5741. }
  5742. /**
  5743. * The handler listening to the 'touchmove' event. It will effectively drag
  5744. * the graph, and eventually zooms and turn it if the user is using two
  5745. * fingers.
  5746. *
  5747. * @param {event} e A touch event.
  5748. */
  5749. function _handleMove(e) {
  5750. if (!_doubleTap && _settings('touchEnabled')) {
  5751. var x0,
  5752. x1,
  5753. y0,
  5754. y1,
  5755. cos,
  5756. sin,
  5757. end,
  5758. pos0,
  5759. pos1,
  5760. diff,
  5761. start,
  5762. dAngle,
  5763. dRatio,
  5764. newStageX,
  5765. newStageY,
  5766. newStageRatio,
  5767. newStageAngle;
  5768. _downTouches = e.touches;
  5769. _isMoving = true;
  5770. if (_movingTimeoutId)
  5771. clearTimeout(_movingTimeoutId);
  5772. _movingTimeoutId = setTimeout(function() {
  5773. _isMoving = false;
  5774. }, _settings('dragTimeout'));
  5775. switch (_touchMode) {
  5776. case 1:
  5777. pos0 = position(_downTouches[0]);
  5778. x0 = pos0.x;
  5779. y0 = pos0.y;
  5780. diff = _camera.cameraPosition(
  5781. x0 - _startTouchX0,
  5782. y0 - _startTouchY0,
  5783. true
  5784. );
  5785. newStageX = _startCameraX - diff.x;
  5786. newStageY = _startCameraY - diff.y;
  5787. if (newStageX !== _camera.x || newStageY !== _camera.y) {
  5788. _lastCameraX = _camera.x;
  5789. _lastCameraY = _camera.y;
  5790. _camera.goTo({
  5791. x: newStageX,
  5792. y: newStageY
  5793. });
  5794. _self.dispatchEvent('mousemove', {
  5795. x: pos0.x - sigma.utils.getWidth(e) / 2,
  5796. y: pos0.y - sigma.utils.getHeight(e) / 2,
  5797. clientX: e.clientX,
  5798. clientY: e.clientY,
  5799. ctrlKey: e.ctrlKey,
  5800. metaKey: e.metaKey,
  5801. altKey: e.altKey,
  5802. shiftKey: e.shiftKey
  5803. });
  5804. _self.dispatchEvent('drag');
  5805. }
  5806. break;
  5807. case 2:
  5808. pos0 = position(_downTouches[0]);
  5809. pos1 = position(_downTouches[1]);
  5810. x0 = pos0.x;
  5811. y0 = pos0.y;
  5812. x1 = pos1.x;
  5813. y1 = pos1.y;
  5814. start = _camera.cameraPosition(
  5815. (_startTouchX0 + _startTouchX1) / 2 -
  5816. sigma.utils.getWidth(e) / 2,
  5817. (_startTouchY0 + _startTouchY1) / 2 -
  5818. sigma.utils.getHeight(e) / 2,
  5819. true
  5820. );
  5821. end = _camera.cameraPosition(
  5822. (x0 + x1) / 2 - sigma.utils.getWidth(e) / 2,
  5823. (y0 + y1) / 2 - sigma.utils.getHeight(e) / 2,
  5824. true
  5825. );
  5826. dAngle = Math.atan2(y1 - y0, x1 - x0) - _startTouchAngle;
  5827. dRatio = Math.sqrt(
  5828. Math.pow(y1 - y0, 2) + Math.pow(x1 - x0, 2)
  5829. ) / _startTouchDistance;
  5830. // Translation:
  5831. x0 = start.x;
  5832. y0 = start.y;
  5833. // Homothetic transformation:
  5834. newStageRatio = _startCameraRatio / dRatio;
  5835. x0 = x0 * dRatio;
  5836. y0 = y0 * dRatio;
  5837. // Rotation:
  5838. newStageAngle = _startCameraAngle - dAngle;
  5839. cos = Math.cos(-dAngle);
  5840. sin = Math.sin(-dAngle);
  5841. x1 = x0 * cos + y0 * sin;
  5842. y1 = y0 * cos - x0 * sin;
  5843. x0 = x1;
  5844. y0 = y1;
  5845. // Finalize:
  5846. newStageX = x0 - end.x + _startCameraX;
  5847. newStageY = y0 - end.y + _startCameraY;
  5848. if (
  5849. newStageRatio !== _camera.ratio ||
  5850. newStageAngle !== _camera.angle ||
  5851. newStageX !== _camera.x ||
  5852. newStageY !== _camera.y
  5853. ) {
  5854. _lastCameraX = _camera.x;
  5855. _lastCameraY = _camera.y;
  5856. _lastCameraAngle = _camera.angle;
  5857. _lastCameraRatio = _camera.ratio;
  5858. _camera.goTo({
  5859. x: newStageX,
  5860. y: newStageY,
  5861. angle: newStageAngle,
  5862. ratio: newStageRatio
  5863. });
  5864. _self.dispatchEvent('drag');
  5865. }
  5866. break;
  5867. }
  5868. e.preventDefault();
  5869. return false;
  5870. }
  5871. }
  5872. /**
  5873. * The handler listening to the double tap custom event. It will
  5874. * basically zoom into the graph.
  5875. *
  5876. * @param {event} e A touch event.
  5877. */
  5878. function _doubleTapHandler(e) {
  5879. var pos,
  5880. ratio,
  5881. animation;
  5882. if (e.touches && e.touches.length === 1 && _settings('touchEnabled')) {
  5883. _doubleTap = true;
  5884. ratio = 1 / _settings('doubleClickZoomingRatio');
  5885. pos = position(e.touches[0]);
  5886. _self.dispatchEvent('doubleclick', {
  5887. x: pos.x - sigma.utils.getWidth(e) / 2,
  5888. y: pos.y - sigma.utils.getHeight(e) / 2,
  5889. clientX: e.clientX,
  5890. clientY: e.clientY,
  5891. ctrlKey: e.ctrlKey,
  5892. metaKey: e.metaKey,
  5893. altKey: e.altKey,
  5894. shiftKey: e.shiftKey
  5895. });
  5896. if (_settings('doubleClickEnabled')) {
  5897. pos = _camera.cameraPosition(
  5898. pos.x - sigma.utils.getWidth(e) / 2,
  5899. pos.y - sigma.utils.getHeight(e) / 2,
  5900. true
  5901. );
  5902. animation = {
  5903. duration: _settings('doubleClickZoomDuration'),
  5904. onComplete: function() {
  5905. _doubleTap = false;
  5906. }
  5907. };
  5908. sigma.utils.zoomTo(_camera, pos.x, pos.y, ratio, animation);
  5909. }
  5910. if (e.preventDefault)
  5911. e.preventDefault();
  5912. else
  5913. e.returnValue = false;
  5914. e.stopPropagation();
  5915. return false;
  5916. }
  5917. }
  5918. };
  5919. }).call(this);
  5920. ;(function(undefined) {
  5921. 'use strict';
  5922. if (typeof sigma === 'undefined')
  5923. throw 'sigma is not declared';
  5924. if (typeof conrad === 'undefined')
  5925. throw 'conrad is not declared';
  5926. // Initialize packages:
  5927. sigma.utils.pkg('sigma.renderers');
  5928. /**
  5929. * This function is the constructor of the canvas sigma's renderer.
  5930. *
  5931. * @param {sigma.classes.graph} graph The graph to render.
  5932. * @param {sigma.classes.camera} camera The camera.
  5933. * @param {configurable} settings The sigma instance settings
  5934. * function.
  5935. * @param {object} object The options object.
  5936. * @return {sigma.renderers.canvas} The renderer instance.
  5937. */
  5938. sigma.renderers.canvas = function(graph, camera, settings, options) {
  5939. if (typeof options !== 'object')
  5940. throw 'sigma.renderers.canvas: Wrong arguments.';
  5941. if (!(options.container instanceof HTMLElement))
  5942. throw 'Container not found.';
  5943. var k,
  5944. i,
  5945. l,
  5946. a,
  5947. fn,
  5948. self = this;
  5949. sigma.classes.dispatcher.extend(this);
  5950. // Initialize main attributes:
  5951. Object.defineProperty(this, 'conradId', {
  5952. value: sigma.utils.id()
  5953. });
  5954. this.graph = graph;
  5955. this.camera = camera;
  5956. this.contexts = {};
  5957. this.domElements = {};
  5958. this.options = options;
  5959. this.container = this.options.container;
  5960. this.settings = (
  5961. typeof options.settings === 'object' &&
  5962. options.settings
  5963. ) ?
  5964. settings.embedObjects(options.settings) :
  5965. settings;
  5966. // Node indexes:
  5967. this.nodesOnScreen = [];
  5968. this.edgesOnScreen = [];
  5969. // Conrad related attributes:
  5970. this.jobs = {};
  5971. // Find the prefix:
  5972. this.options.prefix = 'renderer' + this.conradId + ':';
  5973. // Initialize the DOM elements:
  5974. if (
  5975. !this.settings('batchEdgesDrawing')
  5976. ) {
  5977. this.initDOM('canvas', 'scene');
  5978. this.contexts.edges = this.contexts.scene;
  5979. this.contexts.nodes = this.contexts.scene;
  5980. this.contexts.labels = this.contexts.scene;
  5981. } else {
  5982. this.initDOM('canvas', 'edges');
  5983. this.initDOM('canvas', 'scene');
  5984. this.contexts.nodes = this.contexts.scene;
  5985. this.contexts.labels = this.contexts.scene;
  5986. }
  5987. this.initDOM('canvas', 'mouse');
  5988. this.contexts.hover = this.contexts.mouse;
  5989. // Initialize captors:
  5990. this.captors = [];
  5991. a = this.options.captors || [sigma.captors.mouse, sigma.captors.touch];
  5992. for (i = 0, l = a.length; i < l; i++) {
  5993. fn = typeof a[i] === 'function' ? a[i] : sigma.captors[a[i]];
  5994. this.captors.push(
  5995. new fn(
  5996. this.domElements.mouse,
  5997. this.camera,
  5998. this.settings
  5999. )
  6000. );
  6001. }
  6002. // Deal with sigma events:
  6003. sigma.misc.bindEvents.call(this, this.options.prefix);
  6004. sigma.misc.drawHovers.call(this, this.options.prefix);
  6005. this.resize(false);
  6006. };
  6007. /**
  6008. * This method renders the graph on the canvases.
  6009. *
  6010. * @param {?object} options Eventually an object of options.
  6011. * @return {sigma.renderers.canvas} Returns the instance itself.
  6012. */
  6013. sigma.renderers.canvas.prototype.render = function(options) {
  6014. options = options || {};
  6015. var a,
  6016. i,
  6017. k,
  6018. l,
  6019. o,
  6020. id,
  6021. end,
  6022. job,
  6023. start,
  6024. edges,
  6025. renderers,
  6026. rendererType,
  6027. batchSize,
  6028. tempGCO,
  6029. index = {},
  6030. graph = this.graph,
  6031. nodes = this.graph.nodes,
  6032. prefix = this.options.prefix || '',
  6033. drawEdges = this.settings(options, 'drawEdges'),
  6034. drawNodes = this.settings(options, 'drawNodes'),
  6035. drawLabels = this.settings(options, 'drawLabels'),
  6036. drawEdgeLabels = this.settings(options, 'drawEdgeLabels'),
  6037. embedSettings = this.settings.embedObjects(options, {
  6038. prefix: this.options.prefix
  6039. });
  6040. // Call the resize function:
  6041. this.resize(false);
  6042. // Check the 'hideEdgesOnMove' setting:
  6043. if (this.settings(options, 'hideEdgesOnMove'))
  6044. if (this.camera.isAnimated || this.camera.isMoving)
  6045. drawEdges = false;
  6046. // Apply the camera's view:
  6047. this.camera.applyView(
  6048. undefined,
  6049. this.options.prefix,
  6050. {
  6051. width: this.width,
  6052. height: this.height
  6053. }
  6054. );
  6055. // Clear canvases:
  6056. this.clear();
  6057. // Kill running jobs:
  6058. for (k in this.jobs)
  6059. if (conrad.hasJob(k))
  6060. conrad.killJob(k);
  6061. // Find which nodes are on screen:
  6062. this.edgesOnScreen = [];
  6063. this.nodesOnScreen = this.camera.quadtree.area(
  6064. this.camera.getRectangle(this.width, this.height)
  6065. );
  6066. for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++)
  6067. index[a[i].id] = a[i];
  6068. // Draw edges:
  6069. // - If settings('batchEdgesDrawing') is true, the edges are displayed per
  6070. // batches. If not, they are drawn in one frame.
  6071. if (drawEdges) {
  6072. // First, let's identify which edges to draw. To do this, we just keep
  6073. // every edges that have at least one extremity displayed according to
  6074. // the quadtree and the "hidden" attribute. We also do not keep hidden
  6075. // edges.
  6076. for (a = graph.edges(), i = 0, l = a.length; i < l; i++) {
  6077. o = a[i];
  6078. if (
  6079. (index[o.source] || index[o.target]) &&
  6080. (!o.hidden && !nodes(o.source).hidden && !nodes(o.target).hidden)
  6081. )
  6082. this.edgesOnScreen.push(o);
  6083. }
  6084. // If the "batchEdgesDrawing" settings is true, edges are batched:
  6085. if (this.settings(options, 'batchEdgesDrawing')) {
  6086. id = 'edges_' + this.conradId;
  6087. batchSize = embedSettings('canvasEdgesBatchSize');
  6088. edges = this.edgesOnScreen;
  6089. l = edges.length;
  6090. start = 0;
  6091. end = Math.min(edges.length, start + batchSize);
  6092. job = function() {
  6093. tempGCO = this.contexts.edges.globalCompositeOperation;
  6094. this.contexts.edges.globalCompositeOperation = 'destination-over';
  6095. renderers = sigma.canvas.edges;
  6096. for (i = start; i < end; i++) {
  6097. o = edges[i];
  6098. (renderers[
  6099. o.type || this.settings(options, 'defaultEdgeType')
  6100. ] || renderers.def)(
  6101. o,
  6102. graph.nodes(o.source),
  6103. graph.nodes(o.target),
  6104. this.contexts.edges,
  6105. embedSettings
  6106. );
  6107. }
  6108. // Draw edge labels:
  6109. if (drawEdgeLabels) {
  6110. renderers = sigma.canvas.edges.labels;
  6111. for (i = start; i < end; i++) {
  6112. o = edges[i];
  6113. if (!o.hidden)
  6114. (renderers[
  6115. o.type || this.settings(options, 'defaultEdgeType')
  6116. ] || renderers.def)(
  6117. o,
  6118. graph.nodes(o.source),
  6119. graph.nodes(o.target),
  6120. this.contexts.labels,
  6121. embedSettings
  6122. );
  6123. }
  6124. }
  6125. // Restore original globalCompositeOperation:
  6126. this.contexts.edges.globalCompositeOperation = tempGCO;
  6127. // Catch job's end:
  6128. if (end === edges.length) {
  6129. delete this.jobs[id];
  6130. return false;
  6131. }
  6132. start = end + 1;
  6133. end = Math.min(edges.length, start + batchSize);
  6134. return true;
  6135. };
  6136. this.jobs[id] = job;
  6137. conrad.addJob(id, job.bind(this));
  6138. // If not, they are drawn in one frame:
  6139. } else {
  6140. renderers = sigma.canvas.edges;
  6141. for (a = this.edgesOnScreen, i = 0, l = a.length; i < l; i++) {
  6142. o = a[i];
  6143. (renderers[
  6144. o.type || this.settings(options, 'defaultEdgeType')
  6145. ] || renderers.def)(
  6146. o,
  6147. graph.nodes(o.source),
  6148. graph.nodes(o.target),
  6149. this.contexts.edges,
  6150. embedSettings
  6151. );
  6152. }
  6153. // Draw edge labels:
  6154. // - No batching
  6155. if (drawEdgeLabels) {
  6156. renderers = sigma.canvas.edges.labels;
  6157. for (a = this.edgesOnScreen, i = 0, l = a.length; i < l; i++)
  6158. if (!a[i].hidden)
  6159. (renderers[
  6160. a[i].type || this.settings(options, 'defaultEdgeType')
  6161. ] || renderers.def)(
  6162. a[i],
  6163. graph.nodes(a[i].source),
  6164. graph.nodes(a[i].target),
  6165. this.contexts.labels,
  6166. embedSettings
  6167. );
  6168. }
  6169. }
  6170. }
  6171. // Draw nodes:
  6172. // - No batching
  6173. if (drawNodes) {
  6174. renderers = sigma.canvas.nodes;
  6175. for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++)
  6176. if (!a[i].hidden)
  6177. (renderers[
  6178. a[i].type || this.settings(options, 'defaultNodeType')
  6179. ] || renderers.def)(
  6180. a[i],
  6181. this.contexts.nodes,
  6182. embedSettings
  6183. );
  6184. }
  6185. // Draw labels:
  6186. // - No batching
  6187. if (drawLabels) {
  6188. renderers = sigma.canvas.labels;
  6189. for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++)
  6190. if (!a[i].hidden)
  6191. (renderers[
  6192. a[i].type || this.settings(options, 'defaultNodeType')
  6193. ] || renderers.def)(
  6194. a[i],
  6195. this.contexts.labels,
  6196. embedSettings
  6197. );
  6198. }
  6199. this.dispatchEvent('render');
  6200. return this;
  6201. };
  6202. /**
  6203. * This method creates a DOM element of the specified type, switches its
  6204. * position to "absolute", references it to the domElements attribute, and
  6205. * finally appends it to the container.
  6206. *
  6207. * @param {string} tag The label tag.
  6208. * @param {string} id The id of the element (to store it in "domElements").
  6209. */
  6210. sigma.renderers.canvas.prototype.initDOM = function(tag, id) {
  6211. var dom = document.createElement(tag);
  6212. dom.style.position = 'absolute';
  6213. dom.setAttribute('class', 'sigma-' + id);
  6214. this.domElements[id] = dom;
  6215. this.container.appendChild(dom);
  6216. if (tag.toLowerCase() === 'canvas')
  6217. this.contexts[id] = dom.getContext('2d');
  6218. };
  6219. /**
  6220. * This method resizes each DOM elements in the container and stores the new
  6221. * dimensions. Then, it renders the graph.
  6222. *
  6223. * @param {?number} width The new width of the container.
  6224. * @param {?number} height The new height of the container.
  6225. * @return {sigma.renderers.canvas} Returns the instance itself.
  6226. */
  6227. sigma.renderers.canvas.prototype.resize = function(w, h) {
  6228. var k,
  6229. oldWidth = this.width,
  6230. oldHeight = this.height,
  6231. pixelRatio = 1;
  6232. // TODO:
  6233. // *****
  6234. // This pixelRatio is the solution to display with the good definition
  6235. // on canvases on Retina displays (ie oversampling). Unfortunately, it
  6236. // has a huge performance cost...
  6237. // > pixelRatio = window.devicePixelRatio || 1;
  6238. if (w !== undefined && h !== undefined) {
  6239. this.width = w;
  6240. this.height = h;
  6241. } else {
  6242. this.width = this.container.offsetWidth;
  6243. this.height = this.container.offsetHeight;
  6244. w = this.width;
  6245. h = this.height;
  6246. }
  6247. if (oldWidth !== this.width || oldHeight !== this.height) {
  6248. for (k in this.domElements) {
  6249. this.domElements[k].style.width = w + 'px';
  6250. this.domElements[k].style.height = h + 'px';
  6251. if (this.domElements[k].tagName.toLowerCase() === 'canvas') {
  6252. this.domElements[k].setAttribute('width', (w * pixelRatio) + 'px');
  6253. this.domElements[k].setAttribute('height', (h * pixelRatio) + 'px');
  6254. if (pixelRatio !== 1)
  6255. this.contexts[k].scale(pixelRatio, pixelRatio);
  6256. }
  6257. }
  6258. }
  6259. return this;
  6260. };
  6261. /**
  6262. * This method clears each canvas.
  6263. *
  6264. * @return {sigma.renderers.canvas} Returns the instance itself.
  6265. */
  6266. sigma.renderers.canvas.prototype.clear = function() {
  6267. var k;
  6268. for (k in this.domElements)
  6269. if (this.domElements[k].tagName === 'CANVAS')
  6270. this.domElements[k].width = this.domElements[k].width;
  6271. return this;
  6272. };
  6273. /**
  6274. * This method kills contexts and other attributes.
  6275. */
  6276. sigma.renderers.canvas.prototype.kill = function() {
  6277. var k,
  6278. captor;
  6279. // Kill captors:
  6280. while ((captor = this.captors.pop()))
  6281. captor.kill();
  6282. delete this.captors;
  6283. // Kill contexts:
  6284. for (k in this.domElements) {
  6285. this.domElements[k].parentNode.removeChild(this.domElements[k]);
  6286. delete this.domElements[k];
  6287. delete this.contexts[k];
  6288. }
  6289. delete this.domElements;
  6290. delete this.contexts;
  6291. };
  6292. /**
  6293. * The labels, nodes and edges renderers are stored in the three following
  6294. * objects. When an element is drawn, its type will be checked and if a
  6295. * renderer with the same name exists, it will be used. If not found, the
  6296. * default renderer will be used instead.
  6297. *
  6298. * They are stored in different files, in the "./canvas" folder.
  6299. */
  6300. sigma.utils.pkg('sigma.canvas.nodes');
  6301. sigma.utils.pkg('sigma.canvas.edges');
  6302. sigma.utils.pkg('sigma.canvas.labels');
  6303. }).call(this);
  6304. ;(function(undefined) {
  6305. 'use strict';
  6306. if (typeof sigma === 'undefined')
  6307. throw 'sigma is not declared';
  6308. // Initialize packages:
  6309. sigma.utils.pkg('sigma.renderers');
  6310. /**
  6311. * This function is the constructor of the canvas sigma's renderer.
  6312. *
  6313. * @param {sigma.classes.graph} graph The graph to render.
  6314. * @param {sigma.classes.camera} camera The camera.
  6315. * @param {configurable} settings The sigma instance settings
  6316. * function.
  6317. * @param {object} object The options object.
  6318. * @return {sigma.renderers.canvas} The renderer instance.
  6319. */
  6320. sigma.renderers.webgl = function(graph, camera, settings, options) {
  6321. if (typeof options !== 'object')
  6322. throw 'sigma.renderers.webgl: Wrong arguments.';
  6323. if (!(options.container instanceof HTMLElement))
  6324. throw 'Container not found.';
  6325. var k,
  6326. i,
  6327. l,
  6328. a,
  6329. fn,
  6330. _self = this;
  6331. sigma.classes.dispatcher.extend(this);
  6332. // Conrad related attributes:
  6333. this.jobs = {};
  6334. Object.defineProperty(this, 'conradId', {
  6335. value: sigma.utils.id()
  6336. });
  6337. // Initialize main attributes:
  6338. this.graph = graph;
  6339. this.camera = camera;
  6340. this.contexts = {};
  6341. this.domElements = {};
  6342. this.options = options;
  6343. this.container = this.options.container;
  6344. this.settings = (
  6345. typeof options.settings === 'object' &&
  6346. options.settings
  6347. ) ?
  6348. settings.embedObjects(options.settings) :
  6349. settings;
  6350. // Find the prefix:
  6351. this.options.prefix = this.camera.readPrefix;
  6352. // Initialize programs hash
  6353. Object.defineProperty(this, 'nodePrograms', {
  6354. value: {}
  6355. });
  6356. Object.defineProperty(this, 'edgePrograms', {
  6357. value: {}
  6358. });
  6359. Object.defineProperty(this, 'nodeFloatArrays', {
  6360. value: {}
  6361. });
  6362. Object.defineProperty(this, 'edgeFloatArrays', {
  6363. value: {}
  6364. });
  6365. // Initialize the DOM elements:
  6366. if (this.settings(options, 'batchEdgesDrawing')) {
  6367. this.initDOM('canvas', 'edges', true);
  6368. this.initDOM('canvas', 'nodes', true);
  6369. } else {
  6370. this.initDOM('canvas', 'scene', true);
  6371. this.contexts.nodes = this.contexts.scene;
  6372. this.contexts.edges = this.contexts.scene;
  6373. }
  6374. this.initDOM('canvas', 'labels');
  6375. this.initDOM('canvas', 'mouse');
  6376. this.contexts.hover = this.contexts.mouse;
  6377. // Initialize captors:
  6378. this.captors = [];
  6379. a = this.options.captors || [sigma.captors.mouse, sigma.captors.touch];
  6380. for (i = 0, l = a.length; i < l; i++) {
  6381. fn = typeof a[i] === 'function' ? a[i] : sigma.captors[a[i]];
  6382. this.captors.push(
  6383. new fn(
  6384. this.domElements.mouse,
  6385. this.camera,
  6386. this.settings
  6387. )
  6388. );
  6389. }
  6390. // Deal with sigma events:
  6391. sigma.misc.bindEvents.call(this, this.camera.prefix);
  6392. sigma.misc.drawHovers.call(this, this.camera.prefix);
  6393. this.resize();
  6394. };
  6395. /**
  6396. * This method will generate the nodes and edges float arrays. This step is
  6397. * separated from the "render" method, because to keep WebGL efficient, since
  6398. * all the camera and middlewares are modelised as matrices and they do not
  6399. * require the float arrays to be regenerated.
  6400. *
  6401. * Basically, when the user moves the camera or applies some specific linear
  6402. * transformations, this process step will be skipped, and the "render"
  6403. * method will efficiently refresh the rendering.
  6404. *
  6405. * And when the user modifies the graph colors or positions (applying a new
  6406. * layout or filtering the colors, for instance), this "process" step will be
  6407. * required to regenerate the float arrays.
  6408. *
  6409. * @return {sigma.renderers.webgl} Returns the instance itself.
  6410. */
  6411. sigma.renderers.webgl.prototype.process = function() {
  6412. var a,
  6413. i,
  6414. l,
  6415. k,
  6416. type,
  6417. renderer,
  6418. graph = this.graph,
  6419. options = sigma.utils.extend(options, this.options);
  6420. // Empty float arrays:
  6421. for (k in this.nodeFloatArrays)
  6422. delete this.nodeFloatArrays[k];
  6423. for (k in this.edgeFloatArrays)
  6424. delete this.edgeFloatArrays[k];
  6425. // Sort edges and nodes per types:
  6426. for (a = graph.edges(), i = 0, l = a.length; i < l; i++) {
  6427. type = a[i].type || this.settings(options, 'defaultEdgeType');
  6428. k = (type && sigma.webgl.edges[type]) ? type : 'def';
  6429. if (!this.edgeFloatArrays[k])
  6430. this.edgeFloatArrays[k] = {
  6431. edges: []
  6432. };
  6433. this.edgeFloatArrays[k].edges.push(a[i]);
  6434. }
  6435. for (a = graph.nodes(), i = 0, l = a.length; i < l; i++) {
  6436. type = a[i].type || this.settings(options, 'defaultNodeType');
  6437. k = (type && sigma.webgl.nodes[type]) ? type : 'def';
  6438. if (!this.nodeFloatArrays[k])
  6439. this.nodeFloatArrays[k] = {
  6440. nodes: []
  6441. };
  6442. this.nodeFloatArrays[k].nodes.push(a[i]);
  6443. }
  6444. // Push edges:
  6445. for (k in this.edgeFloatArrays) {
  6446. renderer = sigma.webgl.edges[k];
  6447. for (a = this.edgeFloatArrays[k].edges, i = 0, l = a.length; i < l; i++) {
  6448. if (!this.edgeFloatArrays[k].array)
  6449. this.edgeFloatArrays[k].array = new Float32Array(
  6450. a.length * renderer.POINTS * renderer.ATTRIBUTES
  6451. );
  6452. // Just check that the edge and both its extremities are visible:
  6453. if (
  6454. !a[i].hidden &&
  6455. !graph.nodes(a[i].source).hidden &&
  6456. !graph.nodes(a[i].target).hidden
  6457. )
  6458. renderer.addEdge(
  6459. a[i],
  6460. graph.nodes(a[i].source),
  6461. graph.nodes(a[i].target),
  6462. this.edgeFloatArrays[k].array,
  6463. i * renderer.POINTS * renderer.ATTRIBUTES,
  6464. options.prefix,
  6465. this.settings
  6466. );
  6467. }
  6468. }
  6469. // Push nodes:
  6470. for (k in this.nodeFloatArrays) {
  6471. renderer = sigma.webgl.nodes[k];
  6472. for (a = this.nodeFloatArrays[k].nodes, i = 0, l = a.length; i < l; i++) {
  6473. if (!this.nodeFloatArrays[k].array)
  6474. this.nodeFloatArrays[k].array = new Float32Array(
  6475. a.length * renderer.POINTS * renderer.ATTRIBUTES
  6476. );
  6477. // Just check that the edge and both its extremities are visible:
  6478. if (
  6479. !a[i].hidden
  6480. )
  6481. renderer.addNode(
  6482. a[i],
  6483. this.nodeFloatArrays[k].array,
  6484. i * renderer.POINTS * renderer.ATTRIBUTES,
  6485. options.prefix,
  6486. this.settings
  6487. );
  6488. }
  6489. }
  6490. return this;
  6491. };
  6492. /**
  6493. * This method renders the graph. It basically calls each program (and
  6494. * generate them if they do not exist yet) to render nodes and edges, batched
  6495. * per renderer.
  6496. *
  6497. * As in the canvas renderer, it is possible to display edges, nodes and / or
  6498. * labels in batches, to make the whole thing way more scalable.
  6499. *
  6500. * @param {?object} params Eventually an object of options.
  6501. * @return {sigma.renderers.webgl} Returns the instance itself.
  6502. */
  6503. sigma.renderers.webgl.prototype.render = function(params) {
  6504. var a,
  6505. i,
  6506. l,
  6507. k,
  6508. o,
  6509. program,
  6510. renderer,
  6511. self = this,
  6512. graph = this.graph,
  6513. nodesGl = this.contexts.nodes,
  6514. edgesGl = this.contexts.edges,
  6515. matrix = this.camera.getMatrix(),
  6516. options = sigma.utils.extend(params, this.options),
  6517. drawLabels = this.settings(options, 'drawLabels'),
  6518. drawEdges = this.settings(options, 'drawEdges'),
  6519. drawNodes = this.settings(options, 'drawNodes');
  6520. // Call the resize function:
  6521. this.resize(false);
  6522. // Check the 'hideEdgesOnMove' setting:
  6523. if (this.settings(options, 'hideEdgesOnMove'))
  6524. if (this.camera.isAnimated || this.camera.isMoving)
  6525. drawEdges = false;
  6526. // Clear canvases:
  6527. this.clear();
  6528. // Translate matrix to [width/2, height/2]:
  6529. matrix = sigma.utils.matrices.multiply(
  6530. matrix,
  6531. sigma.utils.matrices.translation(this.width / 2, this.height / 2)
  6532. );
  6533. // Kill running jobs:
  6534. for (k in this.jobs)
  6535. if (conrad.hasJob(k))
  6536. conrad.killJob(k);
  6537. if (drawEdges) {
  6538. if (this.settings(options, 'batchEdgesDrawing'))
  6539. (function() {
  6540. var a,
  6541. k,
  6542. i,
  6543. id,
  6544. job,
  6545. arr,
  6546. end,
  6547. start,
  6548. renderer,
  6549. batchSize,
  6550. currentProgram;
  6551. id = 'edges_' + this.conradId;
  6552. batchSize = this.settings(options, 'webglEdgesBatchSize');
  6553. a = Object.keys(this.edgeFloatArrays);
  6554. if (!a.length)
  6555. return;
  6556. i = 0;
  6557. renderer = sigma.webgl.edges[a[i]];
  6558. arr = this.edgeFloatArrays[a[i]].array;
  6559. start = 0;
  6560. end = Math.min(
  6561. start + batchSize * renderer.POINTS,
  6562. arr.length / renderer.ATTRIBUTES
  6563. );
  6564. job = function() {
  6565. // Check program:
  6566. if (!this.edgePrograms[a[i]])
  6567. this.edgePrograms[a[i]] = renderer.initProgram(edgesGl);
  6568. if (start < end) {
  6569. edgesGl.useProgram(this.edgePrograms[a[i]]);
  6570. renderer.render(
  6571. edgesGl,
  6572. this.edgePrograms[a[i]],
  6573. arr,
  6574. {
  6575. settings: this.settings,
  6576. matrix: matrix,
  6577. width: this.width,
  6578. height: this.height,
  6579. ratio: this.camera.ratio,
  6580. scalingRatio: this.settings(
  6581. options,
  6582. 'webglOversamplingRatio'
  6583. ),
  6584. start: start,
  6585. count: end - start
  6586. }
  6587. );
  6588. }
  6589. // Catch job's end:
  6590. if (
  6591. end >= arr.length / renderer.ATTRIBUTES &&
  6592. i === a.length - 1
  6593. ) {
  6594. delete this.jobs[id];
  6595. return false;
  6596. }
  6597. if (end >= arr.length / renderer.ATTRIBUTES) {
  6598. i++;
  6599. arr = this.edgeFloatArrays[a[i]].array;
  6600. renderer = sigma.webgl.edges[a[i]];
  6601. start = 0;
  6602. end = Math.min(
  6603. start + batchSize * renderer.POINTS,
  6604. arr.length / renderer.ATTRIBUTES
  6605. );
  6606. } else {
  6607. start = end;
  6608. end = Math.min(
  6609. start + batchSize * renderer.POINTS,
  6610. arr.length / renderer.ATTRIBUTES
  6611. );
  6612. }
  6613. return true;
  6614. };
  6615. this.jobs[id] = job;
  6616. conrad.addJob(id, job.bind(this));
  6617. }).call(this);
  6618. else {
  6619. for (k in this.edgeFloatArrays) {
  6620. renderer = sigma.webgl.edges[k];
  6621. // Check program:
  6622. if (!this.edgePrograms[k])
  6623. this.edgePrograms[k] = renderer.initProgram(edgesGl);
  6624. // Render
  6625. if (this.edgeFloatArrays[k]) {
  6626. edgesGl.useProgram(this.edgePrograms[k]);
  6627. renderer.render(
  6628. edgesGl,
  6629. this.edgePrograms[k],
  6630. this.edgeFloatArrays[k].array,
  6631. {
  6632. settings: this.settings,
  6633. matrix: matrix,
  6634. width: this.width,
  6635. height: this.height,
  6636. ratio: this.camera.ratio,
  6637. scalingRatio: this.settings(options, 'webglOversamplingRatio')
  6638. }
  6639. );
  6640. }
  6641. }
  6642. }
  6643. }
  6644. if (drawNodes) {
  6645. // Enable blending:
  6646. nodesGl.blendFunc(nodesGl.SRC_ALPHA, nodesGl.ONE_MINUS_SRC_ALPHA);
  6647. nodesGl.enable(nodesGl.BLEND);
  6648. for (k in this.nodeFloatArrays) {
  6649. renderer = sigma.webgl.nodes[k];
  6650. // Check program:
  6651. if (!this.nodePrograms[k])
  6652. this.nodePrograms[k] = renderer.initProgram(nodesGl);
  6653. // Render
  6654. if (this.nodeFloatArrays[k]) {
  6655. nodesGl.useProgram(this.nodePrograms[k]);
  6656. renderer.render(
  6657. nodesGl,
  6658. this.nodePrograms[k],
  6659. this.nodeFloatArrays[k].array,
  6660. {
  6661. settings: this.settings,
  6662. matrix: matrix,
  6663. width: this.width,
  6664. height: this.height,
  6665. ratio: this.camera.ratio,
  6666. scalingRatio: this.settings(options, 'webglOversamplingRatio')
  6667. }
  6668. );
  6669. }
  6670. }
  6671. }
  6672. if (drawLabels) {
  6673. a = this.camera.quadtree.area(
  6674. this.camera.getRectangle(this.width, this.height)
  6675. );
  6676. // Apply camera view to these nodes:
  6677. this.camera.applyView(
  6678. undefined,
  6679. undefined,
  6680. {
  6681. nodes: a,
  6682. edges: [],
  6683. width: this.width,
  6684. height: this.height
  6685. }
  6686. );
  6687. o = function(key) {
  6688. return self.settings({
  6689. prefix: self.camera.prefix
  6690. }, key);
  6691. };
  6692. for (i = 0, l = a.length; i < l; i++)
  6693. if (!a[i].hidden)
  6694. (
  6695. sigma.canvas.labels[
  6696. a[i].type ||
  6697. this.settings(options, 'defaultNodeType')
  6698. ] || sigma.canvas.labels.def
  6699. )(a[i], this.contexts.labels, o);
  6700. }
  6701. this.dispatchEvent('render');
  6702. return this;
  6703. };
  6704. /**
  6705. * This method creates a DOM element of the specified type, switches its
  6706. * position to "absolute", references it to the domElements attribute, and
  6707. * finally appends it to the container.
  6708. *
  6709. * @param {string} tag The label tag.
  6710. * @param {string} id The id of the element (to store it in
  6711. * "domElements").
  6712. * @param {?boolean} webgl Will init the WebGL context if true.
  6713. */
  6714. sigma.renderers.webgl.prototype.initDOM = function(tag, id, webgl) {
  6715. var gl,
  6716. dom = document.createElement(tag),
  6717. self = this;
  6718. dom.style.position = 'absolute';
  6719. dom.setAttribute('class', 'sigma-' + id);
  6720. this.domElements[id] = dom;
  6721. this.container.appendChild(dom);
  6722. if (tag.toLowerCase() === 'canvas') {
  6723. this.contexts[id] = dom.getContext(webgl ? 'experimental-webgl' : '2d', {
  6724. preserveDrawingBuffer: true
  6725. });
  6726. // Adding webgl context loss listeners
  6727. if (webgl) {
  6728. dom.addEventListener('webglcontextlost', function(e) {
  6729. e.preventDefault();
  6730. }, false);
  6731. dom.addEventListener('webglcontextrestored', function(e) {
  6732. self.render();
  6733. }, false);
  6734. }
  6735. }
  6736. };
  6737. /**
  6738. * This method resizes each DOM elements in the container and stores the new
  6739. * dimensions. Then, it renders the graph.
  6740. *
  6741. * @param {?number} width The new width of the container.
  6742. * @param {?number} height The new height of the container.
  6743. * @return {sigma.renderers.webgl} Returns the instance itself.
  6744. */
  6745. sigma.renderers.webgl.prototype.resize = function(w, h) {
  6746. var k,
  6747. oldWidth = this.width,
  6748. oldHeight = this.height;
  6749. if (w !== undefined && h !== undefined) {
  6750. this.width = w;
  6751. this.height = h;
  6752. } else {
  6753. this.width = this.container.offsetWidth;
  6754. this.height = this.container.offsetHeight;
  6755. w = this.width;
  6756. h = this.height;
  6757. }
  6758. if (oldWidth !== this.width || oldHeight !== this.height) {
  6759. for (k in this.domElements) {
  6760. this.domElements[k].style.width = w + 'px';
  6761. this.domElements[k].style.height = h + 'px';
  6762. if (this.domElements[k].tagName.toLowerCase() === 'canvas') {
  6763. // If simple 2D canvas:
  6764. if (this.contexts[k] && this.contexts[k].scale) {
  6765. this.domElements[k].setAttribute('width', w + 'px');
  6766. this.domElements[k].setAttribute('height', h + 'px');
  6767. } else {
  6768. this.domElements[k].setAttribute(
  6769. 'width',
  6770. (w * this.settings('webglOversamplingRatio')) + 'px'
  6771. );
  6772. this.domElements[k].setAttribute(
  6773. 'height',
  6774. (h * this.settings('webglOversamplingRatio')) + 'px'
  6775. );
  6776. }
  6777. }
  6778. }
  6779. }
  6780. // Scale:
  6781. for (k in this.contexts)
  6782. if (this.contexts[k] && this.contexts[k].viewport)
  6783. this.contexts[k].viewport(
  6784. 0,
  6785. 0,
  6786. this.width * this.settings('webglOversamplingRatio'),
  6787. this.height * this.settings('webglOversamplingRatio')
  6788. );
  6789. return this;
  6790. };
  6791. /**
  6792. * This method clears each canvas.
  6793. *
  6794. * @return {sigma.renderers.webgl} Returns the instance itself.
  6795. */
  6796. sigma.renderers.webgl.prototype.clear = function() {
  6797. var k;
  6798. for (k in this.domElements)
  6799. if (this.domElements[k].tagName === 'CANVAS')
  6800. this.domElements[k].width = this.domElements[k].width;
  6801. this.contexts.nodes.clear(this.contexts.nodes.COLOR_BUFFER_BIT);
  6802. this.contexts.edges.clear(this.contexts.edges.COLOR_BUFFER_BIT);
  6803. return this;
  6804. };
  6805. /**
  6806. * This method kills contexts and other attributes.
  6807. */
  6808. sigma.renderers.webgl.prototype.kill = function() {
  6809. var k,
  6810. captor;
  6811. // Kill captors:
  6812. while ((captor = this.captors.pop()))
  6813. captor.kill();
  6814. delete this.captors;
  6815. // Kill contexts:
  6816. for (k in this.domElements) {
  6817. this.domElements[k].parentNode.removeChild(this.domElements[k]);
  6818. delete this.domElements[k];
  6819. delete this.contexts[k];
  6820. }
  6821. delete this.domElements;
  6822. delete this.contexts;
  6823. };
  6824. /**
  6825. * The object "sigma.webgl.nodes" contains the different WebGL node
  6826. * renderers. The default one draw nodes as discs. Here are the attributes
  6827. * any node renderer must have:
  6828. *
  6829. * {number} POINTS The number of points required to draw a node.
  6830. * {number} ATTRIBUTES The number of attributes needed to draw one point.
  6831. * {function} addNode A function that adds a node to the data stack that
  6832. * will be given to the buffer. Here is the arguments:
  6833. * > {object} node
  6834. * > {number} index The node index in the
  6835. * nodes array.
  6836. * > {Float32Array} data The stack.
  6837. * > {object} options Some options.
  6838. * {function} render The function that will effectively render the nodes
  6839. * into the buffer.
  6840. * > {WebGLRenderingContext} gl
  6841. * > {WebGLProgram} program
  6842. * > {Float32Array} data The stack to give to the
  6843. * buffer.
  6844. * > {object} params An object containing some
  6845. * options, like width,
  6846. * height, the camera ratio.
  6847. * {function} initProgram The function that will initiate the program, with
  6848. * the relevant shaders and parameters. It must return
  6849. * the newly created program.
  6850. *
  6851. * Check sigma.webgl.nodes.def or sigma.webgl.nodes.fast to see how it
  6852. * works more precisely.
  6853. */
  6854. sigma.utils.pkg('sigma.webgl.nodes');
  6855. /**
  6856. * The object "sigma.webgl.edges" contains the different WebGL edge
  6857. * renderers. The default one draw edges as direct lines. Here are the
  6858. * attributes any edge renderer must have:
  6859. *
  6860. * {number} POINTS The number of points required to draw an edge.
  6861. * {number} ATTRIBUTES The number of attributes needed to draw one point.
  6862. * {function} addEdge A function that adds an edge to the data stack that
  6863. * will be given to the buffer. Here is the arguments:
  6864. * > {object} edge
  6865. * > {object} source
  6866. * > {object} target
  6867. * > {Float32Array} data The stack.
  6868. * > {object} options Some options.
  6869. * {function} render The function that will effectively render the edges
  6870. * into the buffer.
  6871. * > {WebGLRenderingContext} gl
  6872. * > {WebGLProgram} program
  6873. * > {Float32Array} data The stack to give to the
  6874. * buffer.
  6875. * > {object} params An object containing some
  6876. * options, like width,
  6877. * height, the camera ratio.
  6878. * {function} initProgram The function that will initiate the program, with
  6879. * the relevant shaders and parameters. It must return
  6880. * the newly created program.
  6881. *
  6882. * Check sigma.webgl.edges.def or sigma.webgl.edges.fast to see how it
  6883. * works more precisely.
  6884. */
  6885. sigma.utils.pkg('sigma.webgl.edges');
  6886. /**
  6887. * The object "sigma.canvas.labels" contains the different
  6888. * label renderers for the WebGL renderer. Since displaying texts in WebGL is
  6889. * definitely painful and since there a way less labels to display than nodes
  6890. * or edges, the default renderer simply renders them in a canvas.
  6891. *
  6892. * A labels renderer is a simple function, taking as arguments the related
  6893. * node, the renderer and a settings function.
  6894. */
  6895. sigma.utils.pkg('sigma.canvas.labels');
  6896. }).call(this);
  6897. ;(function(undefined) {
  6898. 'use strict';
  6899. if (typeof sigma === 'undefined')
  6900. throw 'sigma is not declared';
  6901. if (typeof conrad === 'undefined')
  6902. throw 'conrad is not declared';
  6903. // Initialize packages:
  6904. sigma.utils.pkg('sigma.renderers');
  6905. /**
  6906. * This function is the constructor of the svg sigma's renderer.
  6907. *
  6908. * @param {sigma.classes.graph} graph The graph to render.
  6909. * @param {sigma.classes.camera} camera The camera.
  6910. * @param {configurable} settings The sigma instance settings
  6911. * function.
  6912. * @param {object} object The options object.
  6913. * @return {sigma.renderers.svg} The renderer instance.
  6914. */
  6915. sigma.renderers.svg = function(graph, camera, settings, options) {
  6916. if (typeof options !== 'object')
  6917. throw 'sigma.renderers.svg: Wrong arguments.';
  6918. if (!(options.container instanceof HTMLElement))
  6919. throw 'Container not found.';
  6920. var i,
  6921. l,
  6922. a,
  6923. fn,
  6924. self = this;
  6925. sigma.classes.dispatcher.extend(this);
  6926. // Initialize main attributes:
  6927. this.graph = graph;
  6928. this.camera = camera;
  6929. this.domElements = {
  6930. graph: null,
  6931. groups: {},
  6932. nodes: {},
  6933. edges: {},
  6934. labels: {},
  6935. hovers: {}
  6936. };
  6937. this.measurementCanvas = null;
  6938. this.options = options;
  6939. this.container = this.options.container;
  6940. this.settings = (
  6941. typeof options.settings === 'object' &&
  6942. options.settings
  6943. ) ?
  6944. settings.embedObjects(options.settings) :
  6945. settings;
  6946. // Is the renderer meant to be freestyle?
  6947. this.settings('freeStyle', !!this.options.freeStyle);
  6948. // SVG xmlns
  6949. this.settings('xmlns', 'http://www.w3.org/2000/svg');
  6950. // Indexes:
  6951. this.nodesOnScreen = [];
  6952. this.edgesOnScreen = [];
  6953. // Find the prefix:
  6954. this.options.prefix = 'renderer' + sigma.utils.id() + ':';
  6955. // Initialize the DOM elements
  6956. this.initDOM('svg');
  6957. // Initialize captors:
  6958. this.captors = [];
  6959. a = this.options.captors || [sigma.captors.mouse, sigma.captors.touch];
  6960. for (i = 0, l = a.length; i < l; i++) {
  6961. fn = typeof a[i] === 'function' ? a[i] : sigma.captors[a[i]];
  6962. this.captors.push(
  6963. new fn(
  6964. this.domElements.graph,
  6965. this.camera,
  6966. this.settings
  6967. )
  6968. );
  6969. }
  6970. // Bind resize:
  6971. window.addEventListener('resize', function() {
  6972. self.resize();
  6973. });
  6974. // Deal with sigma events:
  6975. // TODO: keep an option to override the DOM events?
  6976. sigma.misc.bindDOMEvents.call(this, this.domElements.graph);
  6977. this.bindHovers(this.options.prefix);
  6978. // Resize
  6979. this.resize(false);
  6980. };
  6981. /**
  6982. * This method renders the graph on the svg scene.
  6983. *
  6984. * @param {?object} options Eventually an object of options.
  6985. * @return {sigma.renderers.svg} Returns the instance itself.
  6986. */
  6987. sigma.renderers.svg.prototype.render = function(options) {
  6988. options = options || {};
  6989. var a,
  6990. i,
  6991. k,
  6992. e,
  6993. l,
  6994. o,
  6995. source,
  6996. target,
  6997. start,
  6998. edges,
  6999. renderers,
  7000. subrenderers,
  7001. index = {},
  7002. graph = this.graph,
  7003. nodes = this.graph.nodes,
  7004. prefix = this.options.prefix || '',
  7005. drawEdges = this.settings(options, 'drawEdges'),
  7006. drawNodes = this.settings(options, 'drawNodes'),
  7007. drawLabels = this.settings(options, 'drawLabels'),
  7008. embedSettings = this.settings.embedObjects(options, {
  7009. prefix: this.options.prefix,
  7010. forceLabels: this.options.forceLabels
  7011. });
  7012. // Check the 'hideEdgesOnMove' setting:
  7013. if (this.settings(options, 'hideEdgesOnMove'))
  7014. if (this.camera.isAnimated || this.camera.isMoving)
  7015. drawEdges = false;
  7016. // Apply the camera's view:
  7017. this.camera.applyView(
  7018. undefined,
  7019. this.options.prefix,
  7020. {
  7021. width: this.width,
  7022. height: this.height
  7023. }
  7024. );
  7025. // Hiding everything
  7026. // TODO: find a more sensible way to perform this operation
  7027. this.hideDOMElements(this.domElements.nodes);
  7028. this.hideDOMElements(this.domElements.edges);
  7029. this.hideDOMElements(this.domElements.labels);
  7030. // Find which nodes are on screen
  7031. this.edgesOnScreen = [];
  7032. this.nodesOnScreen = this.camera.quadtree.area(
  7033. this.camera.getRectangle(this.width, this.height)
  7034. );
  7035. // Node index
  7036. for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++)
  7037. index[a[i].id] = a[i];
  7038. // Find which edges are on screen
  7039. for (a = graph.edges(), i = 0, l = a.length; i < l; i++) {
  7040. o = a[i];
  7041. if (
  7042. (index[o.source] || index[o.target]) &&
  7043. (!o.hidden && !nodes(o.source).hidden && !nodes(o.target).hidden)
  7044. )
  7045. this.edgesOnScreen.push(o);
  7046. }
  7047. // Display nodes
  7048. //---------------
  7049. renderers = sigma.svg.nodes;
  7050. subrenderers = sigma.svg.labels;
  7051. //-- First we create the nodes which are not already created
  7052. if (drawNodes)
  7053. for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++) {
  7054. if (!a[i].hidden && !this.domElements.nodes[a[i].id]) {
  7055. // Node
  7056. e = (renderers[a[i].type] || renderers.def).create(
  7057. a[i],
  7058. embedSettings
  7059. );
  7060. this.domElements.nodes[a[i].id] = e;
  7061. this.domElements.groups.nodes.appendChild(e);
  7062. // Label
  7063. e = (subrenderers[a[i].type] || subrenderers.def).create(
  7064. a[i],
  7065. embedSettings
  7066. );
  7067. this.domElements.labels[a[i].id] = e;
  7068. this.domElements.groups.labels.appendChild(e);
  7069. }
  7070. }
  7071. //-- Second we update the nodes
  7072. if (drawNodes)
  7073. for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++) {
  7074. if (a[i].hidden)
  7075. continue;
  7076. // Node
  7077. (renderers[a[i].type] || renderers.def).update(
  7078. a[i],
  7079. this.domElements.nodes[a[i].id],
  7080. embedSettings
  7081. );
  7082. // Label
  7083. (subrenderers[a[i].type] || subrenderers.def).update(
  7084. a[i],
  7085. this.domElements.labels[a[i].id],
  7086. embedSettings
  7087. );
  7088. }
  7089. // Display edges
  7090. //---------------
  7091. renderers = sigma.svg.edges;
  7092. //-- First we create the edges which are not already created
  7093. if (drawEdges)
  7094. for (a = this.edgesOnScreen, i = 0, l = a.length; i < l; i++) {
  7095. if (!this.domElements.edges[a[i].id]) {
  7096. source = nodes(a[i].source);
  7097. target = nodes(a[i].target);
  7098. e = (renderers[a[i].type] || renderers.def).create(
  7099. a[i],
  7100. source,
  7101. target,
  7102. embedSettings
  7103. );
  7104. this.domElements.edges[a[i].id] = e;
  7105. this.domElements.groups.edges.appendChild(e);
  7106. }
  7107. }
  7108. //-- Second we update the edges
  7109. if (drawEdges)
  7110. for (a = this.edgesOnScreen, i = 0, l = a.length; i < l; i++) {
  7111. source = nodes(a[i].source);
  7112. target = nodes(a[i].target);
  7113. (renderers[a[i].type] || renderers.def).update(
  7114. a[i],
  7115. this.domElements.edges[a[i].id],
  7116. source,
  7117. target,
  7118. embedSettings
  7119. );
  7120. }
  7121. this.dispatchEvent('render');
  7122. return this;
  7123. };
  7124. /**
  7125. * This method creates a DOM element of the specified type, switches its
  7126. * position to "absolute", references it to the domElements attribute, and
  7127. * finally appends it to the container.
  7128. *
  7129. * @param {string} tag The label tag.
  7130. * @param {string} id The id of the element (to store it in "domElements").
  7131. */
  7132. sigma.renderers.svg.prototype.initDOM = function(tag) {
  7133. var dom = document.createElementNS(this.settings('xmlns'), tag),
  7134. c = this.settings('classPrefix'),
  7135. g,
  7136. l,
  7137. i;
  7138. dom.style.position = 'absolute';
  7139. dom.setAttribute('class', c + '-svg');
  7140. // Setting SVG namespace
  7141. dom.setAttribute('xmlns', this.settings('xmlns'));
  7142. dom.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
  7143. dom.setAttribute('version', '1.1');
  7144. // Creating the measurement canvas
  7145. var canvas = document.createElement('canvas');
  7146. canvas.setAttribute('class', c + '-measurement-canvas');
  7147. // Appending elements
  7148. this.domElements.graph = this.container.appendChild(dom);
  7149. // Creating groups
  7150. var groups = ['edges', 'nodes', 'labels', 'hovers'];
  7151. for (i = 0, l = groups.length; i < l; i++) {
  7152. g = document.createElementNS(this.settings('xmlns'), 'g');
  7153. g.setAttributeNS(null, 'id', c + '-group-' + groups[i]);
  7154. g.setAttributeNS(null, 'class', c + '-group');
  7155. this.domElements.groups[groups[i]] =
  7156. this.domElements.graph.appendChild(g);
  7157. }
  7158. // Appending measurement canvas
  7159. this.container.appendChild(canvas);
  7160. this.measurementCanvas = canvas.getContext('2d');
  7161. };
  7162. /**
  7163. * This method hides a batch of SVG DOM elements.
  7164. *
  7165. * @param {array} elements An array of elements to hide.
  7166. * @param {object} renderer The renderer to use.
  7167. * @return {sigma.renderers.svg} Returns the instance itself.
  7168. */
  7169. sigma.renderers.svg.prototype.hideDOMElements = function(elements) {
  7170. var o,
  7171. i;
  7172. for (i in elements) {
  7173. o = elements[i];
  7174. sigma.svg.utils.hide(o);
  7175. }
  7176. return this;
  7177. };
  7178. /**
  7179. * This method binds the hover events to the renderer.
  7180. *
  7181. * @param {string} prefix The renderer prefix.
  7182. */
  7183. // TODO: add option about whether to display hovers or not
  7184. sigma.renderers.svg.prototype.bindHovers = function(prefix) {
  7185. var renderers = sigma.svg.hovers,
  7186. self = this,
  7187. hoveredNode;
  7188. function overNode(e) {
  7189. var node = e.data.node,
  7190. embedSettings = self.settings.embedObjects({
  7191. prefix: prefix
  7192. });
  7193. if (!embedSettings('enableHovering'))
  7194. return;
  7195. var hover = (renderers[node.type] || renderers.def).create(
  7196. node,
  7197. self.domElements.nodes[node.id],
  7198. self.measurementCanvas,
  7199. embedSettings
  7200. );
  7201. self.domElements.hovers[node.id] = hover;
  7202. // Inserting the hover in the dom
  7203. self.domElements.groups.hovers.appendChild(hover);
  7204. hoveredNode = node;
  7205. }
  7206. function outNode(e) {
  7207. var node = e.data.node,
  7208. embedSettings = self.settings.embedObjects({
  7209. prefix: prefix
  7210. });
  7211. if (!embedSettings('enableHovering'))
  7212. return;
  7213. // Deleting element
  7214. self.domElements.groups.hovers.removeChild(
  7215. self.domElements.hovers[node.id]
  7216. );
  7217. hoveredNode = null;
  7218. delete self.domElements.hovers[node.id];
  7219. // Reinstate
  7220. self.domElements.groups.nodes.appendChild(
  7221. self.domElements.nodes[node.id]
  7222. );
  7223. }
  7224. // OPTIMIZE: perform a real update rather than a deletion
  7225. function update() {
  7226. if (!hoveredNode)
  7227. return;
  7228. var embedSettings = self.settings.embedObjects({
  7229. prefix: prefix
  7230. });
  7231. // Deleting element before update
  7232. self.domElements.groups.hovers.removeChild(
  7233. self.domElements.hovers[hoveredNode.id]
  7234. );
  7235. delete self.domElements.hovers[hoveredNode.id];
  7236. var hover = (renderers[hoveredNode.type] || renderers.def).create(
  7237. hoveredNode,
  7238. self.domElements.nodes[hoveredNode.id],
  7239. self.measurementCanvas,
  7240. embedSettings
  7241. );
  7242. self.domElements.hovers[hoveredNode.id] = hover;
  7243. // Inserting the hover in the dom
  7244. self.domElements.groups.hovers.appendChild(hover);
  7245. }
  7246. // Binding events
  7247. this.bind('overNode', overNode);
  7248. this.bind('outNode', outNode);
  7249. // Update on render
  7250. this.bind('render', update);
  7251. };
  7252. /**
  7253. * This method resizes each DOM elements in the container and stores the new
  7254. * dimensions. Then, it renders the graph.
  7255. *
  7256. * @param {?number} width The new width of the container.
  7257. * @param {?number} height The new height of the container.
  7258. * @return {sigma.renderers.svg} Returns the instance itself.
  7259. */
  7260. sigma.renderers.svg.prototype.resize = function(w, h) {
  7261. var oldWidth = this.width,
  7262. oldHeight = this.height,
  7263. pixelRatio = 1;
  7264. if (w !== undefined && h !== undefined) {
  7265. this.width = w;
  7266. this.height = h;
  7267. } else {
  7268. this.width = this.container.offsetWidth;
  7269. this.height = this.container.offsetHeight;
  7270. w = this.width;
  7271. h = this.height;
  7272. }
  7273. if (oldWidth !== this.width || oldHeight !== this.height) {
  7274. this.domElements.graph.style.width = w + 'px';
  7275. this.domElements.graph.style.height = h + 'px';
  7276. if (this.domElements.graph.tagName.toLowerCase() === 'svg') {
  7277. this.domElements.graph.setAttribute('width', (w * pixelRatio));
  7278. this.domElements.graph.setAttribute('height', (h * pixelRatio));
  7279. }
  7280. }
  7281. return this;
  7282. };
  7283. /**
  7284. * The labels, nodes and edges renderers are stored in the three following
  7285. * objects. When an element is drawn, its type will be checked and if a
  7286. * renderer with the same name exists, it will be used. If not found, the
  7287. * default renderer will be used instead.
  7288. *
  7289. * They are stored in different files, in the "./svg" folder.
  7290. */
  7291. sigma.utils.pkg('sigma.svg.nodes');
  7292. sigma.utils.pkg('sigma.svg.edges');
  7293. sigma.utils.pkg('sigma.svg.labels');
  7294. }).call(this);
  7295. ;(function(global) {
  7296. 'use strict';
  7297. if (typeof sigma === 'undefined')
  7298. throw 'sigma is not declared';
  7299. // Initialize packages:
  7300. sigma.utils.pkg('sigma.renderers');
  7301. // Check if WebGL is enabled:
  7302. var canvas,
  7303. webgl = !!global.WebGLRenderingContext;
  7304. if (webgl) {
  7305. canvas = document.createElement('canvas');
  7306. try {
  7307. webgl = !!(
  7308. canvas.getContext('webgl') ||
  7309. canvas.getContext('experimental-webgl')
  7310. );
  7311. } catch (e) {
  7312. webgl = false;
  7313. }
  7314. }
  7315. // Copy the good renderer:
  7316. sigma.renderers.def = webgl ?
  7317. sigma.renderers.webgl :
  7318. sigma.renderers.canvas;
  7319. })(this);
  7320. ;(function() {
  7321. 'use strict';
  7322. sigma.utils.pkg('sigma.webgl.nodes');
  7323. /**
  7324. * This node renderer will display nodes as discs, shaped in triangles with
  7325. * the gl.TRIANGLES display mode. So, to be more precise, to draw one node,
  7326. * it will store three times the center of node, with the color and the size,
  7327. * and an angle indicating which "corner" of the triangle to draw.
  7328. *
  7329. * The fragment shader does not deal with anti-aliasing, so make sure that
  7330. * you deal with it somewhere else in the code (by default, the WebGL
  7331. * renderer will oversample the rendering through the webglOversamplingRatio
  7332. * value).
  7333. */
  7334. sigma.webgl.nodes.def = {
  7335. POINTS: 3,
  7336. ATTRIBUTES: 5,
  7337. addNode: function(node, data, i, prefix, settings) {
  7338. var color = sigma.utils.floatColor(
  7339. node.color || settings('defaultNodeColor')
  7340. );
  7341. data[i++] = node[prefix + 'x'];
  7342. data[i++] = node[prefix + 'y'];
  7343. data[i++] = node[prefix + 'size'];
  7344. data[i++] = color;
  7345. data[i++] = 0;
  7346. data[i++] = node[prefix + 'x'];
  7347. data[i++] = node[prefix + 'y'];
  7348. data[i++] = node[prefix + 'size'];
  7349. data[i++] = color;
  7350. data[i++] = 2 * Math.PI / 3;
  7351. data[i++] = node[prefix + 'x'];
  7352. data[i++] = node[prefix + 'y'];
  7353. data[i++] = node[prefix + 'size'];
  7354. data[i++] = color;
  7355. data[i++] = 4 * Math.PI / 3;
  7356. },
  7357. render: function(gl, program, data, params) {
  7358. var buffer;
  7359. // Define attributes:
  7360. var positionLocation =
  7361. gl.getAttribLocation(program, 'a_position'),
  7362. sizeLocation =
  7363. gl.getAttribLocation(program, 'a_size'),
  7364. colorLocation =
  7365. gl.getAttribLocation(program, 'a_color'),
  7366. angleLocation =
  7367. gl.getAttribLocation(program, 'a_angle'),
  7368. resolutionLocation =
  7369. gl.getUniformLocation(program, 'u_resolution'),
  7370. matrixLocation =
  7371. gl.getUniformLocation(program, 'u_matrix'),
  7372. ratioLocation =
  7373. gl.getUniformLocation(program, 'u_ratio'),
  7374. scaleLocation =
  7375. gl.getUniformLocation(program, 'u_scale');
  7376. buffer = gl.createBuffer();
  7377. gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  7378. gl.bufferData(gl.ARRAY_BUFFER, data, gl.DYNAMIC_DRAW);
  7379. gl.uniform2f(resolutionLocation, params.width, params.height);
  7380. gl.uniform1f(
  7381. ratioLocation,
  7382. 1 / Math.pow(params.ratio, params.settings('nodesPowRatio'))
  7383. );
  7384. gl.uniform1f(scaleLocation, params.scalingRatio);
  7385. gl.uniformMatrix3fv(matrixLocation, false, params.matrix);
  7386. gl.enableVertexAttribArray(positionLocation);
  7387. gl.enableVertexAttribArray(sizeLocation);
  7388. gl.enableVertexAttribArray(colorLocation);
  7389. gl.enableVertexAttribArray(angleLocation);
  7390. gl.vertexAttribPointer(
  7391. positionLocation,
  7392. 2,
  7393. gl.FLOAT,
  7394. false,
  7395. this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
  7396. 0
  7397. );
  7398. gl.vertexAttribPointer(
  7399. sizeLocation,
  7400. 1,
  7401. gl.FLOAT,
  7402. false,
  7403. this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
  7404. 8
  7405. );
  7406. gl.vertexAttribPointer(
  7407. colorLocation,
  7408. 1,
  7409. gl.FLOAT,
  7410. false,
  7411. this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
  7412. 12
  7413. );
  7414. gl.vertexAttribPointer(
  7415. angleLocation,
  7416. 1,
  7417. gl.FLOAT,
  7418. false,
  7419. this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
  7420. 16
  7421. );
  7422. gl.drawArrays(
  7423. gl.TRIANGLES,
  7424. params.start || 0,
  7425. params.count || (data.length / this.ATTRIBUTES)
  7426. );
  7427. },
  7428. initProgram: function(gl) {
  7429. var vertexShader,
  7430. fragmentShader,
  7431. program;
  7432. vertexShader = sigma.utils.loadShader(
  7433. gl,
  7434. [
  7435. 'attribute vec2 a_position;',
  7436. 'attribute float a_size;',
  7437. 'attribute float a_color;',
  7438. 'attribute float a_angle;',
  7439. 'uniform vec2 u_resolution;',
  7440. 'uniform float u_ratio;',
  7441. 'uniform float u_scale;',
  7442. 'uniform mat3 u_matrix;',
  7443. 'varying vec4 color;',
  7444. 'varying vec2 center;',
  7445. 'varying float radius;',
  7446. 'void main() {',
  7447. // Multiply the point size twice:
  7448. 'radius = a_size * u_ratio;',
  7449. // Scale from [[-1 1] [-1 1]] to the container:
  7450. 'vec2 position = (u_matrix * vec3(a_position, 1)).xy;',
  7451. // 'center = (position / u_resolution * 2.0 - 1.0) * vec2(1, -1);',
  7452. 'center = position * u_scale;',
  7453. 'center = vec2(center.x, u_scale * u_resolution.y - center.y);',
  7454. 'position = position +',
  7455. '2.0 * radius * vec2(cos(a_angle), sin(a_angle));',
  7456. 'position = (position / u_resolution * 2.0 - 1.0) * vec2(1, -1);',
  7457. 'radius = radius * u_scale;',
  7458. 'gl_Position = vec4(position, 0, 1);',
  7459. // Extract the color:
  7460. 'float c = a_color;',
  7461. 'color.b = mod(c, 256.0); c = floor(c / 256.0);',
  7462. 'color.g = mod(c, 256.0); c = floor(c / 256.0);',
  7463. 'color.r = mod(c, 256.0); c = floor(c / 256.0); color /= 255.0;',
  7464. 'color.a = 1.0;',
  7465. '}'
  7466. ].join('\n'),
  7467. gl.VERTEX_SHADER
  7468. );
  7469. fragmentShader = sigma.utils.loadShader(
  7470. gl,
  7471. [
  7472. 'precision mediump float;',
  7473. 'varying vec4 color;',
  7474. 'varying vec2 center;',
  7475. 'varying float radius;',
  7476. 'void main(void) {',
  7477. 'vec4 color0 = vec4(0.0, 0.0, 0.0, 0.0);',
  7478. 'vec2 m = gl_FragCoord.xy - center;',
  7479. 'float diff = radius - sqrt(m.x * m.x + m.y * m.y);',
  7480. // Here is how we draw a disc instead of a square:
  7481. 'if (diff > 0.0)',
  7482. 'gl_FragColor = color;',
  7483. 'else',
  7484. 'gl_FragColor = color0;',
  7485. '}'
  7486. ].join('\n'),
  7487. gl.FRAGMENT_SHADER
  7488. );
  7489. program = sigma.utils.loadProgram(gl, [vertexShader, fragmentShader]);
  7490. return program;
  7491. }
  7492. };
  7493. })();
  7494. ;(function() {
  7495. 'use strict';
  7496. sigma.utils.pkg('sigma.webgl.nodes');
  7497. /**
  7498. * This node renderer will display nodes in the fastest way: Nodes are basic
  7499. * squares, drawn through the gl.POINTS drawing method. The size of the nodes
  7500. * are represented with the "gl_PointSize" value in the vertex shader.
  7501. *
  7502. * It is the fastest node renderer here since the buffer just takes one line
  7503. * to draw each node (with attributes "x", "y", "size" and "color").
  7504. *
  7505. * Nevertheless, this method has some problems, especially due to some issues
  7506. * with the gl.POINTS:
  7507. * - First, if the center of a node is outside the scene, the point will not
  7508. * be drawn, even if it should be partly on screen.
  7509. * - I tried applying a fragment shader similar to the one in the default
  7510. * node renderer to display them as discs, but it did not work fine on
  7511. * some computers settings, filling the discs with weird gradients not
  7512. * depending on the actual color.
  7513. */
  7514. sigma.webgl.nodes.fast = {
  7515. POINTS: 1,
  7516. ATTRIBUTES: 4,
  7517. addNode: function(node, data, i, prefix, settings) {
  7518. data[i++] = node[prefix + 'x'];
  7519. data[i++] = node[prefix + 'y'];
  7520. data[i++] = node[prefix + 'size'];
  7521. data[i++] = sigma.utils.floatColor(
  7522. node.color || settings('defaultNodeColor')
  7523. );
  7524. },
  7525. render: function(gl, program, data, params) {
  7526. var buffer;
  7527. // Define attributes:
  7528. var positionLocation =
  7529. gl.getAttribLocation(program, 'a_position'),
  7530. sizeLocation =
  7531. gl.getAttribLocation(program, 'a_size'),
  7532. colorLocation =
  7533. gl.getAttribLocation(program, 'a_color'),
  7534. resolutionLocation =
  7535. gl.getUniformLocation(program, 'u_resolution'),
  7536. matrixLocation =
  7537. gl.getUniformLocation(program, 'u_matrix'),
  7538. ratioLocation =
  7539. gl.getUniformLocation(program, 'u_ratio'),
  7540. scaleLocation =
  7541. gl.getUniformLocation(program, 'u_scale');
  7542. buffer = gl.createBuffer();
  7543. gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  7544. gl.bufferData(gl.ARRAY_BUFFER, data, gl.DYNAMIC_DRAW);
  7545. gl.uniform2f(resolutionLocation, params.width, params.height);
  7546. gl.uniform1f(
  7547. ratioLocation,
  7548. 1 / Math.pow(params.ratio, params.settings('nodesPowRatio'))
  7549. );
  7550. gl.uniform1f(scaleLocation, params.scalingRatio);
  7551. gl.uniformMatrix3fv(matrixLocation, false, params.matrix);
  7552. gl.enableVertexAttribArray(positionLocation);
  7553. gl.enableVertexAttribArray(sizeLocation);
  7554. gl.enableVertexAttribArray(colorLocation);
  7555. gl.vertexAttribPointer(
  7556. positionLocation,
  7557. 2,
  7558. gl.FLOAT,
  7559. false,
  7560. this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
  7561. 0
  7562. );
  7563. gl.vertexAttribPointer(
  7564. sizeLocation,
  7565. 1,
  7566. gl.FLOAT,
  7567. false,
  7568. this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
  7569. 8
  7570. );
  7571. gl.vertexAttribPointer(
  7572. colorLocation,
  7573. 1,
  7574. gl.FLOAT,
  7575. false,
  7576. this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
  7577. 12
  7578. );
  7579. gl.drawArrays(
  7580. gl.POINTS,
  7581. params.start || 0,
  7582. params.count || (data.length / this.ATTRIBUTES)
  7583. );
  7584. },
  7585. initProgram: function(gl) {
  7586. var vertexShader,
  7587. fragmentShader,
  7588. program;
  7589. vertexShader = sigma.utils.loadShader(
  7590. gl,
  7591. [
  7592. 'attribute vec2 a_position;',
  7593. 'attribute float a_size;',
  7594. 'attribute float a_color;',
  7595. 'uniform vec2 u_resolution;',
  7596. 'uniform float u_ratio;',
  7597. 'uniform float u_scale;',
  7598. 'uniform mat3 u_matrix;',
  7599. 'varying vec4 color;',
  7600. 'void main() {',
  7601. // Scale from [[-1 1] [-1 1]] to the container:
  7602. 'gl_Position = vec4(',
  7603. '((u_matrix * vec3(a_position, 1)).xy /',
  7604. 'u_resolution * 2.0 - 1.0) * vec2(1, -1),',
  7605. '0,',
  7606. '1',
  7607. ');',
  7608. // Multiply the point size twice:
  7609. // - x SCALING_RATIO to correct the canvas scaling
  7610. // - x 2 to correct the formulae
  7611. 'gl_PointSize = a_size * u_ratio * u_scale * 2.0;',
  7612. // Extract the color:
  7613. 'float c = a_color;',
  7614. 'color.b = mod(c, 256.0); c = floor(c / 256.0);',
  7615. 'color.g = mod(c, 256.0); c = floor(c / 256.0);',
  7616. 'color.r = mod(c, 256.0); c = floor(c / 256.0); color /= 255.0;',
  7617. 'color.a = 1.0;',
  7618. '}'
  7619. ].join('\n'),
  7620. gl.VERTEX_SHADER
  7621. );
  7622. fragmentShader = sigma.utils.loadShader(
  7623. gl,
  7624. [
  7625. 'precision mediump float;',
  7626. 'varying vec4 color;',
  7627. 'void main(void) {',
  7628. 'gl_FragColor = color;',
  7629. '}'
  7630. ].join('\n'),
  7631. gl.FRAGMENT_SHADER
  7632. );
  7633. program = sigma.utils.loadProgram(gl, [vertexShader, fragmentShader]);
  7634. return program;
  7635. }
  7636. };
  7637. })();
  7638. ;(function() {
  7639. 'use strict';
  7640. sigma.utils.pkg('sigma.webgl.edges');
  7641. /**
  7642. * This edge renderer will display edges as lines going from the source node
  7643. * to the target node. To deal with edge thicknesses, the lines are made of
  7644. * two triangles forming rectangles, with the gl.TRIANGLES drawing mode.
  7645. *
  7646. * It is expensive, since drawing a single edge requires 6 points, each
  7647. * having 7 attributes (source position, target position, thickness, color
  7648. * and a flag indicating which vertice of the rectangle it is).
  7649. */
  7650. sigma.webgl.edges.def = {
  7651. POINTS: 6,
  7652. ATTRIBUTES: 7,
  7653. addEdge: function(edge, source, target, data, i, prefix, settings) {
  7654. var w = (edge[prefix + 'size'] || 1) / 2,
  7655. x1 = source[prefix + 'x'],
  7656. y1 = source[prefix + 'y'],
  7657. x2 = target[prefix + 'x'],
  7658. y2 = target[prefix + 'y'],
  7659. color = edge.color;
  7660. if (!color)
  7661. switch (settings('edgeColor')) {
  7662. case 'source':
  7663. color = source.color || settings('defaultNodeColor');
  7664. break;
  7665. case 'target':
  7666. color = target.color || settings('defaultNodeColor');
  7667. break;
  7668. default:
  7669. color = settings('defaultEdgeColor');
  7670. break;
  7671. }
  7672. // Normalize color:
  7673. color = sigma.utils.floatColor(color);
  7674. data[i++] = x1;
  7675. data[i++] = y1;
  7676. data[i++] = x2;
  7677. data[i++] = y2;
  7678. data[i++] = w;
  7679. data[i++] = 0.0;
  7680. data[i++] = color;
  7681. data[i++] = x2;
  7682. data[i++] = y2;
  7683. data[i++] = x1;
  7684. data[i++] = y1;
  7685. data[i++] = w;
  7686. data[i++] = 1.0;
  7687. data[i++] = color;
  7688. data[i++] = x2;
  7689. data[i++] = y2;
  7690. data[i++] = x1;
  7691. data[i++] = y1;
  7692. data[i++] = w;
  7693. data[i++] = 0.0;
  7694. data[i++] = color;
  7695. data[i++] = x2;
  7696. data[i++] = y2;
  7697. data[i++] = x1;
  7698. data[i++] = y1;
  7699. data[i++] = w;
  7700. data[i++] = 0.0;
  7701. data[i++] = color;
  7702. data[i++] = x1;
  7703. data[i++] = y1;
  7704. data[i++] = x2;
  7705. data[i++] = y2;
  7706. data[i++] = w;
  7707. data[i++] = 1.0;
  7708. data[i++] = color;
  7709. data[i++] = x1;
  7710. data[i++] = y1;
  7711. data[i++] = x2;
  7712. data[i++] = y2;
  7713. data[i++] = w;
  7714. data[i++] = 0.0;
  7715. data[i++] = color;
  7716. },
  7717. render: function(gl, program, data, params) {
  7718. var buffer;
  7719. // Define attributes:
  7720. var colorLocation =
  7721. gl.getAttribLocation(program, 'a_color'),
  7722. positionLocation1 =
  7723. gl.getAttribLocation(program, 'a_position1'),
  7724. positionLocation2 =
  7725. gl.getAttribLocation(program, 'a_position2'),
  7726. thicknessLocation =
  7727. gl.getAttribLocation(program, 'a_thickness'),
  7728. minusLocation =
  7729. gl.getAttribLocation(program, 'a_minus'),
  7730. resolutionLocation =
  7731. gl.getUniformLocation(program, 'u_resolution'),
  7732. matrixLocation =
  7733. gl.getUniformLocation(program, 'u_matrix'),
  7734. matrixHalfPiLocation =
  7735. gl.getUniformLocation(program, 'u_matrixHalfPi'),
  7736. matrixHalfPiMinusLocation =
  7737. gl.getUniformLocation(program, 'u_matrixHalfPiMinus'),
  7738. ratioLocation =
  7739. gl.getUniformLocation(program, 'u_ratio'),
  7740. scaleLocation =
  7741. gl.getUniformLocation(program, 'u_scale');
  7742. buffer = gl.createBuffer();
  7743. gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  7744. gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
  7745. gl.uniform2f(resolutionLocation, params.width, params.height);
  7746. gl.uniform1f(
  7747. ratioLocation,
  7748. params.ratio / Math.pow(params.ratio, params.settings('edgesPowRatio'))
  7749. );
  7750. gl.uniform1f(scaleLocation, params.scalingRatio);
  7751. gl.uniformMatrix3fv(matrixLocation, false, params.matrix);
  7752. gl.uniformMatrix2fv(
  7753. matrixHalfPiLocation,
  7754. false,
  7755. sigma.utils.matrices.rotation(Math.PI / 2, true)
  7756. );
  7757. gl.uniformMatrix2fv(
  7758. matrixHalfPiMinusLocation,
  7759. false,
  7760. sigma.utils.matrices.rotation(-Math.PI / 2, true)
  7761. );
  7762. gl.enableVertexAttribArray(colorLocation);
  7763. gl.enableVertexAttribArray(positionLocation1);
  7764. gl.enableVertexAttribArray(positionLocation2);
  7765. gl.enableVertexAttribArray(thicknessLocation);
  7766. gl.enableVertexAttribArray(minusLocation);
  7767. gl.vertexAttribPointer(positionLocation1,
  7768. 2,
  7769. gl.FLOAT,
  7770. false,
  7771. this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
  7772. 0
  7773. );
  7774. gl.vertexAttribPointer(positionLocation2,
  7775. 2,
  7776. gl.FLOAT,
  7777. false,
  7778. this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
  7779. 8
  7780. );
  7781. gl.vertexAttribPointer(thicknessLocation,
  7782. 1,
  7783. gl.FLOAT,
  7784. false,
  7785. this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
  7786. 16
  7787. );
  7788. gl.vertexAttribPointer(minusLocation,
  7789. 1,
  7790. gl.FLOAT,
  7791. false,
  7792. this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
  7793. 20
  7794. );
  7795. gl.vertexAttribPointer(colorLocation,
  7796. 1,
  7797. gl.FLOAT,
  7798. false,
  7799. this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
  7800. 24
  7801. );
  7802. gl.drawArrays(
  7803. gl.TRIANGLES,
  7804. params.start || 0,
  7805. params.count || (data.length / this.ATTRIBUTES)
  7806. );
  7807. },
  7808. initProgram: function(gl) {
  7809. var vertexShader,
  7810. fragmentShader,
  7811. program;
  7812. vertexShader = sigma.utils.loadShader(
  7813. gl,
  7814. [
  7815. 'attribute vec2 a_position1;',
  7816. 'attribute vec2 a_position2;',
  7817. 'attribute float a_thickness;',
  7818. 'attribute float a_minus;',
  7819. 'attribute float a_color;',
  7820. 'uniform vec2 u_resolution;',
  7821. 'uniform float u_ratio;',
  7822. 'uniform float u_scale;',
  7823. 'uniform mat3 u_matrix;',
  7824. 'uniform mat2 u_matrixHalfPi;',
  7825. 'uniform mat2 u_matrixHalfPiMinus;',
  7826. 'varying vec4 color;',
  7827. 'void main() {',
  7828. // Find the good point:
  7829. 'vec2 position = a_thickness * u_ratio *',
  7830. 'normalize(a_position2 - a_position1);',
  7831. 'mat2 matrix = a_minus * u_matrixHalfPiMinus +',
  7832. '(1.0 - a_minus) * u_matrixHalfPi;',
  7833. 'position = matrix * position + a_position1;',
  7834. // Scale from [[-1 1] [-1 1]] to the container:
  7835. 'gl_Position = vec4(',
  7836. '((u_matrix * vec3(position, 1)).xy /',
  7837. 'u_resolution * 2.0 - 1.0) * vec2(1, -1),',
  7838. '0,',
  7839. '1',
  7840. ');',
  7841. // Extract the color:
  7842. 'float c = a_color;',
  7843. 'color.b = mod(c, 256.0); c = floor(c / 256.0);',
  7844. 'color.g = mod(c, 256.0); c = floor(c / 256.0);',
  7845. 'color.r = mod(c, 256.0); c = floor(c / 256.0); color /= 255.0;',
  7846. 'color.a = 1.0;',
  7847. '}'
  7848. ].join('\n'),
  7849. gl.VERTEX_SHADER
  7850. );
  7851. fragmentShader = sigma.utils.loadShader(
  7852. gl,
  7853. [
  7854. 'precision mediump float;',
  7855. 'varying vec4 color;',
  7856. 'void main(void) {',
  7857. 'gl_FragColor = color;',
  7858. '}'
  7859. ].join('\n'),
  7860. gl.FRAGMENT_SHADER
  7861. );
  7862. program = sigma.utils.loadProgram(gl, [vertexShader, fragmentShader]);
  7863. return program;
  7864. }
  7865. };
  7866. })();
  7867. ;(function() {
  7868. 'use strict';
  7869. sigma.utils.pkg('sigma.webgl.edges');
  7870. /**
  7871. * This edge renderer will display edges as lines with the gl.LINES display
  7872. * mode. Since this mode does not support well thickness, edges are all drawn
  7873. * with the same thickness (3px), independantly of the edge attributes or the
  7874. * zooming ratio.
  7875. */
  7876. sigma.webgl.edges.fast = {
  7877. POINTS: 2,
  7878. ATTRIBUTES: 3,
  7879. addEdge: function(edge, source, target, data, i, prefix, settings) {
  7880. var w = (edge[prefix + 'size'] || 1) / 2,
  7881. x1 = source[prefix + 'x'],
  7882. y1 = source[prefix + 'y'],
  7883. x2 = target[prefix + 'x'],
  7884. y2 = target[prefix + 'y'],
  7885. color = edge.color;
  7886. if (!color)
  7887. switch (settings('edgeColor')) {
  7888. case 'source':
  7889. color = source.color || settings('defaultNodeColor');
  7890. break;
  7891. case 'target':
  7892. color = target.color || settings('defaultNodeColor');
  7893. break;
  7894. default:
  7895. color = settings('defaultEdgeColor');
  7896. break;
  7897. }
  7898. // Normalize color:
  7899. color = sigma.utils.floatColor(color);
  7900. data[i++] = x1;
  7901. data[i++] = y1;
  7902. data[i++] = color;
  7903. data[i++] = x2;
  7904. data[i++] = y2;
  7905. data[i++] = color;
  7906. },
  7907. render: function(gl, program, data, params) {
  7908. var buffer;
  7909. // Define attributes:
  7910. var colorLocation =
  7911. gl.getAttribLocation(program, 'a_color'),
  7912. positionLocation =
  7913. gl.getAttribLocation(program, 'a_position'),
  7914. resolutionLocation =
  7915. gl.getUniformLocation(program, 'u_resolution'),
  7916. matrixLocation =
  7917. gl.getUniformLocation(program, 'u_matrix');
  7918. buffer = gl.createBuffer();
  7919. gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  7920. gl.bufferData(gl.ARRAY_BUFFER, data, gl.DYNAMIC_DRAW);
  7921. gl.uniform2f(resolutionLocation, params.width, params.height);
  7922. gl.uniformMatrix3fv(matrixLocation, false, params.matrix);
  7923. gl.enableVertexAttribArray(positionLocation);
  7924. gl.enableVertexAttribArray(colorLocation);
  7925. gl.vertexAttribPointer(positionLocation,
  7926. 2,
  7927. gl.FLOAT,
  7928. false,
  7929. this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
  7930. 0
  7931. );
  7932. gl.vertexAttribPointer(colorLocation,
  7933. 1,
  7934. gl.FLOAT,
  7935. false,
  7936. this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
  7937. 8
  7938. );
  7939. gl.lineWidth(3);
  7940. gl.drawArrays(
  7941. gl.LINES,
  7942. params.start || 0,
  7943. params.count || (data.length / this.ATTRIBUTES)
  7944. );
  7945. },
  7946. initProgram: function(gl) {
  7947. var vertexShader,
  7948. fragmentShader,
  7949. program;
  7950. vertexShader = sigma.utils.loadShader(
  7951. gl,
  7952. [
  7953. 'attribute vec2 a_position;',
  7954. 'attribute float a_color;',
  7955. 'uniform vec2 u_resolution;',
  7956. 'uniform mat3 u_matrix;',
  7957. 'varying vec4 color;',
  7958. 'void main() {',
  7959. // Scale from [[-1 1] [-1 1]] to the container:
  7960. 'gl_Position = vec4(',
  7961. '((u_matrix * vec3(a_position, 1)).xy /',
  7962. 'u_resolution * 2.0 - 1.0) * vec2(1, -1),',
  7963. '0,',
  7964. '1',
  7965. ');',
  7966. // Extract the color:
  7967. 'float c = a_color;',
  7968. 'color.b = mod(c, 256.0); c = floor(c / 256.0);',
  7969. 'color.g = mod(c, 256.0); c = floor(c / 256.0);',
  7970. 'color.r = mod(c, 256.0); c = floor(c / 256.0); color /= 255.0;',
  7971. 'color.a = 1.0;',
  7972. '}'
  7973. ].join('\n'),
  7974. gl.VERTEX_SHADER
  7975. );
  7976. fragmentShader = sigma.utils.loadShader(
  7977. gl,
  7978. [
  7979. 'precision mediump float;',
  7980. 'varying vec4 color;',
  7981. 'void main(void) {',
  7982. 'gl_FragColor = color;',
  7983. '}'
  7984. ].join('\n'),
  7985. gl.FRAGMENT_SHADER
  7986. );
  7987. program = sigma.utils.loadProgram(gl, [vertexShader, fragmentShader]);
  7988. return program;
  7989. }
  7990. };
  7991. })();
  7992. ;(function() {
  7993. 'use strict';
  7994. sigma.utils.pkg('sigma.webgl.edges');
  7995. /**
  7996. * This edge renderer will display edges as arrows going from the source node
  7997. * to the target node. To deal with edge thicknesses, the lines are made of
  7998. * three triangles: two forming rectangles, with the gl.TRIANGLES drawing
  7999. * mode.
  8000. *
  8001. * It is expensive, since drawing a single edge requires 9 points, each
  8002. * having a lot of attributes.
  8003. */
  8004. sigma.webgl.edges.arrow = {
  8005. POINTS: 9,
  8006. ATTRIBUTES: 11,
  8007. addEdge: function(edge, source, target, data, i, prefix, settings) {
  8008. var w = (edge[prefix + 'size'] || 1) / 2,
  8009. x1 = source[prefix + 'x'],
  8010. y1 = source[prefix + 'y'],
  8011. x2 = target[prefix + 'x'],
  8012. y2 = target[prefix + 'y'],
  8013. targetSize = target[prefix + 'size'],
  8014. color = edge.color;
  8015. if (!color)
  8016. switch (settings('edgeColor')) {
  8017. case 'source':
  8018. color = source.color || settings('defaultNodeColor');
  8019. break;
  8020. case 'target':
  8021. color = target.color || settings('defaultNodeColor');
  8022. break;
  8023. default:
  8024. color = settings('defaultEdgeColor');
  8025. break;
  8026. }
  8027. // Normalize color:
  8028. color = sigma.utils.floatColor(color);
  8029. data[i++] = x1;
  8030. data[i++] = y1;
  8031. data[i++] = x2;
  8032. data[i++] = y2;
  8033. data[i++] = w;
  8034. data[i++] = targetSize;
  8035. data[i++] = 0.0;
  8036. data[i++] = 0.0;
  8037. data[i++] = 0.0;
  8038. data[i++] = 0.0;
  8039. data[i++] = color;
  8040. data[i++] = x2;
  8041. data[i++] = y2;
  8042. data[i++] = x1;
  8043. data[i++] = y1;
  8044. data[i++] = w;
  8045. data[i++] = targetSize;
  8046. data[i++] = 1.0;
  8047. data[i++] = 1.0;
  8048. data[i++] = 0.0;
  8049. data[i++] = 0.0;
  8050. data[i++] = color;
  8051. data[i++] = x2;
  8052. data[i++] = y2;
  8053. data[i++] = x1;
  8054. data[i++] = y1;
  8055. data[i++] = w;
  8056. data[i++] = targetSize;
  8057. data[i++] = 1.0;
  8058. data[i++] = 0.0;
  8059. data[i++] = 0.0;
  8060. data[i++] = 0.0;
  8061. data[i++] = color;
  8062. data[i++] = x2;
  8063. data[i++] = y2;
  8064. data[i++] = x1;
  8065. data[i++] = y1;
  8066. data[i++] = w;
  8067. data[i++] = targetSize;
  8068. data[i++] = 1.0;
  8069. data[i++] = 0.0;
  8070. data[i++] = 0.0;
  8071. data[i++] = 0.0;
  8072. data[i++] = color;
  8073. data[i++] = x1;
  8074. data[i++] = y1;
  8075. data[i++] = x2;
  8076. data[i++] = y2;
  8077. data[i++] = w;
  8078. data[i++] = targetSize;
  8079. data[i++] = 0.0;
  8080. data[i++] = 1.0;
  8081. data[i++] = 0.0;
  8082. data[i++] = 0.0;
  8083. data[i++] = color;
  8084. data[i++] = x1;
  8085. data[i++] = y1;
  8086. data[i++] = x2;
  8087. data[i++] = y2;
  8088. data[i++] = w;
  8089. data[i++] = targetSize;
  8090. data[i++] = 0.0;
  8091. data[i++] = 0.0;
  8092. data[i++] = 0.0;
  8093. data[i++] = 0.0;
  8094. data[i++] = color;
  8095. // Arrow head:
  8096. data[i++] = x2;
  8097. data[i++] = y2;
  8098. data[i++] = x1;
  8099. data[i++] = y1;
  8100. data[i++] = w;
  8101. data[i++] = targetSize;
  8102. data[i++] = 1.0;
  8103. data[i++] = 0.0;
  8104. data[i++] = 1.0;
  8105. data[i++] = -1.0;
  8106. data[i++] = color;
  8107. data[i++] = x2;
  8108. data[i++] = y2;
  8109. data[i++] = x1;
  8110. data[i++] = y1;
  8111. data[i++] = w;
  8112. data[i++] = targetSize;
  8113. data[i++] = 1.0;
  8114. data[i++] = 0.0;
  8115. data[i++] = 1.0;
  8116. data[i++] = 0.0;
  8117. data[i++] = color;
  8118. data[i++] = x2;
  8119. data[i++] = y2;
  8120. data[i++] = x1;
  8121. data[i++] = y1;
  8122. data[i++] = w;
  8123. data[i++] = targetSize;
  8124. data[i++] = 1.0;
  8125. data[i++] = 0.0;
  8126. data[i++] = 1.0;
  8127. data[i++] = 1.0;
  8128. data[i++] = color;
  8129. },
  8130. render: function(gl, program, data, params) {
  8131. var buffer;
  8132. // Define attributes:
  8133. var positionLocation1 =
  8134. gl.getAttribLocation(program, 'a_pos1'),
  8135. positionLocation2 =
  8136. gl.getAttribLocation(program, 'a_pos2'),
  8137. thicknessLocation =
  8138. gl.getAttribLocation(program, 'a_thickness'),
  8139. targetSizeLocation =
  8140. gl.getAttribLocation(program, 'a_tSize'),
  8141. delayLocation =
  8142. gl.getAttribLocation(program, 'a_delay'),
  8143. minusLocation =
  8144. gl.getAttribLocation(program, 'a_minus'),
  8145. headLocation =
  8146. gl.getAttribLocation(program, 'a_head'),
  8147. headPositionLocation =
  8148. gl.getAttribLocation(program, 'a_headPosition'),
  8149. colorLocation =
  8150. gl.getAttribLocation(program, 'a_color'),
  8151. resolutionLocation =
  8152. gl.getUniformLocation(program, 'u_resolution'),
  8153. matrixLocation =
  8154. gl.getUniformLocation(program, 'u_matrix'),
  8155. matrixHalfPiLocation =
  8156. gl.getUniformLocation(program, 'u_matrixHalfPi'),
  8157. matrixHalfPiMinusLocation =
  8158. gl.getUniformLocation(program, 'u_matrixHalfPiMinus'),
  8159. ratioLocation =
  8160. gl.getUniformLocation(program, 'u_ratio'),
  8161. nodeRatioLocation =
  8162. gl.getUniformLocation(program, 'u_nodeRatio'),
  8163. arrowHeadLocation =
  8164. gl.getUniformLocation(program, 'u_arrowHead'),
  8165. scaleLocation =
  8166. gl.getUniformLocation(program, 'u_scale');
  8167. buffer = gl.createBuffer();
  8168. gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  8169. gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
  8170. gl.uniform2f(resolutionLocation, params.width, params.height);
  8171. gl.uniform1f(
  8172. ratioLocation,
  8173. params.ratio / Math.pow(params.ratio, params.settings('edgesPowRatio'))
  8174. );
  8175. gl.uniform1f(
  8176. nodeRatioLocation,
  8177. Math.pow(params.ratio, params.settings('nodesPowRatio')) /
  8178. params.ratio
  8179. );
  8180. gl.uniform1f(arrowHeadLocation, 5.0);
  8181. gl.uniform1f(scaleLocation, params.scalingRatio);
  8182. gl.uniformMatrix3fv(matrixLocation, false, params.matrix);
  8183. gl.uniformMatrix2fv(
  8184. matrixHalfPiLocation,
  8185. false,
  8186. sigma.utils.matrices.rotation(Math.PI / 2, true)
  8187. );
  8188. gl.uniformMatrix2fv(
  8189. matrixHalfPiMinusLocation,
  8190. false,
  8191. sigma.utils.matrices.rotation(-Math.PI / 2, true)
  8192. );
  8193. gl.enableVertexAttribArray(positionLocation1);
  8194. gl.enableVertexAttribArray(positionLocation2);
  8195. gl.enableVertexAttribArray(thicknessLocation);
  8196. gl.enableVertexAttribArray(targetSizeLocation);
  8197. gl.enableVertexAttribArray(delayLocation);
  8198. gl.enableVertexAttribArray(minusLocation);
  8199. gl.enableVertexAttribArray(headLocation);
  8200. gl.enableVertexAttribArray(headPositionLocation);
  8201. gl.enableVertexAttribArray(colorLocation);
  8202. gl.vertexAttribPointer(positionLocation1,
  8203. 2,
  8204. gl.FLOAT,
  8205. false,
  8206. this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
  8207. 0
  8208. );
  8209. gl.vertexAttribPointer(positionLocation2,
  8210. 2,
  8211. gl.FLOAT,
  8212. false,
  8213. this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
  8214. 8
  8215. );
  8216. gl.vertexAttribPointer(thicknessLocation,
  8217. 1,
  8218. gl.FLOAT,
  8219. false,
  8220. this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
  8221. 16
  8222. );
  8223. gl.vertexAttribPointer(targetSizeLocation,
  8224. 1,
  8225. gl.FLOAT,
  8226. false,
  8227. this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
  8228. 20
  8229. );
  8230. gl.vertexAttribPointer(delayLocation,
  8231. 1,
  8232. gl.FLOAT,
  8233. false,
  8234. this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
  8235. 24
  8236. );
  8237. gl.vertexAttribPointer(minusLocation,
  8238. 1,
  8239. gl.FLOAT,
  8240. false,
  8241. this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
  8242. 28
  8243. );
  8244. gl.vertexAttribPointer(headLocation,
  8245. 1,
  8246. gl.FLOAT,
  8247. false,
  8248. this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
  8249. 32
  8250. );
  8251. gl.vertexAttribPointer(headPositionLocation,
  8252. 1,
  8253. gl.FLOAT,
  8254. false,
  8255. this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
  8256. 36
  8257. );
  8258. gl.vertexAttribPointer(colorLocation,
  8259. 1,
  8260. gl.FLOAT,
  8261. false,
  8262. this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
  8263. 40
  8264. );
  8265. gl.drawArrays(
  8266. gl.TRIANGLES,
  8267. params.start || 0,
  8268. params.count || (data.length / this.ATTRIBUTES)
  8269. );
  8270. },
  8271. initProgram: function(gl) {
  8272. var vertexShader,
  8273. fragmentShader,
  8274. program;
  8275. vertexShader = sigma.utils.loadShader(
  8276. gl,
  8277. [
  8278. 'attribute vec2 a_pos1;',
  8279. 'attribute vec2 a_pos2;',
  8280. 'attribute float a_thickness;',
  8281. 'attribute float a_tSize;',
  8282. 'attribute float a_delay;',
  8283. 'attribute float a_minus;',
  8284. 'attribute float a_head;',
  8285. 'attribute float a_headPosition;',
  8286. 'attribute float a_color;',
  8287. 'uniform vec2 u_resolution;',
  8288. 'uniform float u_ratio;',
  8289. 'uniform float u_nodeRatio;',
  8290. 'uniform float u_arrowHead;',
  8291. 'uniform float u_scale;',
  8292. 'uniform mat3 u_matrix;',
  8293. 'uniform mat2 u_matrixHalfPi;',
  8294. 'uniform mat2 u_matrixHalfPiMinus;',
  8295. 'varying vec4 color;',
  8296. 'void main() {',
  8297. // Find the good point:
  8298. 'vec2 pos = normalize(a_pos2 - a_pos1);',
  8299. 'mat2 matrix = (1.0 - a_head) *',
  8300. '(',
  8301. 'a_minus * u_matrixHalfPiMinus +',
  8302. '(1.0 - a_minus) * u_matrixHalfPi',
  8303. ') + a_head * (',
  8304. 'a_headPosition * u_matrixHalfPiMinus * 0.6 +',
  8305. '(a_headPosition * a_headPosition - 1.0) * mat2(1.0)',
  8306. ');',
  8307. 'pos = a_pos1 + (',
  8308. // Deal with body:
  8309. '(1.0 - a_head) * a_thickness * u_ratio * matrix * pos +',
  8310. // Deal with head:
  8311. 'a_head * u_arrowHead * a_thickness * u_ratio * matrix * pos +',
  8312. // Deal with delay:
  8313. 'a_delay * pos * (',
  8314. 'a_tSize / u_nodeRatio +',
  8315. 'u_arrowHead * a_thickness * u_ratio',
  8316. ')',
  8317. ');',
  8318. // Scale from [[-1 1] [-1 1]] to the container:
  8319. 'gl_Position = vec4(',
  8320. '((u_matrix * vec3(pos, 1)).xy /',
  8321. 'u_resolution * 2.0 - 1.0) * vec2(1, -1),',
  8322. '0,',
  8323. '1',
  8324. ');',
  8325. // Extract the color:
  8326. 'float c = a_color;',
  8327. 'color.b = mod(c, 256.0); c = floor(c / 256.0);',
  8328. 'color.g = mod(c, 256.0); c = floor(c / 256.0);',
  8329. 'color.r = mod(c, 256.0); c = floor(c / 256.0); color /= 255.0;',
  8330. 'color.a = 1.0;',
  8331. '}'
  8332. ].join('\n'),
  8333. gl.VERTEX_SHADER
  8334. );
  8335. fragmentShader = sigma.utils.loadShader(
  8336. gl,
  8337. [
  8338. 'precision mediump float;',
  8339. 'varying vec4 color;',
  8340. 'void main(void) {',
  8341. 'gl_FragColor = color;',
  8342. '}'
  8343. ].join('\n'),
  8344. gl.FRAGMENT_SHADER
  8345. );
  8346. program = sigma.utils.loadProgram(gl, [vertexShader, fragmentShader]);
  8347. return program;
  8348. }
  8349. };
  8350. })();
  8351. ;(function(undefined) {
  8352. 'use strict';
  8353. if (typeof sigma === 'undefined')
  8354. throw 'sigma is not declared';
  8355. // Initialize packages:
  8356. sigma.utils.pkg('sigma.canvas.labels');
  8357. /**
  8358. * This label renderer will just display the label on the right of the node.
  8359. *
  8360. * @param {object} node The node object.
  8361. * @param {CanvasRenderingContext2D} context The canvas context.
  8362. * @param {configurable} settings The settings function.
  8363. */
  8364. sigma.canvas.labels.def = function(node, context, settings) {
  8365. var fontSize,
  8366. prefix = settings('prefix') || '',
  8367. size = node[prefix + 'size'];
  8368. if (size < settings('labelThreshold'))
  8369. return;
  8370. if (!node.label || typeof node.label !== 'string')
  8371. return;
  8372. fontSize = (settings('labelSize') === 'fixed') ?
  8373. settings('defaultLabelSize') :
  8374. settings('labelSizeRatio') * size;
  8375. context.font = (settings('fontStyle') ? settings('fontStyle') + ' ' : '') +
  8376. fontSize + 'px ' + settings('font');
  8377. context.fillStyle = (settings('labelColor') === 'node') ?
  8378. (node.color || settings('defaultNodeColor')) :
  8379. settings('defaultLabelColor');
  8380. context.fillText(
  8381. node.label,
  8382. Math.round(node[prefix + 'x'] + size + 3),
  8383. Math.round(node[prefix + 'y'] + fontSize / 3)
  8384. );
  8385. };
  8386. }).call(this);
  8387. ;(function(undefined) {
  8388. 'use strict';
  8389. if (typeof sigma === 'undefined')
  8390. throw 'sigma is not declared';
  8391. // Initialize packages:
  8392. sigma.utils.pkg('sigma.canvas.hovers');
  8393. /**
  8394. * This hover renderer will basically display the label with a background.
  8395. *
  8396. * @param {object} node The node object.
  8397. * @param {CanvasRenderingContext2D} context The canvas context.
  8398. * @param {configurable} settings The settings function.
  8399. */
  8400. sigma.canvas.hovers.def = function(node, context, settings) {
  8401. var x,
  8402. y,
  8403. w,
  8404. h,
  8405. e,
  8406. fontStyle = settings('hoverFontStyle') || settings('fontStyle'),
  8407. prefix = settings('prefix') || '',
  8408. size = node[prefix + 'size'],
  8409. fontSize = (settings('labelSize') === 'fixed') ?
  8410. settings('defaultLabelSize') :
  8411. settings('labelSizeRatio') * size;
  8412. // Label background:
  8413. context.font = (fontStyle ? fontStyle + ' ' : '') +
  8414. fontSize + 'px ' + (settings('hoverFont') || settings('font'));
  8415. context.beginPath();
  8416. context.fillStyle = settings('labelHoverBGColor') === 'node' ?
  8417. (node.color || settings('defaultNodeColor')) :
  8418. settings('defaultHoverLabelBGColor');
  8419. if (node.label && settings('labelHoverShadow')) {
  8420. context.shadowOffsetX = 0;
  8421. context.shadowOffsetY = 0;
  8422. context.shadowBlur = 8;
  8423. context.shadowColor = settings('labelHoverShadowColor');
  8424. }
  8425. if (node.label && typeof node.label === 'string') {
  8426. x = Math.round(node[prefix + 'x'] - fontSize / 2 - 2);
  8427. y = Math.round(node[prefix + 'y'] - fontSize / 2 - 2);
  8428. w = Math.round(
  8429. context.measureText(node.label).width + fontSize / 2 + size + 7
  8430. );
  8431. h = Math.round(fontSize + 4);
  8432. e = Math.round(fontSize / 2 + 2);
  8433. context.moveTo(x, y + e);
  8434. context.arcTo(x, y, x + e, y, e);
  8435. context.lineTo(x + w, y);
  8436. context.lineTo(x + w, y + h);
  8437. context.lineTo(x + e, y + h);
  8438. context.arcTo(x, y + h, x, y + h - e, e);
  8439. context.lineTo(x, y + e);
  8440. context.closePath();
  8441. context.fill();
  8442. context.shadowOffsetX = 0;
  8443. context.shadowOffsetY = 0;
  8444. context.shadowBlur = 0;
  8445. }
  8446. // Node border:
  8447. if (settings('borderSize') > 0) {
  8448. context.beginPath();
  8449. context.fillStyle = settings('nodeBorderColor') === 'node' ?
  8450. (node.color || settings('defaultNodeColor')) :
  8451. settings('defaultNodeBorderColor');
  8452. context.arc(
  8453. node[prefix + 'x'],
  8454. node[prefix + 'y'],
  8455. size + settings('borderSize'),
  8456. 0,
  8457. Math.PI * 2,
  8458. true
  8459. );
  8460. context.closePath();
  8461. context.fill();
  8462. }
  8463. // Node:
  8464. var nodeRenderer = sigma.canvas.nodes[node.type] || sigma.canvas.nodes.def;
  8465. nodeRenderer(node, context, settings);
  8466. // Display the label:
  8467. if (node.label && typeof node.label === 'string') {
  8468. context.fillStyle = (settings('labelHoverColor') === 'node') ?
  8469. (node.color || settings('defaultNodeColor')) :
  8470. settings('defaultLabelHoverColor');
  8471. context.fillText(
  8472. node.label,
  8473. Math.round(node[prefix + 'x'] + size + 3),
  8474. Math.round(node[prefix + 'y'] + fontSize / 3)
  8475. );
  8476. }
  8477. };
  8478. }).call(this);
  8479. ;(function() {
  8480. 'use strict';
  8481. sigma.utils.pkg('sigma.canvas.nodes');
  8482. /**
  8483. * The default node renderer. It renders the node as a simple disc.
  8484. *
  8485. * @param {object} node The node object.
  8486. * @param {CanvasRenderingContext2D} context The canvas context.
  8487. * @param {configurable} settings The settings function.
  8488. */
  8489. sigma.canvas.nodes.def = function(node, context, settings) {
  8490. var prefix = settings('prefix') || '';
  8491. context.fillStyle = node.color || settings('defaultNodeColor');
  8492. context.beginPath();
  8493. context.arc(
  8494. node[prefix + 'x'],
  8495. node[prefix + 'y'],
  8496. node[prefix + 'size'],
  8497. 0,
  8498. Math.PI * 2,
  8499. true
  8500. );
  8501. context.closePath();
  8502. context.fill();
  8503. };
  8504. })();
  8505. ;(function() {
  8506. 'use strict';
  8507. sigma.utils.pkg('sigma.canvas.edges');
  8508. /**
  8509. * The default edge renderer. It renders the edge as a simple line.
  8510. *
  8511. * @param {object} edge The edge object.
  8512. * @param {object} source node The edge source node.
  8513. * @param {object} target node The edge target node.
  8514. * @param {CanvasRenderingContext2D} context The canvas context.
  8515. * @param {configurable} settings The settings function.
  8516. */
  8517. sigma.canvas.edges.def = function(edge, source, target, context, settings) {
  8518. var color = edge.color,
  8519. prefix = settings('prefix') || '',
  8520. size = edge[prefix + 'size'] || 1,
  8521. edgeColor = settings('edgeColor'),
  8522. defaultNodeColor = settings('defaultNodeColor'),
  8523. defaultEdgeColor = settings('defaultEdgeColor');
  8524. if (!color)
  8525. switch (edgeColor) {
  8526. case 'source':
  8527. color = source.color || defaultNodeColor;
  8528. break;
  8529. case 'target':
  8530. color = target.color || defaultNodeColor;
  8531. break;
  8532. default:
  8533. color = defaultEdgeColor;
  8534. break;
  8535. }
  8536. context.strokeStyle = color;
  8537. context.lineWidth = size;
  8538. context.beginPath();
  8539. context.moveTo(
  8540. source[prefix + 'x'],
  8541. source[prefix + 'y']
  8542. );
  8543. context.lineTo(
  8544. target[prefix + 'x'],
  8545. target[prefix + 'y']
  8546. );
  8547. context.stroke();
  8548. };
  8549. })();
  8550. ;(function() {
  8551. 'use strict';
  8552. sigma.utils.pkg('sigma.canvas.edges');
  8553. /**
  8554. * This edge renderer will display edges as curves.
  8555. *
  8556. * @param {object} edge The edge object.
  8557. * @param {object} source node The edge source node.
  8558. * @param {object} target node The edge target node.
  8559. * @param {CanvasRenderingContext2D} context The canvas context.
  8560. * @param {configurable} settings The settings function.
  8561. */
  8562. sigma.canvas.edges.curve = function(edge, source, target, context, settings) {
  8563. var color = edge.color,
  8564. prefix = settings('prefix') || '',
  8565. size = edge[prefix + 'size'] || 1,
  8566. edgeColor = settings('edgeColor'),
  8567. defaultNodeColor = settings('defaultNodeColor'),
  8568. defaultEdgeColor = settings('defaultEdgeColor'),
  8569. cp = {},
  8570. sSize = source[prefix + 'size'],
  8571. sX = source[prefix + 'x'],
  8572. sY = source[prefix + 'y'],
  8573. tX = target[prefix + 'x'],
  8574. tY = target[prefix + 'y'];
  8575. cp = (source.id === target.id) ?
  8576. sigma.utils.getSelfLoopControlPoints(sX, sY, sSize) :
  8577. sigma.utils.getQuadraticControlPoint(sX, sY, tX, tY);
  8578. if (!color)
  8579. switch (edgeColor) {
  8580. case 'source':
  8581. color = source.color || defaultNodeColor;
  8582. break;
  8583. case 'target':
  8584. color = target.color || defaultNodeColor;
  8585. break;
  8586. default:
  8587. color = defaultEdgeColor;
  8588. break;
  8589. }
  8590. context.strokeStyle = color;
  8591. context.lineWidth = size;
  8592. context.beginPath();
  8593. context.moveTo(sX, sY);
  8594. if (source.id === target.id) {
  8595. context.bezierCurveTo(cp.x1, cp.y1, cp.x2, cp.y2, tX, tY);
  8596. } else {
  8597. context.quadraticCurveTo(cp.x, cp.y, tX, tY);
  8598. }
  8599. context.stroke();
  8600. };
  8601. })();
  8602. ;(function() {
  8603. 'use strict';
  8604. sigma.utils.pkg('sigma.canvas.edges');
  8605. /**
  8606. * This edge renderer will display edges as arrows going from the source node
  8607. *
  8608. * @param {object} edge The edge object.
  8609. * @param {object} source node The edge source node.
  8610. * @param {object} target node The edge target node.
  8611. * @param {CanvasRenderingContext2D} context The canvas context.
  8612. * @param {configurable} settings The settings function.
  8613. */
  8614. sigma.canvas.edges.arrow = function(edge, source, target, context, settings) {
  8615. var color = edge.color,
  8616. prefix = settings('prefix') || '',
  8617. edgeColor = settings('edgeColor'),
  8618. defaultNodeColor = settings('defaultNodeColor'),
  8619. defaultEdgeColor = settings('defaultEdgeColor'),
  8620. size = edge[prefix + 'size'] || 1,
  8621. tSize = target[prefix + 'size'],
  8622. sX = source[prefix + 'x'],
  8623. sY = source[prefix + 'y'],
  8624. tX = target[prefix + 'x'],
  8625. tY = target[prefix + 'y'],
  8626. aSize = Math.max(size * 2.5, settings('minArrowSize')),
  8627. d = Math.sqrt(Math.pow(tX - sX, 2) + Math.pow(tY - sY, 2)),
  8628. aX = sX + (tX - sX) * (d - aSize - tSize) / d,
  8629. aY = sY + (tY - sY) * (d - aSize - tSize) / d,
  8630. vX = (tX - sX) * aSize / d,
  8631. vY = (tY - sY) * aSize / d;
  8632. if (!color)
  8633. switch (edgeColor) {
  8634. case 'source':
  8635. color = source.color || defaultNodeColor;
  8636. break;
  8637. case 'target':
  8638. color = target.color || defaultNodeColor;
  8639. break;
  8640. default:
  8641. color = defaultEdgeColor;
  8642. break;
  8643. }
  8644. context.strokeStyle = color;
  8645. context.lineWidth = size;
  8646. context.beginPath();
  8647. context.moveTo(sX, sY);
  8648. context.lineTo(
  8649. aX,
  8650. aY
  8651. );
  8652. context.stroke();
  8653. context.fillStyle = color;
  8654. context.beginPath();
  8655. context.moveTo(aX + vX, aY + vY);
  8656. context.lineTo(aX + vY * 0.6, aY - vX * 0.6);
  8657. context.lineTo(aX - vY * 0.6, aY + vX * 0.6);
  8658. context.lineTo(aX + vX, aY + vY);
  8659. context.closePath();
  8660. context.fill();
  8661. };
  8662. })();
  8663. ;(function() {
  8664. 'use strict';
  8665. sigma.utils.pkg('sigma.canvas.edges');
  8666. /**
  8667. * This edge renderer will display edges as curves with arrow heading.
  8668. *
  8669. * @param {object} edge The edge object.
  8670. * @param {object} source node The edge source node.
  8671. * @param {object} target node The edge target node.
  8672. * @param {CanvasRenderingContext2D} context The canvas context.
  8673. * @param {configurable} settings The settings function.
  8674. */
  8675. sigma.canvas.edges.curvedArrow =
  8676. function(edge, source, target, context, settings) {
  8677. var color = edge.color,
  8678. prefix = settings('prefix') || '',
  8679. edgeColor = settings('edgeColor'),
  8680. defaultNodeColor = settings('defaultNodeColor'),
  8681. defaultEdgeColor = settings('defaultEdgeColor'),
  8682. cp = {},
  8683. size = edge[prefix + 'size'] || 1,
  8684. tSize = target[prefix + 'size'],
  8685. sX = source[prefix + 'x'],
  8686. sY = source[prefix + 'y'],
  8687. tX = target[prefix + 'x'],
  8688. tY = target[prefix + 'y'],
  8689. aSize = Math.max(size * 2.5, settings('minArrowSize')),
  8690. d,
  8691. aX,
  8692. aY,
  8693. vX,
  8694. vY;
  8695. cp = (source.id === target.id) ?
  8696. sigma.utils.getSelfLoopControlPoints(sX, sY, tSize) :
  8697. sigma.utils.getQuadraticControlPoint(sX, sY, tX, tY);
  8698. if (source.id === target.id) {
  8699. d = Math.sqrt(Math.pow(tX - cp.x1, 2) + Math.pow(tY - cp.y1, 2));
  8700. aX = cp.x1 + (tX - cp.x1) * (d - aSize - tSize) / d;
  8701. aY = cp.y1 + (tY - cp.y1) * (d - aSize - tSize) / d;
  8702. vX = (tX - cp.x1) * aSize / d;
  8703. vY = (tY - cp.y1) * aSize / d;
  8704. }
  8705. else {
  8706. d = Math.sqrt(Math.pow(tX - cp.x, 2) + Math.pow(tY - cp.y, 2));
  8707. aX = cp.x + (tX - cp.x) * (d - aSize - tSize) / d;
  8708. aY = cp.y + (tY - cp.y) * (d - aSize - tSize) / d;
  8709. vX = (tX - cp.x) * aSize / d;
  8710. vY = (tY - cp.y) * aSize / d;
  8711. }
  8712. if (!color)
  8713. switch (edgeColor) {
  8714. case 'source':
  8715. color = source.color || defaultNodeColor;
  8716. break;
  8717. case 'target':
  8718. color = target.color || defaultNodeColor;
  8719. break;
  8720. default:
  8721. color = defaultEdgeColor;
  8722. break;
  8723. }
  8724. context.strokeStyle = color;
  8725. context.lineWidth = size;
  8726. context.beginPath();
  8727. context.moveTo(sX, sY);
  8728. if (source.id === target.id) {
  8729. context.bezierCurveTo(cp.x2, cp.y2, cp.x1, cp.y1, aX, aY);
  8730. } else {
  8731. context.quadraticCurveTo(cp.x, cp.y, aX, aY);
  8732. }
  8733. context.stroke();
  8734. context.fillStyle = color;
  8735. context.beginPath();
  8736. context.moveTo(aX + vX, aY + vY);
  8737. context.lineTo(aX + vY * 0.6, aY - vX * 0.6);
  8738. context.lineTo(aX - vY * 0.6, aY + vX * 0.6);
  8739. context.lineTo(aX + vX, aY + vY);
  8740. context.closePath();
  8741. context.fill();
  8742. };
  8743. })();
  8744. ;(function() {
  8745. 'use strict';
  8746. sigma.utils.pkg('sigma.canvas.edgehovers');
  8747. /**
  8748. * This hover renderer will display the edge with a different color or size.
  8749. *
  8750. * @param {object} edge The edge object.
  8751. * @param {object} source node The edge source node.
  8752. * @param {object} target node The edge target node.
  8753. * @param {CanvasRenderingContext2D} context The canvas context.
  8754. * @param {configurable} settings The settings function.
  8755. */
  8756. sigma.canvas.edgehovers.def =
  8757. function(edge, source, target, context, settings) {
  8758. var color = edge.color,
  8759. prefix = settings('prefix') || '',
  8760. size = edge[prefix + 'size'] || 1,
  8761. edgeColor = settings('edgeColor'),
  8762. defaultNodeColor = settings('defaultNodeColor'),
  8763. defaultEdgeColor = settings('defaultEdgeColor');
  8764. if (!color)
  8765. switch (edgeColor) {
  8766. case 'source':
  8767. color = source.color || defaultNodeColor;
  8768. break;
  8769. case 'target':
  8770. color = target.color || defaultNodeColor;
  8771. break;
  8772. default:
  8773. color = defaultEdgeColor;
  8774. break;
  8775. }
  8776. if (settings('edgeHoverColor') === 'edge') {
  8777. color = edge.hover_color || color;
  8778. } else {
  8779. color = edge.hover_color || settings('defaultEdgeHoverColor') || color;
  8780. }
  8781. size *= settings('edgeHoverSizeRatio');
  8782. context.strokeStyle = color;
  8783. context.lineWidth = size;
  8784. context.beginPath();
  8785. context.moveTo(
  8786. source[prefix + 'x'],
  8787. source[prefix + 'y']
  8788. );
  8789. context.lineTo(
  8790. target[prefix + 'x'],
  8791. target[prefix + 'y']
  8792. );
  8793. context.stroke();
  8794. };
  8795. })();
  8796. ;(function() {
  8797. 'use strict';
  8798. sigma.utils.pkg('sigma.canvas.edgehovers');
  8799. /**
  8800. * This hover renderer will display the edge with a different color or size.
  8801. *
  8802. * @param {object} edge The edge object.
  8803. * @param {object} source node The edge source node.
  8804. * @param {object} target node The edge target node.
  8805. * @param {CanvasRenderingContext2D} context The canvas context.
  8806. * @param {configurable} settings The settings function.
  8807. */
  8808. sigma.canvas.edgehovers.curve =
  8809. function(edge, source, target, context, settings) {
  8810. var color = edge.color,
  8811. prefix = settings('prefix') || '',
  8812. size = settings('edgeHoverSizeRatio') * (edge[prefix + 'size'] || 1),
  8813. edgeColor = settings('edgeColor'),
  8814. defaultNodeColor = settings('defaultNodeColor'),
  8815. defaultEdgeColor = settings('defaultEdgeColor'),
  8816. cp = {},
  8817. sSize = source[prefix + 'size'],
  8818. sX = source[prefix + 'x'],
  8819. sY = source[prefix + 'y'],
  8820. tX = target[prefix + 'x'],
  8821. tY = target[prefix + 'y'];
  8822. cp = (source.id === target.id) ?
  8823. sigma.utils.getSelfLoopControlPoints(sX, sY, sSize) :
  8824. sigma.utils.getQuadraticControlPoint(sX, sY, tX, tY);
  8825. if (!color)
  8826. switch (edgeColor) {
  8827. case 'source':
  8828. color = source.color || defaultNodeColor;
  8829. break;
  8830. case 'target':
  8831. color = target.color || defaultNodeColor;
  8832. break;
  8833. default:
  8834. color = defaultEdgeColor;
  8835. break;
  8836. }
  8837. if (settings('edgeHoverColor') === 'edge') {
  8838. color = edge.hover_color || color;
  8839. } else {
  8840. color = edge.hover_color || settings('defaultEdgeHoverColor') || color;
  8841. }
  8842. context.strokeStyle = color;
  8843. context.lineWidth = size;
  8844. context.beginPath();
  8845. context.moveTo(sX, sY);
  8846. if (source.id === target.id) {
  8847. context.bezierCurveTo(cp.x1, cp.y1, cp.x2, cp.y2, tX, tY);
  8848. } else {
  8849. context.quadraticCurveTo(cp.x, cp.y, tX, tY);
  8850. }
  8851. context.stroke();
  8852. };
  8853. })();
  8854. ;(function() {
  8855. 'use strict';
  8856. sigma.utils.pkg('sigma.canvas.edgehovers');
  8857. /**
  8858. * This hover renderer will display the edge with a different color or size.
  8859. *
  8860. * @param {object} edge The edge object.
  8861. * @param {object} source node The edge source node.
  8862. * @param {object} target node The edge target node.
  8863. * @param {CanvasRenderingContext2D} context The canvas context.
  8864. * @param {configurable} settings The settings function.
  8865. */
  8866. sigma.canvas.edgehovers.arrow =
  8867. function(edge, source, target, context, settings) {
  8868. var color = edge.color,
  8869. prefix = settings('prefix') || '',
  8870. edgeColor = settings('edgeColor'),
  8871. defaultNodeColor = settings('defaultNodeColor'),
  8872. defaultEdgeColor = settings('defaultEdgeColor'),
  8873. size = edge[prefix + 'size'] || 1,
  8874. tSize = target[prefix + 'size'],
  8875. sX = source[prefix + 'x'],
  8876. sY = source[prefix + 'y'],
  8877. tX = target[prefix + 'x'],
  8878. tY = target[prefix + 'y'];
  8879. size = (edge.hover) ?
  8880. settings('edgeHoverSizeRatio') * size : size;
  8881. var aSize = size * 2.5,
  8882. d = Math.sqrt(Math.pow(tX - sX, 2) + Math.pow(tY - sY, 2)),
  8883. aX = sX + (tX - sX) * (d - aSize - tSize) / d,
  8884. aY = sY + (tY - sY) * (d - aSize - tSize) / d,
  8885. vX = (tX - sX) * aSize / d,
  8886. vY = (tY - sY) * aSize / d;
  8887. if (!color)
  8888. switch (edgeColor) {
  8889. case 'source':
  8890. color = source.color || defaultNodeColor;
  8891. break;
  8892. case 'target':
  8893. color = target.color || defaultNodeColor;
  8894. break;
  8895. default:
  8896. color = defaultEdgeColor;
  8897. break;
  8898. }
  8899. if (settings('edgeHoverColor') === 'edge') {
  8900. color = edge.hover_color || color;
  8901. } else {
  8902. color = edge.hover_color || settings('defaultEdgeHoverColor') || color;
  8903. }
  8904. context.strokeStyle = color;
  8905. context.lineWidth = size;
  8906. context.beginPath();
  8907. context.moveTo(sX, sY);
  8908. context.lineTo(
  8909. aX,
  8910. aY
  8911. );
  8912. context.stroke();
  8913. context.fillStyle = color;
  8914. context.beginPath();
  8915. context.moveTo(aX + vX, aY + vY);
  8916. context.lineTo(aX + vY * 0.6, aY - vX * 0.6);
  8917. context.lineTo(aX - vY * 0.6, aY + vX * 0.6);
  8918. context.lineTo(aX + vX, aY + vY);
  8919. context.closePath();
  8920. context.fill();
  8921. };
  8922. })();
  8923. ;(function() {
  8924. 'use strict';
  8925. sigma.utils.pkg('sigma.canvas.edgehovers');
  8926. /**
  8927. * This hover renderer will display the edge with a different color or size.
  8928. *
  8929. * @param {object} edge The edge object.
  8930. * @param {object} source node The edge source node.
  8931. * @param {object} target node The edge target node.
  8932. * @param {CanvasRenderingContext2D} context The canvas context.
  8933. * @param {configurable} settings The settings function.
  8934. */
  8935. sigma.canvas.edgehovers.curvedArrow =
  8936. function(edge, source, target, context, settings) {
  8937. var color = edge.color,
  8938. prefix = settings('prefix') || '',
  8939. edgeColor = settings('edgeColor'),
  8940. defaultNodeColor = settings('defaultNodeColor'),
  8941. defaultEdgeColor = settings('defaultEdgeColor'),
  8942. cp = {},
  8943. size = settings('edgeHoverSizeRatio') * (edge[prefix + 'size'] || 1),
  8944. tSize = target[prefix + 'size'],
  8945. sX = source[prefix + 'x'],
  8946. sY = source[prefix + 'y'],
  8947. tX = target[prefix + 'x'],
  8948. tY = target[prefix + 'y'],
  8949. d,
  8950. aSize,
  8951. aX,
  8952. aY,
  8953. vX,
  8954. vY;
  8955. cp = (source.id === target.id) ?
  8956. sigma.utils.getSelfLoopControlPoints(sX, sY, tSize) :
  8957. sigma.utils.getQuadraticControlPoint(sX, sY, tX, tY);
  8958. if (source.id === target.id) {
  8959. d = Math.sqrt(Math.pow(tX - cp.x1, 2) + Math.pow(tY - cp.y1, 2));
  8960. aSize = size * 2.5;
  8961. aX = cp.x1 + (tX - cp.x1) * (d - aSize - tSize) / d;
  8962. aY = cp.y1 + (tY - cp.y1) * (d - aSize - tSize) / d;
  8963. vX = (tX - cp.x1) * aSize / d;
  8964. vY = (tY - cp.y1) * aSize / d;
  8965. }
  8966. else {
  8967. d = Math.sqrt(Math.pow(tX - cp.x, 2) + Math.pow(tY - cp.y, 2));
  8968. aSize = size * 2.5;
  8969. aX = cp.x + (tX - cp.x) * (d - aSize - tSize) / d;
  8970. aY = cp.y + (tY - cp.y) * (d - aSize - tSize) / d;
  8971. vX = (tX - cp.x) * aSize / d;
  8972. vY = (tY - cp.y) * aSize / d;
  8973. }
  8974. if (!color)
  8975. switch (edgeColor) {
  8976. case 'source':
  8977. color = source.color || defaultNodeColor;
  8978. break;
  8979. case 'target':
  8980. color = target.color || defaultNodeColor;
  8981. break;
  8982. default:
  8983. color = defaultEdgeColor;
  8984. break;
  8985. }
  8986. if (settings('edgeHoverColor') === 'edge') {
  8987. color = edge.hover_color || color;
  8988. } else {
  8989. color = edge.hover_color || settings('defaultEdgeHoverColor') || color;
  8990. }
  8991. context.strokeStyle = color;
  8992. context.lineWidth = size;
  8993. context.beginPath();
  8994. context.moveTo(sX, sY);
  8995. if (source.id === target.id) {
  8996. context.bezierCurveTo(cp.x2, cp.y2, cp.x1, cp.y1, aX, aY);
  8997. } else {
  8998. context.quadraticCurveTo(cp.x, cp.y, aX, aY);
  8999. }
  9000. context.stroke();
  9001. context.fillStyle = color;
  9002. context.beginPath();
  9003. context.moveTo(aX + vX, aY + vY);
  9004. context.lineTo(aX + vY * 0.6, aY - vX * 0.6);
  9005. context.lineTo(aX - vY * 0.6, aY + vX * 0.6);
  9006. context.lineTo(aX + vX, aY + vY);
  9007. context.closePath();
  9008. context.fill();
  9009. };
  9010. })();
  9011. ;(function(undefined) {
  9012. 'use strict';
  9013. if (typeof sigma === 'undefined')
  9014. throw 'sigma is not declared';
  9015. // Initialize packages:
  9016. sigma.utils.pkg('sigma.canvas.extremities');
  9017. /**
  9018. * The default renderer for hovered edge extremities. It renders the edge
  9019. * extremities as hovered.
  9020. *
  9021. * @param {object} edge The edge object.
  9022. * @param {object} source node The edge source node.
  9023. * @param {object} target node The edge target node.
  9024. * @param {CanvasRenderingContext2D} context The canvas context.
  9025. * @param {configurable} settings The settings function.
  9026. */
  9027. sigma.canvas.extremities.def =
  9028. function(edge, source, target, context, settings) {
  9029. // Source Node:
  9030. (
  9031. sigma.canvas.hovers[source.type] ||
  9032. sigma.canvas.hovers.def
  9033. ) (
  9034. source, context, settings
  9035. );
  9036. // Target Node:
  9037. (
  9038. sigma.canvas.hovers[target.type] ||
  9039. sigma.canvas.hovers.def
  9040. ) (
  9041. target, context, settings
  9042. );
  9043. };
  9044. }).call(this);
  9045. ;(function() {
  9046. 'use strict';
  9047. sigma.utils.pkg('sigma.svg.utils');
  9048. /**
  9049. * Some useful functions used by sigma's SVG renderer.
  9050. */
  9051. sigma.svg.utils = {
  9052. /**
  9053. * SVG Element show.
  9054. *
  9055. * @param {DOMElement} element The DOM element to show.
  9056. */
  9057. show: function(element) {
  9058. element.style.display = '';
  9059. return this;
  9060. },
  9061. /**
  9062. * SVG Element hide.
  9063. *
  9064. * @param {DOMElement} element The DOM element to hide.
  9065. */
  9066. hide: function(element) {
  9067. element.style.display = 'none';
  9068. return this;
  9069. }
  9070. };
  9071. })();
  9072. ;(function() {
  9073. 'use strict';
  9074. sigma.utils.pkg('sigma.svg.nodes');
  9075. /**
  9076. * The default node renderer. It renders the node as a simple disc.
  9077. */
  9078. sigma.svg.nodes.def = {
  9079. /**
  9080. * SVG Element creation.
  9081. *
  9082. * @param {object} node The node object.
  9083. * @param {configurable} settings The settings function.
  9084. */
  9085. create: function(node, settings) {
  9086. var prefix = settings('prefix') || '',
  9087. circle = document.createElementNS(settings('xmlns'), 'circle');
  9088. // Defining the node's circle
  9089. circle.setAttributeNS(null, 'data-node-id', node.id);
  9090. circle.setAttributeNS(null, 'class', settings('classPrefix') + '-node');
  9091. circle.setAttributeNS(
  9092. null, 'fill', node.color || settings('defaultNodeColor'));
  9093. // Returning the DOM Element
  9094. return circle;
  9095. },
  9096. /**
  9097. * SVG Element update.
  9098. *
  9099. * @param {object} node The node object.
  9100. * @param {DOMElement} circle The node DOM element.
  9101. * @param {configurable} settings The settings function.
  9102. */
  9103. update: function(node, circle, settings) {
  9104. var prefix = settings('prefix') || '';
  9105. // Applying changes
  9106. // TODO: optimize - check if necessary
  9107. circle.setAttributeNS(null, 'cx', node[prefix + 'x']);
  9108. circle.setAttributeNS(null, 'cy', node[prefix + 'y']);
  9109. circle.setAttributeNS(null, 'r', node[prefix + 'size']);
  9110. // Updating only if not freestyle
  9111. if (!settings('freeStyle'))
  9112. circle.setAttributeNS(
  9113. null, 'fill', node.color || settings('defaultNodeColor'));
  9114. // Showing
  9115. circle.style.display = '';
  9116. return this;
  9117. }
  9118. };
  9119. })();
  9120. ;(function() {
  9121. 'use strict';
  9122. sigma.utils.pkg('sigma.svg.edges');
  9123. /**
  9124. * The default edge renderer. It renders the node as a simple line.
  9125. */
  9126. sigma.svg.edges.def = {
  9127. /**
  9128. * SVG Element creation.
  9129. *
  9130. * @param {object} edge The edge object.
  9131. * @param {object} source The source node object.
  9132. * @param {object} target The target node object.
  9133. * @param {configurable} settings The settings function.
  9134. */
  9135. create: function(edge, source, target, settings) {
  9136. var color = edge.color,
  9137. prefix = settings('prefix') || '',
  9138. edgeColor = settings('edgeColor'),
  9139. defaultNodeColor = settings('defaultNodeColor'),
  9140. defaultEdgeColor = settings('defaultEdgeColor');
  9141. if (!color)
  9142. switch (edgeColor) {
  9143. case 'source':
  9144. color = source.color || defaultNodeColor;
  9145. break;
  9146. case 'target':
  9147. color = target.color || defaultNodeColor;
  9148. break;
  9149. default:
  9150. color = defaultEdgeColor;
  9151. break;
  9152. }
  9153. var line = document.createElementNS(settings('xmlns'), 'line');
  9154. // Attributes
  9155. line.setAttributeNS(null, 'data-edge-id', edge.id);
  9156. line.setAttributeNS(null, 'class', settings('classPrefix') + '-edge');
  9157. line.setAttributeNS(null, 'stroke', color);
  9158. return line;
  9159. },
  9160. /**
  9161. * SVG Element update.
  9162. *
  9163. * @param {object} edge The edge object.
  9164. * @param {DOMElement} line The line DOM Element.
  9165. * @param {object} source The source node object.
  9166. * @param {object} target The target node object.
  9167. * @param {configurable} settings The settings function.
  9168. */
  9169. update: function(edge, line, source, target, settings) {
  9170. var prefix = settings('prefix') || '';
  9171. line.setAttributeNS(null, 'stroke-width', edge[prefix + 'size'] || 1);
  9172. line.setAttributeNS(null, 'x1', source[prefix + 'x']);
  9173. line.setAttributeNS(null, 'y1', source[prefix + 'y']);
  9174. line.setAttributeNS(null, 'x2', target[prefix + 'x']);
  9175. line.setAttributeNS(null, 'y2', target[prefix + 'y']);
  9176. // Showing
  9177. line.style.display = '';
  9178. return this;
  9179. }
  9180. };
  9181. })();
  9182. ;(function() {
  9183. 'use strict';
  9184. sigma.utils.pkg('sigma.svg.edges');
  9185. /**
  9186. * The curve edge renderer. It renders the node as a bezier curve.
  9187. */
  9188. sigma.svg.edges.curve = {
  9189. /**
  9190. * SVG Element creation.
  9191. *
  9192. * @param {object} edge The edge object.
  9193. * @param {object} source The source node object.
  9194. * @param {object} target The target node object.
  9195. * @param {configurable} settings The settings function.
  9196. */
  9197. create: function(edge, source, target, settings) {
  9198. var color = edge.color,
  9199. prefix = settings('prefix') || '',
  9200. edgeColor = settings('edgeColor'),
  9201. defaultNodeColor = settings('defaultNodeColor'),
  9202. defaultEdgeColor = settings('defaultEdgeColor');
  9203. if (!color)
  9204. switch (edgeColor) {
  9205. case 'source':
  9206. color = source.color || defaultNodeColor;
  9207. break;
  9208. case 'target':
  9209. color = target.color || defaultNodeColor;
  9210. break;
  9211. default:
  9212. color = defaultEdgeColor;
  9213. break;
  9214. }
  9215. var path = document.createElementNS(settings('xmlns'), 'path');
  9216. // Attributes
  9217. path.setAttributeNS(null, 'data-edge-id', edge.id);
  9218. path.setAttributeNS(null, 'class', settings('classPrefix') + '-edge');
  9219. path.setAttributeNS(null, 'stroke', color);
  9220. return path;
  9221. },
  9222. /**
  9223. * SVG Element update.
  9224. *
  9225. * @param {object} edge The edge object.
  9226. * @param {DOMElement} line The line DOM Element.
  9227. * @param {object} source The source node object.
  9228. * @param {object} target The target node object.
  9229. * @param {configurable} settings The settings function.
  9230. */
  9231. update: function(edge, path, source, target, settings) {
  9232. var prefix = settings('prefix') || '';
  9233. path.setAttributeNS(null, 'stroke-width', edge[prefix + 'size'] || 1);
  9234. // Control point
  9235. var cx = (source[prefix + 'x'] + target[prefix + 'x']) / 2 +
  9236. (target[prefix + 'y'] - source[prefix + 'y']) / 4,
  9237. cy = (source[prefix + 'y'] + target[prefix + 'y']) / 2 +
  9238. (source[prefix + 'x'] - target[prefix + 'x']) / 4;
  9239. // Path
  9240. var p = 'M' + source[prefix + 'x'] + ',' + source[prefix + 'y'] + ' ' +
  9241. 'Q' + cx + ',' + cy + ' ' +
  9242. target[prefix + 'x'] + ',' + target[prefix + 'y'];
  9243. // Updating attributes
  9244. path.setAttributeNS(null, 'd', p);
  9245. path.setAttributeNS(null, 'fill', 'none');
  9246. // Showing
  9247. path.style.display = '';
  9248. return this;
  9249. }
  9250. };
  9251. })();
  9252. ;(function(undefined) {
  9253. 'use strict';
  9254. if (typeof sigma === 'undefined')
  9255. throw 'sigma is not declared';
  9256. // Initialize packages:
  9257. sigma.utils.pkg('sigma.svg.labels');
  9258. /**
  9259. * The default label renderer. It renders the label as a simple text.
  9260. */
  9261. sigma.svg.labels.def = {
  9262. /**
  9263. * SVG Element creation.
  9264. *
  9265. * @param {object} node The node object.
  9266. * @param {configurable} settings The settings function.
  9267. */
  9268. create: function(node, settings) {
  9269. var prefix = settings('prefix') || '',
  9270. size = node[prefix + 'size'],
  9271. text = document.createElementNS(settings('xmlns'), 'text');
  9272. var fontSize = (settings('labelSize') === 'fixed') ?
  9273. settings('defaultLabelSize') :
  9274. settings('labelSizeRatio') * size;
  9275. var fontColor = (settings('labelColor') === 'node') ?
  9276. (node.color || settings('defaultNodeColor')) :
  9277. settings('defaultLabelColor');
  9278. text.setAttributeNS(null, 'data-label-target', node.id);
  9279. text.setAttributeNS(null, 'class', settings('classPrefix') + '-label');
  9280. text.setAttributeNS(null, 'font-size', fontSize);
  9281. text.setAttributeNS(null, 'font-family', settings('font'));
  9282. text.setAttributeNS(null, 'fill', fontColor);
  9283. text.innerHTML = node.label;
  9284. text.textContent = node.label;
  9285. return text;
  9286. },
  9287. /**
  9288. * SVG Element update.
  9289. *
  9290. * @param {object} node The node object.
  9291. * @param {DOMElement} text The label DOM element.
  9292. * @param {configurable} settings The settings function.
  9293. */
  9294. update: function(node, text, settings) {
  9295. var prefix = settings('prefix') || '',
  9296. size = node[prefix + 'size'];
  9297. var fontSize = (settings('labelSize') === 'fixed') ?
  9298. settings('defaultLabelSize') :
  9299. settings('labelSizeRatio') * size;
  9300. // Case when we don't want to display the label
  9301. if (!settings('forceLabels') && size < settings('labelThreshold'))
  9302. return;
  9303. if (typeof node.label !== 'string')
  9304. return;
  9305. // Updating
  9306. text.setAttributeNS(null, 'x',
  9307. Math.round(node[prefix + 'x'] + size + 3));
  9308. text.setAttributeNS(null, 'y',
  9309. Math.round(node[prefix + 'y'] + fontSize / 3));
  9310. // Showing
  9311. text.style.display = '';
  9312. return this;
  9313. }
  9314. };
  9315. }).call(this);
  9316. ;(function(undefined) {
  9317. 'use strict';
  9318. if (typeof sigma === 'undefined')
  9319. throw 'sigma is not declared';
  9320. // Initialize packages:
  9321. sigma.utils.pkg('sigma.svg.hovers');
  9322. /**
  9323. * The default hover renderer.
  9324. */
  9325. sigma.svg.hovers.def = {
  9326. /**
  9327. * SVG Element creation.
  9328. *
  9329. * @param {object} node The node object.
  9330. * @param {CanvasElement} measurementCanvas A fake canvas handled by
  9331. * the svg to perform some measurements and
  9332. * passed by the renderer.
  9333. * @param {DOMElement} nodeCircle The node DOM Element.
  9334. * @param {configurable} settings The settings function.
  9335. */
  9336. create: function(node, nodeCircle, measurementCanvas, settings) {
  9337. // Defining visual properties
  9338. var x,
  9339. y,
  9340. w,
  9341. h,
  9342. e,
  9343. d,
  9344. fontStyle = settings('hoverFontStyle') || settings('fontStyle'),
  9345. prefix = settings('prefix') || '',
  9346. size = node[prefix + 'size'],
  9347. fontSize = (settings('labelSize') === 'fixed') ?
  9348. settings('defaultLabelSize') :
  9349. settings('labelSizeRatio') * size,
  9350. fontColor = (settings('labelHoverColor') === 'node') ?
  9351. (node.color || settings('defaultNodeColor')) :
  9352. settings('defaultLabelHoverColor');
  9353. // Creating elements
  9354. var group = document.createElementNS(settings('xmlns'), 'g'),
  9355. rectangle = document.createElementNS(settings('xmlns'), 'rect'),
  9356. circle = document.createElementNS(settings('xmlns'), 'circle'),
  9357. text = document.createElementNS(settings('xmlns'), 'text');
  9358. // Defining properties
  9359. group.setAttributeNS(null, 'class', settings('classPrefix') + '-hover');
  9360. group.setAttributeNS(null, 'data-node-id', node.id);
  9361. if (typeof node.label === 'string') {
  9362. // Text
  9363. text.innerHTML = node.label;
  9364. text.textContent = node.label;
  9365. text.setAttributeNS(
  9366. null,
  9367. 'class',
  9368. settings('classPrefix') + '-hover-label');
  9369. text.setAttributeNS(null, 'font-size', fontSize);
  9370. text.setAttributeNS(null, 'font-family', settings('font'));
  9371. text.setAttributeNS(null, 'fill', fontColor);
  9372. text.setAttributeNS(null, 'x',
  9373. Math.round(node[prefix + 'x'] + size + 3));
  9374. text.setAttributeNS(null, 'y',
  9375. Math.round(node[prefix + 'y'] + fontSize / 3));
  9376. // Measures
  9377. // OPTIMIZE: Find a better way than a measurement canvas
  9378. x = Math.round(node[prefix + 'x'] - fontSize / 2 - 2);
  9379. y = Math.round(node[prefix + 'y'] - fontSize / 2 - 2);
  9380. w = Math.round(
  9381. measurementCanvas.measureText(node.label).width +
  9382. fontSize / 2 + size + 9
  9383. );
  9384. h = Math.round(fontSize + 4);
  9385. e = Math.round(fontSize / 2 + 2);
  9386. // Circle
  9387. circle.setAttributeNS(
  9388. null,
  9389. 'class',
  9390. settings('classPrefix') + '-hover-area');
  9391. circle.setAttributeNS(null, 'fill', '#fff');
  9392. circle.setAttributeNS(null, 'cx', node[prefix + 'x']);
  9393. circle.setAttributeNS(null, 'cy', node[prefix + 'y']);
  9394. circle.setAttributeNS(null, 'r', e);
  9395. // Rectangle
  9396. rectangle.setAttributeNS(
  9397. null,
  9398. 'class',
  9399. settings('classPrefix') + '-hover-area');
  9400. rectangle.setAttributeNS(null, 'fill', '#fff');
  9401. rectangle.setAttributeNS(null, 'x', node[prefix + 'x'] + e / 4);
  9402. rectangle.setAttributeNS(null, 'y', node[prefix + 'y'] - e);
  9403. rectangle.setAttributeNS(null, 'width', w);
  9404. rectangle.setAttributeNS(null, 'height', h);
  9405. }
  9406. // Appending childs
  9407. group.appendChild(circle);
  9408. group.appendChild(rectangle);
  9409. group.appendChild(text);
  9410. group.appendChild(nodeCircle);
  9411. return group;
  9412. }
  9413. };
  9414. }).call(this);
  9415. ;(function(undefined) {
  9416. 'use strict';
  9417. if (typeof sigma === 'undefined')
  9418. throw 'sigma is not declared';
  9419. // Initialize packages:
  9420. sigma.utils.pkg('sigma.middlewares');
  9421. sigma.utils.pkg('sigma.utils');
  9422. /**
  9423. * This middleware will rescale the graph such that it takes an optimal space
  9424. * on the renderer.
  9425. *
  9426. * As each middleware, this function is executed in the scope of the sigma
  9427. * instance.
  9428. *
  9429. * @param {?string} readPrefix The read prefix.
  9430. * @param {?string} writePrefix The write prefix.
  9431. * @param {object} options The parameters.
  9432. */
  9433. sigma.middlewares.rescale = function(readPrefix, writePrefix, options) {
  9434. var i,
  9435. l,
  9436. a,
  9437. b,
  9438. c,
  9439. d,
  9440. scale,
  9441. margin,
  9442. n = this.graph.nodes(),
  9443. e = this.graph.edges(),
  9444. settings = this.settings.embedObjects(options || {}),
  9445. bounds = settings('bounds') || sigma.utils.getBoundaries(
  9446. this.graph,
  9447. readPrefix,
  9448. true
  9449. ),
  9450. minX = bounds.minX,
  9451. minY = bounds.minY,
  9452. maxX = bounds.maxX,
  9453. maxY = bounds.maxY,
  9454. sizeMax = bounds.sizeMax,
  9455. weightMax = bounds.weightMax,
  9456. w = settings('width') || 1,
  9457. h = settings('height') || 1,
  9458. rescaleSettings = settings('autoRescale'),
  9459. validSettings = {
  9460. nodePosition: 1,
  9461. nodeSize: 1,
  9462. edgeSize: 1
  9463. };
  9464. /**
  9465. * What elements should we rescale?
  9466. */
  9467. if (!(rescaleSettings instanceof Array))
  9468. rescaleSettings = ['nodePosition', 'nodeSize', 'edgeSize'];
  9469. for (i = 0, l = rescaleSettings.length; i < l; i++)
  9470. if (!validSettings[rescaleSettings[i]])
  9471. throw new Error(
  9472. 'The rescale setting "' + rescaleSettings[i] + '" is not recognized.'
  9473. );
  9474. var np = ~rescaleSettings.indexOf('nodePosition'),
  9475. ns = ~rescaleSettings.indexOf('nodeSize'),
  9476. es = ~rescaleSettings.indexOf('edgeSize');
  9477. /**
  9478. * First, we compute the scaling ratio, without considering the sizes
  9479. * of the nodes : Each node will have its center in the canvas, but might
  9480. * be partially out of it.
  9481. */
  9482. scale = settings('scalingMode') === 'outside' ?
  9483. Math.max(
  9484. w / Math.max(maxX - minX, 1),
  9485. h / Math.max(maxY - minY, 1)
  9486. ) :
  9487. Math.min(
  9488. w / Math.max(maxX - minX, 1),
  9489. h / Math.max(maxY - minY, 1)
  9490. );
  9491. /**
  9492. * Then, we correct that scaling ratio considering a margin, which is
  9493. * basically the size of the biggest node.
  9494. * This has to be done as a correction since to compare the size of the
  9495. * biggest node to the X and Y values, we have to first get an
  9496. * approximation of the scaling ratio.
  9497. **/
  9498. margin =
  9499. (
  9500. settings('rescaleIgnoreSize') ?
  9501. 0 :
  9502. (settings('maxNodeSize') || sizeMax) / scale
  9503. ) +
  9504. (settings('sideMargin') || 0);
  9505. maxX += margin;
  9506. minX -= margin;
  9507. maxY += margin;
  9508. minY -= margin;
  9509. // Fix the scaling with the new extrema:
  9510. scale = settings('scalingMode') === 'outside' ?
  9511. Math.max(
  9512. w / Math.max(maxX - minX, 1),
  9513. h / Math.max(maxY - minY, 1)
  9514. ) :
  9515. Math.min(
  9516. w / Math.max(maxX - minX, 1),
  9517. h / Math.max(maxY - minY, 1)
  9518. );
  9519. // Size homothetic parameters:
  9520. if (!settings('maxNodeSize') && !settings('minNodeSize')) {
  9521. a = 1;
  9522. b = 0;
  9523. } else if (settings('maxNodeSize') === settings('minNodeSize')) {
  9524. a = 0;
  9525. b = +settings('maxNodeSize');
  9526. } else {
  9527. a = (settings('maxNodeSize') - settings('minNodeSize')) / sizeMax;
  9528. b = +settings('minNodeSize');
  9529. }
  9530. if (!settings('maxEdgeSize') && !settings('minEdgeSize')) {
  9531. c = 1;
  9532. d = 0;
  9533. } else if (settings('maxEdgeSize') === settings('minEdgeSize')) {
  9534. c = 0;
  9535. d = +settings('minEdgeSize');
  9536. } else {
  9537. c = (settings('maxEdgeSize') - settings('minEdgeSize')) / weightMax;
  9538. d = +settings('minEdgeSize');
  9539. }
  9540. // Rescale the nodes and edges:
  9541. for (i = 0, l = e.length; i < l; i++)
  9542. e[i][writePrefix + 'size'] =
  9543. e[i][readPrefix + 'size'] * (es ? c : 1) + (es ? d : 0);
  9544. for (i = 0, l = n.length; i < l; i++) {
  9545. n[i][writePrefix + 'size'] =
  9546. n[i][readPrefix + 'size'] * (ns ? a : 1) + (ns ? b : 0);
  9547. n[i][writePrefix + 'x'] =
  9548. (n[i][readPrefix + 'x'] - (maxX + minX) / 2) * (np ? scale : 1);
  9549. n[i][writePrefix + 'y'] =
  9550. (n[i][readPrefix + 'y'] - (maxY + minY) / 2) * (np ? scale : 1);
  9551. }
  9552. };
  9553. sigma.utils.getBoundaries = function(graph, prefix, doEdges) {
  9554. var i,
  9555. l,
  9556. e = graph.edges(),
  9557. n = graph.nodes(),
  9558. weightMax = -Infinity,
  9559. sizeMax = -Infinity,
  9560. minX = Infinity,
  9561. minY = Infinity,
  9562. maxX = -Infinity,
  9563. maxY = -Infinity;
  9564. if (doEdges)
  9565. for (i = 0, l = e.length; i < l; i++)
  9566. weightMax = Math.max(e[i][prefix + 'size'], weightMax);
  9567. for (i = 0, l = n.length; i < l; i++) {
  9568. sizeMax = Math.max(n[i][prefix + 'size'], sizeMax);
  9569. maxX = Math.max(n[i][prefix + 'x'], maxX);
  9570. minX = Math.min(n[i][prefix + 'x'], minX);
  9571. maxY = Math.max(n[i][prefix + 'y'], maxY);
  9572. minY = Math.min(n[i][prefix + 'y'], minY);
  9573. }
  9574. weightMax = weightMax || 1;
  9575. sizeMax = sizeMax || 1;
  9576. return {
  9577. weightMax: weightMax,
  9578. sizeMax: sizeMax,
  9579. minX: minX,
  9580. minY: minY,
  9581. maxX: maxX,
  9582. maxY: maxY
  9583. };
  9584. };
  9585. }).call(this);
  9586. ;(function(undefined) {
  9587. 'use strict';
  9588. if (typeof sigma === 'undefined')
  9589. throw 'sigma is not declared';
  9590. // Initialize packages:
  9591. sigma.utils.pkg('sigma.middlewares');
  9592. /**
  9593. * This middleware will just copy the graphic properties.
  9594. *
  9595. * @param {?string} readPrefix The read prefix.
  9596. * @param {?string} writePrefix The write prefix.
  9597. */
  9598. sigma.middlewares.copy = function(readPrefix, writePrefix) {
  9599. var i,
  9600. l,
  9601. a;
  9602. if (writePrefix + '' === readPrefix + '')
  9603. return;
  9604. a = this.graph.nodes();
  9605. for (i = 0, l = a.length; i < l; i++) {
  9606. a[i][writePrefix + 'x'] = a[i][readPrefix + 'x'];
  9607. a[i][writePrefix + 'y'] = a[i][readPrefix + 'y'];
  9608. a[i][writePrefix + 'size'] = a[i][readPrefix + 'size'];
  9609. }
  9610. a = this.graph.edges();
  9611. for (i = 0, l = a.length; i < l; i++)
  9612. a[i][writePrefix + 'size'] = a[i][readPrefix + 'size'];
  9613. };
  9614. }).call(this);
  9615. ;(function(undefined) {
  9616. 'use strict';
  9617. if (typeof sigma === 'undefined')
  9618. throw 'sigma is not declared';
  9619. // Initialize packages:
  9620. sigma.utils.pkg('sigma.misc.animation.running');
  9621. /**
  9622. * Generates a unique ID for the animation.
  9623. *
  9624. * @return {string} Returns the new ID.
  9625. */
  9626. var _getID = (function() {
  9627. var id = 0;
  9628. return function() {
  9629. return '' + (++id);
  9630. };
  9631. })();
  9632. /**
  9633. * This function animates a camera. It has to be called with the camera to
  9634. * animate, the values of the coordinates to reach and eventually some
  9635. * options. It returns a number id, that you can use to kill the animation,
  9636. * with the method sigma.misc.animation.kill(id).
  9637. *
  9638. * The available options are:
  9639. *
  9640. * {?number} duration The duration of the animation.
  9641. * {?function} onNewFrame A callback to execute when the animation
  9642. * enter a new frame.
  9643. * {?function} onComplete A callback to execute when the animation
  9644. * is completed or killed.
  9645. * {?(string|function)} easing The name of a function from the package
  9646. * sigma.utils.easings, or a custom easing
  9647. * function.
  9648. *
  9649. * @param {camera} camera The camera to animate.
  9650. * @param {object} target The coordinates to reach.
  9651. * @param {?object} options Eventually an object to specify some options to
  9652. * the function. The available options are
  9653. * presented in the description of the function.
  9654. * @return {number} The animation id, to make it easy to kill
  9655. * through the method "sigma.misc.animation.kill".
  9656. */
  9657. sigma.misc.animation.camera = function(camera, val, options) {
  9658. if (
  9659. !(camera instanceof sigma.classes.camera) ||
  9660. typeof val !== 'object' ||
  9661. !val
  9662. )
  9663. throw 'animation.camera: Wrong arguments.';
  9664. if (
  9665. typeof val.x !== 'number' &&
  9666. typeof val.y !== 'number' &&
  9667. typeof val.ratio !== 'number' &&
  9668. typeof val.angle !== 'number'
  9669. )
  9670. throw 'There must be at least one valid coordinate in the given val.';
  9671. var fn,
  9672. id,
  9673. anim,
  9674. easing,
  9675. duration,
  9676. initialVal,
  9677. o = options || {},
  9678. start = sigma.utils.dateNow();
  9679. // Store initial values:
  9680. initialVal = {
  9681. x: camera.x,
  9682. y: camera.y,
  9683. ratio: camera.ratio,
  9684. angle: camera.angle
  9685. };
  9686. duration = o.duration;
  9687. easing = typeof o.easing !== 'function' ?
  9688. sigma.utils.easings[o.easing || 'quadraticInOut'] :
  9689. o.easing;
  9690. fn = function() {
  9691. var coef,
  9692. t = o.duration ? (sigma.utils.dateNow() - start) / o.duration : 1;
  9693. // If the animation is over:
  9694. if (t >= 1) {
  9695. camera.isAnimated = false;
  9696. camera.goTo({
  9697. x: val.x !== undefined ? val.x : initialVal.x,
  9698. y: val.y !== undefined ? val.y : initialVal.y,
  9699. ratio: val.ratio !== undefined ? val.ratio : initialVal.ratio,
  9700. angle: val.angle !== undefined ? val.angle : initialVal.angle
  9701. });
  9702. cancelAnimationFrame(id);
  9703. delete sigma.misc.animation.running[id];
  9704. // Check callbacks:
  9705. if (typeof o.onComplete === 'function')
  9706. o.onComplete();
  9707. // Else, let's keep going:
  9708. } else {
  9709. coef = easing(t);
  9710. camera.isAnimated = true;
  9711. camera.goTo({
  9712. x: val.x !== undefined ?
  9713. initialVal.x + (val.x - initialVal.x) * coef :
  9714. initialVal.x,
  9715. y: val.y !== undefined ?
  9716. initialVal.y + (val.y - initialVal.y) * coef :
  9717. initialVal.y,
  9718. ratio: val.ratio !== undefined ?
  9719. initialVal.ratio + (val.ratio - initialVal.ratio) * coef :
  9720. initialVal.ratio,
  9721. angle: val.angle !== undefined ?
  9722. initialVal.angle + (val.angle - initialVal.angle) * coef :
  9723. initialVal.angle
  9724. });
  9725. // Check callbacks:
  9726. if (typeof o.onNewFrame === 'function')
  9727. o.onNewFrame();
  9728. anim.frameId = requestAnimationFrame(fn);
  9729. }
  9730. };
  9731. id = _getID();
  9732. anim = {
  9733. frameId: requestAnimationFrame(fn),
  9734. target: camera,
  9735. type: 'camera',
  9736. options: o,
  9737. fn: fn
  9738. };
  9739. sigma.misc.animation.running[id] = anim;
  9740. return id;
  9741. };
  9742. /**
  9743. * Kills a running animation. It triggers the eventual onComplete callback.
  9744. *
  9745. * @param {number} id The id of the animation to kill.
  9746. * @return {object} Returns the sigma.misc.animation package.
  9747. */
  9748. sigma.misc.animation.kill = function(id) {
  9749. if (arguments.length !== 1 || typeof id !== 'number')
  9750. throw 'animation.kill: Wrong arguments.';
  9751. var o = sigma.misc.animation.running[id];
  9752. if (o) {
  9753. cancelAnimationFrame(id);
  9754. delete sigma.misc.animation.running[o.frameId];
  9755. if (o.type === 'camera')
  9756. o.target.isAnimated = false;
  9757. // Check callbacks:
  9758. if (typeof (o.options || {}).onComplete === 'function')
  9759. o.options.onComplete();
  9760. }
  9761. return this;
  9762. };
  9763. /**
  9764. * Kills every running animations, or only the one with the specified type,
  9765. * if a string parameter is given.
  9766. *
  9767. * @param {?(string|object)} filter A string to filter the animations to kill
  9768. * on their type (example: "camera"), or an
  9769. * object to filter on their target.
  9770. * @return {number} Returns the number of animations killed
  9771. * that way.
  9772. */
  9773. sigma.misc.animation.killAll = function(filter) {
  9774. var o,
  9775. id,
  9776. count = 0,
  9777. type = typeof filter === 'string' ? filter : null,
  9778. target = typeof filter === 'object' ? filter : null,
  9779. running = sigma.misc.animation.running;
  9780. for (id in running)
  9781. if (
  9782. (!type || running[id].type === type) &&
  9783. (!target || running[id].target === target)
  9784. ) {
  9785. o = sigma.misc.animation.running[id];
  9786. cancelAnimationFrame(o.frameId);
  9787. delete sigma.misc.animation.running[id];
  9788. if (o.type === 'camera')
  9789. o.target.isAnimated = false;
  9790. // Increment counter:
  9791. count++;
  9792. // Check callbacks:
  9793. if (typeof (o.options || {}).onComplete === 'function')
  9794. o.options.onComplete();
  9795. }
  9796. return count;
  9797. };
  9798. /**
  9799. * Returns "true" if any animation that is currently still running matches
  9800. * the filter given to the function.
  9801. *
  9802. * @param {string|object} filter A string to filter the animations to kill
  9803. * on their type (example: "camera"), or an
  9804. * object to filter on their target.
  9805. * @return {boolean} Returns true if any running animation
  9806. * matches.
  9807. */
  9808. sigma.misc.animation.has = function(filter) {
  9809. var id,
  9810. type = typeof filter === 'string' ? filter : null,
  9811. target = typeof filter === 'object' ? filter : null,
  9812. running = sigma.misc.animation.running;
  9813. for (id in running)
  9814. if (
  9815. (!type || running[id].type === type) &&
  9816. (!target || running[id].target === target)
  9817. )
  9818. return true;
  9819. return false;
  9820. };
  9821. }).call(this);
  9822. ;(function(undefined) {
  9823. 'use strict';
  9824. if (typeof sigma === 'undefined')
  9825. throw 'sigma is not declared';
  9826. // Initialize packages:
  9827. sigma.utils.pkg('sigma.misc');
  9828. /**
  9829. * This helper will bind any no-DOM renderer (for instance canvas or WebGL)
  9830. * to its captors, to properly dispatch the good events to the sigma instance
  9831. * to manage clicking, hovering etc...
  9832. *
  9833. * It has to be called in the scope of the related renderer.
  9834. */
  9835. sigma.misc.bindEvents = function(prefix) {
  9836. var i,
  9837. l,
  9838. mX,
  9839. mY,
  9840. captor,
  9841. self = this;
  9842. function getNodes(e) {
  9843. if (e) {
  9844. mX = 'x' in e.data ? e.data.x : mX;
  9845. mY = 'y' in e.data ? e.data.y : mY;
  9846. }
  9847. var i,
  9848. j,
  9849. l,
  9850. n,
  9851. x,
  9852. y,
  9853. s,
  9854. inserted,
  9855. selected = [],
  9856. modifiedX = mX + self.width / 2,
  9857. modifiedY = mY + self.height / 2,
  9858. point = self.camera.cameraPosition(
  9859. mX,
  9860. mY
  9861. ),
  9862. nodes = self.camera.quadtree.point(
  9863. point.x,
  9864. point.y
  9865. );
  9866. if (nodes.length)
  9867. for (i = 0, l = nodes.length; i < l; i++) {
  9868. n = nodes[i];
  9869. x = n[prefix + 'x'];
  9870. y = n[prefix + 'y'];
  9871. s = n[prefix + 'size'];
  9872. if (
  9873. !n.hidden &&
  9874. modifiedX > x - s &&
  9875. modifiedX < x + s &&
  9876. modifiedY > y - s &&
  9877. modifiedY < y + s &&
  9878. Math.sqrt(
  9879. Math.pow(modifiedX - x, 2) +
  9880. Math.pow(modifiedY - y, 2)
  9881. ) < s
  9882. ) {
  9883. // Insert the node:
  9884. inserted = false;
  9885. for (j = 0; j < selected.length; j++)
  9886. if (n.size > selected[j].size) {
  9887. selected.splice(j, 0, n);
  9888. inserted = true;
  9889. break;
  9890. }
  9891. if (!inserted)
  9892. selected.push(n);
  9893. }
  9894. }
  9895. return selected;
  9896. }
  9897. function getEdges(e) {
  9898. if (!self.settings('enableEdgeHovering')) {
  9899. // No event if the setting is off:
  9900. return [];
  9901. }
  9902. var isCanvas = (
  9903. sigma.renderers.canvas && self instanceof sigma.renderers.canvas);
  9904. if (!isCanvas) {
  9905. // A quick hardcoded rule to prevent people from using this feature
  9906. // with the WebGL renderer (which is not good enough at the moment):
  9907. throw new Error(
  9908. 'The edge events feature is not compatible with the WebGL renderer'
  9909. );
  9910. }
  9911. if (e) {
  9912. mX = 'x' in e.data ? e.data.x : mX;
  9913. mY = 'y' in e.data ? e.data.y : mY;
  9914. }
  9915. var i,
  9916. j,
  9917. l,
  9918. a,
  9919. edge,
  9920. s,
  9921. maxEpsilon = self.settings('edgeHoverPrecision'),
  9922. source,
  9923. target,
  9924. cp,
  9925. nodeIndex = {},
  9926. inserted,
  9927. selected = [],
  9928. modifiedX = mX + self.width / 2,
  9929. modifiedY = mY + self.height / 2,
  9930. point = self.camera.cameraPosition(
  9931. mX,
  9932. mY
  9933. ),
  9934. edges = [];
  9935. if (isCanvas) {
  9936. var nodesOnScreen = self.camera.quadtree.area(
  9937. self.camera.getRectangle(self.width, self.height)
  9938. );
  9939. for (a = nodesOnScreen, i = 0, l = a.length; i < l; i++)
  9940. nodeIndex[a[i].id] = a[i];
  9941. }
  9942. if (self.camera.edgequadtree !== undefined) {
  9943. edges = self.camera.edgequadtree.point(
  9944. point.x,
  9945. point.y
  9946. );
  9947. }
  9948. function insertEdge(selected, edge) {
  9949. inserted = false;
  9950. for (j = 0; j < selected.length; j++)
  9951. if (edge.size > selected[j].size) {
  9952. selected.splice(j, 0, edge);
  9953. inserted = true;
  9954. break;
  9955. }
  9956. if (!inserted)
  9957. selected.push(edge);
  9958. }
  9959. if (edges.length)
  9960. for (i = 0, l = edges.length; i < l; i++) {
  9961. edge = edges[i];
  9962. source = self.graph.nodes(edge.source);
  9963. target = self.graph.nodes(edge.target);
  9964. // (HACK) we can't get edge[prefix + 'size'] on WebGL renderer:
  9965. s = edge[prefix + 'size'] ||
  9966. edge['read_' + prefix + 'size'];
  9967. // First, let's identify which edges are drawn. To do this, we keep
  9968. // every edges that have at least one extremity displayed according to
  9969. // the quadtree and the "hidden" attribute. We also do not keep hidden
  9970. // edges.
  9971. // Then, let's check if the mouse is on the edge (we suppose that it
  9972. // is a line segment).
  9973. if (
  9974. !edge.hidden &&
  9975. !source.hidden && !target.hidden &&
  9976. (!isCanvas ||
  9977. (nodeIndex[edge.source] || nodeIndex[edge.target])) &&
  9978. sigma.utils.getDistance(
  9979. source[prefix + 'x'],
  9980. source[prefix + 'y'],
  9981. modifiedX,
  9982. modifiedY) > source[prefix + 'size'] &&
  9983. sigma.utils.getDistance(
  9984. target[prefix + 'x'],
  9985. target[prefix + 'y'],
  9986. modifiedX,
  9987. modifiedY) > target[prefix + 'size']
  9988. ) {
  9989. if (edge.type == 'curve' || edge.type == 'curvedArrow') {
  9990. if (source.id === target.id) {
  9991. cp = sigma.utils.getSelfLoopControlPoints(
  9992. source[prefix + 'x'],
  9993. source[prefix + 'y'],
  9994. source[prefix + 'size']
  9995. );
  9996. if (
  9997. sigma.utils.isPointOnBezierCurve(
  9998. modifiedX,
  9999. modifiedY,
  10000. source[prefix + 'x'],
  10001. source[prefix + 'y'],
  10002. target[prefix + 'x'],
  10003. target[prefix + 'y'],
  10004. cp.x1,
  10005. cp.y1,
  10006. cp.x2,
  10007. cp.y2,
  10008. Math.max(s, maxEpsilon)
  10009. )) {
  10010. insertEdge(selected, edge);
  10011. }
  10012. }
  10013. else {
  10014. cp = sigma.utils.getQuadraticControlPoint(
  10015. source[prefix + 'x'],
  10016. source[prefix + 'y'],
  10017. target[prefix + 'x'],
  10018. target[prefix + 'y']);
  10019. if (
  10020. sigma.utils.isPointOnQuadraticCurve(
  10021. modifiedX,
  10022. modifiedY,
  10023. source[prefix + 'x'],
  10024. source[prefix + 'y'],
  10025. target[prefix + 'x'],
  10026. target[prefix + 'y'],
  10027. cp.x,
  10028. cp.y,
  10029. Math.max(s, maxEpsilon)
  10030. )) {
  10031. insertEdge(selected, edge);
  10032. }
  10033. }
  10034. } else if (
  10035. sigma.utils.isPointOnSegment(
  10036. modifiedX,
  10037. modifiedY,
  10038. source[prefix + 'x'],
  10039. source[prefix + 'y'],
  10040. target[prefix + 'x'],
  10041. target[prefix + 'y'],
  10042. Math.max(s, maxEpsilon)
  10043. )) {
  10044. insertEdge(selected, edge);
  10045. }
  10046. }
  10047. }
  10048. return selected;
  10049. }
  10050. function bindCaptor(captor) {
  10051. var nodes,
  10052. edges,
  10053. overNodes = {},
  10054. overEdges = {};
  10055. function onClick(e) {
  10056. if (!self.settings('eventsEnabled'))
  10057. return;
  10058. self.dispatchEvent('click', e.data);
  10059. nodes = getNodes(e);
  10060. edges = getEdges(e);
  10061. if (nodes.length) {
  10062. self.dispatchEvent('clickNode', {
  10063. node: nodes[0],
  10064. captor: e.data
  10065. });
  10066. self.dispatchEvent('clickNodes', {
  10067. node: nodes,
  10068. captor: e.data
  10069. });
  10070. } else if (edges.length) {
  10071. self.dispatchEvent('clickEdge', {
  10072. edge: edges[0],
  10073. captor: e.data
  10074. });
  10075. self.dispatchEvent('clickEdges', {
  10076. edge: edges,
  10077. captor: e.data
  10078. });
  10079. } else
  10080. self.dispatchEvent('clickStage', {captor: e.data});
  10081. }
  10082. function onDoubleClick(e) {
  10083. if (!self.settings('eventsEnabled'))
  10084. return;
  10085. self.dispatchEvent('doubleClick', e.data);
  10086. nodes = getNodes(e);
  10087. edges = getEdges(e);
  10088. if (nodes.length) {
  10089. self.dispatchEvent('doubleClickNode', {
  10090. node: nodes[0],
  10091. captor: e.data
  10092. });
  10093. self.dispatchEvent('doubleClickNodes', {
  10094. node: nodes,
  10095. captor: e.data
  10096. });
  10097. } else if (edges.length) {
  10098. self.dispatchEvent('doubleClickEdge', {
  10099. edge: edges[0],
  10100. captor: e.data
  10101. });
  10102. self.dispatchEvent('doubleClickEdges', {
  10103. edge: edges,
  10104. captor: e.data
  10105. });
  10106. } else
  10107. self.dispatchEvent('doubleClickStage', {captor: e.data});
  10108. }
  10109. function onRightClick(e) {
  10110. if (!self.settings('eventsEnabled'))
  10111. return;
  10112. self.dispatchEvent('rightClick', e.data);
  10113. nodes = getNodes(e);
  10114. edges = getEdges(e);
  10115. if (nodes.length) {
  10116. self.dispatchEvent('rightClickNode', {
  10117. node: nodes[0],
  10118. captor: e.data
  10119. });
  10120. self.dispatchEvent('rightClickNodes', {
  10121. node: nodes,
  10122. captor: e.data
  10123. });
  10124. } else if (edges.length) {
  10125. self.dispatchEvent('rightClickEdge', {
  10126. edge: edges[0],
  10127. captor: e.data
  10128. });
  10129. self.dispatchEvent('rightClickEdges', {
  10130. edge: edges,
  10131. captor: e.data
  10132. });
  10133. } else
  10134. self.dispatchEvent('rightClickStage', {captor: e.data});
  10135. }
  10136. function onOut(e) {
  10137. if (!self.settings('eventsEnabled'))
  10138. return;
  10139. var k,
  10140. i,
  10141. l,
  10142. le,
  10143. outNodes = [],
  10144. outEdges = [];
  10145. for (k in overNodes)
  10146. outNodes.push(overNodes[k]);
  10147. overNodes = {};
  10148. // Dispatch both single and multi events:
  10149. for (i = 0, l = outNodes.length; i < l; i++)
  10150. self.dispatchEvent('outNode', {
  10151. node: outNodes[i],
  10152. captor: e.data
  10153. });
  10154. if (outNodes.length)
  10155. self.dispatchEvent('outNodes', {
  10156. nodes: outNodes,
  10157. captor: e.data
  10158. });
  10159. overEdges = {};
  10160. // Dispatch both single and multi events:
  10161. for (i = 0, le = outEdges.length; i < le; i++)
  10162. self.dispatchEvent('outEdge', {
  10163. edge: outEdges[i],
  10164. captor: e.data
  10165. });
  10166. if (outEdges.length)
  10167. self.dispatchEvent('outEdges', {
  10168. edges: outEdges,
  10169. captor: e.data
  10170. });
  10171. }
  10172. function onMove(e) {
  10173. if (!self.settings('eventsEnabled'))
  10174. return;
  10175. nodes = getNodes(e);
  10176. edges = getEdges(e);
  10177. var i,
  10178. k,
  10179. node,
  10180. edge,
  10181. newOutNodes = [],
  10182. newOverNodes = [],
  10183. currentOverNodes = {},
  10184. l = nodes.length,
  10185. newOutEdges = [],
  10186. newOverEdges = [],
  10187. currentOverEdges = {},
  10188. le = edges.length;
  10189. // Check newly overred nodes:
  10190. for (i = 0; i < l; i++) {
  10191. node = nodes[i];
  10192. currentOverNodes[node.id] = node;
  10193. if (!overNodes[node.id]) {
  10194. newOverNodes.push(node);
  10195. overNodes[node.id] = node;
  10196. }
  10197. }
  10198. // Check no more overred nodes:
  10199. for (k in overNodes)
  10200. if (!currentOverNodes[k]) {
  10201. newOutNodes.push(overNodes[k]);
  10202. delete overNodes[k];
  10203. }
  10204. // Dispatch both single and multi events:
  10205. for (i = 0, l = newOverNodes.length; i < l; i++)
  10206. self.dispatchEvent('overNode', {
  10207. node: newOverNodes[i],
  10208. captor: e.data
  10209. });
  10210. for (i = 0, l = newOutNodes.length; i < l; i++)
  10211. self.dispatchEvent('outNode', {
  10212. node: newOutNodes[i],
  10213. captor: e.data
  10214. });
  10215. if (newOverNodes.length)
  10216. self.dispatchEvent('overNodes', {
  10217. nodes: newOverNodes,
  10218. captor: e.data
  10219. });
  10220. if (newOutNodes.length)
  10221. self.dispatchEvent('outNodes', {
  10222. nodes: newOutNodes,
  10223. captor: e.data
  10224. });
  10225. // Check newly overred edges:
  10226. for (i = 0; i < le; i++) {
  10227. edge = edges[i];
  10228. currentOverEdges[edge.id] = edge;
  10229. if (!overEdges[edge.id]) {
  10230. newOverEdges.push(edge);
  10231. overEdges[edge.id] = edge;
  10232. }
  10233. }
  10234. // Check no more overred edges:
  10235. for (k in overEdges)
  10236. if (!currentOverEdges[k]) {
  10237. newOutEdges.push(overEdges[k]);
  10238. delete overEdges[k];
  10239. }
  10240. // Dispatch both single and multi events:
  10241. for (i = 0, le = newOverEdges.length; i < le; i++)
  10242. self.dispatchEvent('overEdge', {
  10243. edge: newOverEdges[i],
  10244. captor: e.data
  10245. });
  10246. for (i = 0, le = newOutEdges.length; i < le; i++)
  10247. self.dispatchEvent('outEdge', {
  10248. edge: newOutEdges[i],
  10249. captor: e.data
  10250. });
  10251. if (newOverEdges.length)
  10252. self.dispatchEvent('overEdges', {
  10253. edges: newOverEdges,
  10254. captor: e.data
  10255. });
  10256. if (newOutEdges.length)
  10257. self.dispatchEvent('outEdges', {
  10258. edges: newOutEdges,
  10259. captor: e.data
  10260. });
  10261. }
  10262. // Bind events:
  10263. captor.bind('click', onClick);
  10264. captor.bind('mousedown', onMove);
  10265. captor.bind('mouseup', onMove);
  10266. captor.bind('mousemove', onMove);
  10267. captor.bind('mouseout', onOut);
  10268. captor.bind('doubleclick', onDoubleClick);
  10269. captor.bind('rightclick', onRightClick);
  10270. self.bind('render', onMove);
  10271. }
  10272. for (i = 0, l = this.captors.length; i < l; i++)
  10273. bindCaptor(this.captors[i]);
  10274. };
  10275. }).call(this);
  10276. ;(function(undefined) {
  10277. 'use strict';
  10278. if (typeof sigma === 'undefined')
  10279. throw 'sigma is not declared';
  10280. // Initialize packages:
  10281. sigma.utils.pkg('sigma.misc');
  10282. /**
  10283. * This helper will bind any DOM renderer (for instance svg)
  10284. * to its captors, to properly dispatch the good events to the sigma instance
  10285. * to manage clicking, hovering etc...
  10286. *
  10287. * It has to be called in the scope of the related renderer.
  10288. */
  10289. sigma.misc.bindDOMEvents = function(container) {
  10290. var self = this,
  10291. graph = this.graph;
  10292. // DOMElement abstraction
  10293. function Element(domElement) {
  10294. // Helpers
  10295. this.attr = function(attrName) {
  10296. return domElement.getAttributeNS(null, attrName);
  10297. };
  10298. // Properties
  10299. this.tag = domElement.tagName;
  10300. this.class = this.attr('class');
  10301. this.id = this.attr('id');
  10302. // Methods
  10303. this.isNode = function() {
  10304. return !!~this.class.indexOf(self.settings('classPrefix') + '-node');
  10305. };
  10306. this.isEdge = function() {
  10307. return !!~this.class.indexOf(self.settings('classPrefix') + '-edge');
  10308. };
  10309. this.isHover = function() {
  10310. return !!~this.class.indexOf(self.settings('classPrefix') + '-hover');
  10311. };
  10312. }
  10313. // Click
  10314. function click(e) {
  10315. if (!self.settings('eventsEnabled'))
  10316. return;
  10317. // Generic event
  10318. self.dispatchEvent('click', e);
  10319. // Are we on a node?
  10320. var element = new Element(e.target);
  10321. if (element.isNode())
  10322. self.dispatchEvent('clickNode', {
  10323. node: graph.nodes(element.attr('data-node-id'))
  10324. });
  10325. else
  10326. self.dispatchEvent('clickStage');
  10327. e.preventDefault();
  10328. e.stopPropagation();
  10329. }
  10330. // Double click
  10331. function doubleClick(e) {
  10332. if (!self.settings('eventsEnabled'))
  10333. return;
  10334. // Generic event
  10335. self.dispatchEvent('doubleClick', e);
  10336. // Are we on a node?
  10337. var element = new Element(e.target);
  10338. if (element.isNode())
  10339. self.dispatchEvent('doubleClickNode', {
  10340. node: graph.nodes(element.attr('data-node-id'))
  10341. });
  10342. else
  10343. self.dispatchEvent('doubleClickStage');
  10344. e.preventDefault();
  10345. e.stopPropagation();
  10346. }
  10347. // On over
  10348. function onOver(e) {
  10349. var target = e.toElement || e.target;
  10350. if (!self.settings('eventsEnabled') || !target)
  10351. return;
  10352. var el = new Element(target);
  10353. if (el.isNode()) {
  10354. self.dispatchEvent('overNode', {
  10355. node: graph.nodes(el.attr('data-node-id'))
  10356. });
  10357. }
  10358. else if (el.isEdge()) {
  10359. var edge = graph.edges(el.attr('data-edge-id'));
  10360. self.dispatchEvent('overEdge', {
  10361. edge: edge,
  10362. source: graph.nodes(edge.source),
  10363. target: graph.nodes(edge.target)
  10364. });
  10365. }
  10366. }
  10367. // On out
  10368. function onOut(e) {
  10369. var target = e.fromElement || e.originalTarget;
  10370. if (!self.settings('eventsEnabled'))
  10371. return;
  10372. var el = new Element(target);
  10373. if (el.isNode()) {
  10374. self.dispatchEvent('outNode', {
  10375. node: graph.nodes(el.attr('data-node-id'))
  10376. });
  10377. }
  10378. else if (el.isEdge()) {
  10379. var edge = graph.edges(el.attr('data-edge-id'));
  10380. self.dispatchEvent('outEdge', {
  10381. edge: edge,
  10382. source: graph.nodes(edge.source),
  10383. target: graph.nodes(edge.target)
  10384. });
  10385. }
  10386. }
  10387. // Registering Events:
  10388. // Click
  10389. container.addEventListener('click', click, false);
  10390. sigma.utils.doubleClick(container, 'click', doubleClick);
  10391. // Touch counterparts
  10392. container.addEventListener('touchstart', click, false);
  10393. sigma.utils.doubleClick(container, 'touchstart', doubleClick);
  10394. // Mouseover
  10395. container.addEventListener('mouseover', onOver, true);
  10396. // Mouseout
  10397. container.addEventListener('mouseout', onOut, true);
  10398. };
  10399. }).call(this);
  10400. ;(function(undefined) {
  10401. 'use strict';
  10402. if (typeof sigma === 'undefined')
  10403. throw 'sigma is not declared';
  10404. // Initialize packages:
  10405. sigma.utils.pkg('sigma.misc');
  10406. /**
  10407. * This method listens to "overNode", "outNode", "overEdge" and "outEdge"
  10408. * events from a renderer and renders the nodes differently on the top layer.
  10409. * The goal is to make any node label readable with the mouse, and to
  10410. * highlight hovered nodes and edges.
  10411. *
  10412. * It has to be called in the scope of the related renderer.
  10413. */
  10414. sigma.misc.drawHovers = function(prefix) {
  10415. var self = this,
  10416. hoveredNodes = {},
  10417. hoveredEdges = {};
  10418. this.bind('overNode', function(event) {
  10419. var node = event.data.node;
  10420. if (!node.hidden) {
  10421. hoveredNodes[node.id] = node;
  10422. draw();
  10423. }
  10424. });
  10425. this.bind('outNode', function(event) {
  10426. delete hoveredNodes[event.data.node.id];
  10427. draw();
  10428. });
  10429. this.bind('overEdge', function(event) {
  10430. var edge = event.data.edge;
  10431. if (!edge.hidden) {
  10432. hoveredEdges[edge.id] = edge;
  10433. draw();
  10434. }
  10435. });
  10436. this.bind('outEdge', function(event) {
  10437. delete hoveredEdges[event.data.edge.id];
  10438. draw();
  10439. });
  10440. this.bind('render', function(event) {
  10441. draw();
  10442. });
  10443. function draw() {
  10444. // Clear self.contexts.hover:
  10445. self.contexts.hover.canvas.width = self.contexts.hover.canvas.width;
  10446. var k,
  10447. source,
  10448. target,
  10449. hoveredNode,
  10450. hoveredEdge,
  10451. defaultNodeType = self.settings('defaultNodeType'),
  10452. defaultEdgeType = self.settings('defaultEdgeType'),
  10453. nodeRenderers = sigma.canvas.hovers,
  10454. edgeRenderers = sigma.canvas.edgehovers,
  10455. extremitiesRenderers = sigma.canvas.extremities,
  10456. embedSettings = self.settings.embedObjects({
  10457. prefix: prefix
  10458. });
  10459. // Node render: single hover
  10460. if (
  10461. embedSettings('enableHovering') &&
  10462. embedSettings('singleHover') &&
  10463. Object.keys(hoveredNodes).length
  10464. ) {
  10465. hoveredNode = hoveredNodes[Object.keys(hoveredNodes)[0]];
  10466. (
  10467. nodeRenderers[hoveredNode.type] ||
  10468. nodeRenderers[defaultNodeType] ||
  10469. nodeRenderers.def
  10470. )(
  10471. hoveredNode,
  10472. self.contexts.hover,
  10473. embedSettings
  10474. );
  10475. }
  10476. // Node render: multiple hover
  10477. if (
  10478. embedSettings('enableHovering') &&
  10479. !embedSettings('singleHover')
  10480. )
  10481. for (k in hoveredNodes)
  10482. (
  10483. nodeRenderers[hoveredNodes[k].type] ||
  10484. nodeRenderers[defaultNodeType] ||
  10485. nodeRenderers.def
  10486. )(
  10487. hoveredNodes[k],
  10488. self.contexts.hover,
  10489. embedSettings
  10490. );
  10491. // Edge render: single hover
  10492. if (
  10493. embedSettings('enableEdgeHovering') &&
  10494. embedSettings('singleHover') &&
  10495. Object.keys(hoveredEdges).length
  10496. ) {
  10497. hoveredEdge = hoveredEdges[Object.keys(hoveredEdges)[0]];
  10498. source = self.graph.nodes(hoveredEdge.source);
  10499. target = self.graph.nodes(hoveredEdge.target);
  10500. if (! hoveredEdge.hidden) {
  10501. (
  10502. edgeRenderers[hoveredEdge.type] ||
  10503. edgeRenderers[defaultEdgeType] ||
  10504. edgeRenderers.def
  10505. ) (
  10506. hoveredEdge,
  10507. source,
  10508. target,
  10509. self.contexts.hover,
  10510. embedSettings
  10511. );
  10512. if (embedSettings('edgeHoverExtremities')) {
  10513. (
  10514. extremitiesRenderers[hoveredEdge.type] ||
  10515. extremitiesRenderers.def
  10516. )(
  10517. hoveredEdge,
  10518. source,
  10519. target,
  10520. self.contexts.hover,
  10521. embedSettings
  10522. );
  10523. } else {
  10524. // Avoid edges rendered over nodes:
  10525. (
  10526. sigma.canvas.nodes[source.type] ||
  10527. sigma.canvas.nodes.def
  10528. ) (
  10529. source,
  10530. self.contexts.hover,
  10531. embedSettings
  10532. );
  10533. (
  10534. sigma.canvas.nodes[target.type] ||
  10535. sigma.canvas.nodes.def
  10536. ) (
  10537. target,
  10538. self.contexts.hover,
  10539. embedSettings
  10540. );
  10541. }
  10542. }
  10543. }
  10544. // Edge render: multiple hover
  10545. if (
  10546. embedSettings('enableEdgeHovering') &&
  10547. !embedSettings('singleHover')
  10548. ) {
  10549. for (k in hoveredEdges) {
  10550. hoveredEdge = hoveredEdges[k];
  10551. source = self.graph.nodes(hoveredEdge.source);
  10552. target = self.graph.nodes(hoveredEdge.target);
  10553. if (!hoveredEdge.hidden) {
  10554. (
  10555. edgeRenderers[hoveredEdge.type] ||
  10556. edgeRenderers[defaultEdgeType] ||
  10557. edgeRenderers.def
  10558. ) (
  10559. hoveredEdge,
  10560. source,
  10561. target,
  10562. self.contexts.hover,
  10563. embedSettings
  10564. );
  10565. if (embedSettings('edgeHoverExtremities')) {
  10566. (
  10567. extremitiesRenderers[hoveredEdge.type] ||
  10568. extremitiesRenderers.def
  10569. )(
  10570. hoveredEdge,
  10571. source,
  10572. target,
  10573. self.contexts.hover,
  10574. embedSettings
  10575. );
  10576. } else {
  10577. // Avoid edges rendered over nodes:
  10578. (
  10579. sigma.canvas.nodes[source.type] ||
  10580. sigma.canvas.nodes.def
  10581. ) (
  10582. source,
  10583. self.contexts.hover,
  10584. embedSettings
  10585. );
  10586. (
  10587. sigma.canvas.nodes[target.type] ||
  10588. sigma.canvas.nodes.def
  10589. ) (
  10590. target,
  10591. self.contexts.hover,
  10592. embedSettings
  10593. );
  10594. }
  10595. }
  10596. }
  10597. }
  10598. }
  10599. };
  10600. }).call(this);