| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399144001440114402144031440414405144061440714408144091441014411144121441314414144151441614417144181441914420144211442214423144241442514426144271442814429144301443114432144331443414435144361443714438144391444014441144421444314444144451444614447144481444914450144511445214453144541445514456144571445814459144601446114462144631446414465144661446714468144691447014471144721447314474144751447614477144781447914480144811448214483144841448514486144871448814489144901449114492144931449414495144961449714498144991450014501145021450314504145051450614507145081450914510145111451214513145141451514516145171451814519145201452114522145231452414525145261452714528145291453014531145321453314534145351453614537145381453914540145411454214543145441454514546145471454814549145501455114552145531455414555145561455714558145591456014561145621456314564145651456614567145681456914570145711457214573145741457514576145771457814579145801458114582145831458414585145861458714588145891459014591145921459314594145951459614597145981459914600146011460214603146041460514606146071460814609146101461114612146131461414615146161461714618146191462014621146221462314624146251462614627146281462914630146311463214633146341463514636146371463814639146401464114642146431464414645146461464714648146491465014651146521465314654146551465614657146581465914660146611466214663146641466514666146671466814669146701467114672146731467414675146761467714678146791468014681146821468314684146851468614687146881468914690146911469214693146941469514696146971469814699147001470114702147031470414705147061470714708147091471014711147121471314714147151471614717147181471914720147211472214723147241472514726147271472814729147301473114732147331473414735147361473714738147391474014741147421474314744147451474614747147481474914750147511475214753147541475514756147571475814759147601476114762147631476414765147661476714768147691477014771147721477314774147751477614777147781477914780147811478214783147841478514786147871478814789147901479114792147931479414795147961479714798147991480014801148021480314804148051480614807148081480914810148111481214813148141481514816148171481814819148201482114822148231482414825148261482714828148291483014831148321483314834148351483614837148381483914840148411484214843148441484514846148471484814849148501485114852148531485414855148561485714858148591486014861148621486314864148651486614867148681486914870148711487214873148741487514876148771487814879148801488114882148831488414885148861488714888148891489014891148921489314894148951489614897148981489914900149011490214903149041490514906149071490814909149101491114912149131491414915149161491714918149191492014921149221492314924149251492614927149281492914930149311493214933149341493514936149371493814939149401494114942149431494414945149461494714948149491495014951149521495314954149551495614957149581495914960149611496214963149641496514966149671496814969149701497114972149731497414975149761497714978149791498014981149821498314984149851498614987149881498914990149911499214993149941499514996149971499814999150001500115002150031500415005150061500715008150091501015011150121501315014150151501615017150181501915020150211502215023150241502515026150271502815029150301503115032150331503415035150361503715038150391504015041150421504315044150451504615047150481504915050150511505215053150541505515056150571505815059150601506115062150631506415065150661506715068150691507015071150721507315074150751507615077150781507915080150811508215083150841508515086150871508815089150901509115092150931509415095150961509715098150991510015101151021510315104151051510615107151081510915110151111511215113151141511515116151171511815119151201512115122151231512415125151261512715128151291513015131151321513315134151351513615137151381513915140151411514215143151441514515146151471514815149151501515115152151531515415155151561515715158151591516015161151621516315164151651516615167151681516915170151711517215173151741517515176151771517815179151801518115182151831518415185151861518715188151891519015191151921519315194151951519615197151981519915200152011520215203152041520515206152071520815209152101521115212152131521415215152161521715218152191522015221152221522315224152251522615227152281522915230152311523215233152341523515236152371523815239152401524115242152431524415245152461524715248152491525015251152521525315254152551525615257152581525915260152611526215263152641526515266152671526815269152701527115272152731527415275152761527715278152791528015281152821528315284152851528615287152881528915290152911529215293152941529515296152971529815299153001530115302153031530415305153061530715308153091531015311153121531315314153151531615317153181531915320153211532215323153241532515326153271532815329153301533115332153331533415335153361533715338153391534015341153421534315344153451534615347153481534915350153511535215353153541535515356153571535815359153601536115362153631536415365153661536715368153691537015371153721537315374153751537615377153781537915380153811538215383153841538515386153871538815389153901539115392153931539415395153961539715398153991540015401154021540315404154051540615407154081540915410154111541215413154141541515416154171541815419154201542115422154231542415425 |
- /* nvd3 version 1.8.5 (https://github.com/novus/nvd3) 2016-12-01 */
- (function(){
-
- // set up main nv object
- var nv = {};
-
- // the major global objects under the nv namespace
- nv.dev = false; //set false when in production
- nv.tooltip = nv.tooltip || {}; // For the tooltip system
- nv.utils = nv.utils || {}; // Utility subsystem
- nv.models = nv.models || {}; //stores all the possible models/components
- nv.charts = {}; //stores all the ready to use charts
- nv.logs = {}; //stores some statistics and potential error messages
- nv.dom = {}; //DOM manipulation functions
-
- // Node/CommonJS - require D3
- if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined' && typeof(d3) == 'undefined') {
- d3 = require('d3');
- }
-
- nv.dispatch = d3.dispatch('render_start', 'render_end');
-
- // Function bind polyfill
- // Needed ONLY for phantomJS as it's missing until version 2.0 which is unreleased as of this comment
- // https://github.com/ariya/phantomjs/issues/10522
- // http://kangax.github.io/compat-table/es5/#Function.prototype.bind
- // phantomJS is used for running the test suite
- if (!Function.prototype.bind) {
- Function.prototype.bind = function (oThis) {
- if (typeof this !== "function") {
- // closest thing possible to the ECMAScript 5 internal IsCallable function
- throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
- }
-
- var aArgs = Array.prototype.slice.call(arguments, 1),
- fToBind = this,
- fNOP = function () {},
- fBound = function () {
- return fToBind.apply(this instanceof fNOP && oThis
- ? this
- : oThis,
- aArgs.concat(Array.prototype.slice.call(arguments)));
- };
-
- fNOP.prototype = this.prototype;
- fBound.prototype = new fNOP();
- return fBound;
- };
- }
-
- // Development render timers - disabled if dev = false
- if (nv.dev) {
- nv.dispatch.on('render_start', function(e) {
- nv.logs.startTime = +new Date();
- });
-
- nv.dispatch.on('render_end', function(e) {
- nv.logs.endTime = +new Date();
- nv.logs.totalTime = nv.logs.endTime - nv.logs.startTime;
- nv.log('total', nv.logs.totalTime); // used for development, to keep track of graph generation times
- });
- }
-
- // Logs all arguments, and returns the last so you can test things in place
- // Note: in IE8 console.log is an object not a function, and if modernizr is used
- // then calling Function.prototype.bind with with anything other than a function
- // causes a TypeError to be thrown.
- nv.log = function() {
- if (nv.dev && window.console && console.log && console.log.apply)
- console.log.apply(console, arguments);
- else if (nv.dev && window.console && typeof console.log == "function" && Function.prototype.bind) {
- var log = Function.prototype.bind.call(console.log, console);
- log.apply(console, arguments);
- }
- return arguments[arguments.length - 1];
- };
-
- // print console warning, should be used by deprecated functions
- nv.deprecated = function(name, info) {
- if (console && console.warn) {
- console.warn('nvd3 warning: `' + name + '` has been deprecated. ', info || '');
- }
- };
-
- // The nv.render function is used to queue up chart rendering
- // in non-blocking async functions.
- // When all queued charts are done rendering, nv.dispatch.render_end is invoked.
- nv.render = function render(step) {
- // number of graphs to generate in each timeout loop
- step = step || 1;
-
- nv.render.active = true;
- nv.dispatch.render_start();
-
- var renderLoop = function() {
- var chart, graph;
-
- for (var i = 0; i < step && (graph = nv.render.queue[i]); i++) {
- chart = graph.generate();
- if (typeof graph.callback == typeof(Function)) graph.callback(chart);
- }
-
- nv.render.queue.splice(0, i);
-
- if (nv.render.queue.length) {
- setTimeout(renderLoop);
- }
- else {
- nv.dispatch.render_end();
- nv.render.active = false;
- }
- };
-
- setTimeout(renderLoop);
- };
-
- nv.render.active = false;
- nv.render.queue = [];
-
- /*
- Adds a chart to the async rendering queue. This method can take arguments in two forms:
- nv.addGraph({
- generate: <Function>
- callback: <Function>
- })
-
- or
-
- nv.addGraph(<generate Function>, <callback Function>)
-
- The generate function should contain code that creates the NVD3 model, sets options
- on it, adds data to an SVG element, and invokes the chart model. The generate function
- should return the chart model. See examples/lineChart.html for a usage example.
-
- The callback function is optional, and it is called when the generate function completes.
- */
- nv.addGraph = function(obj) {
- if (typeof arguments[0] === typeof(Function)) {
- obj = {generate: arguments[0], callback: arguments[1]};
- }
-
- nv.render.queue.push(obj);
-
- if (!nv.render.active) {
- nv.render();
- }
- };
-
- // Node/CommonJS exports
- if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined') {
- module.exports = nv;
- }
-
- if (typeof(window) !== 'undefined') {
- window.nv = nv;
- }
- /* Facade for queueing DOM write operations
- * with Fastdom (https://github.com/wilsonpage/fastdom)
- * if available.
- * This could easily be extended to support alternate
- * implementations in the future.
- */
- nv.dom.write = function(callback) {
- if (window.fastdom !== undefined) {
- return fastdom.mutate(callback);
- }
- return callback();
- };
-
- /* Facade for queueing DOM read operations
- * with Fastdom (https://github.com/wilsonpage/fastdom)
- * if available.
- * This could easily be extended to support alternate
- * implementations in the future.
- */
- nv.dom.read = function(callback) {
- if (window.fastdom !== undefined) {
- return fastdom.measure(callback);
- }
- return callback();
- };
- /* Utility class to handle creation of an interactive layer.
- This places a rectangle on top of the chart. When you mouse move over it, it sends a dispatch
- containing the X-coordinate. It can also render a vertical line where the mouse is located.
-
- dispatch.elementMousemove is the important event to latch onto. It is fired whenever the mouse moves over
- the rectangle. The dispatch is given one object which contains the mouseX/Y location.
- It also has 'pointXValue', which is the conversion of mouseX to the x-axis scale.
- */
- nv.interactiveGuideline = function() {
- "use strict";
-
- var margin = { left: 0, top: 0 } //Pass the chart's top and left magins. Used to calculate the mouseX/Y.
- , width = null
- , height = null
- , xScale = d3.scale.linear()
- , dispatch = d3.dispatch('elementMousemove', 'elementMouseout', 'elementClick', 'elementDblclick', 'elementMouseDown', 'elementMouseUp')
- , showGuideLine = true
- , svgContainer = null // Must pass the chart's svg, we'll use its mousemove event.
- , tooltip = nv.models.tooltip()
- , isMSIE = window.ActiveXObject// Checkt if IE by looking for activeX. (excludes IE11)
- ;
-
- tooltip
- .duration(0)
- .hideDelay(0)
- .hidden(false);
-
- function layer(selection) {
- selection.each(function(data) {
- var container = d3.select(this);
- var availableWidth = (width || 960), availableHeight = (height || 400);
- var wrap = container.selectAll("g.nv-wrap.nv-interactiveLineLayer")
- .data([data]);
- var wrapEnter = wrap.enter()
- .append("g").attr("class", " nv-wrap nv-interactiveLineLayer");
- wrapEnter.append("g").attr("class","nv-interactiveGuideLine");
-
- if (!svgContainer) {
- return;
- }
-
- function mouseHandler() {
- var d3mouse = d3.mouse(this);
- var mouseX = d3mouse[0];
- var mouseY = d3mouse[1];
- var subtractMargin = true;
- var mouseOutAnyReason = false;
- if (isMSIE) {
- /*
- D3.js (or maybe SVG.getScreenCTM) has a nasty bug in Internet Explorer 10.
- d3.mouse() returns incorrect X,Y mouse coordinates when mouse moving
- over a rect in IE 10.
- However, d3.event.offsetX/Y also returns the mouse coordinates
- relative to the triggering <rect>. So we use offsetX/Y on IE.
- */
- mouseX = d3.event.offsetX;
- mouseY = d3.event.offsetY;
-
- /*
- On IE, if you attach a mouse event listener to the <svg> container,
- it will actually trigger it for all the child elements (like <path>, <circle>, etc).
- When this happens on IE, the offsetX/Y is set to where ever the child element
- is located.
- As a result, we do NOT need to subtract margins to figure out the mouse X/Y
- position under this scenario. Removing the line below *will* cause
- the interactive layer to not work right on IE.
- */
- if(d3.event.target.tagName !== "svg") {
- subtractMargin = false;
- }
-
- if (d3.event.target.className.baseVal.match("nv-legend")) {
- mouseOutAnyReason = true;
- }
-
- }
-
- if(subtractMargin) {
- mouseX -= margin.left;
- mouseY -= margin.top;
- }
-
- /* If mouseX/Y is outside of the chart's bounds,
- trigger a mouseOut event.
- */
- if (d3.event.type === 'mouseout'
- || mouseX < 0 || mouseY < 0
- || mouseX > availableWidth || mouseY > availableHeight
- || (d3.event.relatedTarget && d3.event.relatedTarget.ownerSVGElement === undefined)
- || mouseOutAnyReason
- ) {
-
- if (isMSIE) {
- if (d3.event.relatedTarget
- && d3.event.relatedTarget.ownerSVGElement === undefined
- && (d3.event.relatedTarget.className === undefined
- || d3.event.relatedTarget.className.match(tooltip.nvPointerEventsClass))) {
-
- return;
- }
- }
- dispatch.elementMouseout({
- mouseX: mouseX,
- mouseY: mouseY
- });
- layer.renderGuideLine(null); //hide the guideline
- tooltip.hidden(true);
- return;
- } else {
- tooltip.hidden(false);
- }
-
-
- var scaleIsOrdinal = typeof xScale.rangeBands === 'function';
- var pointXValue = undefined;
-
- // Ordinal scale has no invert method
- if (scaleIsOrdinal) {
- var elementIndex = d3.bisect(xScale.range(), mouseX) - 1;
- // Check if mouseX is in the range band
- if (xScale.range()[elementIndex] + xScale.rangeBand() >= mouseX) {
- pointXValue = xScale.domain()[d3.bisect(xScale.range(), mouseX) - 1];
- }
- else {
- dispatch.elementMouseout({
- mouseX: mouseX,
- mouseY: mouseY
- });
- layer.renderGuideLine(null); //hide the guideline
- tooltip.hidden(true);
- return;
- }
- }
- else {
- pointXValue = xScale.invert(mouseX);
- }
-
- dispatch.elementMousemove({
- mouseX: mouseX,
- mouseY: mouseY,
- pointXValue: pointXValue
- });
-
- //If user double clicks the layer, fire a elementDblclick
- if (d3.event.type === "dblclick") {
- dispatch.elementDblclick({
- mouseX: mouseX,
- mouseY: mouseY,
- pointXValue: pointXValue
- });
- }
-
- // if user single clicks the layer, fire elementClick
- if (d3.event.type === 'click') {
- dispatch.elementClick({
- mouseX: mouseX,
- mouseY: mouseY,
- pointXValue: pointXValue
- });
- }
-
- // if user presses mouse down the layer, fire elementMouseDown
- if (d3.event.type === 'mousedown') {
- dispatch.elementMouseDown({
- mouseX: mouseX,
- mouseY: mouseY,
- pointXValue: pointXValue
- });
- }
-
- // if user presses mouse down the layer, fire elementMouseUp
- if (d3.event.type === 'mouseup') {
- dispatch.elementMouseUp({
- mouseX: mouseX,
- mouseY: mouseY,
- pointXValue: pointXValue
- });
- }
- }
-
- svgContainer
- .on("touchmove",mouseHandler)
- .on("mousemove",mouseHandler, true)
- .on("mouseout" ,mouseHandler,true)
- .on("mousedown" ,mouseHandler,true)
- .on("mouseup" ,mouseHandler,true)
- .on("dblclick" ,mouseHandler)
- .on("click", mouseHandler)
- ;
-
- layer.guideLine = null;
- //Draws a vertical guideline at the given X postion.
- layer.renderGuideLine = function(x) {
- if (!showGuideLine) return;
- if (layer.guideLine && layer.guideLine.attr("x1") === x) return;
- nv.dom.write(function() {
- var line = wrap.select(".nv-interactiveGuideLine")
- .selectAll("line")
- .data((x != null) ? [nv.utils.NaNtoZero(x)] : [], String);
- line.enter()
- .append("line")
- .attr("class", "nv-guideline")
- .attr("x1", function(d) { return d;})
- .attr("x2", function(d) { return d;})
- .attr("y1", availableHeight)
- .attr("y2",0);
- line.exit().remove();
- });
- }
- });
- }
-
- layer.dispatch = dispatch;
- layer.tooltip = tooltip;
-
- layer.margin = function(_) {
- if (!arguments.length) return margin;
- margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
- margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
- return layer;
- };
-
- layer.width = function(_) {
- if (!arguments.length) return width;
- width = _;
- return layer;
- };
-
- layer.height = function(_) {
- if (!arguments.length) return height;
- height = _;
- return layer;
- };
-
- layer.xScale = function(_) {
- if (!arguments.length) return xScale;
- xScale = _;
- return layer;
- };
-
- layer.showGuideLine = function(_) {
- if (!arguments.length) return showGuideLine;
- showGuideLine = _;
- return layer;
- };
-
- layer.svgContainer = function(_) {
- if (!arguments.length) return svgContainer;
- svgContainer = _;
- return layer;
- };
-
- return layer;
- };
-
- /* Utility class that uses d3.bisect to find the index in a given array, where a search value can be inserted.
- This is different from normal bisectLeft; this function finds the nearest index to insert the search value.
-
- For instance, lets say your array is [1,2,3,5,10,30], and you search for 28.
- Normal d3.bisectLeft will return 4, because 28 is inserted after the number 10. But interactiveBisect will return 5
- because 28 is closer to 30 than 10.
-
- Unit tests can be found in: interactiveBisectTest.html
-
- Has the following known issues:
- * Will not work if the data points move backwards (ie, 10,9,8,7, etc) or if the data points are in random order.
- * Won't work if there are duplicate x coordinate values.
- */
- nv.interactiveBisect = function (values, searchVal, xAccessor) {
- "use strict";
- if (! (values instanceof Array)) {
- return null;
- }
- var _xAccessor;
- if (typeof xAccessor !== 'function') {
- _xAccessor = function(d) {
- return d.x;
- }
- } else {
- _xAccessor = xAccessor;
- }
- var _cmp = function(d, v) {
- // Accessors are no longer passed the index of the element along with
- // the element itself when invoked by d3.bisector.
- //
- // Starting at D3 v3.4.4, d3.bisector() started inspecting the
- // function passed to determine if it should consider it an accessor
- // or a comparator. This meant that accessors that take two arguments
- // (expecting an index as the second parameter) are treated as
- // comparators where the second argument is the search value against
- // which the first argument is compared.
- return _xAccessor(d) - v;
- };
-
- var bisect = d3.bisector(_cmp).left;
- var index = d3.max([0, bisect(values,searchVal) - 1]);
- var currentValue = _xAccessor(values[index]);
-
- if (typeof currentValue === 'undefined') {
- currentValue = index;
- }
-
- if (currentValue === searchVal) {
- return index; //found exact match
- }
-
- var nextIndex = d3.min([index+1, values.length - 1]);
- var nextValue = _xAccessor(values[nextIndex]);
-
- if (typeof nextValue === 'undefined') {
- nextValue = nextIndex;
- }
-
- if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal)) {
- return index;
- } else {
- return nextIndex
- }
- };
-
- /*
- Returns the index in the array "values" that is closest to searchVal.
- Only returns an index if searchVal is within some "threshold".
- Otherwise, returns null.
- */
- nv.nearestValueIndex = function (values, searchVal, threshold) {
- "use strict";
- var yDistMax = Infinity, indexToHighlight = null;
- values.forEach(function(d,i) {
- var delta = Math.abs(searchVal - d);
- if ( d != null && delta <= yDistMax && delta < threshold) {
- yDistMax = delta;
- indexToHighlight = i;
- }
- });
- return indexToHighlight;
- };
-
- /* Model which can be instantiated to handle tooltip rendering.
- Example usage:
- var tip = nv.models.tooltip().gravity('w').distance(23)
- .data(myDataObject);
-
- tip(); //just invoke the returned function to render tooltip.
- */
- nv.models.tooltip = function() {
- "use strict";
-
- /*
- Tooltip data. If data is given in the proper format, a consistent tooltip is generated.
- Example Format of data:
- {
- key: "Date",
- value: "August 2009",
- series: [
- {key: "Series 1", value: "Value 1", color: "#000"},
- {key: "Series 2", value: "Value 2", color: "#00f"}
- ]
- }
- */
- var id = "nvtooltip-" + Math.floor(Math.random() * 100000) // Generates a unique id when you create a new tooltip() object.
- , data = null
- , gravity = 'w' // Can be 'n','s','e','w'. Determines how tooltip is positioned.
- , distance = 25 // Distance to offset tooltip from the mouse location.
- , snapDistance = 0 // Tolerance allowed before tooltip is moved from its current position (creates 'snapping' effect)
- , classes = null // Attaches additional CSS classes to the tooltip DIV that is created.
- , hidden = true // Start off hidden, toggle with hide/show functions below.
- , hideDelay = 200 // Delay (in ms) before the tooltip hides after calling hide().
- , tooltip = null // d3 select of the tooltip div.
- , lastPosition = { left: null, top: null } // Last position the tooltip was in.
- , enabled = true // True -> tooltips are rendered. False -> don't render tooltips.
- , duration = 100 // Tooltip movement duration, in ms.
- , headerEnabled = true // If is to show the tooltip header.
- , nvPointerEventsClass = "nv-pointer-events-none" // CSS class to specify whether element should not have mouse events.
- ;
-
- // Format function for the tooltip values column.
- var valueFormatter = function(d, i) {
- return d;
- };
-
- // Format function for the tooltip header value.
- var headerFormatter = function(d) {
- return d;
- };
-
- var keyFormatter = function(d, i) {
- return d;
- };
-
- // By default, the tooltip model renders a beautiful table inside a DIV.
- // You can override this function if a custom tooltip is desired.
- var contentGenerator = function(d) {
- if (d === null) {
- return '';
- }
-
- var table = d3.select(document.createElement("table"));
- if (headerEnabled) {
- var theadEnter = table.selectAll("thead")
- .data([d])
- .enter().append("thead");
-
- theadEnter.append("tr")
- .append("td")
- .attr("colspan", 3)
- .append("strong")
- .classed("x-value", true)
- .html(headerFormatter(d.value));
- }
-
- var tbodyEnter = table.selectAll("tbody")
- .data([d])
- .enter().append("tbody");
-
- var trowEnter = tbodyEnter.selectAll("tr")
- .data(function(p) { return p.series})
- .enter()
- .append("tr")
- .classed("highlight", function(p) { return p.highlight});
-
- trowEnter.append("td")
- .classed("legend-color-guide",true)
- .append("div")
- .style("background-color", function(p) { return p.color});
-
- trowEnter.append("td")
- .classed("key",true)
- .classed("total",function(p) { return !!p.total})
- .html(function(p, i) { return keyFormatter(p.key, i)});
-
- trowEnter.append("td")
- .classed("value",true)
- .html(function(p, i) { return valueFormatter(p.value, i) });
-
- trowEnter.filter(function (p,i) { return p.percent !== undefined }).append("td")
- .classed("percent", true)
- .html(function(p, i) { return "(" + d3.format('%')(p.percent) + ")" });
-
- trowEnter.selectAll("td").each(function(p) {
- if (p.highlight) {
- var opacityScale = d3.scale.linear().domain([0,1]).range(["#fff",p.color]);
- var opacity = 0.6;
- d3.select(this)
- .style("border-bottom-color", opacityScale(opacity))
- .style("border-top-color", opacityScale(opacity))
- ;
- }
- });
-
- var html = table.node().outerHTML;
- if (d.footer !== undefined)
- html += "<div class='footer'>" + d.footer + "</div>";
- return html;
-
- };
-
- /*
- Function that returns the position (relative to the viewport/document.body)
- the tooltip should be placed in.
- Should return: {
- left: <leftPos>,
- top: <topPos>
- }
- */
- var position = function() {
- var pos = {
- left: d3.event !== null ? d3.event.clientX : 0,
- top: d3.event !== null ? d3.event.clientY : 0
- };
-
- if(getComputedStyle(document.body).transform != 'none') {
- // Take the offset into account, as now the tooltip is relative
- // to document.body.
- var client = document.body.getBoundingClientRect();
- pos.left -= client.left;
- pos.top -= client.top;
- }
-
- return pos;
- };
-
- var dataSeriesExists = function(d) {
- if (d && d.series) {
- if (nv.utils.isArray(d.series)) {
- return true;
- }
- // if object, it's okay just convert to array of the object
- if (nv.utils.isObject(d.series)) {
- d.series = [d.series];
- return true;
- }
- }
- return false;
- };
-
- // Calculates the gravity offset of the tooltip. Parameter is position of tooltip
- // relative to the viewport.
- var calcGravityOffset = function(pos) {
- var height = tooltip.node().offsetHeight,
- width = tooltip.node().offsetWidth,
- clientWidth = document.documentElement.clientWidth, // Don't want scrollbars.
- clientHeight = document.documentElement.clientHeight, // Don't want scrollbars.
- left, top, tmp;
-
- // calculate position based on gravity
- switch (gravity) {
- case 'e':
- left = - width - distance;
- top = - (height / 2);
- if(pos.left + left < 0) left = distance;
- if((tmp = pos.top + top) < 0) top -= tmp;
- if((tmp = pos.top + top + height) > clientHeight) top -= tmp - clientHeight;
- break;
- case 'w':
- left = distance;
- top = - (height / 2);
- if (pos.left + left + width > clientWidth) left = - width - distance;
- if ((tmp = pos.top + top) < 0) top -= tmp;
- if ((tmp = pos.top + top + height) > clientHeight) top -= tmp - clientHeight;
- break;
- case 'n':
- left = - (width / 2) - 5; // - 5 is an approximation of the mouse's height.
- top = distance;
- if (pos.top + top + height > clientHeight) top = - height - distance;
- if ((tmp = pos.left + left) < 0) left -= tmp;
- if ((tmp = pos.left + left + width) > clientWidth) left -= tmp - clientWidth;
- break;
- case 's':
- left = - (width / 2);
- top = - height - distance;
- if (pos.top + top < 0) top = distance;
- if ((tmp = pos.left + left) < 0) left -= tmp;
- if ((tmp = pos.left + left + width) > clientWidth) left -= tmp - clientWidth;
- break;
- case 'center':
- left = - (width / 2);
- top = - (height / 2);
- break;
- default:
- left = 0;
- top = 0;
- break;
- }
-
- return { 'left': left, 'top': top };
- };
-
- /*
- Positions the tooltip in the correct place, as given by the position() function.
- */
- var positionTooltip = function() {
- nv.dom.read(function() {
- var pos = position(),
- gravityOffset = calcGravityOffset(pos),
- left = pos.left + gravityOffset.left,
- top = pos.top + gravityOffset.top;
-
- // delay hiding a bit to avoid flickering
- if (hidden) {
- tooltip
- .interrupt()
- .transition()
- .delay(hideDelay)
- .duration(0)
- .style('opacity', 0);
- } else {
- // using tooltip.style('transform') returns values un-usable for tween
- var old_translate = 'translate(' + lastPosition.left + 'px, ' + lastPosition.top + 'px)';
- var new_translate = 'translate(' + Math.round(left) + 'px, ' + Math.round(top) + 'px)';
- var translateInterpolator = d3.interpolateString(old_translate, new_translate);
- var is_hidden = tooltip.style('opacity') < 0.1;
-
- tooltip
- .interrupt() // cancel running transitions
- .transition()
- .duration(is_hidden ? 0 : duration)
- // using tween since some versions of d3 can't auto-tween a translate on a div
- .styleTween('transform', function (d) {
- return translateInterpolator;
- }, 'important')
- // Safari has its own `-webkit-transform` and does not support `transform`
- .styleTween('-webkit-transform', function (d) {
- return translateInterpolator;
- })
- .style('-ms-transform', new_translate)
- .style('opacity', 1);
- }
-
- lastPosition.left = left;
- lastPosition.top = top;
- });
- };
-
- // Creates new tooltip container, or uses existing one on DOM.
- function initTooltip() {
- if (!tooltip || !tooltip.node()) {
- // Create new tooltip div if it doesn't exist on DOM.
-
- var data = [1];
- tooltip = d3.select(document.body).select('#'+id).data(data);
-
- tooltip.enter().append('div')
- .attr("class", "nvtooltip " + (classes ? classes : "xy-tooltip"))
- .attr("id", id)
- .style("top", 0).style("left", 0)
- .style('opacity', 0)
- .style('position', 'fixed')
- .selectAll("div, table, td, tr").classed(nvPointerEventsClass, true)
- .classed(nvPointerEventsClass, true);
-
- tooltip.exit().remove()
- }
- }
-
- // Draw the tooltip onto the DOM.
- function nvtooltip() {
- if (!enabled) return;
- if (!dataSeriesExists(data)) return;
-
- nv.dom.write(function () {
- initTooltip();
- // Generate data and set it into tooltip.
- // Bonus - If you override contentGenerator and return falsey you can use something like
- // React or Knockout to bind the data for your tooltip.
- var newContent = contentGenerator(data);
- if (newContent) {
- tooltip.node().innerHTML = newContent;
- }
-
- positionTooltip();
- });
-
- return nvtooltip;
- }
-
- nvtooltip.nvPointerEventsClass = nvPointerEventsClass;
- nvtooltip.options = nv.utils.optionsFunc.bind(nvtooltip);
-
- nvtooltip._options = Object.create({}, {
- // simple read/write options
- duration: {get: function(){return duration;}, set: function(_){duration=_;}},
- gravity: {get: function(){return gravity;}, set: function(_){gravity=_;}},
- distance: {get: function(){return distance;}, set: function(_){distance=_;}},
- snapDistance: {get: function(){return snapDistance;}, set: function(_){snapDistance=_;}},
- classes: {get: function(){return classes;}, set: function(_){classes=_;}},
- enabled: {get: function(){return enabled;}, set: function(_){enabled=_;}},
- hideDelay: {get: function(){return hideDelay;}, set: function(_){hideDelay=_;}},
- contentGenerator: {get: function(){return contentGenerator;}, set: function(_){contentGenerator=_;}},
- valueFormatter: {get: function(){return valueFormatter;}, set: function(_){valueFormatter=_;}},
- headerFormatter: {get: function(){return headerFormatter;}, set: function(_){headerFormatter=_;}},
- keyFormatter: {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}},
- headerEnabled: {get: function(){return headerEnabled;}, set: function(_){headerEnabled=_;}},
- position: {get: function(){return position;}, set: function(_){position=_;}},
-
- // Deprecated options
- chartContainer: {get: function(){return document.body;}, set: function(_){
- // deprecated after 1.8.3
- nv.deprecated('chartContainer', 'feature removed after 1.8.3');
- }},
- fixedTop: {get: function(){return null;}, set: function(_){
- // deprecated after 1.8.1
- nv.deprecated('fixedTop', 'feature removed after 1.8.1');
- }},
- offset: {get: function(){return {left: 0, top: 0};}, set: function(_){
- // deprecated after 1.8.1
- nv.deprecated('offset', 'use chart.tooltip.distance() instead');
- }},
-
- // options with extra logic
- hidden: {get: function(){return hidden;}, set: function(_){
- if (hidden != _) {
- hidden = !!_;
- nvtooltip();
- }
- }},
- data: {get: function(){return data;}, set: function(_){
- // if showing a single data point, adjust data format with that
- if (_.point) {
- _.value = _.point.x;
- _.series = _.series || {};
- _.series.value = _.point.y;
- _.series.color = _.point.color || _.series.color;
- }
- data = _;
- }},
-
- // read only properties
- node: {get: function(){return tooltip.node();}, set: function(_){}},
- id: {get: function(){return id;}, set: function(_){}}
- });
-
- nv.utils.initOptions(nvtooltip);
- return nvtooltip;
- };
-
-
- /*
- Gets the browser window size
-
- Returns object with height and width properties
- */
- nv.utils.windowSize = function() {
- // Sane defaults
- var size = {width: 640, height: 480};
-
- // Most recent browsers use
- if (window.innerWidth && window.innerHeight) {
- size.width = window.innerWidth;
- size.height = window.innerHeight;
- return (size);
- }
-
- // IE can use depending on mode it is in
- if (document.compatMode=='CSS1Compat' &&
- document.documentElement &&
- document.documentElement.offsetWidth ) {
-
- size.width = document.documentElement.offsetWidth;
- size.height = document.documentElement.offsetHeight;
- return (size);
- }
-
- // Earlier IE uses Doc.body
- if (document.body && document.body.offsetWidth) {
- size.width = document.body.offsetWidth;
- size.height = document.body.offsetHeight;
- return (size);
- }
-
- return (size);
- };
-
-
- /* handle dumb browser quirks... isinstance breaks if you use frames
- typeof returns 'object' for null, NaN is a number, etc.
- */
- nv.utils.isArray = Array.isArray;
- nv.utils.isObject = function(a) {
- return a !== null && typeof a === 'object';
- };
- nv.utils.isFunction = function(a) {
- return typeof a === 'function';
- };
- nv.utils.isDate = function(a) {
- return toString.call(a) === '[object Date]';
- };
- nv.utils.isNumber = function(a) {
- return !isNaN(a) && typeof a === 'number';
- };
-
-
- /*
- Binds callback function to run when window is resized
- */
- nv.utils.windowResize = function(handler) {
- if (window.addEventListener) {
- window.addEventListener('resize', handler);
- } else {
- nv.log("ERROR: Failed to bind to window.resize with: ", handler);
- }
- // return object with clear function to remove the single added callback.
- return {
- callback: handler,
- clear: function() {
- window.removeEventListener('resize', handler);
- }
- }
- };
-
-
- /*
- Backwards compatible way to implement more d3-like coloring of graphs.
- Can take in nothing, an array, or a function/scale
- To use a normal scale, get the range and pass that because we must be able
- to take two arguments and use the index to keep backward compatibility
- */
- nv.utils.getColor = function(color) {
- //if you pass in nothing, get default colors back
- if (color === undefined) {
- return nv.utils.defaultColor();
-
- //if passed an array, turn it into a color scale
- } else if(nv.utils.isArray(color)) {
- var color_scale = d3.scale.ordinal().range(color);
- return function(d, i) {
- var key = i === undefined ? d : i;
- return d.color || color_scale(key);
- };
-
- //if passed a function or scale, return it, or whatever it may be
- //external libs, such as angularjs-nvd3-directives use this
- } else {
- //can't really help it if someone passes rubbish as color
- return color;
- }
- };
-
-
- /*
- Default color chooser uses a color scale of 20 colors from D3
- https://github.com/mbostock/d3/wiki/Ordinal-Scales#categorical-colors
- */
- nv.utils.defaultColor = function() {
- // get range of the scale so we'll turn it into our own function.
- return nv.utils.getColor(d3.scale.category20().range());
- };
-
-
- /*
- Returns a color function that takes the result of 'getKey' for each series and
- looks for a corresponding color from the dictionary
- */
- nv.utils.customTheme = function(dictionary, getKey, defaultColors) {
- // use default series.key if getKey is undefined
- getKey = getKey || function(series) { return series.key };
- defaultColors = defaultColors || d3.scale.category20().range();
-
- // start at end of default color list and walk back to index 0
- var defIndex = defaultColors.length;
-
- return function(series, index) {
- var key = getKey(series);
- if (nv.utils.isFunction(dictionary[key])) {
- return dictionary[key]();
- } else if (dictionary[key] !== undefined) {
- return dictionary[key];
- } else {
- // no match in dictionary, use a default color
- if (!defIndex) {
- // used all the default colors, start over
- defIndex = defaultColors.length;
- }
- defIndex = defIndex - 1;
- return defaultColors[defIndex];
- }
- };
- };
-
-
- /*
- From the PJAX example on d3js.org, while this is not really directly needed
- it's a very cool method for doing pjax, I may expand upon it a little bit,
- open to suggestions on anything that may be useful
- */
- nv.utils.pjax = function(links, content) {
-
- var load = function(href) {
- d3.html(href, function(fragment) {
- var target = d3.select(content).node();
- target.parentNode.replaceChild(
- d3.select(fragment).select(content).node(),
- target);
- nv.utils.pjax(links, content);
- });
- };
-
- d3.selectAll(links).on("click", function() {
- history.pushState(this.href, this.textContent, this.href);
- load(this.href);
- d3.event.preventDefault();
- });
-
- d3.select(window).on("popstate", function() {
- if (d3.event.state) {
- load(d3.event.state);
- }
- });
- };
-
-
- /*
- For when we want to approximate the width in pixels for an SVG:text element.
- Most common instance is when the element is in a display:none; container.
- Forumla is : text.length * font-size * constant_factor
- */
- nv.utils.calcApproxTextWidth = function (svgTextElem) {
- if (nv.utils.isFunction(svgTextElem.style) && nv.utils.isFunction(svgTextElem.text)) {
- var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""), 10);
- var textLength = svgTextElem.text().length;
- return nv.utils.NaNtoZero(textLength * fontSize * 0.5);
- }
- return 0;
- };
-
-
- /*
- Numbers that are undefined, null or NaN, convert them to zeros.
- */
- nv.utils.NaNtoZero = function(n) {
- if (!nv.utils.isNumber(n)
- || isNaN(n)
- || n === null
- || n === Infinity
- || n === -Infinity) {
-
- return 0;
- }
- return n;
- };
-
- /*
- Add a way to watch for d3 transition ends to d3
- */
- d3.selection.prototype.watchTransition = function(renderWatch){
- var args = [this].concat([].slice.call(arguments, 1));
- return renderWatch.transition.apply(renderWatch, args);
- };
-
-
- /*
- Helper object to watch when d3 has rendered something
- */
- nv.utils.renderWatch = function(dispatch, duration) {
- if (!(this instanceof nv.utils.renderWatch)) {
- return new nv.utils.renderWatch(dispatch, duration);
- }
-
- var _duration = duration !== undefined ? duration : 250;
- var renderStack = [];
- var self = this;
-
- this.models = function(models) {
- models = [].slice.call(arguments, 0);
- models.forEach(function(model){
- model.__rendered = false;
- (function(m){
- m.dispatch.on('renderEnd', function(arg){
- m.__rendered = true;
- self.renderEnd('model');
- });
- })(model);
-
- if (renderStack.indexOf(model) < 0) {
- renderStack.push(model);
- }
- });
- return this;
- };
-
- this.reset = function(duration) {
- if (duration !== undefined) {
- _duration = duration;
- }
- renderStack = [];
- };
-
- this.transition = function(selection, args, duration) {
- args = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
-
- if (args.length > 1) {
- duration = args.pop();
- } else {
- duration = _duration !== undefined ? _duration : 250;
- }
- selection.__rendered = false;
-
- if (renderStack.indexOf(selection) < 0) {
- renderStack.push(selection);
- }
-
- if (duration === 0) {
- selection.__rendered = true;
- selection.delay = function() { return this; };
- selection.duration = function() { return this; };
- return selection;
- } else {
- if (selection.length === 0) {
- selection.__rendered = true;
- } else if (selection.every( function(d){ return !d.length; } )) {
- selection.__rendered = true;
- } else {
- selection.__rendered = false;
- }
-
- var n = 0;
- return selection
- .transition()
- .duration(duration)
- .each(function(){ ++n; })
- .each('end', function(d, i) {
- if (--n === 0) {
- selection.__rendered = true;
- self.renderEnd.apply(this, args);
- }
- });
- }
- };
-
- this.renderEnd = function() {
- if (renderStack.every( function(d){ return d.__rendered; } )) {
- renderStack.forEach( function(d){ d.__rendered = false; });
- dispatch.renderEnd.apply(this, arguments);
- }
- }
-
- };
-
-
- /*
- Takes multiple objects and combines them into the first one (dst)
- example: nv.utils.deepExtend({a: 1}, {a: 2, b: 3}, {c: 4});
- gives: {a: 2, b: 3, c: 4}
- */
- nv.utils.deepExtend = function(dst){
- var sources = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
- sources.forEach(function(source) {
- for (var key in source) {
- var isArray = nv.utils.isArray(dst[key]);
- var isObject = nv.utils.isObject(dst[key]);
- var srcObj = nv.utils.isObject(source[key]);
-
- if (isObject && !isArray && srcObj) {
- nv.utils.deepExtend(dst[key], source[key]);
- } else {
- dst[key] = source[key];
- }
- }
- });
- };
-
-
- /*
- state utility object, used to track d3 states in the models
- */
- nv.utils.state = function(){
- if (!(this instanceof nv.utils.state)) {
- return new nv.utils.state();
- }
- var state = {};
- var _self = this;
- var _setState = function(){};
- var _getState = function(){ return {}; };
- var init = null;
- var changed = null;
-
- this.dispatch = d3.dispatch('change', 'set');
-
- this.dispatch.on('set', function(state){
- _setState(state, true);
- });
-
- this.getter = function(fn){
- _getState = fn;
- return this;
- };
-
- this.setter = function(fn, callback) {
- if (!callback) {
- callback = function(){};
- }
- _setState = function(state, update){
- fn(state);
- if (update) {
- callback();
- }
- };
- return this;
- };
-
- this.init = function(state){
- init = init || {};
- nv.utils.deepExtend(init, state);
- };
-
- var _set = function(){
- var settings = _getState();
-
- if (JSON.stringify(settings) === JSON.stringify(state)) {
- return false;
- }
-
- for (var key in settings) {
- if (state[key] === undefined) {
- state[key] = {};
- }
- state[key] = settings[key];
- changed = true;
- }
- return true;
- };
-
- this.update = function(){
- if (init) {
- _setState(init, false);
- init = null;
- }
- if (_set.call(this)) {
- this.dispatch.change(state);
- }
- };
-
- };
-
-
- /*
- Snippet of code you can insert into each nv.models.* to give you the ability to
- do things like:
- chart.options({
- showXAxis: true,
- tooltips: true
- });
-
- To enable in the chart:
- chart.options = nv.utils.optionsFunc.bind(chart);
- */
- nv.utils.optionsFunc = function(args) {
- if (args) {
- d3.map(args).forEach((function(key,value) {
- if (nv.utils.isFunction(this[key])) {
- this[key](value);
- }
- }).bind(this));
- }
- return this;
- };
-
-
- /*
- numTicks: requested number of ticks
- data: the chart data
-
- returns the number of ticks to actually use on X axis, based on chart data
- to avoid duplicate ticks with the same value
- */
- nv.utils.calcTicksX = function(numTicks, data) {
- // find max number of values from all data streams
- var numValues = 1;
- var i = 0;
- for (i; i < data.length; i += 1) {
- var stream_len = data[i] && data[i].values ? data[i].values.length : 0;
- numValues = stream_len > numValues ? stream_len : numValues;
- }
- nv.log("Requested number of ticks: ", numTicks);
- nv.log("Calculated max values to be: ", numValues);
- // make sure we don't have more ticks than values to avoid duplicates
- numTicks = numTicks > numValues ? numTicks = numValues - 1 : numTicks;
- // make sure we have at least one tick
- numTicks = numTicks < 1 ? 1 : numTicks;
- // make sure it's an integer
- numTicks = Math.floor(numTicks);
- nv.log("Calculating tick count as: ", numTicks);
- return numTicks;
- };
-
-
- /*
- returns number of ticks to actually use on Y axis, based on chart data
- */
- nv.utils.calcTicksY = function(numTicks, data) {
- // currently uses the same logic but we can adjust here if needed later
- return nv.utils.calcTicksX(numTicks, data);
- };
-
-
- /*
- Add a particular option from an options object onto chart
- Options exposed on a chart are a getter/setter function that returns chart
- on set to mimic typical d3 option chaining, e.g. svg.option1('a').option2('b');
-
- option objects should be generated via Object.create() to provide
- the option of manipulating data via get/set functions.
- */
- nv.utils.initOption = function(chart, name) {
- // if it's a call option, just call it directly, otherwise do get/set
- if (chart._calls && chart._calls[name]) {
- chart[name] = chart._calls[name];
- } else {
- chart[name] = function (_) {
- if (!arguments.length) return chart._options[name];
- chart._overrides[name] = true;
- chart._options[name] = _;
- return chart;
- };
- // calling the option as _option will ignore if set by option already
- // so nvd3 can set options internally but the stop if set manually
- chart['_' + name] = function(_) {
- if (!arguments.length) return chart._options[name];
- if (!chart._overrides[name]) {
- chart._options[name] = _;
- }
- return chart;
- }
- }
- };
-
-
- /*
- Add all options in an options object to the chart
- */
- nv.utils.initOptions = function(chart) {
- chart._overrides = chart._overrides || {};
- var ops = Object.getOwnPropertyNames(chart._options || {});
- var calls = Object.getOwnPropertyNames(chart._calls || {});
- ops = ops.concat(calls);
- for (var i in ops) {
- nv.utils.initOption(chart, ops[i]);
- }
- };
-
-
- /*
- Inherit options from a D3 object
- d3.rebind makes calling the function on target actually call it on source
- Also use _d3options so we can track what we inherit for documentation and chained inheritance
- */
- nv.utils.inheritOptionsD3 = function(target, d3_source, oplist) {
- target._d3options = oplist.concat(target._d3options || []);
- oplist.unshift(d3_source);
- oplist.unshift(target);
- d3.rebind.apply(this, oplist);
- };
-
-
- /*
- Remove duplicates from an array
- */
- nv.utils.arrayUnique = function(a) {
- return a.sort().filter(function(item, pos) {
- return !pos || item != a[pos - 1];
- });
- };
-
-
- /*
- Keeps a list of custom symbols to draw from in addition to d3.svg.symbol
- Necessary since d3 doesn't let you extend its list -_-
- Add new symbols by doing nv.utils.symbols.set('name', function(size){...});
- */
- nv.utils.symbolMap = d3.map();
-
-
- /*
- Replaces d3.svg.symbol so that we can look both there and our own map
- */
- nv.utils.symbol = function() {
- var type,
- size = 64;
- function symbol(d,i) {
- var t = type.call(this,d,i);
- var s = size.call(this,d,i);
- if (d3.svg.symbolTypes.indexOf(t) !== -1) {
- return d3.svg.symbol().type(t).size(s)();
- } else {
- return nv.utils.symbolMap.get(t)(s);
- }
- }
- symbol.type = function(_) {
- if (!arguments.length) return type;
- type = d3.functor(_);
- return symbol;
- };
- symbol.size = function(_) {
- if (!arguments.length) return size;
- size = d3.functor(_);
- return symbol;
- };
- return symbol;
- };
-
-
- /*
- Inherit option getter/setter functions from source to target
- d3.rebind makes calling the function on target actually call it on source
- Also track via _inherited and _d3options so we can track what we inherit
- for documentation generation purposes and chained inheritance
- */
- nv.utils.inheritOptions = function(target, source) {
- // inherit all the things
- var ops = Object.getOwnPropertyNames(source._options || {});
- var calls = Object.getOwnPropertyNames(source._calls || {});
- var inherited = source._inherited || [];
- var d3ops = source._d3options || [];
- var args = ops.concat(calls).concat(inherited).concat(d3ops);
- args.unshift(source);
- args.unshift(target);
- d3.rebind.apply(this, args);
- // pass along the lists to keep track of them, don't allow duplicates
- target._inherited = nv.utils.arrayUnique(ops.concat(calls).concat(inherited).concat(ops).concat(target._inherited || []));
- target._d3options = nv.utils.arrayUnique(d3ops.concat(target._d3options || []));
- };
-
-
- /*
- Runs common initialize code on the svg before the chart builds
- */
- nv.utils.initSVG = function(svg) {
- svg.classed({'nvd3-svg':true});
- };
-
-
- /*
- Sanitize and provide default for the container height.
- */
- nv.utils.sanitizeHeight = function(height, container) {
- return (height || parseInt(container.style('height'), 10) || 400);
- };
-
-
- /*
- Sanitize and provide default for the container width.
- */
- nv.utils.sanitizeWidth = function(width, container) {
- return (width || parseInt(container.style('width'), 10) || 960);
- };
-
-
- /*
- Calculate the available height for a chart.
- */
- nv.utils.availableHeight = function(height, container, margin) {
- return Math.max(0,nv.utils.sanitizeHeight(height, container) - margin.top - margin.bottom);
- };
-
- /*
- Calculate the available width for a chart.
- */
- nv.utils.availableWidth = function(width, container, margin) {
- return Math.max(0,nv.utils.sanitizeWidth(width, container) - margin.left - margin.right);
- };
-
- /*
- Clear any rendered chart components and display a chart's 'noData' message
- */
- nv.utils.noData = function(chart, container) {
- var opt = chart.options(),
- margin = opt.margin(),
- noData = opt.noData(),
- data = (noData == null) ? ["No Data Available."] : [noData],
- height = nv.utils.availableHeight(null, container, margin),
- width = nv.utils.availableWidth(null, container, margin),
- x = margin.left + width/2,
- y = margin.top + height/2;
-
- //Remove any previously created chart components
- container.selectAll('g').remove();
-
- var noDataText = container.selectAll('.nv-noData').data(data);
-
- noDataText.enter().append('text')
- .attr('class', 'nvd3 nv-noData')
- .attr('dy', '-.7em')
- .style('text-anchor', 'middle');
-
- noDataText
- .attr('x', x)
- .attr('y', y)
- .text(function(t){ return t; });
- };
-
- /*
- Wrap long labels.
- */
- nv.utils.wrapTicks = function (text, width) {
- text.each(function() {
- var text = d3.select(this),
- words = text.text().split(/\s+/).reverse(),
- word,
- line = [],
- lineNumber = 0,
- lineHeight = 1.1,
- y = text.attr("y"),
- dy = parseFloat(text.attr("dy")),
- tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
- while (word = words.pop()) {
- line.push(word);
- tspan.text(line.join(" "));
- if (tspan.node().getComputedTextLength() > width) {
- line.pop();
- tspan.text(line.join(" "));
- line = [word];
- tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
- }
- }
- });
- };
-
- /*
- Check equality of 2 array
- */
- nv.utils.arrayEquals = function (array1, array2) {
- if (array1 === array2)
- return true;
-
- if (!array1 || !array2)
- return false;
-
- // compare lengths - can save a lot of time
- if (array1.length != array2.length)
- return false;
-
- for (var i = 0,
- l = array1.length; i < l; i++) {
- // Check if we have nested arrays
- if (array1[i] instanceof Array && array2[i] instanceof Array) {
- // recurse into the nested arrays
- if (!nv.arrayEquals(array1[i], array2[i]))
- return false;
- } else if (array1[i] != array2[i]) {
- // Warning - two different object instances will never be equal: {x:20} != {x:20}
- return false;
- }
- }
- return true;
- };
- nv.models.axis = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var axis = d3.svg.axis();
- var scale = d3.scale.linear();
-
- var margin = {top: 0, right: 0, bottom: 0, left: 0}
- , width = 75 //only used for tickLabel currently
- , height = 60 //only used for tickLabel currently
- , axisLabelText = null
- , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes
- , rotateLabels = 0
- , rotateYLabel = true
- , staggerLabels = false
- , isOrdinal = false
- , ticks = null
- , axisLabelDistance = 0
- , fontSize = undefined
- , duration = 250
- , dispatch = d3.dispatch('renderEnd')
- ;
- axis
- .scale(scale)
- .orient('bottom')
- .tickFormat(function(d) { return d })
- ;
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var scale0;
- var renderWatch = nv.utils.renderWatch(dispatch, duration);
-
- function chart(selection) {
- renderWatch.reset();
- selection.each(function(data) {
- var container = d3.select(this);
- nv.utils.initSVG(container);
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]);
- var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis');
- var gEnter = wrapEnter.append('g');
- var g = wrap.select('g');
-
- if (ticks !== null)
- axis.ticks(ticks);
- else if (axis.orient() == 'top' || axis.orient() == 'bottom')
- axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100);
-
- //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component
- g.watchTransition(renderWatch, 'axis').call(axis);
-
- scale0 = scale0 || axis.scale();
-
- var fmt = axis.tickFormat();
- if (fmt == null) {
- fmt = scale0.tickFormat();
- }
-
- var axisLabel = g.selectAll('text.nv-axislabel')
- .data([axisLabelText || null]);
- axisLabel.exit().remove();
-
- //only skip when fontSize is undefined so it can be cleared with a null or blank string
- if (fontSize !== undefined) {
- g.selectAll('g').select("text").style('font-size', fontSize);
- }
-
- var xLabelMargin;
- var axisMaxMin;
- var w;
- switch (axis.orient()) {
- case 'top':
- axisLabel.enter().append('text').attr('class', 'nv-axislabel');
- w = 0;
- if (scale.range().length === 1) {
- w = isOrdinal ? scale.range()[0] * 2 + scale.rangeBand() : 0;
- } else if (scale.range().length === 2) {
- w = isOrdinal ? scale.range()[0] + scale.range()[1] + scale.rangeBand() : scale.range()[1];
- } else if ( scale.range().length > 2){
- w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
- };
- axisLabel
- .attr('text-anchor', 'middle')
- .attr('y', 0)
- .attr('x', w/2);
- if (showMaxMin) {
- axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
- .data(scale.domain());
- axisMaxMin.enter().append('g').attr('class',function(d,i){
- return ['nv-axisMaxMin','nv-axisMaxMin-x',(i == 0 ? 'nv-axisMin-x':'nv-axisMax-x')].join(' ')
- }).append('text');
- axisMaxMin.exit().remove();
- axisMaxMin
- .attr('transform', function(d,i) {
- return 'translate(' + nv.utils.NaNtoZero(scale(d)) + ',0)'
- })
- .select('text')
- .attr('dy', '-0.5em')
- .attr('y', -axis.tickPadding())
- .attr('text-anchor', 'middle')
- .text(function(d,i) {
- var v = fmt(d);
- return ('' + v).match('NaN') ? '' : v;
- });
- axisMaxMin.watchTransition(renderWatch, 'min-max top')
- .attr('transform', function(d,i) {
- return 'translate(' + nv.utils.NaNtoZero(scale.range()[i]) + ',0)'
- });
- }
- break;
- case 'bottom':
- xLabelMargin = axisLabelDistance + 36;
- var maxTextWidth = 30;
- var textHeight = 0;
- var xTicks = g.selectAll('g').select("text");
- var rotateLabelsRule = '';
- if (rotateLabels%360) {
- //Reset transform on ticks so textHeight can be calculated correctly
- xTicks.attr('transform', '');
- //Calculate the longest xTick width
- xTicks.each(function(d,i){
- var box = this.getBoundingClientRect();
- var width = box.width;
- textHeight = box.height;
- if(width > maxTextWidth) maxTextWidth = width;
- });
- rotateLabelsRule = 'rotate(' + rotateLabels + ' 0,' + (textHeight/2 + axis.tickPadding()) + ')';
- //Convert to radians before calculating sin. Add 30 to margin for healthy padding.
- var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180));
- xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30;
- //Rotate all xTicks
- xTicks
- .attr('transform', rotateLabelsRule)
- .style('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end');
- } else {
- if (staggerLabels) {
- xTicks
- .attr('transform', function(d,i) {
- return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')'
- });
- } else {
- xTicks.attr('transform', "translate(0,0)");
- }
- }
- axisLabel.enter().append('text').attr('class', 'nv-axislabel');
- w = 0;
- if (scale.range().length === 1) {
- w = isOrdinal ? scale.range()[0] * 2 + scale.rangeBand() : 0;
- } else if (scale.range().length === 2) {
- w = isOrdinal ? scale.range()[0] + scale.range()[1] + scale.rangeBand() : scale.range()[1];
- } else if ( scale.range().length > 2){
- w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
- };
- axisLabel
- .attr('text-anchor', 'middle')
- .attr('y', xLabelMargin)
- .attr('x', w/2);
- if (showMaxMin) {
- //if (showMaxMin && !isOrdinal) {
- axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
- //.data(scale.domain())
- .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]);
- axisMaxMin.enter().append('g').attr('class',function(d,i){
- return ['nv-axisMaxMin','nv-axisMaxMin-x',(i == 0 ? 'nv-axisMin-x':'nv-axisMax-x')].join(' ')
- }).append('text');
- axisMaxMin.exit().remove();
- axisMaxMin
- .attr('transform', function(d,i) {
- return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)'
- })
- .select('text')
- .attr('dy', '.71em')
- .attr('y', axis.tickPadding())
- .attr('transform', rotateLabelsRule)
- .style('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle')
- .text(function(d,i) {
- var v = fmt(d);
- return ('' + v).match('NaN') ? '' : v;
- });
- axisMaxMin.watchTransition(renderWatch, 'min-max bottom')
- .attr('transform', function(d,i) {
- return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)'
- });
- }
-
- break;
- case 'right':
- axisLabel.enter().append('text').attr('class', 'nv-axislabel');
- axisLabel
- .style('text-anchor', rotateYLabel ? 'middle' : 'begin')
- .attr('transform', rotateYLabel ? 'rotate(90)' : '')
- .attr('y', rotateYLabel ? (-Math.max(margin.right, width) + 12 - (axisLabelDistance || 0)) : -10) //TODO: consider calculating this based on largest tick width... OR at least expose this on chart
- .attr('x', rotateYLabel ? (d3.max(scale.range()) / 2) : axis.tickPadding());
- if (showMaxMin) {
- axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
- .data(scale.domain());
- axisMaxMin.enter().append('g').attr('class',function(d,i){
- return ['nv-axisMaxMin','nv-axisMaxMin-y',(i == 0 ? 'nv-axisMin-y':'nv-axisMax-y')].join(' ')
- }).append('text')
- .style('opacity', 0);
- axisMaxMin.exit().remove();
- axisMaxMin
- .attr('transform', function(d,i) {
- return 'translate(0,' + nv.utils.NaNtoZero(scale(d)) + ')'
- })
- .select('text')
- .attr('dy', '.32em')
- .attr('y', 0)
- .attr('x', axis.tickPadding())
- .style('text-anchor', 'start')
- .text(function(d, i) {
- var v = fmt(d);
- return ('' + v).match('NaN') ? '' : v;
- });
- axisMaxMin.watchTransition(renderWatch, 'min-max right')
- .attr('transform', function(d,i) {
- return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')'
- })
- .select('text')
- .style('opacity', 1);
- }
- break;
- case 'left':
- /*
- //For dynamically placing the label. Can be used with dynamically-sized chart axis margins
- var yTicks = g.selectAll('g').select("text");
- yTicks.each(function(d,i){
- var labelPadding = this.getBoundingClientRect().width + axis.tickPadding() + 16;
- if(labelPadding > width) width = labelPadding;
- });
- */
- axisLabel.enter().append('text').attr('class', 'nv-axislabel');
- axisLabel
- .style('text-anchor', rotateYLabel ? 'middle' : 'end')
- .attr('transform', rotateYLabel ? 'rotate(-90)' : '')
- .attr('y', rotateYLabel ? (-Math.max(margin.left, width) + 25 - (axisLabelDistance || 0)) : -10)
- .attr('x', rotateYLabel ? (-d3.max(scale.range()) / 2) : -axis.tickPadding());
- if (showMaxMin) {
- axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
- .data(scale.domain());
- axisMaxMin.enter().append('g').attr('class',function(d,i){
- return ['nv-axisMaxMin','nv-axisMaxMin-y',(i == 0 ? 'nv-axisMin-y':'nv-axisMax-y')].join(' ')
- }).append('text')
- .style('opacity', 0);
- axisMaxMin.exit().remove();
- axisMaxMin
- .attr('transform', function(d,i) {
- return 'translate(0,' + nv.utils.NaNtoZero(scale0(d)) + ')'
- })
- .select('text')
- .attr('dy', '.32em')
- .attr('y', 0)
- .attr('x', -axis.tickPadding())
- .attr('text-anchor', 'end')
- .text(function(d,i) {
- var v = fmt(d);
- return ('' + v).match('NaN') ? '' : v;
- });
- axisMaxMin.watchTransition(renderWatch, 'min-max right')
- .attr('transform', function(d,i) {
- return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')'
- })
- .select('text')
- .style('opacity', 1);
- }
- break;
- }
- axisLabel.text(function(d) { return d });
-
- if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) {
- //check if max and min overlap other values, if so, hide the values that overlap
- g.selectAll('g') // the g's wrapping each tick
- .each(function(d,i) {
- d3.select(this).select('text').attr('opacity', 1);
- if (scale(d) < scale.range()[1] + 10 || scale(d) > scale.range()[0] - 10) { // 10 is assuming text height is 16... if d is 0, leave it!
- if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
- d3.select(this).attr('opacity', 0);
-
- d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!!
- }
- });
-
- //if Max and Min = 0 only show min, Issue #281
- if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0) {
- wrap.selectAll('g.nv-axisMaxMin').style('opacity', function (d, i) {
- return !i ? 1 : 0
- });
- }
- }
-
- if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) {
- var maxMinRange = [];
- wrap.selectAll('g.nv-axisMaxMin')
- .each(function(d,i) {
- try {
- if (i) // i== 1, max position
- maxMinRange.push(scale(d) - this.getBoundingClientRect().width - 4); //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case)
- else // i==0, min position
- maxMinRange.push(scale(d) + this.getBoundingClientRect().width + 4)
- }catch (err) {
- if (i) // i== 1, max position
- maxMinRange.push(scale(d) - 4); //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case)
- else // i==0, min position
- maxMinRange.push(scale(d) + 4);
- }
- });
- // the g's wrapping each tick
- g.selectAll('g').each(function(d, i) {
- if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) {
- if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
- d3.select(this).remove();
- else
- d3.select(this).select('text').remove(); // Don't remove the ZERO line!!
- }
- });
- }
-
- //Highlight zero tick line
- g.selectAll('.tick')
- .filter(function (d) {
- /*
- The filter needs to return only ticks at or near zero.
- Numbers like 0.00001 need to count as zero as well,
- and the arithmetic trick below solves that.
- */
- return !parseFloat(Math.round(d * 100000) / 1000000) && (d !== undefined)
- })
- .classed('zero', true);
-
- //store old scales for use in transitions on update
- scale0 = scale.copy();
-
- });
-
- renderWatch.renderEnd('axis immediate');
- return chart;
- }
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- // expose chart's sub-components
- chart.axis = axis;
- chart.dispatch = dispatch;
-
- chart.options = nv.utils.optionsFunc.bind(chart);
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- axisLabelDistance: {get: function(){return axisLabelDistance;}, set: function(_){axisLabelDistance=_;}},
- staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
- rotateLabels: {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
- rotateYLabel: {get: function(){return rotateYLabel;}, set: function(_){rotateYLabel=_;}},
- showMaxMin: {get: function(){return showMaxMin;}, set: function(_){showMaxMin=_;}},
- axisLabel: {get: function(){return axisLabelText;}, set: function(_){axisLabelText=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- ticks: {get: function(){return ticks;}, set: function(_){ticks=_;}},
- width: {get: function(){return width;}, set: function(_){width=_;}},
- fontSize: {get: function(){return fontSize;}, set: function(_){fontSize=_;}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top !== undefined ? _.top : margin.top;
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- duration: {get: function(){return duration;}, set: function(_){
- duration=_;
- renderWatch.reset(duration);
- }},
- scale: {get: function(){return scale;}, set: function(_){
- scale = _;
- axis.scale(scale);
- isOrdinal = typeof scale.rangeBands === 'function';
- nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']);
- }}
- });
-
- nv.utils.initOptions(chart);
- nv.utils.inheritOptionsD3(chart, axis, ['orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat']);
- nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']);
-
- return chart;
- };
- nv.models.boxPlot = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var margin = {top: 0, right: 0, bottom: 0, left: 0},
- width = 960,
- height = 500,
- id = Math.floor(Math.random() * 10000), // Create semi-unique ID in case user doesn't select one
- xScale = d3.scale.ordinal(),
- yScale = d3.scale.linear(),
- getX = function(d) { return d.label }, // Default data model selectors.
- getQ1 = function(d) { return d.values.Q1 },
- getQ2 = function(d) { return d.values.Q2 },
- getQ3 = function(d) { return d.values.Q3 },
- getWl = function(d) { return d.values.whisker_low },
- getWh = function(d) { return d.values.whisker_high },
- getColor = function(d) { return d.color },
- getOlItems = function(d) { return d.values.outliers },
- getOlValue = function(d, i, j) { return d },
- getOlLabel = function(d, i, j) { return d },
- getOlColor = function(d, i, j) { return undefined },
- color = nv.utils.defaultColor(),
- container = null,
- xDomain, xRange,
- yDomain, yRange,
- dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd'),
- duration = 250,
- maxBoxWidth = null;
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var xScale0, yScale0;
- var renderWatch = nv.utils.renderWatch(dispatch, duration);
-
- function chart(selection) {
- renderWatch.reset();
- selection.each(function(data) {
- var availableWidth = width - margin.left - margin.right,
- availableHeight = height - margin.top - margin.bottom;
-
- container = d3.select(this);
- nv.utils.initSVG(container);
-
- // Setup Scales
- xScale.domain(xDomain || data.map(function(d,i) { return getX(d,i); }))
- .rangeBands(xRange || [0, availableWidth], 0.1);
-
- // if we know yDomain, no need to calculate
- var yData = []
- if (!yDomain) {
- // (y-range is based on quartiles, whiskers and outliers)
- var values = [], yMin, yMax;
- data.forEach(function (d, i) {
- var q1 = getQ1(d), q3 = getQ3(d), wl = getWl(d), wh = getWh(d);
- var olItems = getOlItems(d);
- if (olItems) {
- olItems.forEach(function (e, i) {
- values.push(getOlValue(e, i, undefined));
- });
- }
- if (wl) { values.push(wl) }
- if (q1) { values.push(q1) }
- if (q3) { values.push(q3) }
- if (wh) { values.push(wh) }
- });
- yMin = d3.min(values);
- yMax = d3.max(values);
- yData = [ yMin, yMax ] ;
- }
-
- yScale.domain(yDomain || yData);
- yScale.range(yRange || [availableHeight, 0]);
-
- //store old scales if they exist
- xScale0 = xScale0 || xScale;
- yScale0 = yScale0 || yScale.copy().range([yScale(0),yScale(0)]);
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-wrap').data([data]);
- var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap');
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- var boxplots = wrap.selectAll('.nv-boxplot').data(function(d) { return d });
- var boxEnter = boxplots.enter().append('g').style('stroke-opacity', 1e-6).style('fill-opacity', 1e-6);
- boxplots
- .attr('class', 'nv-boxplot')
- .attr('transform', function(d,i,j) { return 'translate(' + (xScale(getX(d,i)) + xScale.rangeBand() * 0.05) + ', 0)'; })
- .classed('hover', function(d) { return d.hover });
- boxplots
- .watchTransition(renderWatch, 'nv-boxplot: boxplots')
- .style('stroke-opacity', 1)
- .style('fill-opacity', 0.75)
- .delay(function(d,i) { return i * duration / data.length })
- .attr('transform', function(d,i) {
- return 'translate(' + (xScale(getX(d,i)) + xScale.rangeBand() * 0.05) + ', 0)';
- });
- boxplots.exit().remove();
-
- // ----- add the SVG elements for each boxPlot -----
-
- // conditionally append whisker lines
- boxEnter.each(function(d,i) {
- var box = d3.select(this);
- [getWl, getWh].forEach(function (f) {
- if (f(d) !== undefined && f(d) !== null) {
- var key = (f === getWl) ? 'low' : 'high';
- box.append('line')
- .style('stroke', getColor(d) || color(d,i))
- .attr('class', 'nv-boxplot-whisker nv-boxplot-' + key);
- box.append('line')
- .style('stroke', getColor(d) || color(d,i))
- .attr('class', 'nv-boxplot-tick nv-boxplot-' + key);
- }
- });
- });
-
- var box_width = function() { return (maxBoxWidth === null ? xScale.rangeBand() * 0.9 : Math.min(75, xScale.rangeBand() * 0.9)); };
- var box_left = function() { return xScale.rangeBand() * 0.45 - box_width()/2; };
- var box_right = function() { return xScale.rangeBand() * 0.45 + box_width()/2; };
-
- // update whisker lines and ticks
- [getWl, getWh].forEach(function (f) {
- var key = (f === getWl) ? 'low' : 'high';
- var endpoint = (f === getWl) ? getQ1 : getQ3;
- boxplots.select('line.nv-boxplot-whisker.nv-boxplot-' + key)
- .watchTransition(renderWatch, 'nv-boxplot: boxplots')
- .attr('x1', xScale.rangeBand() * 0.45 )
- .attr('y1', function(d,i) { return yScale(f(d)); })
- .attr('x2', xScale.rangeBand() * 0.45 )
- .attr('y2', function(d,i) { return yScale(endpoint(d)); });
- boxplots.select('line.nv-boxplot-tick.nv-boxplot-' + key)
- .watchTransition(renderWatch, 'nv-boxplot: boxplots')
- .attr('x1', box_left )
- .attr('y1', function(d,i) { return yScale(f(d)); })
- .attr('x2', box_right )
- .attr('y2', function(d,i) { return yScale(f(d)); });
- });
-
- [getWl, getWh].forEach(function (f) {
- var key = (f === getWl) ? 'low' : 'high';
- boxEnter.selectAll('.nv-boxplot-' + key)
- .on('mouseover', function(d,i,j) {
- d3.select(this).classed('hover', true);
- dispatch.elementMouseover({
- series: { key: f(d), color: getColor(d) || color(d,j) },
- e: d3.event
- });
- })
- .on('mouseout', function(d,i,j) {
- d3.select(this).classed('hover', false);
- dispatch.elementMouseout({
- series: { key: f(d), color: getColor(d) || color(d,j) },
- e: d3.event
- });
- })
- .on('mousemove', function(d,i) {
- dispatch.elementMousemove({e: d3.event});
- });
- });
-
- // boxes
- boxEnter.append('rect')
- .attr('class', 'nv-boxplot-box')
- // tooltip events
- .on('mouseover', function(d,i) {
- d3.select(this).classed('hover', true);
- dispatch.elementMouseover({
- key: getX(d),
- value: getX(d),
- series: [
- { key: 'Q3', value: getQ3(d), color: getColor(d) || color(d,i) },
- { key: 'Q2', value: getQ2(d), color: getColor(d) || color(d,i) },
- { key: 'Q1', value: getQ1(d), color: getColor(d) || color(d,i) }
- ],
- data: d,
- index: i,
- e: d3.event
- });
- })
- .on('mouseout', function(d,i) {
- d3.select(this).classed('hover', false);
- dispatch.elementMouseout({
- key: getX(d),
- value: getX(d),
- series: [
- { key: 'Q3', value: getQ3(d), color: getColor(d) || color(d,i) },
- { key: 'Q2', value: getQ2(d), color: getColor(d) || color(d,i) },
- { key: 'Q1', value: getQ1(d), color: getColor(d) || color(d,i) }
- ],
- data: d,
- index: i,
- e: d3.event
- });
- })
- .on('mousemove', function(d,i) {
- dispatch.elementMousemove({e: d3.event});
- });
-
- // box transitions
- boxplots.select('rect.nv-boxplot-box')
- .watchTransition(renderWatch, 'nv-boxplot: boxes')
- .attr('y', function(d,i) { return yScale(getQ3(d)); })
- .attr('width', box_width)
- .attr('x', box_left )
- .attr('height', function(d,i) { return Math.abs(yScale(getQ3(d)) - yScale(getQ1(d))) || 1 })
- .style('fill', function(d,i) { return getColor(d) || color(d,i) })
- .style('stroke', function(d,i) { return getColor(d) || color(d,i) });
-
- // median line
- boxEnter.append('line').attr('class', 'nv-boxplot-median');
-
- boxplots.select('line.nv-boxplot-median')
- .watchTransition(renderWatch, 'nv-boxplot: boxplots line')
- .attr('x1', box_left)
- .attr('y1', function(d,i) { return yScale(getQ2(d)); })
- .attr('x2', box_right)
- .attr('y2', function(d,i) { return yScale(getQ2(d)); });
-
- // outliers
- var outliers = boxplots.selectAll('.nv-boxplot-outlier').data(function(d) {
- return getOlItems(d) || [];
- });
- outliers.enter().append('circle')
- .style('fill', function(d,i,j) { return getOlColor(d,i,j) || color(d,j) })
- .style('stroke', function(d,i,j) { return getOlColor(d,i,j) || color(d,j) })
- .style('z-index', 9000)
- .on('mouseover', function(d,i,j) {
- d3.select(this).classed('hover', true);
- dispatch.elementMouseover({
- series: { key: getOlLabel(d,i,j), color: getOlColor(d,i,j) || color(d,j) },
- e: d3.event
- });
- })
- .on('mouseout', function(d,i,j) {
- d3.select(this).classed('hover', false);
- dispatch.elementMouseout({
- series: { key: getOlLabel(d,i,j), color: getOlColor(d,i,j) || color(d,j) },
- e: d3.event
- });
- })
- .on('mousemove', function(d,i) {
- dispatch.elementMousemove({e: d3.event});
- });
- outliers.attr('class', 'nv-boxplot-outlier');
- outliers
- .watchTransition(renderWatch, 'nv-boxplot: nv-boxplot-outlier')
- .attr('cx', xScale.rangeBand() * 0.45)
- .attr('cy', function(d,i,j) { return yScale(getOlValue(d,i,j)); })
- .attr('r', '3');
- outliers.exit().remove();
-
- //store old scales for use in transitions on update
- xScale0 = xScale.copy();
- yScale0 = yScale.copy();
- });
-
- renderWatch.renderEnd('nv-boxplot immediate');
- return chart;
- }
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- chart.dispatch = dispatch;
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- maxBoxWidth: {get: function(){return maxBoxWidth;}, set: function(_){maxBoxWidth=_;}},
- x: {get: function(){return getX;}, set: function(_){getX=_;}},
- q1: {get: function(){return getQ1;}, set: function(_){getQ1=_;}},
- q2: {get: function(){return getQ2;}, set: function(_){getQ2=_;}},
- q3: {get: function(){return getQ3;}, set: function(_){getQ3=_;}},
- wl: {get: function(){return getWl;}, set: function(_){getWl=_;}},
- wh: {get: function(){return getWh;}, set: function(_){getWh=_;}},
- itemColor: {get: function(){return getColor;}, set: function(_){getColor=_;}},
- outliers: {get: function(){return getOlItems;}, set: function(_){getOlItems=_;}},
- outlierValue: {get: function(){return getOlValue;}, set: function(_){getOlValue=_;}},
- outlierLabel: {get: function(){return getOlLabel;}, set: function(_){getOlLabel=_;}},
- outlierColor: {get: function(){return getOlColor;}, set: function(_){getOlColor=_;}},
- xScale: {get: function(){return xScale;}, set: function(_){xScale=_;}},
- yScale: {get: function(){return yScale;}, set: function(_){yScale=_;}},
- xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
- yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
- xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
- yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
- id: {get: function(){return id;}, set: function(_){id=_;}},
- // rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}},
- y: {
- get: function() {
- console.warn('BoxPlot \'y\' chart option is deprecated. Please use model overrides instead.');
- return {};
- },
- set: function(_) {
- console.warn('BoxPlot \'y\' chart option is deprecated. Please use model overrides instead.');
- }
- },
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top !== undefined ? _.top : margin.top;
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- }},
- duration: {get: function(){return duration;}, set: function(_){
- duration = _;
- renderWatch.reset(duration);
- }}
- });
-
- nv.utils.initOptions(chart);
-
- return chart;
- };
- nv.models.boxPlotChart = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var boxplot = nv.models.boxPlot(),
- xAxis = nv.models.axis(),
- yAxis = nv.models.axis();
-
- var margin = {top: 15, right: 10, bottom: 50, left: 60},
- width = null,
- height = null,
- color = nv.utils.getColor(),
- showXAxis = true,
- showYAxis = true,
- rightAlignYAxis = false,
- staggerLabels = false,
- tooltip = nv.models.tooltip(),
- x, y,
- noData = 'No Data Available.',
- dispatch = d3.dispatch('beforeUpdate', 'renderEnd'),
- duration = 250;
-
- xAxis
- .orient('bottom')
- .showMaxMin(false)
- .tickFormat(function(d) { return d })
- ;
- yAxis
- .orient((rightAlignYAxis) ? 'right' : 'left')
- .tickFormat(d3.format(',.1f'))
- ;
-
- tooltip.duration(0);
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var renderWatch = nv.utils.renderWatch(dispatch, duration);
-
- function chart(selection) {
- renderWatch.reset();
- renderWatch.models(boxplot);
- if (showXAxis) renderWatch.models(xAxis);
- if (showYAxis) renderWatch.models(yAxis);
-
- selection.each(function(data) {
- var container = d3.select(this), that = this;
- nv.utils.initSVG(container);
- var availableWidth = (width || parseInt(container.style('width')) || 960) - margin.left - margin.right;
- var availableHeight = (height || parseInt(container.style('height')) || 400) - margin.top - margin.bottom;
-
- chart.update = function() {
- dispatch.beforeUpdate();
- container.transition().duration(duration).call(chart);
- };
- chart.container = this;
-
- // TODO still need to find a way to validate quartile data presence using boxPlot callbacks.
- // Display No Data message if there's nothing to show. (quartiles required at minimum).
- if (!data || !data.length) {
- var noDataText = container.selectAll('.nv-noData').data([noData]);
-
- noDataText.enter().append('text')
- .attr('class', 'nvd3 nv-noData')
- .attr('dy', '-.7em')
- .style('text-anchor', 'middle');
-
- noDataText
- .attr('x', margin.left + availableWidth / 2)
- .attr('y', margin.top + availableHeight / 2)
- .text(function(d) { return d });
-
- return chart;
- } else {
- container.selectAll('.nv-noData').remove();
- }
-
- // Setup Scales
- x = boxplot.xScale();
- y = boxplot.yScale().clamp(true);
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-wrap.nv-boxPlotWithAxes').data([data]);
- var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-boxPlotWithAxes').append('g');
- var defsEnter = gEnter.append('defs');
- var g = wrap.select('g');
-
- gEnter.append('g').attr('class', 'nv-x nv-axis');
- gEnter.append('g').attr('class', 'nv-y nv-axis')
- .append('g').attr('class', 'nv-zeroLine')
- .append('line');
-
- gEnter.append('g').attr('class', 'nv-barsWrap');
- g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- if (rightAlignYAxis) {
- g.select('.nv-y.nv-axis')
- .attr('transform', 'translate(' + availableWidth + ',0)');
- }
-
- // Main Chart Component(s)
- boxplot.width(availableWidth).height(availableHeight);
-
- var barsWrap = g.select('.nv-barsWrap')
- .datum(data.filter(function(d) { return !d.disabled }))
-
- barsWrap.transition().call(boxplot);
-
- defsEnter.append('clipPath')
- .attr('id', 'nv-x-label-clip-' + boxplot.id())
- .append('rect');
-
- g.select('#nv-x-label-clip-' + boxplot.id() + ' rect')
- .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
- .attr('height', 16)
- .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
-
- // Setup Axes
- if (showXAxis) {
- xAxis
- .scale(x)
- .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
- .tickSize(-availableHeight, 0);
-
- g.select('.nv-x.nv-axis').attr('transform', 'translate(0,' + y.range()[0] + ')');
- g.select('.nv-x.nv-axis').call(xAxis);
-
- var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
- if (staggerLabels) {
- xTicks
- .selectAll('text')
- .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 === 0 ? '5' : '17') + ')' })
- }
- }
-
- if (showYAxis) {
- yAxis
- .scale(y)
- .ticks( Math.floor(availableHeight/36) ) // can't use nv.utils.calcTicksY with Object data
- .tickSize( -availableWidth, 0);
-
- g.select('.nv-y.nv-axis').call(yAxis);
- }
-
- // Zero line
- g.select('.nv-zeroLine line')
- .attr('x1',0)
- .attr('x2',availableWidth)
- .attr('y1', y(0))
- .attr('y2', y(0))
- ;
-
- //============================================================
- // Event Handling/Dispatching (in chart's scope)
- //------------------------------------------------------------
- });
-
- renderWatch.renderEnd('nv-boxplot chart immediate');
- return chart;
- }
-
- //============================================================
- // Event Handling/Dispatching (out of chart's scope)
- //------------------------------------------------------------
-
- boxplot.dispatch.on('elementMouseover.tooltip', function(evt) {
- tooltip.data(evt).hidden(false);
- });
-
- boxplot.dispatch.on('elementMouseout.tooltip', function(evt) {
- tooltip.data(evt).hidden(true);
- });
-
- boxplot.dispatch.on('elementMousemove.tooltip', function(evt) {
- tooltip();
- });
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- chart.dispatch = dispatch;
- chart.boxplot = boxplot;
- chart.xAxis = xAxis;
- chart.yAxis = yAxis;
- chart.tooltip = tooltip;
-
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
- showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
- showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
- tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
- noData: {get: function(){return noData;}, set: function(_){noData=_;}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top !== undefined ? _.top : margin.top;
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- duration: {get: function(){return duration;}, set: function(_){
- duration = _;
- renderWatch.reset(duration);
- boxplot.duration(duration);
- xAxis.duration(duration);
- yAxis.duration(duration);
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- boxplot.color(color);
- }},
- rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
- rightAlignYAxis = _;
- yAxis.orient( (_) ? 'right' : 'left');
- }}
- });
-
- nv.utils.inheritOptions(chart, boxplot);
- nv.utils.initOptions(chart);
-
- return chart;
- }
-
- // Chart design based on the recommendations of Stephen Few. Implementation
- // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
- // http://projects.instantcognition.com/protovis/bulletchart/
-
- nv.models.bullet = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var margin = {top: 0, right: 0, bottom: 0, left: 0}
- , orient = 'left' // TODO top & bottom
- , reverse = false
- , ranges = function(d) { return d.ranges }
- , markers = function(d) { return d.markers ? d.markers : [] }
- , markerLines = function(d) { return d.markerLines ? d.markerLines : [0] }
- , measures = function(d) { return d.measures }
- , rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] }
- , markerLabels = function(d) { return d.markerLabels ? d.markerLabels : [] }
- , markerLineLabels = function(d) { return d.markerLineLabels ? d.markerLineLabels : [] }
- , measureLabels = function(d) { return d.measureLabels ? d.measureLabels : [] }
- , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
- , width = 380
- , height = 30
- , container = null
- , tickFormat = null
- , color = nv.utils.getColor(['#1f77b4'])
- , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove')
- , defaultRangeLabels = ["Maximum", "Mean", "Minimum"]
- , legacyRangeClassNames = ["Max", "Avg", "Min"]
- , duration = 1000
- ;
-
- function sortLabels(labels, values){
- var lz = labels.slice();
- labels.sort(function(a, b){
- var iA = lz.indexOf(a);
- var iB = lz.indexOf(b);
- return d3.descending(values[iA], values[iB]);
- });
- };
-
- function chart(selection) {
- selection.each(function(d, i) {
- var availableWidth = width - margin.left - margin.right,
- availableHeight = height - margin.top - margin.bottom;
-
- container = d3.select(this);
- nv.utils.initSVG(container);
-
- var rangez = ranges.call(this, d, i).slice(),
- markerz = markers.call(this, d, i).slice(),
- markerLinez = markerLines.call(this, d, i).slice(),
- measurez = measures.call(this, d, i).slice(),
- rangeLabelz = rangeLabels.call(this, d, i).slice(),
- markerLabelz = markerLabels.call(this, d, i).slice(),
- markerLineLabelz = markerLineLabels.call(this, d, i).slice(),
- measureLabelz = measureLabels.call(this, d, i).slice();
-
- // Sort labels according to their sorted values
- sortLabels(rangeLabelz, rangez);
- sortLabels(markerLabelz, markerz);
- sortLabels(markerLineLabelz, markerLinez);
- sortLabels(measureLabelz, measurez);
-
- // sort values descending
- rangez.sort(d3.descending);
- markerz.sort(d3.descending);
- markerLinez.sort(d3.descending);
- measurez.sort(d3.descending);
-
- // Setup Scales
- // Compute the new x-scale.
- var x1 = d3.scale.linear()
- .domain( d3.extent(d3.merge([forceX, rangez])) )
- .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
-
- // Retrieve the old x-scale, if this is an update.
- var x0 = this.__chart__ || d3.scale.linear()
- .domain([0, Infinity])
- .range(x1.range());
-
- // Stash the new scale.
- this.__chart__ = x1;
-
- var rangeMin = d3.min(rangez), //rangez[2]
- rangeMax = d3.max(rangez), //rangez[0]
- rangeAvg = rangez[1];
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]);
- var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet');
- var gEnter = wrapEnter.append('g');
- var g = wrap.select('g');
-
- for(var i=0,il=rangez.length; i<il; i++){
- var rangeClassNames = 'nv-range nv-range'+i;
- if(i <= 2){
- rangeClassNames = rangeClassNames + ' nv-range'+legacyRangeClassNames[i];
- }
- gEnter.append('rect').attr('class', rangeClassNames);
- }
-
- gEnter.append('rect').attr('class', 'nv-measure');
-
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
- w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
- var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) },
- xp1 = function(d) { return d < 0 ? x1(d) : x1(0) };
-
- for(var i=0,il=rangez.length; i<il; i++){
- var range = rangez[i];
- g.select('rect.nv-range'+i)
- .datum(range)
- .attr('height', availableHeight)
- .transition()
- .duration(duration)
- .attr('width', w1(range))
- .attr('x', xp1(range))
- }
-
- g.select('rect.nv-measure')
- .style('fill', color)
- .attr('height', availableHeight / 3)
- .attr('y', availableHeight / 3)
- .on('mouseover', function() {
- dispatch.elementMouseover({
- value: measurez[0],
- label: measureLabelz[0] || 'Current',
- color: d3.select(this).style("fill")
- })
- })
- .on('mousemove', function() {
- dispatch.elementMousemove({
- value: measurez[0],
- label: measureLabelz[0] || 'Current',
- color: d3.select(this).style("fill")
- })
- })
- .on('mouseout', function() {
- dispatch.elementMouseout({
- value: measurez[0],
- label: measureLabelz[0] || 'Current',
- color: d3.select(this).style("fill")
- })
- })
- .transition()
- .duration(duration)
- .attr('width', measurez < 0 ?
- x1(0) - x1(measurez[0])
- : x1(measurez[0]) - x1(0))
- .attr('x', xp1(measurez));
-
- var h3 = availableHeight / 6;
-
- var markerData = markerz.map( function(marker, index) {
- return {value: marker, label: markerLabelz[index]}
- });
- gEnter
- .selectAll("path.nv-markerTriangle")
- .data(markerData)
- .enter()
- .append('path')
- .attr('class', 'nv-markerTriangle')
- .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
- .on('mouseover', function(d) {
- dispatch.elementMouseover({
- value: d.value,
- label: d.label || 'Previous',
- color: d3.select(this).style("fill"),
- pos: [x1(d.value), availableHeight/2]
- })
-
- })
- .on('mousemove', function(d) {
- dispatch.elementMousemove({
- value: d.value,
- label: d.label || 'Previous',
- color: d3.select(this).style("fill")
- })
- })
- .on('mouseout', function(d, i) {
- dispatch.elementMouseout({
- value: d.value,
- label: d.label || 'Previous',
- color: d3.select(this).style("fill")
- })
- });
-
- g.selectAll("path.nv-markerTriangle")
- .data(markerData)
- .transition()
- .duration(duration)
- .attr('transform', function(d) { return 'translate(' + x1(d.value) + ',' + (availableHeight / 2) + ')' });
-
- var markerLinesData = markerLinez.map( function(marker, index) {
- return {value: marker, label: markerLineLabelz[index]}
- });
- gEnter
- .selectAll("line.nv-markerLine")
- .data(markerLinesData)
- .enter()
- .append('line')
- .attr('cursor', '')
- .attr('class', 'nv-markerLine')
- .attr('x1', function(d) { return x1(d.value) })
- .attr('y1', '2')
- .attr('x2', function(d) { return x1(d.value) })
- .attr('y2', availableHeight - 2)
- .on('mouseover', function(d) {
- dispatch.elementMouseover({
- value: d.value,
- label: d.label || 'Previous',
- color: d3.select(this).style("fill"),
- pos: [x1(d.value), availableHeight/2]
- })
-
- })
- .on('mousemove', function(d) {
- dispatch.elementMousemove({
- value: d.value,
- label: d.label || 'Previous',
- color: d3.select(this).style("fill")
- })
- })
- .on('mouseout', function(d, i) {
- dispatch.elementMouseout({
- value: d.value,
- label: d.label || 'Previous',
- color: d3.select(this).style("fill")
- })
- });
-
- g.selectAll("line.nv-markerLine")
- .data(markerLinesData)
- .transition()
- .duration(duration)
- .attr('x1', function(d) { return x1(d.value) })
- .attr('x2', function(d) { return x1(d.value) });
-
- wrap.selectAll('.nv-range')
- .on('mouseover', function(d,i) {
- var label = rangeLabelz[i] || defaultRangeLabels[i];
- dispatch.elementMouseover({
- value: d,
- label: label,
- color: d3.select(this).style("fill")
- })
- })
- .on('mousemove', function() {
- dispatch.elementMousemove({
- value: measurez[0],
- label: measureLabelz[0] || 'Previous',
- color: d3.select(this).style("fill")
- })
- })
- .on('mouseout', function(d,i) {
- var label = rangeLabelz[i] || defaultRangeLabels[i];
- dispatch.elementMouseout({
- value: d,
- label: label,
- color: d3.select(this).style("fill")
- })
- });
- });
-
- return chart;
- }
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- chart.dispatch = dispatch;
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good)
- markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal)
- measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast)
- forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}},
- duration: {get: function(){return duration;}, set: function(_){duration=_;}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top !== undefined ? _.top : margin.top;
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
- orient = _;
- reverse = orient == 'right' || orient == 'bottom';
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- }}
- });
-
- nv.utils.initOptions(chart);
- return chart;
- };
-
-
-
- // Chart design based on the recommendations of Stephen Few. Implementation
- // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
- // http://projects.instantcognition.com/protovis/bulletchart/
- nv.models.bulletChart = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var bullet = nv.models.bullet();
- var tooltip = nv.models.tooltip();
-
- var orient = 'left' // TODO top & bottom
- , reverse = false
- , margin = {top: 5, right: 40, bottom: 20, left: 120}
- , ranges = function(d) { return d.ranges }
- , markers = function(d) { return d.markers ? d.markers : [] }
- , measures = function(d) { return d.measures }
- , width = null
- , height = 55
- , tickFormat = null
- , ticks = null
- , noData = null
- , dispatch = d3.dispatch()
- ;
-
- tooltip
- .duration(0)
- .headerEnabled(false);
-
- function chart(selection) {
- selection.each(function(d, i) {
- var container = d3.select(this);
- nv.utils.initSVG(container);
-
- var availableWidth = nv.utils.availableWidth(width, container, margin),
- availableHeight = height - margin.top - margin.bottom,
- that = this;
-
- chart.update = function() { chart(selection) };
- chart.container = this;
-
- // Display No Data message if there's nothing to show.
- if (!d || !ranges.call(this, d, i)) {
- nv.utils.noData(chart, container)
- return chart;
- } else {
- container.selectAll('.nv-noData').remove();
- }
-
- var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
- markerz = markers.call(this, d, i).slice().sort(d3.descending),
- measurez = measures.call(this, d, i).slice().sort(d3.descending);
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]);
- var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart');
- var gEnter = wrapEnter.append('g');
- var g = wrap.select('g');
-
- gEnter.append('g').attr('class', 'nv-bulletWrap');
- gEnter.append('g').attr('class', 'nv-titles');
-
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- // Compute the new x-scale.
- var x1 = d3.scale.linear()
- .domain([0, Math.max(rangez[0], (markerz[0] || 0), measurez[0])]) // TODO: need to allow forceX and forceY, and xDomain, yDomain
- .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
-
- // Retrieve the old x-scale, if this is an update.
- var x0 = this.__chart__ || d3.scale.linear()
- .domain([0, Infinity])
- .range(x1.range());
-
- // Stash the new scale.
- this.__chart__ = x1;
-
- var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
- w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
-
- var title = gEnter.select('.nv-titles').append('g')
- .attr('text-anchor', 'end')
- .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')');
- title.append('text')
- .attr('class', 'nv-title')
- .text(function(d) { return d.title; });
-
- title.append('text')
- .attr('class', 'nv-subtitle')
- .attr('dy', '1em')
- .text(function(d) { return d.subtitle; });
-
- bullet
- .width(availableWidth)
- .height(availableHeight);
-
- var bulletWrap = g.select('.nv-bulletWrap');
- d3.transition(bulletWrap).call(bullet);
-
- // Compute the tick format.
- var format = tickFormat || x1.tickFormat( availableWidth / 100 );
-
- // Update the tick groups.
- var tick = g.selectAll('g.nv-tick')
- .data(x1.ticks( ticks ? ticks : (availableWidth / 50) ), function(d) {
- return this.textContent || format(d);
- });
-
- // Initialize the ticks with the old scale, x0.
- var tickEnter = tick.enter().append('g')
- .attr('class', 'nv-tick')
- .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' })
- .style('opacity', 1e-6);
-
- tickEnter.append('line')
- .attr('y1', availableHeight)
- .attr('y2', availableHeight * 7 / 6);
-
- tickEnter.append('text')
- .attr('text-anchor', 'middle')
- .attr('dy', '1em')
- .attr('y', availableHeight * 7 / 6)
- .text(format);
-
- // Transition the updating ticks to the new scale, x1.
- var tickUpdate = d3.transition(tick)
- .transition()
- .duration(bullet.duration())
- .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
- .style('opacity', 1);
-
- tickUpdate.select('line')
- .attr('y1', availableHeight)
- .attr('y2', availableHeight * 7 / 6);
-
- tickUpdate.select('text')
- .attr('y', availableHeight * 7 / 6);
-
- // Transition the exiting ticks to the new scale, x1.
- d3.transition(tick.exit())
- .transition()
- .duration(bullet.duration())
- .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
- .style('opacity', 1e-6)
- .remove();
- });
-
- d3.timer.flush();
- return chart;
- }
-
- //============================================================
- // Event Handling/Dispatching (out of chart's scope)
- //------------------------------------------------------------
-
- bullet.dispatch.on('elementMouseover.tooltip', function(evt) {
- evt['series'] = {
- key: evt.label,
- value: evt.value,
- color: evt.color
- };
- tooltip.data(evt).hidden(false);
- });
-
- bullet.dispatch.on('elementMouseout.tooltip', function(evt) {
- tooltip.hidden(true);
- });
-
- bullet.dispatch.on('elementMousemove.tooltip', function(evt) {
- tooltip();
- });
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- chart.bullet = bullet;
- chart.dispatch = dispatch;
- chart.tooltip = tooltip;
-
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good)
- markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal)
- measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast)
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}},
- ticks: {get: function(){return ticks;}, set: function(_){ticks=_;}},
- noData: {get: function(){return noData;}, set: function(_){noData=_;}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top !== undefined ? _.top : margin.top;
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
- orient = _;
- reverse = orient == 'right' || orient == 'bottom';
- }}
- });
-
- nv.utils.inheritOptions(chart, bullet);
- nv.utils.initOptions(chart);
-
- return chart;
- };
-
-
-
- nv.models.candlestickBar = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var margin = {top: 0, right: 0, bottom: 0, left: 0}
- , width = null
- , height = null
- , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
- , container
- , x = d3.scale.linear()
- , y = d3.scale.linear()
- , getX = function(d) { return d.x }
- , getY = function(d) { return d.y }
- , getOpen = function(d) { return d.open }
- , getClose = function(d) { return d.close }
- , getHigh = function(d) { return d.high }
- , getLow = function(d) { return d.low }
- , forceX = []
- , forceY = []
- , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
- , clipEdge = true
- , color = nv.utils.defaultColor()
- , interactive = false
- , xDomain
- , yDomain
- , xRange
- , yRange
- , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove')
- ;
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- function chart(selection) {
- selection.each(function(data) {
- container = d3.select(this);
- var availableWidth = nv.utils.availableWidth(width, container, margin),
- availableHeight = nv.utils.availableHeight(height, container, margin);
-
- nv.utils.initSVG(container);
-
- // Width of the candlestick bars.
- var barWidth = (availableWidth / data[0].values.length) * .45;
-
- // Setup Scales
- x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
-
- if (padData)
- x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
- else
- x.range(xRange || [5 + barWidth / 2, availableWidth - barWidth / 2 - 5]);
-
- y.domain(yDomain || [
- d3.min(data[0].values.map(getLow).concat(forceY)),
- d3.max(data[0].values.map(getHigh).concat(forceY))
- ]
- ).range(yRange || [availableHeight, 0]);
-
- // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
- if (x.domain()[0] === x.domain()[1])
- x.domain()[0] ?
- x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
- : x.domain([-1,1]);
-
- if (y.domain()[0] === y.domain()[1])
- y.domain()[0] ?
- y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
- : y.domain([-1,1]);
-
- // Setup containers and skeleton of chart
- var wrap = d3.select(this).selectAll('g.nv-wrap.nv-candlestickBar').data([data[0].values]);
- var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-candlestickBar');
- var defsEnter = wrapEnter.append('defs');
- var gEnter = wrapEnter.append('g');
- var g = wrap.select('g');
-
- gEnter.append('g').attr('class', 'nv-ticks');
-
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- container
- .on('click', function(d,i) {
- dispatch.chartClick({
- data: d,
- index: i,
- pos: d3.event,
- id: id
- });
- });
-
- defsEnter.append('clipPath')
- .attr('id', 'nv-chart-clip-path-' + id)
- .append('rect');
-
- wrap.select('#nv-chart-clip-path-' + id + ' rect')
- .attr('width', availableWidth)
- .attr('height', availableHeight);
-
- g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
-
- var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
- .data(function(d) { return d });
- ticks.exit().remove();
-
- var tickGroups = ticks.enter().append('g');
-
- // The colors are currently controlled by CSS.
- ticks
- .attr('class', function(d, i, j) { return (getOpen(d, i) > getClose(d, i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i});
-
- var lines = tickGroups.append('line')
- .attr('class', 'nv-candlestick-lines')
- .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; })
- .attr('x1', 0)
- .attr('y1', function(d, i) { return y(getHigh(d, i)); })
- .attr('x2', 0)
- .attr('y2', function(d, i) { return y(getLow(d, i)); });
-
- var rects = tickGroups.append('rect')
- .attr('class', 'nv-candlestick-rects nv-bars')
- .attr('transform', function(d, i) {
- return 'translate(' + (x(getX(d, i)) - barWidth/2) + ','
- + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0))
- + ')';
- })
- .attr('x', 0)
- .attr('y', 0)
- .attr('width', barWidth)
- .attr('height', function(d, i) {
- var open = getOpen(d, i);
- var close = getClose(d, i);
- return open > close ? y(close) - y(open) : y(open) - y(close);
- });
-
- ticks.select('.nv-candlestick-lines').transition()
- .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; })
- .attr('x1', 0)
- .attr('y1', function(d, i) { return y(getHigh(d, i)); })
- .attr('x2', 0)
- .attr('y2', function(d, i) { return y(getLow(d, i)); });
-
- ticks.select('.nv-candlestick-rects').transition()
- .attr('transform', function(d, i) {
- return 'translate(' + (x(getX(d, i)) - barWidth/2) + ','
- + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0))
- + ')';
- })
- .attr('x', 0)
- .attr('y', 0)
- .attr('width', barWidth)
- .attr('height', function(d, i) {
- var open = getOpen(d, i);
- var close = getClose(d, i);
- return open > close ? y(close) - y(open) : y(open) - y(close);
- });
- });
-
- return chart;
- }
-
-
- //Create methods to allow outside functions to highlight a specific bar.
- chart.highlightPoint = function(pointIndex, isHoverOver) {
- chart.clearHighlights();
- container.select(".nv-candlestickBar .nv-tick-0-" + pointIndex)
- .classed("hover", isHoverOver)
- ;
- };
-
- chart.clearHighlights = function() {
- container.select(".nv-candlestickBar .nv-tick.hover")
- .classed("hover", false)
- ;
- };
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- chart.dispatch = dispatch;
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- xScale: {get: function(){return x;}, set: function(_){x=_;}},
- yScale: {get: function(){return y;}, set: function(_){y=_;}},
- xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
- yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
- xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
- yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
- forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
- forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
- padData: {get: function(){return padData;}, set: function(_){padData=_;}},
- clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
- id: {get: function(){return id;}, set: function(_){id=_;}},
- interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
-
- x: {get: function(){return getX;}, set: function(_){getX=_;}},
- y: {get: function(){return getY;}, set: function(_){getY=_;}},
- open: {get: function(){return getOpen();}, set: function(_){getOpen=_;}},
- close: {get: function(){return getClose();}, set: function(_){getClose=_;}},
- high: {get: function(){return getHigh;}, set: function(_){getHigh=_;}},
- low: {get: function(){return getLow;}, set: function(_){getLow=_;}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top != undefined ? _.top : margin.top;
- margin.right = _.right != undefined ? _.right : margin.right;
- margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
- margin.left = _.left != undefined ? _.left : margin.left;
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- }}
- });
-
- nv.utils.initOptions(chart);
- return chart;
- };
-
- nv.models.cumulativeLineChart = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var lines = nv.models.line()
- , xAxis = nv.models.axis()
- , yAxis = nv.models.axis()
- , legend = nv.models.legend()
- , controls = nv.models.legend()
- , interactiveLayer = nv.interactiveGuideline()
- , tooltip = nv.models.tooltip()
- ;
-
- var margin = {top: 30, right: 30, bottom: 50, left: 60}
- , marginTop = null
- , color = nv.utils.defaultColor()
- , width = null
- , height = null
- , showLegend = true
- , showXAxis = true
- , showYAxis = true
- , rightAlignYAxis = false
- , showControls = true
- , useInteractiveGuideline = false
- , rescaleY = true
- , x //can be accessed via chart.xScale()
- , y //can be accessed via chart.yScale()
- , id = lines.id()
- , state = nv.utils.state()
- , defaultState = null
- , noData = null
- , average = function(d) { return d.average }
- , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
- , transitionDuration = 250
- , duration = 250
- , noErrorCheck = false //if set to TRUE, will bypass an error check in the indexify function.
- ;
-
- state.index = 0;
- state.rescaleY = rescaleY;
-
- xAxis.orient('bottom').tickPadding(7);
- yAxis.orient((rightAlignYAxis) ? 'right' : 'left');
-
- tooltip.valueFormatter(function(d, i) {
- return yAxis.tickFormat()(d, i);
- }).headerFormatter(function(d, i) {
- return xAxis.tickFormat()(d, i);
- });
-
- controls.updateState(false);
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var dx = d3.scale.linear()
- , index = {i: 0, x: 0}
- , renderWatch = nv.utils.renderWatch(dispatch, duration)
- ;
-
- var stateGetter = function(data) {
- return function(){
- return {
- active: data.map(function(d) { return !d.disabled }),
- index: index.i,
- rescaleY: rescaleY
- };
- }
- };
-
- var stateSetter = function(data) {
- return function(state) {
- if (state.index !== undefined)
- index.i = state.index;
- if (state.rescaleY !== undefined)
- rescaleY = state.rescaleY;
- if (state.active !== undefined)
- data.forEach(function(series,i) {
- series.disabled = !state.active[i];
- });
- }
- };
-
- function chart(selection) {
- renderWatch.reset();
- renderWatch.models(lines);
- if (showXAxis) renderWatch.models(xAxis);
- if (showYAxis) renderWatch.models(yAxis);
- selection.each(function(data) {
- var container = d3.select(this);
- nv.utils.initSVG(container);
- container.classed('nv-chart-' + id, true);
- var that = this;
-
- var availableWidth = nv.utils.availableWidth(width, container, margin),
- availableHeight = nv.utils.availableHeight(height, container, margin);
-
- chart.update = function() {
- if (duration === 0)
- container.call(chart);
- else
- container.transition().duration(duration).call(chart)
- };
- chart.container = this;
-
- state
- .setter(stateSetter(data), chart.update)
- .getter(stateGetter(data))
- .update();
-
- // DEPRECATED set state.disableddisabled
- state.disabled = data.map(function(d) { return !!d.disabled });
-
- if (!defaultState) {
- var key;
- defaultState = {};
- for (key in state) {
- if (state[key] instanceof Array)
- defaultState[key] = state[key].slice(0);
- else
- defaultState[key] = state[key];
- }
- }
-
- var indexDrag = d3.behavior.drag()
- .on('dragstart', dragStart)
- .on('drag', dragMove)
- .on('dragend', dragEnd);
-
-
- function dragStart(d,i) {
- d3.select(chart.container)
- .style('cursor', 'ew-resize');
- }
-
- function dragMove(d,i) {
- index.x = d3.event.x;
- index.i = Math.round(dx.invert(index.x));
- updateZero();
- }
-
- function dragEnd(d,i) {
- d3.select(chart.container)
- .style('cursor', 'auto');
-
- // update state and send stateChange with new index
- state.index = index.i;
- dispatch.stateChange(state);
- }
-
- // Display No Data message if there's nothing to show.
- if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
- nv.utils.noData(chart, container)
- return chart;
- } else {
- container.selectAll('.nv-noData').remove();
- }
-
- // Setup Scales
- x = lines.xScale();
- y = lines.yScale();
-
- if (!rescaleY) {
- var seriesDomains = data
- .filter(function(series) { return !series.disabled })
- .map(function(series,i) {
- var initialDomain = d3.extent(series.values, lines.y());
-
- //account for series being disabled when losing 95% or more
- if (initialDomain[0] < -.95) initialDomain[0] = -.95;
-
- return [
- (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]),
- (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0])
- ];
- });
-
- var completeDomain = [
- d3.min(seriesDomains, function(d) { return d[0] }),
- d3.max(seriesDomains, function(d) { return d[1] })
- ];
-
- lines.yDomain(completeDomain);
- } else {
- lines.yDomain(null);
- }
-
- dx.domain([0, data[0].values.length - 1]) //Assumes all series have same length
- .range([0, availableWidth])
- .clamp(true);
-
- var data = indexify(index.i, data);
-
- // Setup containers and skeleton of chart
- var interactivePointerEvents = (useInteractiveGuideline) ? "none" : "all";
- var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]);
- var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g');
- var g = wrap.select('g');
-
- gEnter.append('g').attr('class', 'nv-interactive');
- gEnter.append('g').attr('class', 'nv-x nv-axis').style("pointer-events","none");
- gEnter.append('g').attr('class', 'nv-y nv-axis');
- gEnter.append('g').attr('class', 'nv-background');
- gEnter.append('g').attr('class', 'nv-linesWrap').style("pointer-events",interactivePointerEvents);
- gEnter.append('g').attr('class', 'nv-avgLinesWrap').style("pointer-events","none");
- gEnter.append('g').attr('class', 'nv-legendWrap');
- gEnter.append('g').attr('class', 'nv-controlsWrap');
-
- // Legend
- if (!showLegend) {
- g.select('.nv-legendWrap').selectAll('*').remove();
- } else {
- legend.width(availableWidth);
-
- g.select('.nv-legendWrap')
- .datum(data)
- .call(legend);
-
- if (!marginTop && legend.height() !== margin.top) {
- margin.top = legend.height();
- availableHeight = nv.utils.availableHeight(height, container, margin);
- }
-
- g.select('.nv-legendWrap')
- .attr('transform', 'translate(0,' + (-margin.top) +')')
- }
-
- // Controls
- if (!showControls) {
- g.select('.nv-controlsWrap').selectAll('*').remove();
- } else {
- var controlsData = [
- { key: 'Re-scale y-axis', disabled: !rescaleY }
- ];
-
- controls
- .width(140)
- .color(['#444', '#444', '#444'])
- .rightAlign(false)
- .margin({top: 5, right: 0, bottom: 5, left: 20})
- ;
-
- g.select('.nv-controlsWrap')
- .datum(controlsData)
- .attr('transform', 'translate(0,' + (-margin.top) +')')
- .call(controls);
- }
-
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- if (rightAlignYAxis) {
- g.select(".nv-y.nv-axis")
- .attr("transform", "translate(" + availableWidth + ",0)");
- }
-
- // Show error if series goes below 100%
- var tempDisabled = data.filter(function(d) { return d.tempDisabled });
-
- wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates
- if (tempDisabled.length) {
- wrap.append('text').attr('class', 'tempDisabled')
- .attr('x', availableWidth / 2)
- .attr('y', '-.71em')
- .style('text-anchor', 'end')
- .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.');
- }
-
- //Set up interactive layer
- if (useInteractiveGuideline) {
- interactiveLayer
- .width(availableWidth)
- .height(availableHeight)
- .margin({left:margin.left,top:margin.top})
- .svgContainer(container)
- .xScale(x);
- wrap.select(".nv-interactive").call(interactiveLayer);
- }
-
- gEnter.select('.nv-background')
- .append('rect');
-
- g.select('.nv-background rect')
- .attr('width', availableWidth)
- .attr('height', availableHeight);
-
- lines
- //.x(function(d) { return d.x })
- .y(function(d) { return d.display.y })
- .width(availableWidth)
- .height(availableHeight)
- .color(data.map(function(d,i) {
- return d.color || color(d, i);
- }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; }));
-
- var linesWrap = g.select('.nv-linesWrap')
- .datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled }));
-
- linesWrap.call(lines);
-
- //Store a series index number in the data array.
- data.forEach(function(d,i) {
- d.seriesIndex = i;
- });
-
- var avgLineData = data.filter(function(d) {
- return !d.disabled && !!average(d);
- });
-
- var avgLines = g.select(".nv-avgLinesWrap").selectAll("line")
- .data(avgLineData, function(d) { return d.key; });
-
- var getAvgLineY = function(d) {
- //If average lines go off the svg element, clamp them to the svg bounds.
- var yVal = y(average(d));
- if (yVal < 0) return 0;
- if (yVal > availableHeight) return availableHeight;
- return yVal;
- };
-
- avgLines.enter()
- .append('line')
- .style('stroke-width',2)
- .style('stroke-dasharray','10,10')
- .style('stroke',function (d,i) {
- return lines.color()(d,d.seriesIndex);
- })
- .attr('x1',0)
- .attr('x2',availableWidth)
- .attr('y1', getAvgLineY)
- .attr('y2', getAvgLineY);
-
- avgLines
- .style('stroke-opacity',function(d){
- //If average lines go offscreen, make them transparent
- var yVal = y(average(d));
- if (yVal < 0 || yVal > availableHeight) return 0;
- return 1;
- })
- .attr('x1',0)
- .attr('x2',availableWidth)
- .attr('y1', getAvgLineY)
- .attr('y2', getAvgLineY);
-
- avgLines.exit().remove();
-
- //Create index line
- var indexLine = linesWrap.selectAll('.nv-indexLine')
- .data([index]);
- indexLine.enter().append('rect').attr('class', 'nv-indexLine')
- .attr('width', 3)
- .attr('x', -2)
- .attr('fill', 'red')
- .attr('fill-opacity', .5)
- .style("pointer-events","all")
- .call(indexDrag);
-
- indexLine
- .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' })
- .attr('height', availableHeight);
-
- // Setup Axes
- if (showXAxis) {
- xAxis
- .scale(x)
- ._ticks( nv.utils.calcTicksX(availableWidth/70, data) )
- .tickSize(-availableHeight, 0);
-
- g.select('.nv-x.nv-axis')
- .attr('transform', 'translate(0,' + y.range()[0] + ')');
- g.select('.nv-x.nv-axis')
- .call(xAxis);
- }
-
- if (showYAxis) {
- yAxis
- .scale(y)
- ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
- .tickSize( -availableWidth, 0);
-
- g.select('.nv-y.nv-axis')
- .call(yAxis);
- }
-
- //============================================================
- // Event Handling/Dispatching (in chart's scope)
- //------------------------------------------------------------
-
- function updateZero() {
- indexLine
- .data([index]);
-
- //When dragging the index line, turn off line transitions.
- // Then turn them back on when done dragging.
- var oldDuration = chart.duration();
- chart.duration(0);
- chart.update();
- chart.duration(oldDuration);
- }
-
- g.select('.nv-background rect')
- .on('click', function() {
- index.x = d3.mouse(this)[0];
- index.i = Math.round(dx.invert(index.x));
-
- // update state and send stateChange with new index
- state.index = index.i;
- dispatch.stateChange(state);
-
- updateZero();
- });
-
- lines.dispatch.on('elementClick', function(e) {
- index.i = e.pointIndex;
- index.x = dx(index.i);
-
- // update state and send stateChange with new index
- state.index = index.i;
- dispatch.stateChange(state);
-
- updateZero();
- });
-
- controls.dispatch.on('legendClick', function(d,i) {
- d.disabled = !d.disabled;
- rescaleY = !d.disabled;
-
- state.rescaleY = rescaleY;
- dispatch.stateChange(state);
- chart.update();
- });
-
- legend.dispatch.on('stateChange', function(newState) {
- for (var key in newState)
- state[key] = newState[key];
- dispatch.stateChange(state);
- chart.update();
- });
-
- interactiveLayer.dispatch.on('elementMousemove', function(e) {
- lines.clearHighlights();
- var singlePoint, pointIndex, pointXLocation, allData = [];
-
- data
- .filter(function(series, i) {
- series.seriesIndex = i;
- return !series.disabled;
- })
- .forEach(function(series,i) {
- pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
- lines.highlightPoint(i, pointIndex, true);
- var point = series.values[pointIndex];
- if (typeof point === 'undefined') return;
- if (typeof singlePoint === 'undefined') singlePoint = point;
- if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
- allData.push({
- key: series.key,
- value: chart.y()(point, pointIndex),
- color: color(series,series.seriesIndex)
- });
- });
-
- //Highlight the tooltip entry based on which point the mouse is closest to.
- if (allData.length > 2) {
- var yValue = chart.yScale().invert(e.mouseY);
- var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
- var threshold = 0.03 * domainExtent;
- var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
- if (indexToHighlight !== null)
- allData[indexToHighlight].highlight = true;
- }
-
- var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex), pointIndex);
- interactiveLayer.tooltip
- .valueFormatter(function(d,i) {
- return yAxis.tickFormat()(d);
- })
- .data(
- {
- value: xValue,
- series: allData
- }
- )();
-
- interactiveLayer.renderGuideLine(pointXLocation);
- });
-
- interactiveLayer.dispatch.on("elementMouseout",function(e) {
- lines.clearHighlights();
- });
-
- // Update chart from a state object passed to event handler
- dispatch.on('changeState', function(e) {
- if (typeof e.disabled !== 'undefined') {
- data.forEach(function(series,i) {
- series.disabled = e.disabled[i];
- });
-
- state.disabled = e.disabled;
- }
-
- if (typeof e.index !== 'undefined') {
- index.i = e.index;
- index.x = dx(index.i);
-
- state.index = e.index;
-
- indexLine
- .data([index]);
- }
-
- if (typeof e.rescaleY !== 'undefined') {
- rescaleY = e.rescaleY;
- }
-
- chart.update();
- });
-
- });
-
- renderWatch.renderEnd('cumulativeLineChart immediate');
-
- return chart;
- }
-
- //============================================================
- // Event Handling/Dispatching (out of chart's scope)
- //------------------------------------------------------------
-
- lines.dispatch.on('elementMouseover.tooltip', function(evt) {
- var point = {
- x: chart.x()(evt.point),
- y: chart.y()(evt.point),
- color: evt.point.color
- };
- evt.point = point;
- tooltip.data(evt).hidden(false);
- });
-
- lines.dispatch.on('elementMouseout.tooltip', function(evt) {
- tooltip.hidden(true)
- });
-
- //============================================================
- // Functions
- //------------------------------------------------------------
-
- var indexifyYGetter = null;
- /* Normalize the data according to an index point. */
- function indexify(idx, data) {
- if (!indexifyYGetter) indexifyYGetter = lines.y();
- return data.map(function(line, i) {
- if (!line.values) {
- return line;
- }
- var indexValue = line.values[idx];
- if (indexValue == null) {
- return line;
- }
- var v = indexifyYGetter(indexValue, idx);
-
- //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue
- if (v < -.95 && !noErrorCheck) {
- //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100)
-
- line.tempDisabled = true;
- return line;
- }
-
- line.tempDisabled = false;
-
- line.values = line.values.map(function(point, pointIndex) {
- point.display = {'y': (indexifyYGetter(point, pointIndex) - v) / (1 + v) };
- return point;
- });
-
- return line;
- })
- }
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- // expose chart's sub-components
- chart.dispatch = dispatch;
- chart.lines = lines;
- chart.legend = legend;
- chart.controls = controls;
- chart.xAxis = xAxis;
- chart.yAxis = yAxis;
- chart.interactiveLayer = interactiveLayer;
- chart.state = state;
- chart.tooltip = tooltip;
-
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- rescaleY: {get: function(){return rescaleY;}, set: function(_){rescaleY=_;}},
- showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
- showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
- average: {get: function(){return average;}, set: function(_){average=_;}},
- defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
- noData: {get: function(){return noData;}, set: function(_){noData=_;}},
- showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
- showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
- noErrorCheck: {get: function(){return noErrorCheck;}, set: function(_){noErrorCheck=_;}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- if (_.top !== undefined) {
- margin.top = _.top;
- marginTop = _.top;
- }
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- legend.color(color);
- }},
- useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
- useInteractiveGuideline = _;
- if (_ === true) {
- chart.interactive(false);
- chart.useVoronoi(false);
- }
- }},
- rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
- rightAlignYAxis = _;
- yAxis.orient( (_) ? 'right' : 'left');
- }},
- duration: {get: function(){return duration;}, set: function(_){
- duration = _;
- lines.duration(duration);
- xAxis.duration(duration);
- yAxis.duration(duration);
- renderWatch.reset(duration);
- }}
- });
-
- nv.utils.inheritOptions(chart, lines);
- nv.utils.initOptions(chart);
-
- return chart;
- };
- //TODO: consider deprecating by adding necessary features to multiBar model
- nv.models.discreteBar = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var margin = {top: 0, right: 0, bottom: 0, left: 0}
- , width = 960
- , height = 500
- , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
- , container
- , x = d3.scale.ordinal()
- , y = d3.scale.linear()
- , getX = function(d) { return d.x }
- , getY = function(d) { return d.y }
- , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
- , color = nv.utils.defaultColor()
- , showValues = false
- , valueFormat = d3.format(',.2f')
- , xDomain
- , yDomain
- , xRange
- , yRange
- , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
- , rectClass = 'discreteBar'
- , duration = 250
- ;
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var x0, y0;
- var renderWatch = nv.utils.renderWatch(dispatch, duration);
-
- function chart(selection) {
- renderWatch.reset();
- selection.each(function(data) {
- var availableWidth = width - margin.left - margin.right,
- availableHeight = height - margin.top - margin.bottom;
-
- container = d3.select(this);
- nv.utils.initSVG(container);
-
- //add series index to each data point for reference
- data.forEach(function(series, i) {
- series.values.forEach(function(point) {
- point.series = i;
- });
- });
-
- // Setup Scales
- // remap and flatten the data for use in calculating the scales' domains
- var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
- data.map(function(d) {
- return d.values.map(function(d,i) {
- return { x: getX(d,i), y: getY(d,i), y0: d.y0 }
- })
- });
-
- x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
- .rangeBands(xRange || [0, availableWidth], .1);
- y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY)));
-
- // If showValues, pad the Y axis range to account for label height
- if (showValues) y.range(yRange || [availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]);
- else y.range(yRange || [availableHeight, 0]);
-
- //store old scales if they exist
- x0 = x0 || x;
- y0 = y0 || y.copy().range([y(0),y(0)]);
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]);
- var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar');
- var gEnter = wrapEnter.append('g');
- var g = wrap.select('g');
-
- gEnter.append('g').attr('class', 'nv-groups');
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later
- var groups = wrap.select('.nv-groups').selectAll('.nv-group')
- .data(function(d) { return d }, function(d) { return d.key });
- groups.enter().append('g')
- .style('stroke-opacity', 1e-6)
- .style('fill-opacity', 1e-6);
- groups.exit()
- .watchTransition(renderWatch, 'discreteBar: exit groups')
- .style('stroke-opacity', 1e-6)
- .style('fill-opacity', 1e-6)
- .remove();
- groups
- .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
- .classed('hover', function(d) { return d.hover });
- groups
- .watchTransition(renderWatch, 'discreteBar: groups')
- .style('stroke-opacity', 1)
- .style('fill-opacity', .75);
-
- var bars = groups.selectAll('g.nv-bar')
- .data(function(d) { return d.values });
- bars.exit().remove();
-
- var barsEnter = bars.enter().append('g')
- .attr('transform', function(d,i,j) {
- return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')'
- })
- .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
- d3.select(this).classed('hover', true);
- dispatch.elementMouseover({
- data: d,
- index: i,
- color: d3.select(this).style("fill")
- });
- })
- .on('mouseout', function(d,i) {
- d3.select(this).classed('hover', false);
- dispatch.elementMouseout({
- data: d,
- index: i,
- color: d3.select(this).style("fill")
- });
- })
- .on('mousemove', function(d,i) {
- dispatch.elementMousemove({
- data: d,
- index: i,
- color: d3.select(this).style("fill")
- });
- })
- .on('click', function(d,i) {
- var element = this;
- dispatch.elementClick({
- data: d,
- index: i,
- color: d3.select(this).style("fill"),
- event: d3.event,
- element: element
- });
- d3.event.stopPropagation();
- })
- .on('dblclick', function(d,i) {
- dispatch.elementDblClick({
- data: d,
- index: i,
- color: d3.select(this).style("fill")
- });
- d3.event.stopPropagation();
- });
-
- barsEnter.append('rect')
- .attr('height', 0)
- .attr('width', x.rangeBand() * .9 / data.length )
-
- if (showValues) {
- barsEnter.append('text')
- .attr('text-anchor', 'middle')
- ;
-
- bars.select('text')
- .text(function(d,i) { return valueFormat(getY(d,i)) })
- .watchTransition(renderWatch, 'discreteBar: bars text')
- .attr('x', x.rangeBand() * .9 / 2)
- .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 })
-
- ;
- } else {
- bars.selectAll('text').remove();
- }
-
- bars
- .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' })
- .style('fill', function(d,i) { return d.color || color(d,i) })
- .style('stroke', function(d,i) { return d.color || color(d,i) })
- .select('rect')
- .attr('class', rectClass)
- .watchTransition(renderWatch, 'discreteBar: bars rect')
- .attr('width', x.rangeBand() * .9 / data.length);
- bars.watchTransition(renderWatch, 'discreteBar: bars')
- //.delay(function(d,i) { return i * 1200 / data[0].values.length })
- .attr('transform', function(d,i) {
- var left = x(getX(d,i)) + x.rangeBand() * .05,
- top = getY(d,i) < 0 ?
- y(0) :
- y(0) - y(getY(d,i)) < 1 ?
- y(0) - 1 : //make 1 px positive bars show up above y=0
- y(getY(d,i));
-
- return 'translate(' + left + ', ' + top + ')'
- })
- .select('rect')
- .attr('height', function(d,i) {
- return Math.max(Math.abs(y(getY(d,i)) - y(0)), 1)
- });
-
-
- //store old scales for use in transitions on update
- x0 = x.copy();
- y0 = y.copy();
-
- });
-
- renderWatch.renderEnd('discreteBar immediate');
- return chart;
- }
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- chart.dispatch = dispatch;
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
- showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}},
- x: {get: function(){return getX;}, set: function(_){getX=_;}},
- y: {get: function(){return getY;}, set: function(_){getY=_;}},
- xScale: {get: function(){return x;}, set: function(_){x=_;}},
- yScale: {get: function(){return y;}, set: function(_){y=_;}},
- xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
- yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
- xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
- yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
- valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
- id: {get: function(){return id;}, set: function(_){id=_;}},
- rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top !== undefined ? _.top : margin.top;
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- }},
- duration: {get: function(){return duration;}, set: function(_){
- duration = _;
- renderWatch.reset(duration);
- }}
- });
-
- nv.utils.initOptions(chart);
-
- return chart;
- };
-
- nv.models.discreteBarChart = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var discretebar = nv.models.discreteBar()
- , xAxis = nv.models.axis()
- , yAxis = nv.models.axis()
- , legend = nv.models.legend()
- , tooltip = nv.models.tooltip()
- ;
-
- var margin = {top: 15, right: 10, bottom: 50, left: 60}
- , marginTop = null
- , width = null
- , height = null
- , color = nv.utils.getColor()
- , showLegend = false
- , showXAxis = true
- , showYAxis = true
- , rightAlignYAxis = false
- , staggerLabels = false
- , wrapLabels = false
- , rotateLabels = 0
- , x
- , y
- , noData = null
- , dispatch = d3.dispatch('beforeUpdate','renderEnd')
- , duration = 250
- ;
-
- xAxis
- .orient('bottom')
- .showMaxMin(false)
- .tickFormat(function(d) { return d })
- ;
- yAxis
- .orient((rightAlignYAxis) ? 'right' : 'left')
- .tickFormat(d3.format(',.1f'))
- ;
-
- tooltip
- .duration(0)
- .headerEnabled(false)
- .valueFormatter(function(d, i) {
- return yAxis.tickFormat()(d, i);
- })
- .keyFormatter(function(d, i) {
- return xAxis.tickFormat()(d, i);
- });
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var renderWatch = nv.utils.renderWatch(dispatch, duration);
-
- function chart(selection) {
- renderWatch.reset();
- renderWatch.models(discretebar);
- if (showXAxis) renderWatch.models(xAxis);
- if (showYAxis) renderWatch.models(yAxis);
-
- selection.each(function(data) {
- var container = d3.select(this),
- that = this;
- nv.utils.initSVG(container);
- var availableWidth = nv.utils.availableWidth(width, container, margin),
- availableHeight = nv.utils.availableHeight(height, container, margin);
-
- chart.update = function() {
- dispatch.beforeUpdate();
- container.transition().duration(duration).call(chart);
- };
- chart.container = this;
-
- // Display No Data message if there's nothing to show.
- if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
- nv.utils.noData(chart, container);
- return chart;
- } else {
- container.selectAll('.nv-noData').remove();
- }
-
- // Setup Scales
- x = discretebar.xScale();
- y = discretebar.yScale().clamp(true);
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]);
- var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g');
- var defsEnter = gEnter.append('defs');
- var g = wrap.select('g');
-
- gEnter.append('g').attr('class', 'nv-x nv-axis');
- gEnter.append('g').attr('class', 'nv-y nv-axis')
- .append('g').attr('class', 'nv-zeroLine')
- .append('line');
-
- gEnter.append('g').attr('class', 'nv-barsWrap');
- gEnter.append('g').attr('class', 'nv-legendWrap');
-
- g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- // Legend
- if (!showLegend) {
- g.select('.nv-legendWrap').selectAll('*').remove();
- } else {
- legend.width(availableWidth);
-
- g.select('.nv-legendWrap')
- .datum(data)
- .call(legend);
-
- if (!marginTop && legend.height() !== margin.top) {
- margin.top = legend.height();
- availableHeight = nv.utils.availableHeight(height, container, margin);
- }
-
- wrap.select('.nv-legendWrap')
- .attr('transform', 'translate(0,' + (-margin.top) +')')
- }
-
- if (rightAlignYAxis) {
- g.select(".nv-y.nv-axis")
- .attr("transform", "translate(" + availableWidth + ",0)");
- }
-
- // Main Chart Component(s)
- discretebar
- .width(availableWidth)
- .height(availableHeight);
-
- var barsWrap = g.select('.nv-barsWrap')
- .datum(data.filter(function(d) { return !d.disabled }));
-
- barsWrap.transition().call(discretebar);
-
-
- defsEnter.append('clipPath')
- .attr('id', 'nv-x-label-clip-' + discretebar.id())
- .append('rect');
-
- g.select('#nv-x-label-clip-' + discretebar.id() + ' rect')
- .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
- .attr('height', 16)
- .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
-
- // Setup Axes
- if (showXAxis) {
- xAxis
- .scale(x)
- ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
- .tickSize(-availableHeight, 0);
-
- g.select('.nv-x.nv-axis')
- .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')');
- g.select('.nv-x.nv-axis').call(xAxis);
-
- var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
- if (staggerLabels) {
- xTicks
- .selectAll('text')
- .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
- }
-
- if (rotateLabels) {
- xTicks
- .selectAll('.tick text')
- .attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
- .style('text-anchor', rotateLabels > 0 ? 'start' : 'end');
- }
-
- if (wrapLabels) {
- g.selectAll('.tick text')
- .call(nv.utils.wrapTicks, chart.xAxis.rangeBand())
- }
- }
-
- if (showYAxis) {
- yAxis
- .scale(y)
- ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
- .tickSize( -availableWidth, 0);
-
- g.select('.nv-y.nv-axis').call(yAxis);
- }
-
- // Zero line
- g.select(".nv-zeroLine line")
- .attr("x1",0)
- .attr("x2",(rightAlignYAxis) ? -availableWidth : availableWidth)
- .attr("y1", y(0))
- .attr("y2", y(0))
- ;
- });
-
- renderWatch.renderEnd('discreteBar chart immediate');
- return chart;
- }
-
- //============================================================
- // Event Handling/Dispatching (out of chart's scope)
- //------------------------------------------------------------
-
- discretebar.dispatch.on('elementMouseover.tooltip', function(evt) {
- evt['series'] = {
- key: chart.x()(evt.data),
- value: chart.y()(evt.data),
- color: evt.color
- };
- tooltip.data(evt).hidden(false);
- });
-
- discretebar.dispatch.on('elementMouseout.tooltip', function(evt) {
- tooltip.hidden(true);
- });
-
- discretebar.dispatch.on('elementMousemove.tooltip', function(evt) {
- tooltip();
- });
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- chart.dispatch = dispatch;
- chart.discretebar = discretebar;
- chart.legend = legend;
- chart.xAxis = xAxis;
- chart.yAxis = yAxis;
- chart.tooltip = tooltip;
-
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
- staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
- rotateLabels: {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
- wrapLabels: {get: function(){return wrapLabels;}, set: function(_){wrapLabels=!!_;}},
- showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
- showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
- noData: {get: function(){return noData;}, set: function(_){noData=_;}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- if (_.top !== undefined) {
- margin.top = _.top;
- marginTop = _.top;
- }
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- duration: {get: function(){return duration;}, set: function(_){
- duration = _;
- renderWatch.reset(duration);
- discretebar.duration(duration);
- xAxis.duration(duration);
- yAxis.duration(duration);
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- discretebar.color(color);
- legend.color(color);
- }},
- rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
- rightAlignYAxis = _;
- yAxis.orient( (_) ? 'right' : 'left');
- }}
- });
-
- nv.utils.inheritOptions(chart, discretebar);
- nv.utils.initOptions(chart);
-
- return chart;
- }
-
- nv.models.distribution = function() {
- "use strict";
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var margin = {top: 0, right: 0, bottom: 0, left: 0}
- , width = 400 //technically width or height depending on x or y....
- , size = 8
- , axis = 'x' // 'x' or 'y'... horizontal or vertical
- , getData = function(d) { return d[axis] } // defaults d.x or d.y
- , color = nv.utils.defaultColor()
- , scale = d3.scale.linear()
- , domain
- , duration = 250
- , dispatch = d3.dispatch('renderEnd')
- ;
-
- //============================================================
-
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var scale0;
- var renderWatch = nv.utils.renderWatch(dispatch, duration);
-
- //============================================================
-
-
- function chart(selection) {
- renderWatch.reset();
- selection.each(function(data) {
- var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom),
- naxis = axis == 'x' ? 'y' : 'x',
- container = d3.select(this);
- nv.utils.initSVG(container);
-
- //------------------------------------------------------------
- // Setup Scales
-
- scale0 = scale0 || scale;
-
- //------------------------------------------------------------
-
-
- //------------------------------------------------------------
- // Setup containers and skeleton of chart
-
- var wrap = container.selectAll('g.nv-distribution').data([data]);
- var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution');
- var gEnter = wrapEnter.append('g');
- var g = wrap.select('g');
-
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
-
- //------------------------------------------------------------
-
-
- var distWrap = g.selectAll('g.nv-dist')
- .data(function(d) { return d }, function(d) { return d.key });
-
- distWrap.enter().append('g');
- distWrap
- .attr('class', function(d,i) { return 'nv-dist nv-series-' + i })
- .style('stroke', function(d,i) { return color(d, i) });
-
- var dist = distWrap.selectAll('line.nv-dist' + axis)
- .data(function(d) { return d.values })
- dist.enter().append('line')
- .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) })
- .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) })
- renderWatch.transition(distWrap.exit().selectAll('line.nv-dist' + axis), 'dist exit')
- // .transition()
- .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
- .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
- .style('stroke-opacity', 0)
- .remove();
- dist
- .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i })
- .attr(naxis + '1', 0)
- .attr(naxis + '2', size);
- renderWatch.transition(dist, 'dist')
- // .transition()
- .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
- .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
-
-
- scale0 = scale.copy();
-
- });
- renderWatch.renderEnd('distribution immediate');
- return chart;
- }
-
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
- chart.options = nv.utils.optionsFunc.bind(chart);
- chart.dispatch = dispatch;
-
- chart.margin = function(_) {
- if (!arguments.length) return margin;
- margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
- margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
- margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
- margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
- return chart;
- };
-
- chart.width = function(_) {
- if (!arguments.length) return width;
- width = _;
- return chart;
- };
-
- chart.axis = function(_) {
- if (!arguments.length) return axis;
- axis = _;
- return chart;
- };
-
- chart.size = function(_) {
- if (!arguments.length) return size;
- size = _;
- return chart;
- };
-
- chart.getData = function(_) {
- if (!arguments.length) return getData;
- getData = d3.functor(_);
- return chart;
- };
-
- chart.scale = function(_) {
- if (!arguments.length) return scale;
- scale = _;
- return chart;
- };
-
- chart.color = function(_) {
- if (!arguments.length) return color;
- color = nv.utils.getColor(_);
- return chart;
- };
-
- chart.duration = function(_) {
- if (!arguments.length) return duration;
- duration = _;
- renderWatch.reset(duration);
- return chart;
- };
- //============================================================
-
-
- return chart;
- }
- nv.models.focus = function(content) {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var content = content || nv.models.line()
- , xAxis = nv.models.axis()
- , yAxis = nv.models.axis()
- , brush = d3.svg.brush()
- ;
-
- var margin = {top: 10, right: 0, bottom: 30, left: 0}
- , color = nv.utils.defaultColor()
- , width = null
- , height = 70
- , showXAxis = true
- , showYAxis = false
- , rightAlignYAxis = false
- , ticks = null
- , x
- , y
- , brushExtent = null
- , duration = 250
- , dispatch = d3.dispatch('brush', 'onBrush', 'renderEnd')
- , syncBrushing = true
- ;
-
- content.interactive(false);
- content.pointActive(function(d) { return false; });
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var renderWatch = nv.utils.renderWatch(dispatch, duration);
-
- function chart(selection) {
- renderWatch.reset();
- renderWatch.models(content);
- if (showXAxis) renderWatch.models(xAxis);
- if (showYAxis) renderWatch.models(yAxis);
-
- selection.each(function(data) {
- var container = d3.select(this);
- nv.utils.initSVG(container);
- var availableWidth = nv.utils.availableWidth(width, container, margin),
- availableHeight = height - margin.top - margin.bottom;
-
- chart.update = function() {
- if( duration === 0 ) {
- container.call( chart );
- } else {
- container.transition().duration(duration).call(chart);
- }
- };
- chart.container = this;
-
- // Setup Scales
- x = content.xScale();
- y = content.yScale();
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-focus').data([data]);
- var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-focus').append('g');
- var g = wrap.select('g');
-
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- gEnter.append('g').attr('class', 'nv-background').append('rect');
- gEnter.append('g').attr('class', 'nv-x nv-axis');
- gEnter.append('g').attr('class', 'nv-y nv-axis');
- gEnter.append('g').attr('class', 'nv-contentWrap');
- gEnter.append('g').attr('class', 'nv-brushBackground');
- gEnter.append('g').attr('class', 'nv-x nv-brush');
-
- if (rightAlignYAxis) {
- g.select(".nv-y.nv-axis")
- .attr("transform", "translate(" + availableWidth + ",0)");
- }
-
- g.select('.nv-background rect')
- .attr('width', availableWidth)
- .attr('height', availableHeight);
-
- content
- .width(availableWidth)
- .height(availableHeight)
- .color(data.map(function(d,i) {
- return d.color || color(d, i);
- }).filter(function(d,i) { return !data[i].disabled; }));
-
- var contentWrap = g.select('.nv-contentWrap')
- .datum(data.filter(function(d) { return !d.disabled; }));
-
- d3.transition(contentWrap).call(content);
-
- // Setup Brush
- brush
- .x(x)
- .on('brush', function() {
- onBrush(syncBrushing);
- });
-
- brush.on('brushend', function () {
- if (!syncBrushing) {
- dispatch.onBrush(brush.empty() ? x.domain() : brush.extent());
- }
- });
-
- if (brushExtent) brush.extent(brushExtent);
-
- var brushBG = g.select('.nv-brushBackground').selectAll('g')
- .data([brushExtent || brush.extent()]);
-
- var brushBGenter = brushBG.enter()
- .append('g');
-
- brushBGenter.append('rect')
- .attr('class', 'left')
- .attr('x', 0)
- .attr('y', 0)
- .attr('height', availableHeight);
-
- brushBGenter.append('rect')
- .attr('class', 'right')
- .attr('x', 0)
- .attr('y', 0)
- .attr('height', availableHeight);
-
- var gBrush = g.select('.nv-x.nv-brush')
- .call(brush);
- gBrush.selectAll('rect')
- .attr('height', availableHeight);
- gBrush.selectAll('.resize').append('path').attr('d', resizePath);
-
- onBrush(true);
-
- g.select('.nv-background rect')
- .attr('width', availableWidth)
- .attr('height', availableHeight);
-
- if (showXAxis) {
- xAxis.scale(x)
- ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
- .tickSize(-availableHeight, 0);
-
- g.select('.nv-x.nv-axis')
- .attr('transform', 'translate(0,' + y.range()[0] + ')');
- d3.transition(g.select('.nv-x.nv-axis'))
- .call(xAxis);
- }
-
- if (showYAxis) {
- yAxis
- .scale(y)
- ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
- .tickSize( -availableWidth, 0);
-
- d3.transition(g.select('.nv-y.nv-axis'))
- .call(yAxis);
- }
-
- g.select('.nv-x.nv-axis')
- .attr('transform', 'translate(0,' + y.range()[0] + ')');
-
- //============================================================
- // Event Handling/Dispatching (in chart's scope)
- //------------------------------------------------------------
-
- //============================================================
- // Functions
- //------------------------------------------------------------
-
- // Taken from crossfilter (http://square.github.com/crossfilter/)
- function resizePath(d) {
- var e = +(d == 'e'),
- x = e ? 1 : -1,
- y = availableHeight / 3;
- return 'M' + (0.5 * x) + ',' + y
- + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
- + 'V' + (2 * y - 6)
- + 'A6,6 0 0 ' + e + ' ' + (0.5 * x) + ',' + (2 * y)
- + 'Z'
- + 'M' + (2.5 * x) + ',' + (y + 8)
- + 'V' + (2 * y - 8)
- + 'M' + (4.5 * x) + ',' + (y + 8)
- + 'V' + (2 * y - 8);
- }
-
-
- function updateBrushBG() {
- if (!brush.empty()) brush.extent(brushExtent);
- brushBG
- .data([brush.empty() ? x.domain() : brushExtent])
- .each(function(d,i) {
- var leftWidth = x(d[0]) - x.range()[0],
- rightWidth = availableWidth - x(d[1]);
- d3.select(this).select('.left')
- .attr('width', leftWidth < 0 ? 0 : leftWidth);
-
- d3.select(this).select('.right')
- .attr('x', x(d[1]))
- .attr('width', rightWidth < 0 ? 0 : rightWidth);
- });
- }
-
-
- function onBrush(shouldDispatch) {
- brushExtent = brush.empty() ? null : brush.extent();
- var extent = brush.empty() ? x.domain() : brush.extent();
- dispatch.brush({extent: extent, brush: brush});
- updateBrushBG();
- if (shouldDispatch) {
- dispatch.onBrush(extent);
- }
- }
- });
-
- renderWatch.renderEnd('focus immediate');
- return chart;
- }
-
-
- //============================================================
- // Event Handling/Dispatching (out of chart's scope)
- //------------------------------------------------------------
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- // expose chart's sub-components
- chart.dispatch = dispatch;
- chart.content = content;
- chart.brush = brush;
- chart.xAxis = xAxis;
- chart.yAxis = yAxis;
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
- showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
- brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
- syncBrushing: {get: function(){return syncBrushing;}, set: function(_){syncBrushing=_;}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top !== undefined ? _.top : margin.top;
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- duration: {get: function(){return duration;}, set: function(_){
- duration = _;
- renderWatch.reset(duration);
- content.duration(duration);
- xAxis.duration(duration);
- yAxis.duration(duration);
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- content.color(color);
- }},
- interpolate: {get: function(){return content.interpolate();}, set: function(_){
- content.interpolate(_);
- }},
- xTickFormat: {get: function(){return xAxis.tickFormat();}, set: function(_){
- xAxis.tickFormat(_);
- }},
- yTickFormat: {get: function(){return yAxis.tickFormat();}, set: function(_){
- yAxis.tickFormat(_);
- }},
- x: {get: function(){return content.x();}, set: function(_){
- content.x(_);
- }},
- y: {get: function(){return content.y();}, set: function(_){
- content.y(_);
- }},
- rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
- rightAlignYAxis = _;
- yAxis.orient( rightAlignYAxis ? 'right' : 'left');
- }}
- });
-
- nv.utils.inheritOptions(chart, content);
- nv.utils.initOptions(chart);
-
- return chart;
- };
- nv.models.forceDirectedGraph = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
- var margin = {top: 2, right: 0, bottom: 2, left: 0}
- , width = 400
- , height = 32
- , container = null
- , dispatch = d3.dispatch('renderEnd')
- , color = nv.utils.getColor(['#000'])
- , tooltip = nv.models.tooltip()
- , noData = null
- // Force directed graph specific parameters [default values]
- , linkStrength = 0.1
- , friction = 0.9
- , linkDist = 30
- , charge = -120
- , gravity = 0.1
- , theta = 0.8
- , alpha = 0.1
- , radius = 5
- // These functions allow to add extra attributes to ndes and links
- ,nodeExtras = function(nodes) { /* Do nothing */ }
- ,linkExtras = function(links) { /* Do nothing */ }
- ;
-
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var renderWatch = nv.utils.renderWatch(dispatch);
-
- function chart(selection) {
- renderWatch.reset();
-
- selection.each(function(data) {
- container = d3.select(this);
- nv.utils.initSVG(container);
-
- var availableWidth = nv.utils.availableWidth(width, container, margin),
- availableHeight = nv.utils.availableHeight(height, container, margin);
-
- container
- .attr("width", availableWidth)
- .attr("height", availableHeight);
-
- // Display No Data message if there's nothing to show.
- if (!data || !data.links || !data.nodes) {
- nv.utils.noData(chart, container)
- return chart;
- } else {
- container.selectAll('.nv-noData').remove();
- }
- container.selectAll('*').remove();
-
- // Collect names of all fields in the nodes
- var nodeFieldSet = new Set();
- data.nodes.forEach(function(node) {
- var keys = Object.keys(node);
- keys.forEach(function(key) {
- nodeFieldSet.add(key);
- });
- });
-
- var force = d3.layout.force()
- .nodes(data.nodes)
- .links(data.links)
- .size([availableWidth, availableHeight])
- .linkStrength(linkStrength)
- .friction(friction)
- .linkDistance(linkDist)
- .charge(charge)
- .gravity(gravity)
- .theta(theta)
- .alpha(alpha)
- .start();
-
- var link = container.selectAll(".link")
- .data(data.links)
- .enter().append("line")
- .attr("class", "nv-force-link")
- .style("stroke-width", function(d) { return Math.sqrt(d.value); });
-
- var node = container.selectAll(".node")
- .data(data.nodes)
- .enter()
- .append("g")
- .attr("class", "nv-force-node")
- .call(force.drag);
-
- node
- .append("circle")
- .attr("r", radius)
- .style("fill", function(d) { return color(d) } )
- .on("mouseover", function(evt) {
- container.select('.nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex)
- .attr('y1', evt.py);
- container.select('.nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
- .attr('x2', evt.px);
-
- // Add 'series' object to
- var nodeColor = color(evt);
- evt.series = [];
- nodeFieldSet.forEach(function(field) {
- evt.series.push({
- color: nodeColor,
- key: field,
- value: evt[field]
- });
- });
- tooltip.data(evt).hidden(false);
- })
- .on("mouseout", function(d) {
- tooltip.hidden(true);
- });
-
- tooltip.headerFormatter(function(d) {return "Node";});
-
- // Apply extra attributes to nodes and links (if any)
- linkExtras(link);
- nodeExtras(node);
-
- force.on("tick", function() {
- link.attr("x1", function(d) { return d.source.x; })
- .attr("y1", function(d) { return d.source.y; })
- .attr("x2", function(d) { return d.target.x; })
- .attr("y2", function(d) { return d.target.y; });
-
- node.attr("transform", function(d) {
- return "translate(" + d.x + ", " + d.y + ")";
- });
- });
- });
-
- return chart;
- }
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
-
- // Force directed graph specific parameters
- linkStrength:{get: function(){return linkStrength;}, set: function(_){linkStrength=_;}},
- friction: {get: function(){return friction;}, set: function(_){friction=_;}},
- linkDist: {get: function(){return linkDist;}, set: function(_){linkDist=_;}},
- charge: {get: function(){return charge;}, set: function(_){charge=_;}},
- gravity: {get: function(){return gravity;}, set: function(_){gravity=_;}},
- theta: {get: function(){return theta;}, set: function(_){theta=_;}},
- alpha: {get: function(){return alpha;}, set: function(_){alpha=_;}},
- radius: {get: function(){return radius;}, set: function(_){radius=_;}},
-
- //functor options
- x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}},
- y: {get: function(){return getY;}, set: function(_){getY=d3.functor(_);}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top !== undefined ? _.top : margin.top;
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- }},
- noData: {get: function(){return noData;}, set: function(_){noData=_;}},
- nodeExtras: {get: function(){return nodeExtras;}, set: function(_){
- nodeExtras = _;
- }},
- linkExtras: {get: function(){return linkExtras;}, set: function(_){
- linkExtras = _;
- }}
- });
-
- chart.dispatch = dispatch;
- chart.tooltip = tooltip;
- nv.utils.initOptions(chart);
- return chart;
- };
- nv.models.furiousLegend = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var margin = {top: 5, right: 0, bottom: 5, left: 0}
- , width = 400
- , height = 20
- , getKey = function(d) { return d.key }
- , keyFormatter = function (d) { return d }
- , color = nv.utils.getColor()
- , maxKeyLength = 20 //default value for key lengths
- , align = true
- , padding = 28 //define how much space between legend items. - recommend 32 for furious version
- , rightAlign = true
- , updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
- , radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
- , expanded = false
- , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
- , vers = 'classic' //Options are "classic" and "furious"
- ;
-
- function chart(selection) {
- selection.each(function(data) {
- var availableWidth = width - margin.left - margin.right,
- container = d3.select(this);
- nv.utils.initSVG(container);
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-legend').data([data]);
- var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
- var g = wrap.select('g');
-
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- var series = g.selectAll('.nv-series')
- .data(function(d) {
- if(vers != 'furious') return d;
-
- return d.filter(function(n) {
- return expanded ? true : !n.disengaged;
- });
- });
- var seriesEnter = series.enter().append('g').attr('class', 'nv-series')
-
- var seriesShape;
-
- if(vers == 'classic') {
- seriesEnter.append('circle')
- .style('stroke-width', 2)
- .attr('class','nv-legend-symbol')
- .attr('r', 5);
-
- seriesShape = series.select('circle');
- } else if (vers == 'furious') {
- seriesEnter.append('rect')
- .style('stroke-width', 2)
- .attr('class','nv-legend-symbol')
- .attr('rx', 3)
- .attr('ry', 3);
-
- seriesShape = series.select('rect');
-
- seriesEnter.append('g')
- .attr('class', 'nv-check-box')
- .property('innerHTML','<path d="M0.5,5 L22.5,5 L22.5,26.5 L0.5,26.5 L0.5,5 Z" class="nv-box"></path><path d="M5.5,12.8618467 L11.9185089,19.2803556 L31,0.198864511" class="nv-check"></path>')
- .attr('transform', 'translate(-10,-8)scale(0.5)');
-
- var seriesCheckbox = series.select('.nv-check-box');
-
- seriesCheckbox.each(function(d,i) {
- d3.select(this).selectAll('path')
- .attr('stroke', setTextColor(d,i));
- });
- }
-
- seriesEnter.append('text')
- .attr('text-anchor', 'start')
- .attr('class','nv-legend-text')
- .attr('dy', '.32em')
- .attr('dx', '8');
-
- var seriesText = series.select('text.nv-legend-text');
-
- series
- .on('mouseover', function(d,i) {
- dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects
- })
- .on('mouseout', function(d,i) {
- dispatch.legendMouseout(d,i);
- })
- .on('click', function(d,i) {
- dispatch.legendClick(d,i);
- // make sure we re-get data in case it was modified
- var data = series.data();
- if (updateState) {
- if(vers =='classic') {
- if (radioButtonMode) {
- //Radio button mode: set every series to disabled,
- // and enable the clicked series.
- data.forEach(function(series) { series.disabled = true});
- d.disabled = false;
- }
- else {
- d.disabled = !d.disabled;
- if (data.every(function(series) { return series.disabled})) {
- //the default behavior of NVD3 legends is, if every single series
- // is disabled, turn all series' back on.
- data.forEach(function(series) { series.disabled = false});
- }
- }
- } else if(vers == 'furious') {
- if(expanded) {
- d.disengaged = !d.disengaged;
- d.userDisabled = d.userDisabled == undefined ? !!d.disabled : d.userDisabled;
- d.disabled = d.disengaged || d.userDisabled;
- } else if (!expanded) {
- d.disabled = !d.disabled;
- d.userDisabled = d.disabled;
- var engaged = data.filter(function(d) { return !d.disengaged; });
- if (engaged.every(function(series) { return series.userDisabled })) {
- //the default behavior of NVD3 legends is, if every single series
- // is disabled, turn all series' back on.
- data.forEach(function(series) {
- series.disabled = series.userDisabled = false;
- });
- }
- }
- }
- dispatch.stateChange({
- disabled: data.map(function(d) { return !!d.disabled }),
- disengaged: data.map(function(d) { return !!d.disengaged })
- });
-
- }
- })
- .on('dblclick', function(d,i) {
- if(vers == 'furious' && expanded) return;
- dispatch.legendDblclick(d,i);
- if (updateState) {
- // make sure we re-get data in case it was modified
- var data = series.data();
- //the default behavior of NVD3 legends, when double clicking one,
- // is to set all other series' to false, and make the double clicked series enabled.
- data.forEach(function(series) {
- series.disabled = true;
- if(vers == 'furious') series.userDisabled = series.disabled;
- });
- d.disabled = false;
- if(vers == 'furious') d.userDisabled = d.disabled;
- dispatch.stateChange({
- disabled: data.map(function(d) { return !!d.disabled })
- });
- }
- });
-
- series.classed('nv-disabled', function(d) { return d.userDisabled });
- series.exit().remove();
-
- seriesText
- .attr('fill', setTextColor)
- .text(function (d) { return keyFormatter(getKey(d)) });
-
- //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
- // NEW ALIGNING CODE, TODO: clean up
-
- var versPadding;
- switch(vers) {
- case 'furious' :
- versPadding = 23;
- break;
- case 'classic' :
- versPadding = 20;
- }
-
- if (align) {
-
- var seriesWidths = [];
- series.each(function(d,i) {
- var legendText;
- if (keyFormatter(getKey(d)) && keyFormatter(getKey(d)).length > maxKeyLength) {
- var trimmedKey = keyFormatter(getKey(d)).substring(0, maxKeyLength);
- legendText = d3.select(this).select('text').text(trimmedKey + "...");
- d3.select(this).append("svg:title").text(keyFormatter(getKey(d)));
- } else {
- legendText = d3.select(this).select('text');
- }
- var nodeTextLength;
- try {
- nodeTextLength = legendText.node().getComputedTextLength();
- // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead
- if(nodeTextLength <= 0) throw Error();
- }
- catch(e) {
- nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
- }
-
- seriesWidths.push(nodeTextLength + padding);
- });
-
- var seriesPerRow = 0;
- var legendWidth = 0;
- var columnWidths = [];
-
- while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
- columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
- legendWidth += seriesWidths[seriesPerRow++];
- }
- if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
-
- while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
- columnWidths = [];
- seriesPerRow--;
-
- for (var k = 0; k < seriesWidths.length; k++) {
- if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
- columnWidths[k % seriesPerRow] = seriesWidths[k];
- }
-
- legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
- return prev + cur;
- });
- }
-
- var xPositions = [];
- for (var i = 0, curX = 0; i < seriesPerRow; i++) {
- xPositions[i] = curX;
- curX += columnWidths[i];
- }
-
- series
- .attr('transform', function(d, i) {
- return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')';
- });
-
- //position legend as far right as possible within the total width
- if (rightAlign) {
- g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
- }
- else {
- g.attr('transform', 'translate(0' + ',' + margin.top + ')');
- }
-
- height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding);
-
- } else {
-
- var ypos = 5,
- newxpos = 5,
- maxwidth = 0,
- xpos;
- series
- .attr('transform', function(d, i) {
- var length = d3.select(this).select('text').node().getComputedTextLength() + padding;
- xpos = newxpos;
-
- if (width < margin.left + margin.right + xpos + length) {
- newxpos = xpos = 5;
- ypos += versPadding;
- }
-
- newxpos += length;
- if (newxpos > maxwidth) maxwidth = newxpos;
-
- return 'translate(' + xpos + ',' + ypos + ')';
- });
-
- //position legend as far right as possible within the total width
- g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
-
- height = margin.top + margin.bottom + ypos + 15;
- }
-
- if(vers == 'furious') {
- // Size rectangles after text is placed
- seriesShape
- .attr('width', function(d,i) {
- return seriesText[0][i].getComputedTextLength() + 27;
- })
- .attr('height', 18)
- .attr('y', -9)
- .attr('x', -15)
- }
-
- seriesShape
- .style('fill', setBGColor)
- .style('stroke', function(d,i) { return d.color || color(d, i) });
- });
-
- function setTextColor(d,i) {
- if(vers != 'furious') return '#000';
- if(expanded) {
- return d.disengaged ? color(d,i) : '#fff';
- } else if (!expanded) {
- return !!d.disabled ? color(d,i) : '#fff';
- }
- }
-
- function setBGColor(d,i) {
- if(expanded && vers == 'furious') {
- return d.disengaged ? '#fff' : color(d,i);
- } else {
- return !!d.disabled ? '#fff' : color(d,i);
- }
- }
-
- return chart;
- }
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- chart.dispatch = dispatch;
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- key: {get: function(){return getKey;}, set: function(_){getKey=_;}},
- keyFormatter: {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}},
- align: {get: function(){return align;}, set: function(_){align=_;}},
- rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}},
- maxKeyLength: {get: function(){return maxKeyLength;}, set: function(_){maxKeyLength=_;}},
- padding: {get: function(){return padding;}, set: function(_){padding=_;}},
- updateState: {get: function(){return updateState;}, set: function(_){updateState=_;}},
- radioButtonMode:{get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}},
- expanded: {get: function(){return expanded;}, set: function(_){expanded=_;}},
- vers: {get: function(){return vers;}, set: function(_){vers=_;}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top !== undefined ? _.top : margin.top;
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- }}
- });
-
- nv.utils.initOptions(chart);
-
- return chart;
- };
- //TODO: consider deprecating and using multibar with single series for this
- nv.models.historicalBar = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var margin = {top: 0, right: 0, bottom: 0, left: 0}
- , width = null
- , height = null
- , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
- , container = null
- , x = d3.scale.linear()
- , y = d3.scale.linear()
- , getX = function(d) { return d.x }
- , getY = function(d) { return d.y }
- , forceX = []
- , forceY = [0]
- , padData = false
- , clipEdge = true
- , color = nv.utils.defaultColor()
- , xDomain
- , yDomain
- , xRange
- , yRange
- , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
- , interactive = true
- ;
-
- var renderWatch = nv.utils.renderWatch(dispatch, 0);
-
- function chart(selection) {
- selection.each(function(data) {
- renderWatch.reset();
-
- container = d3.select(this);
- var availableWidth = nv.utils.availableWidth(width, container, margin),
- availableHeight = nv.utils.availableHeight(height, container, margin);
-
- nv.utils.initSVG(container);
-
- // Setup Scales
- x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
-
- if (padData)
- x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
- else
- x.range(xRange || [0, availableWidth]);
-
- y.domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) ))
- .range(yRange || [availableHeight, 0]);
-
- // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
- if (x.domain()[0] === x.domain()[1])
- x.domain()[0] ?
- x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
- : x.domain([-1,1]);
-
- if (y.domain()[0] === y.domain()[1])
- y.domain()[0] ?
- y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
- : y.domain([-1,1]);
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-wrap.nv-historicalBar-' + id).data([data[0].values]);
- var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBar-' + id);
- var defsEnter = wrapEnter.append('defs');
- var gEnter = wrapEnter.append('g');
- var g = wrap.select('g');
-
- gEnter.append('g').attr('class', 'nv-bars');
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- container
- .on('click', function(d,i) {
- dispatch.chartClick({
- data: d,
- index: i,
- pos: d3.event,
- id: id
- });
- });
-
- defsEnter.append('clipPath')
- .attr('id', 'nv-chart-clip-path-' + id)
- .append('rect');
-
- wrap.select('#nv-chart-clip-path-' + id + ' rect')
- .attr('width', availableWidth)
- .attr('height', availableHeight);
-
- g.attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
-
- var bars = wrap.select('.nv-bars').selectAll('.nv-bar')
- .data(function(d) { return d }, function(d,i) {return getX(d,i)});
- bars.exit().remove();
-
- bars.enter().append('rect')
- .attr('x', 0 )
- .attr('y', function(d,i) { return nv.utils.NaNtoZero(y(Math.max(0, getY(d,i)))) })
- .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.abs(y(getY(d,i)) - y(0))) })
- .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
- .on('mouseover', function(d,i) {
- if (!interactive) return;
- d3.select(this).classed('hover', true);
- dispatch.elementMouseover({
- data: d,
- index: i,
- color: d3.select(this).style("fill")
- });
-
- })
- .on('mouseout', function(d,i) {
- if (!interactive) return;
- d3.select(this).classed('hover', false);
- dispatch.elementMouseout({
- data: d,
- index: i,
- color: d3.select(this).style("fill")
- });
- })
- .on('mousemove', function(d,i) {
- if (!interactive) return;
- dispatch.elementMousemove({
- data: d,
- index: i,
- color: d3.select(this).style("fill")
- });
- })
- .on('click', function(d,i) {
- if (!interactive) return;
- var element = this;
- dispatch.elementClick({
- data: d,
- index: i,
- color: d3.select(this).style("fill"),
- event: d3.event,
- element: element
- });
- d3.event.stopPropagation();
- })
- .on('dblclick', function(d,i) {
- if (!interactive) return;
- dispatch.elementDblClick({
- data: d,
- index: i,
- color: d3.select(this).style("fill")
- });
- d3.event.stopPropagation();
- });
-
- bars
- .attr('fill', function(d,i) { return color(d, i); })
- .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
- .watchTransition(renderWatch, 'bars')
- .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
- //TODO: better width calculations that don't assume always uniform data spacing;w
- .attr('width', (availableWidth / data[0].values.length) * .9 );
-
- bars.watchTransition(renderWatch, 'bars')
- .attr('y', function(d,i) {
- var rval = getY(d,i) < 0 ?
- y(0) :
- y(0) - y(getY(d,i)) < 1 ?
- y(0) - 1 :
- y(getY(d,i));
- return nv.utils.NaNtoZero(rval);
- })
- .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) });
-
- });
-
- renderWatch.renderEnd('historicalBar immediate');
- return chart;
- }
-
- //Create methods to allow outside functions to highlight a specific bar.
- chart.highlightPoint = function(pointIndex, isHoverOver) {
- container
- .select(".nv-bars .nv-bar-0-" + pointIndex)
- .classed("hover", isHoverOver)
- ;
- };
-
- chart.clearHighlights = function() {
- container
- .select(".nv-bars .nv-bar.hover")
- .classed("hover", false)
- ;
- };
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- chart.dispatch = dispatch;
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
- forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
- padData: {get: function(){return padData;}, set: function(_){padData=_;}},
- x: {get: function(){return getX;}, set: function(_){getX=_;}},
- y: {get: function(){return getY;}, set: function(_){getY=_;}},
- xScale: {get: function(){return x;}, set: function(_){x=_;}},
- yScale: {get: function(){return y;}, set: function(_){y=_;}},
- xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
- yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
- xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
- yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
- clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
- id: {get: function(){return id;}, set: function(_){id=_;}},
- interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top !== undefined ? _.top : margin.top;
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- }}
- });
-
- nv.utils.initOptions(chart);
-
- return chart;
- };
-
- nv.models.historicalBarChart = function(bar_model) {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var bars = bar_model || nv.models.historicalBar()
- , xAxis = nv.models.axis()
- , yAxis = nv.models.axis()
- , legend = nv.models.legend()
- , interactiveLayer = nv.interactiveGuideline()
- , tooltip = nv.models.tooltip()
- ;
-
-
- var margin = {top: 30, right: 90, bottom: 50, left: 90}
- , marginTop = null
- , color = nv.utils.defaultColor()
- , width = null
- , height = null
- , showLegend = false
- , showXAxis = true
- , showYAxis = true
- , rightAlignYAxis = false
- , useInteractiveGuideline = false
- , x
- , y
- , state = {}
- , defaultState = null
- , noData = null
- , dispatch = d3.dispatch('tooltipHide', 'stateChange', 'changeState', 'renderEnd')
- , transitionDuration = 250
- ;
-
- xAxis.orient('bottom').tickPadding(7);
- yAxis.orient( (rightAlignYAxis) ? 'right' : 'left');
- tooltip
- .duration(0)
- .headerEnabled(false)
- .valueFormatter(function(d, i) {
- return yAxis.tickFormat()(d, i);
- })
- .headerFormatter(function(d, i) {
- return xAxis.tickFormat()(d, i);
- });
-
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var renderWatch = nv.utils.renderWatch(dispatch, 0);
-
- function chart(selection) {
- selection.each(function(data) {
- renderWatch.reset();
- renderWatch.models(bars);
- if (showXAxis) renderWatch.models(xAxis);
- if (showYAxis) renderWatch.models(yAxis);
-
- var container = d3.select(this),
- that = this;
- nv.utils.initSVG(container);
- var availableWidth = nv.utils.availableWidth(width, container, margin),
- availableHeight = nv.utils.availableHeight(height, container, margin);
-
- chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
- chart.container = this;
-
- //set state.disabled
- state.disabled = data.map(function(d) { return !!d.disabled });
-
- if (!defaultState) {
- var key;
- defaultState = {};
- for (key in state) {
- if (state[key] instanceof Array)
- defaultState[key] = state[key].slice(0);
- else
- defaultState[key] = state[key];
- }
- }
-
- // Display noData message if there's nothing to show.
- if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
- nv.utils.noData(chart, container)
- return chart;
- } else {
- container.selectAll('.nv-noData').remove();
- }
-
- // Setup Scales
- x = bars.xScale();
- y = bars.yScale();
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-wrap.nv-historicalBarChart').data([data]);
- var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBarChart').append('g');
- var g = wrap.select('g');
-
- gEnter.append('g').attr('class', 'nv-x nv-axis');
- gEnter.append('g').attr('class', 'nv-y nv-axis');
- gEnter.append('g').attr('class', 'nv-barsWrap');
- gEnter.append('g').attr('class', 'nv-legendWrap');
- gEnter.append('g').attr('class', 'nv-interactive');
-
- // Legend
- if (!showLegend) {
- g.select('.nv-legendWrap').selectAll('*').remove();
- } else {
- legend.width(availableWidth);
-
- g.select('.nv-legendWrap')
- .datum(data)
- .call(legend);
-
- if (!marginTop && legend.height() !== margin.top) {
- margin.top = legend.height();
- availableHeight = nv.utils.availableHeight(height, container, margin);
- }
-
- wrap.select('.nv-legendWrap')
- .attr('transform', 'translate(0,' + (-margin.top) +')')
- }
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- if (rightAlignYAxis) {
- g.select(".nv-y.nv-axis")
- .attr("transform", "translate(" + availableWidth + ",0)");
- }
-
- //Set up interactive layer
- if (useInteractiveGuideline) {
- interactiveLayer
- .width(availableWidth)
- .height(availableHeight)
- .margin({left:margin.left, top:margin.top})
- .svgContainer(container)
- .xScale(x);
- wrap.select(".nv-interactive").call(interactiveLayer);
- }
- bars
- .width(availableWidth)
- .height(availableHeight)
- .color(data.map(function(d,i) {
- return d.color || color(d, i);
- }).filter(function(d,i) { return !data[i].disabled }));
-
- var barsWrap = g.select('.nv-barsWrap')
- .datum(data.filter(function(d) { return !d.disabled }));
- barsWrap.transition().call(bars);
-
- // Setup Axes
- if (showXAxis) {
- xAxis
- .scale(x)
- ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
- .tickSize(-availableHeight, 0);
-
- g.select('.nv-x.nv-axis')
- .attr('transform', 'translate(0,' + y.range()[0] + ')');
- g.select('.nv-x.nv-axis')
- .transition()
- .call(xAxis);
- }
-
- if (showYAxis) {
- yAxis
- .scale(y)
- ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
- .tickSize( -availableWidth, 0);
-
- g.select('.nv-y.nv-axis')
- .transition()
- .call(yAxis);
- }
-
- //============================================================
- // Event Handling/Dispatching (in chart's scope)
- //------------------------------------------------------------
-
- interactiveLayer.dispatch.on('elementMousemove', function(e) {
- bars.clearHighlights();
-
- var singlePoint, pointIndex, pointXLocation, allData = [];
- data
- .filter(function(series, i) {
- series.seriesIndex = i;
- return !series.disabled;
- })
- .forEach(function(series,i) {
- pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
- bars.highlightPoint(pointIndex,true);
- var point = series.values[pointIndex];
- if (point === undefined) return;
- if (singlePoint === undefined) singlePoint = point;
- if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
- allData.push({
- key: series.key,
- value: chart.y()(point, pointIndex),
- color: color(series,series.seriesIndex),
- data: series.values[pointIndex]
- });
- });
-
- var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
- interactiveLayer.tooltip
- .valueFormatter(function(d,i) {
- return yAxis.tickFormat()(d);
- })
- .data({
- value: xValue,
- index: pointIndex,
- series: allData
- })();
-
- interactiveLayer.renderGuideLine(pointXLocation);
-
- });
-
- interactiveLayer.dispatch.on("elementMouseout",function(e) {
- dispatch.tooltipHide();
- bars.clearHighlights();
- });
-
- legend.dispatch.on('legendClick', function(d,i) {
- d.disabled = !d.disabled;
-
- if (!data.filter(function(d) { return !d.disabled }).length) {
- data.map(function(d) {
- d.disabled = false;
- wrap.selectAll('.nv-series').classed('disabled', false);
- return d;
- });
- }
-
- state.disabled = data.map(function(d) { return !!d.disabled });
- dispatch.stateChange(state);
-
- selection.transition().call(chart);
- });
-
- legend.dispatch.on('legendDblclick', function(d) {
- //Double clicking should always enable current series, and disabled all others.
- data.forEach(function(d) {
- d.disabled = true;
- });
- d.disabled = false;
-
- state.disabled = data.map(function(d) { return !!d.disabled });
- dispatch.stateChange(state);
- chart.update();
- });
-
- dispatch.on('changeState', function(e) {
- if (typeof e.disabled !== 'undefined') {
- data.forEach(function(series,i) {
- series.disabled = e.disabled[i];
- });
-
- state.disabled = e.disabled;
- }
-
- chart.update();
- });
- });
-
- renderWatch.renderEnd('historicalBarChart immediate');
- return chart;
- }
-
- //============================================================
- // Event Handling/Dispatching (out of chart's scope)
- //------------------------------------------------------------
-
- bars.dispatch.on('elementMouseover.tooltip', function(evt) {
- evt['series'] = {
- key: chart.x()(evt.data),
- value: chart.y()(evt.data),
- color: evt.color
- };
- tooltip.data(evt).hidden(false);
- });
-
- bars.dispatch.on('elementMouseout.tooltip', function(evt) {
- tooltip.hidden(true);
- });
-
- bars.dispatch.on('elementMousemove.tooltip', function(evt) {
- tooltip();
- });
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- // expose chart's sub-components
- chart.dispatch = dispatch;
- chart.bars = bars;
- chart.legend = legend;
- chart.xAxis = xAxis;
- chart.yAxis = yAxis;
- chart.interactiveLayer = interactiveLayer;
- chart.tooltip = tooltip;
-
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
- showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
- showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
- defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
- noData: {get: function(){return noData;}, set: function(_){noData=_;}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- if (_.top !== undefined) {
- margin.top = _.top;
- marginTop = _.top;
- }
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- legend.color(color);
- bars.color(color);
- }},
- duration: {get: function(){return transitionDuration;}, set: function(_){
- transitionDuration=_;
- renderWatch.reset(transitionDuration);
- yAxis.duration(transitionDuration);
- xAxis.duration(transitionDuration);
- }},
- rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
- rightAlignYAxis = _;
- yAxis.orient( (_) ? 'right' : 'left');
- }},
- useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
- useInteractiveGuideline = _;
- if (_ === true) {
- chart.interactive(false);
- }
- }}
- });
-
- nv.utils.inheritOptions(chart, bars);
- nv.utils.initOptions(chart);
-
- return chart;
- };
-
-
- // ohlcChart is just a historical chart with ohlc bars and some tweaks
- nv.models.ohlcBarChart = function() {
- var chart = nv.models.historicalBarChart(nv.models.ohlcBar());
-
- // special default tooltip since we show multiple values per x
- chart.useInteractiveGuideline(true);
- chart.interactiveLayer.tooltip.contentGenerator(function(data) {
- // we assume only one series exists for this chart
- var d = data.series[0].data;
- // match line colors as defined in nv.d3.css
- var color = d.open < d.close ? "2ca02c" : "d62728";
- return '' +
- '<h3 style="color: #' + color + '">' + data.value + '</h3>' +
- '<table>' +
- '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' +
- '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' +
- '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' +
- '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' +
- '</table>';
- });
- return chart;
- };
-
- // candlestickChart is just a historical chart with candlestick bars and some tweaks
- nv.models.candlestickBarChart = function() {
- var chart = nv.models.historicalBarChart(nv.models.candlestickBar());
-
- // special default tooltip since we show multiple values per x
- chart.useInteractiveGuideline(true);
- chart.interactiveLayer.tooltip.contentGenerator(function(data) {
- // we assume only one series exists for this chart
- var d = data.series[0].data;
- // match line colors as defined in nv.d3.css
- var color = d.open < d.close ? "2ca02c" : "d62728";
- return '' +
- '<h3 style="color: #' + color + '">' + data.value + '</h3>' +
- '<table>' +
- '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' +
- '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' +
- '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' +
- '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' +
- '</table>';
- });
- return chart;
- };
- nv.models.legend = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var margin = {top: 5, right: 0, bottom: 5, left: 0}
- , width = 400
- , height = 20
- , getKey = function(d) { return d.key }
- , keyFormatter = function (d) { return d }
- , color = nv.utils.getColor()
- , maxKeyLength = 20 //default value for key lengths
- , align = true
- , padding = 32 //define how much space between legend items. - recommend 32 for furious version
- , rightAlign = true
- , updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
- , radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
- , expanded = false
- , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
- , vers = 'classic' //Options are "classic" and "furious"
- ;
-
- function chart(selection) {
- selection.each(function(data) {
- var availableWidth = width - margin.left - margin.right,
- container = d3.select(this);
- nv.utils.initSVG(container);
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-legend').data([data]);
- var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
- var g = wrap.select('g');
-
- if (rightAlign)
- wrap.attr('transform', 'translate(' + (- margin.right) + ',' + margin.top + ')');
- else
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- var series = g.selectAll('.nv-series')
- .data(function(d) {
- if(vers != 'furious') return d;
-
- return d.filter(function(n) {
- return expanded ? true : !n.disengaged;
- });
- });
-
- var seriesEnter = series.enter().append('g').attr('class', 'nv-series');
- var seriesShape;
-
- var versPadding;
- switch(vers) {
- case 'furious' :
- versPadding = 23;
- break;
- case 'classic' :
- versPadding = 20;
- }
-
- if(vers == 'classic') {
- seriesEnter.append('circle')
- .style('stroke-width', 2)
- .attr('class','nv-legend-symbol')
- .attr('r', 5);
-
- seriesShape = series.select('.nv-legend-symbol');
- } else if (vers == 'furious') {
- seriesEnter.append('rect')
- .style('stroke-width', 2)
- .attr('class','nv-legend-symbol')
- .attr('rx', 3)
- .attr('ry', 3);
- seriesShape = series.select('.nv-legend-symbol');
-
- seriesEnter.append('g')
- .attr('class', 'nv-check-box')
- .property('innerHTML','<path d="M0.5,5 L22.5,5 L22.5,26.5 L0.5,26.5 L0.5,5 Z" class="nv-box"></path><path d="M5.5,12.8618467 L11.9185089,19.2803556 L31,0.198864511" class="nv-check"></path>')
- .attr('transform', 'translate(-10,-8)scale(0.5)');
-
- var seriesCheckbox = series.select('.nv-check-box');
-
- seriesCheckbox.each(function(d,i) {
- d3.select(this).selectAll('path')
- .attr('stroke', setTextColor(d,i));
- });
- }
-
- seriesEnter.append('text')
- .attr('text-anchor', 'start')
- .attr('class','nv-legend-text')
- .attr('dy', '.32em')
- .attr('dx', '8');
-
- var seriesText = series.select('text.nv-legend-text');
-
- series
- .on('mouseover', function(d,i) {
- dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects
- })
- .on('mouseout', function(d,i) {
- dispatch.legendMouseout(d,i);
- })
- .on('click', function(d,i) {
- dispatch.legendClick(d,i);
- // make sure we re-get data in case it was modified
- var data = series.data();
- if (updateState) {
- if(vers =='classic') {
- if (radioButtonMode) {
- //Radio button mode: set every series to disabled,
- // and enable the clicked series.
- data.forEach(function(series) { series.disabled = true});
- d.disabled = false;
- }
- else {
- d.disabled = !d.disabled;
- if (data.every(function(series) { return series.disabled})) {
- //the default behavior of NVD3 legends is, if every single series
- // is disabled, turn all series' back on.
- data.forEach(function(series) { series.disabled = false});
- }
- }
- } else if(vers == 'furious') {
- if(expanded) {
- d.disengaged = !d.disengaged;
- d.userDisabled = d.userDisabled == undefined ? !!d.disabled : d.userDisabled;
- d.disabled = d.disengaged || d.userDisabled;
- } else if (!expanded) {
- d.disabled = !d.disabled;
- d.userDisabled = d.disabled;
- var engaged = data.filter(function(d) { return !d.disengaged; });
- if (engaged.every(function(series) { return series.userDisabled })) {
- //the default behavior of NVD3 legends is, if every single series
- // is disabled, turn all series' back on.
- data.forEach(function(series) {
- series.disabled = series.userDisabled = false;
- });
- }
- }
- }
- dispatch.stateChange({
- disabled: data.map(function(d) { return !!d.disabled }),
- disengaged: data.map(function(d) { return !!d.disengaged })
- });
-
- }
- })
- .on('dblclick', function(d,i) {
- if(vers == 'furious' && expanded) return;
- dispatch.legendDblclick(d,i);
- if (updateState) {
- // make sure we re-get data in case it was modified
- var data = series.data();
- //the default behavior of NVD3 legends, when double clicking one,
- // is to set all other series' to false, and make the double clicked series enabled.
- data.forEach(function(series) {
- series.disabled = true;
- if(vers == 'furious') series.userDisabled = series.disabled;
- });
- d.disabled = false;
- if(vers == 'furious') d.userDisabled = d.disabled;
- dispatch.stateChange({
- disabled: data.map(function(d) { return !!d.disabled })
- });
- }
- });
-
- series.classed('nv-disabled', function(d) { return d.userDisabled });
- series.exit().remove();
-
- seriesText
- .attr('fill', setTextColor)
- .text(function (d) { return keyFormatter(getKey(d)) });
-
- //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
- // NEW ALIGNING CODE, TODO: clean up
- var legendWidth = 0;
- if (align) {
-
- var seriesWidths = [];
- series.each(function(d,i) {
- var legendText;
- if (keyFormatter(getKey(d)) && keyFormatter(getKey(d)).length > maxKeyLength) {
- var trimmedKey = keyFormatter(getKey(d)).substring(0, maxKeyLength);
- legendText = d3.select(this).select('text').text(trimmedKey + "...");
- d3.select(this).append("svg:title").text(keyFormatter(getKey(d)));
- } else {
- legendText = d3.select(this).select('text');
- }
- var nodeTextLength;
- try {
- nodeTextLength = legendText.node().getComputedTextLength();
- // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead
- if(nodeTextLength <= 0) throw Error();
- }
- catch(e) {
- nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
- }
-
- seriesWidths.push(nodeTextLength + padding);
- });
-
- var seriesPerRow = 0;
- var columnWidths = [];
- legendWidth = 0;
-
- while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
- columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
- legendWidth += seriesWidths[seriesPerRow++];
- }
- if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
-
- while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
- columnWidths = [];
- seriesPerRow--;
-
- for (var k = 0; k < seriesWidths.length; k++) {
- if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
- columnWidths[k % seriesPerRow] = seriesWidths[k];
- }
-
- legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
- return prev + cur;
- });
- }
-
- var xPositions = [];
- for (var i = 0, curX = 0; i < seriesPerRow; i++) {
- xPositions[i] = curX;
- curX += columnWidths[i];
- }
-
- series
- .attr('transform', function(d, i) {
- return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')';
- });
-
- //position legend as far right as possible within the total width
- if (rightAlign) {
- g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
- }
- else {
- g.attr('transform', 'translate(0' + ',' + margin.top + ')');
- }
-
- height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding);
-
- } else {
-
- var ypos = 5,
- newxpos = 5,
- maxwidth = 0,
- xpos;
- series
- .attr('transform', function(d, i) {
- var length = d3.select(this).select('text').node().getComputedTextLength() + padding;
- xpos = newxpos;
-
- if (width < margin.left + margin.right + xpos + length) {
- newxpos = xpos = 5;
- ypos += versPadding;
- }
-
- newxpos += length;
- if (newxpos > maxwidth) maxwidth = newxpos;
-
- if(legendWidth < xpos + maxwidth) {
- legendWidth = xpos + maxwidth;
- }
- return 'translate(' + xpos + ',' + ypos + ')';
- });
-
- //position legend as far right as possible within the total width
- g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
-
- height = margin.top + margin.bottom + ypos + 15;
- }
-
- if(vers == 'furious') {
- // Size rectangles after text is placed
- seriesShape
- .attr('width', function(d,i) {
- return seriesText[0][i].getComputedTextLength() + 27;
- })
- .attr('height', 18)
- .attr('y', -9)
- .attr('x', -15);
-
- // The background for the expanded legend (UI)
- gEnter.insert('rect',':first-child')
- .attr('class', 'nv-legend-bg')
- .attr('fill', '#eee')
- // .attr('stroke', '#444')
- .attr('opacity',0);
-
- var seriesBG = g.select('.nv-legend-bg');
-
- seriesBG
- .transition().duration(300)
- .attr('x', -versPadding )
- .attr('width', legendWidth + versPadding - 12)
- .attr('height', height + 10)
- .attr('y', -margin.top - 10)
- .attr('opacity', expanded ? 1 : 0);
-
-
- }
-
- seriesShape
- .style('fill', setBGColor)
- .style('fill-opacity', setBGOpacity)
- .style('stroke', setBGColor);
- });
-
- function setTextColor(d,i) {
- if(vers != 'furious') return '#000';
- if(expanded) {
- return d.disengaged ? '#000' : '#fff';
- } else if (!expanded) {
- if(!d.color) d.color = color(d,i);
- return !!d.disabled ? d.color : '#fff';
- }
- }
-
- function setBGColor(d,i) {
- if(expanded && vers == 'furious') {
- return d.disengaged ? '#eee' : d.color || color(d,i);
- } else {
- return d.color || color(d,i);
- }
- }
-
-
- function setBGOpacity(d,i) {
- if(expanded && vers == 'furious') {
- return 1;
- } else {
- return !!d.disabled ? 0 : 1;
- }
- }
-
- return chart;
- }
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- chart.dispatch = dispatch;
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- key: {get: function(){return getKey;}, set: function(_){getKey=_;}},
- keyFormatter: {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}},
- align: {get: function(){return align;}, set: function(_){align=_;}},
- maxKeyLength: {get: function(){return maxKeyLength;}, set: function(_){maxKeyLength=_;}},
- rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}},
- padding: {get: function(){return padding;}, set: function(_){padding=_;}},
- updateState: {get: function(){return updateState;}, set: function(_){updateState=_;}},
- radioButtonMode:{get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}},
- expanded: {get: function(){return expanded;}, set: function(_){expanded=_;}},
- vers: {get: function(){return vers;}, set: function(_){vers=_;}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top !== undefined ? _.top : margin.top;
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- }}
- });
-
- nv.utils.initOptions(chart);
-
- return chart;
- };
-
- nv.models.line = function() {
- "use strict";
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var scatter = nv.models.scatter()
- ;
-
- var margin = {top: 0, right: 0, bottom: 0, left: 0}
- , width = 960
- , height = 500
- , container = null
- , strokeWidth = 1.5
- , color = nv.utils.defaultColor() // a function that returns a color
- , getX = function(d) { return d.x } // accessor to get the x value from a data point
- , getY = function(d) { return d.y } // accessor to get the y value from a data point
- , defined = function(d,i) { return !isNaN(getY(d,i)) && getY(d,i) !== null } // allows a line to be not continuous when it is not defined
- , isArea = function(d) { return d.area } // decides if a line is an area or just a line
- , clipEdge = false // if true, masks lines within x and y scale
- , x //can be accessed via chart.xScale()
- , y //can be accessed via chart.yScale()
- , interpolate = "linear" // controls the line interpolation
- , duration = 250
- , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
- ;
-
- scatter
- .pointSize(16) // default size
- .pointDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor
- ;
-
- //============================================================
-
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var x0, y0 //used to store previous scales
- , renderWatch = nv.utils.renderWatch(dispatch, duration)
- ;
-
- //============================================================
-
-
- function chart(selection) {
- renderWatch.reset();
- renderWatch.models(scatter);
- selection.each(function(data) {
- container = d3.select(this);
- var availableWidth = nv.utils.availableWidth(width, container, margin),
- availableHeight = nv.utils.availableHeight(height, container, margin);
- nv.utils.initSVG(container);
-
- // Setup Scales
- x = scatter.xScale();
- y = scatter.yScale();
-
- x0 = x0 || x;
- y0 = y0 || y;
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]);
- var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line');
- var defsEnter = wrapEnter.append('defs');
- var gEnter = wrapEnter.append('g');
- var g = wrap.select('g');
-
- gEnter.append('g').attr('class', 'nv-groups');
- gEnter.append('g').attr('class', 'nv-scatterWrap');
-
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- scatter
- .width(availableWidth)
- .height(availableHeight);
-
- var scatterWrap = wrap.select('.nv-scatterWrap');
- scatterWrap.call(scatter);
-
- defsEnter.append('clipPath')
- .attr('id', 'nv-edge-clip-' + scatter.id())
- .append('rect');
-
- wrap.select('#nv-edge-clip-' + scatter.id() + ' rect')
- .attr('width', availableWidth)
- .attr('height', (availableHeight > 0) ? availableHeight : 0);
-
- g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
- scatterWrap
- .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
-
- var groups = wrap.select('.nv-groups').selectAll('.nv-group')
- .data(function(d) { return d }, function(d) { return d.key });
- groups.enter().append('g')
- .style('stroke-opacity', 1e-6)
- .style('stroke-width', function(d) { return d.strokeWidth || strokeWidth })
- .style('fill-opacity', 1e-6);
-
- groups.exit().remove();
-
- groups
- .attr('class', function(d,i) {
- return (d.classed || '') + ' nv-group nv-series-' + i;
- })
- .classed('hover', function(d) { return d.hover })
- .style('fill', function(d,i){ return color(d, i) })
- .style('stroke', function(d,i){ return color(d, i)});
- groups.watchTransition(renderWatch, 'line: groups')
- .style('stroke-opacity', 1)
- .style('fill-opacity', function(d) { return d.fillOpacity || .5});
-
- var areaPaths = groups.selectAll('path.nv-area')
- .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area
- areaPaths.enter().append('path')
- .attr('class', 'nv-area')
- .attr('d', function(d) {
- return d3.svg.area()
- .interpolate(interpolate)
- .defined(defined)
- .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
- .y0(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
- .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
- //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
- .apply(this, [d.values])
- });
- groups.exit().selectAll('path.nv-area')
- .remove();
-
- areaPaths.watchTransition(renderWatch, 'line: areaPaths')
- .attr('d', function(d) {
- return d3.svg.area()
- .interpolate(interpolate)
- .defined(defined)
- .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
- .y0(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
- .y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
- //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
- .apply(this, [d.values])
- });
-
- var linePaths = groups.selectAll('path.nv-line')
- .data(function(d) { return [d.values] });
-
- linePaths.enter().append('path')
- .attr('class', 'nv-line')
- .attr('d',
- d3.svg.line()
- .interpolate(interpolate)
- .defined(defined)
- .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
- .y(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
- );
-
- linePaths.watchTransition(renderWatch, 'line: linePaths')
- .attr('d',
- d3.svg.line()
- .interpolate(interpolate)
- .defined(defined)
- .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
- .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
- );
-
- //store old scales for use in transitions on update
- x0 = x.copy();
- y0 = y.copy();
- });
- renderWatch.renderEnd('line immediate');
- return chart;
- }
-
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- chart.dispatch = dispatch;
- chart.scatter = scatter;
- // Pass through events
- scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); });
- scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); });
- scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); });
-
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- defined: {get: function(){return defined;}, set: function(_){defined=_;}},
- interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
- clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top !== undefined ? _.top : margin.top;
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- duration: {get: function(){return duration;}, set: function(_){
- duration = _;
- renderWatch.reset(duration);
- scatter.duration(duration);
- }},
- isArea: {get: function(){return isArea;}, set: function(_){
- isArea = d3.functor(_);
- }},
- x: {get: function(){return getX;}, set: function(_){
- getX = _;
- scatter.x(_);
- }},
- y: {get: function(){return getY;}, set: function(_){
- getY = _;
- scatter.y(_);
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- scatter.color(color);
- }}
- });
-
- nv.utils.inheritOptions(chart, scatter);
- nv.utils.initOptions(chart);
-
- return chart;
- };
- nv.models.lineChart = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var lines = nv.models.line()
- , xAxis = nv.models.axis()
- , yAxis = nv.models.axis()
- , legend = nv.models.legend()
- , interactiveLayer = nv.interactiveGuideline()
- , tooltip = nv.models.tooltip()
- , focus = nv.models.focus(nv.models.line())
- ;
-
- var margin = {top: 30, right: 20, bottom: 50, left: 60}
- , marginTop = null
- , color = nv.utils.defaultColor()
- , width = null
- , height = null
- , showLegend = true
- , legendPosition = 'top'
- , showXAxis = true
- , showYAxis = true
- , rightAlignYAxis = false
- , useInteractiveGuideline = false
- , x
- , y
- , focusEnable = false
- , state = nv.utils.state()
- , defaultState = null
- , noData = null
- , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
- , duration = 250
- ;
-
- // set options on sub-objects for this chart
- xAxis.orient('bottom').tickPadding(7);
- yAxis.orient(rightAlignYAxis ? 'right' : 'left');
-
- lines.clipEdge(true).duration(0);
-
- tooltip.valueFormatter(function(d, i) {
- return yAxis.tickFormat()(d, i);
- }).headerFormatter(function(d, i) {
- return xAxis.tickFormat()(d, i);
- });
-
- interactiveLayer.tooltip.valueFormatter(function(d, i) {
- return yAxis.tickFormat()(d, i);
- }).headerFormatter(function(d, i) {
- return xAxis.tickFormat()(d, i);
- });
-
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var renderWatch = nv.utils.renderWatch(dispatch, duration);
-
- var stateGetter = function(data) {
- return function(){
- return {
- active: data.map(function(d) { return !d.disabled; })
- };
- };
- };
-
- var stateSetter = function(data) {
- return function(state) {
- if (state.active !== undefined)
- data.forEach(function(series,i) {
- series.disabled = !state.active[i];
- });
- };
- };
-
- function chart(selection) {
- renderWatch.reset();
- renderWatch.models(lines);
- if (showXAxis) renderWatch.models(xAxis);
- if (showYAxis) renderWatch.models(yAxis);
-
- selection.each(function(data) {
- var container = d3.select(this);
- nv.utils.initSVG(container);
- var availableWidth = nv.utils.availableWidth(width, container, margin),
- availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0);
- chart.update = function() {
- if( duration === 0 ) {
- container.call( chart );
- } else {
- container.transition().duration(duration).call(chart);
- }
- };
- chart.container = this;
-
- state
- .setter(stateSetter(data), chart.update)
- .getter(stateGetter(data))
- .update();
-
- // DEPRECATED set state.disabled
- state.disabled = data.map(function(d) { return !!d.disabled; });
-
- if (!defaultState) {
- var key;
- defaultState = {};
- for (key in state) {
- if (state[key] instanceof Array)
- defaultState[key] = state[key].slice(0);
- else
- defaultState[key] = state[key];
- }
- }
-
- // Display noData message if there's nothing to show.
- if (!data || !data.length || !data.filter(function(d) { return d.values.length; }).length) {
- nv.utils.noData(chart, container);
- return chart;
- } else {
- container.selectAll('.nv-noData').remove();
- }
-
- /* Update `main' graph on brush update. */
- focus.dispatch.on("onBrush", function(extent) {
- onBrush(extent);
- });
-
- // Setup Scales
- x = lines.xScale();
- y = lines.yScale();
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]);
- var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g');
- var g = wrap.select('g');
-
- gEnter.append('g').attr('class', 'nv-legendWrap');
-
- var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
- focusEnter.append('g').attr('class', 'nv-background').append('rect');
- focusEnter.append('g').attr('class', 'nv-x nv-axis');
- focusEnter.append('g').attr('class', 'nv-y nv-axis');
- focusEnter.append('g').attr('class', 'nv-linesWrap');
- focusEnter.append('g').attr('class', 'nv-interactive');
-
- var contextEnter = gEnter.append('g').attr('class', 'nv-focusWrap');
-
- // Legend
- if (!showLegend) {
- g.select('.nv-legendWrap').selectAll('*').remove();
- } else {
- legend.width(availableWidth);
-
- g.select('.nv-legendWrap')
- .datum(data)
- .call(legend);
-
- if (legendPosition === 'bottom') {
- wrap.select('.nv-legendWrap')
- .attr('transform', 'translate(0,' + availableHeight +')');
- } else if (legendPosition === 'top') {
- if (!marginTop && legend.height() !== margin.top) {
- margin.top = legend.height();
- availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0);
- }
-
- wrap.select('.nv-legendWrap')
- .attr('transform', 'translate(0,' + (-margin.top) +')');
- }
- }
-
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- if (rightAlignYAxis) {
- g.select(".nv-y.nv-axis")
- .attr("transform", "translate(" + availableWidth + ",0)");
- }
-
- //Set up interactive layer
- if (useInteractiveGuideline) {
- interactiveLayer
- .width(availableWidth)
- .height(availableHeight)
- .margin({left:margin.left, top:margin.top})
- .svgContainer(container)
- .xScale(x);
- wrap.select(".nv-interactive").call(interactiveLayer);
- }
-
- g.select('.nv-focus .nv-background rect')
- .attr('width', availableWidth)
- .attr('height', availableHeight);
-
- lines
- .width(availableWidth)
- .height(availableHeight)
- .color(data.map(function(d,i) {
- return d.color || color(d, i);
- }).filter(function(d,i) { return !data[i].disabled; }));
-
- var linesWrap = g.select('.nv-linesWrap')
- .datum(data.filter(function(d) { return !d.disabled; }));
-
-
- // Setup Main (Focus) Axes
- if (showXAxis) {
- xAxis
- .scale(x)
- ._ticks(nv.utils.calcTicksX(availableWidth/100, data) )
- .tickSize(-availableHeight, 0);
- }
-
- if (showYAxis) {
- yAxis
- .scale(y)
- ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
- .tickSize( -availableWidth, 0);
- }
-
- //============================================================
- // Update Axes
- //============================================================
- function updateXAxis() {
- if(showXAxis) {
- g.select('.nv-focus .nv-x.nv-axis')
- .transition()
- .duration(duration)
- .call(xAxis)
- ;
- }
- }
-
- function updateYAxis() {
- if(showYAxis) {
- g.select('.nv-focus .nv-y.nv-axis')
- .transition()
- .duration(duration)
- .call(yAxis)
- ;
- }
- }
-
- g.select('.nv-focus .nv-x.nv-axis')
- .attr('transform', 'translate(0,' + availableHeight + ')');
-
- //============================================================
- // Update Focus
- //============================================================
- if(!focusEnable) {
- linesWrap.call(lines);
- updateXAxis();
- updateYAxis();
- } else {
- focus.width(availableWidth);
- g.select('.nv-focusWrap')
- .attr('transform', 'translate(0,' + ( availableHeight + margin.bottom + focus.margin().top) + ')')
- .datum(data.filter(function(d) { return !d.disabled; }))
- .call(focus);
- var extent = focus.brush.empty() ? focus.xDomain() : focus.brush.extent();
- if(extent !== null){
- onBrush(extent);
- }
- }
- //============================================================
- // Event Handling/Dispatching (in chart's scope)
- //------------------------------------------------------------
-
- legend.dispatch.on('stateChange', function(newState) {
- for (var key in newState)
- state[key] = newState[key];
- dispatch.stateChange(state);
- chart.update();
- });
-
- interactiveLayer.dispatch.on('elementMousemove', function(e) {
- lines.clearHighlights();
- var singlePoint, pointIndex, pointXLocation, allData = [];
- data
- .filter(function(series, i) {
- series.seriesIndex = i;
- return !series.disabled && !series.disableTooltip;
- })
- .forEach(function(series,i) {
- var extent = focusEnable ? (focus.brush.empty() ? focus.xScale().domain() : focus.brush.extent()) : x.domain();
- var currentValues = series.values.filter(function(d,i) {
- // Checks if the x point is between the extents, handling case where extent[0] is greater than extent[1]
- // (e.g. x domain is manually set to reverse the x-axis)
- if(extent[0] <= extent[1]) {
- return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
- } else {
- return lines.x()(d,i) >= extent[1] && lines.x()(d,i) <= extent[0];
- }
- });
-
- pointIndex = nv.interactiveBisect(currentValues, e.pointXValue, lines.x());
- var point = currentValues[pointIndex];
- var pointYValue = chart.y()(point, pointIndex);
- if (pointYValue !== null) {
- lines.highlightPoint(i, pointIndex, true);
- }
- if (point === undefined) return;
- if (singlePoint === undefined) singlePoint = point;
- if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
- allData.push({
- key: series.key,
- value: pointYValue,
- color: color(series,series.seriesIndex),
- data: point
- });
- });
- //Highlight the tooltip entry based on which point the mouse is closest to.
- if (allData.length > 2) {
- var yValue = chart.yScale().invert(e.mouseY);
- var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
- var threshold = 0.03 * domainExtent;
- var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value;}),yValue,threshold);
- if (indexToHighlight !== null)
- allData[indexToHighlight].highlight = true;
- }
-
- var defaultValueFormatter = function(d,i) {
- return d == null ? "N/A" : yAxis.tickFormat()(d);
- };
-
- interactiveLayer.tooltip
- .valueFormatter(interactiveLayer.tooltip.valueFormatter() || defaultValueFormatter)
- .data({
- value: chart.x()( singlePoint,pointIndex ),
- index: pointIndex,
- series: allData
- })();
-
- interactiveLayer.renderGuideLine(pointXLocation);
-
- });
-
- interactiveLayer.dispatch.on('elementClick', function(e) {
- var pointXLocation, allData = [];
-
- data.filter(function(series, i) {
- series.seriesIndex = i;
- return !series.disabled;
- }).forEach(function(series) {
- var pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
- var point = series.values[pointIndex];
- if (typeof point === 'undefined') return;
- if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
- var yPos = chart.yScale()(chart.y()(point,pointIndex));
- allData.push({
- point: point,
- pointIndex: pointIndex,
- pos: [pointXLocation, yPos],
- seriesIndex: series.seriesIndex,
- series: series
- });
- });
-
- lines.dispatch.elementClick(allData);
- });
-
- interactiveLayer.dispatch.on("elementMouseout",function(e) {
- lines.clearHighlights();
- });
-
- dispatch.on('changeState', function(e) {
- if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
- data.forEach(function(series,i) {
- series.disabled = e.disabled[i];
- });
-
- state.disabled = e.disabled;
- }
- chart.update();
- });
-
- //============================================================
- // Functions
- //------------------------------------------------------------
-
- // Taken from crossfilter (http://square.github.com/crossfilter/)
- function resizePath(d) {
- var e = +(d == 'e'),
- x = e ? 1 : -1,
- y = availableHeight / 3;
- return 'M' + (0.5 * x) + ',' + y
- + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
- + 'V' + (2 * y - 6)
- + 'A6,6 0 0 ' + e + ' ' + (0.5 * x) + ',' + (2 * y)
- + 'Z'
- + 'M' + (2.5 * x) + ',' + (y + 8)
- + 'V' + (2 * y - 8)
- + 'M' + (4.5 * x) + ',' + (y + 8)
- + 'V' + (2 * y - 8);
- }
-
- function onBrush(extent) {
- // Update Main (Focus)
- var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
- .datum(
- data.filter(function(d) { return !d.disabled; })
- .map(function(d,i) {
- return {
- key: d.key,
- area: d.area,
- classed: d.classed,
- values: d.values.filter(function(d,i) {
- return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
- }),
- disableTooltip: d.disableTooltip
- };
- })
- );
- focusLinesWrap.transition().duration(duration).call(lines);
-
- // Update Main (Focus) Axes
- updateXAxis();
- updateYAxis();
- }
- });
-
- renderWatch.renderEnd('lineChart immediate');
- return chart;
- }
-
-
- //============================================================
- // Event Handling/Dispatching (out of chart's scope)
- //------------------------------------------------------------
-
- lines.dispatch.on('elementMouseover.tooltip', function(evt) {
- if(!evt.series.disableTooltip){
- tooltip.data(evt).hidden(false);
- }
- });
-
- lines.dispatch.on('elementMouseout.tooltip', function(evt) {
- tooltip.hidden(true);
- });
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- // expose chart's sub-components
- chart.dispatch = dispatch;
- chart.lines = lines;
- chart.legend = legend;
- chart.focus = focus;
- chart.xAxis = xAxis;
- chart.x2Axis = focus.xAxis
- chart.yAxis = yAxis;
- chart.y2Axis = focus.yAxis
- chart.interactiveLayer = interactiveLayer;
- chart.tooltip = tooltip;
- chart.state = state;
- chart.dispatch = dispatch;
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
- legendPosition: {get: function(){return legendPosition;}, set: function(_){legendPosition=_;}},
- showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
- showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
- defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
- noData: {get: function(){return noData;}, set: function(_){noData=_;}},
- // Focus options, mostly passed onto focus model.
- focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}},
- focusHeight: {get: function(){return focus.height();}, set: function(_){focus.height(_);}},
- focusShowAxisX: {get: function(){return focus.showXAxis();}, set: function(_){focus.showXAxis(_);}},
- focusShowAxisY: {get: function(){return focus.showYAxis();}, set: function(_){focus.showYAxis(_);}},
- brushExtent: {get: function(){return focus.brushExtent();}, set: function(_){focus.brushExtent(_);}},
-
- // options that require extra logic in the setter
- focusMargin: {get: function(){return focus.margin}, set: function(_){
- if (_.top !== undefined) {
- margin.top = _.top;
- marginTop = _.top;
- }
- focus.margin.right = _.right !== undefined ? _.right : focus.margin.right;
- focus.margin.bottom = _.bottom !== undefined ? _.bottom : focus.margin.bottom;
- focus.margin.left = _.left !== undefined ? _.left : focus.margin.left;
- }},
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top !== undefined ? _.top : margin.top;
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- duration: {get: function(){return duration;}, set: function(_){
- duration = _;
- renderWatch.reset(duration);
- lines.duration(duration);
- focus.duration(duration);
- xAxis.duration(duration);
- yAxis.duration(duration);
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- legend.color(color);
- lines.color(color);
- focus.color(color);
- }},
- interpolate: {get: function(){return lines.interpolate();}, set: function(_){
- lines.interpolate(_);
- focus.interpolate(_);
- }},
- xTickFormat: {get: function(){return xAxis.tickFormat();}, set: function(_){
- xAxis.tickFormat(_);
- focus.xTickFormat(_);
- }},
- yTickFormat: {get: function(){return yAxis.tickFormat();}, set: function(_){
- yAxis.tickFormat(_);
- focus.yTickFormat(_);
- }},
- x: {get: function(){return lines.x();}, set: function(_){
- lines.x(_);
- focus.x(_);
- }},
- y: {get: function(){return lines.y();}, set: function(_){
- lines.y(_);
- focus.y(_);
- }},
- rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
- rightAlignYAxis = _;
- yAxis.orient( rightAlignYAxis ? 'right' : 'left');
- }},
- useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
- useInteractiveGuideline = _;
- if (useInteractiveGuideline) {
- lines.interactive(false);
- lines.useVoronoi(false);
- }
- }}
- });
-
- nv.utils.inheritOptions(chart, lines);
- nv.utils.initOptions(chart);
-
- return chart;
- };
-
- nv.models.lineWithFocusChart = function() {
- return nv.models.lineChart()
- .margin({ bottom: 30 })
- .focusEnable( true );
- };
- nv.models.linePlusBarChart = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var lines = nv.models.line()
- , lines2 = nv.models.line()
- , bars = nv.models.historicalBar()
- , bars2 = nv.models.historicalBar()
- , xAxis = nv.models.axis()
- , x2Axis = nv.models.axis()
- , y1Axis = nv.models.axis()
- , y2Axis = nv.models.axis()
- , y3Axis = nv.models.axis()
- , y4Axis = nv.models.axis()
- , legend = nv.models.legend()
- , brush = d3.svg.brush()
- , tooltip = nv.models.tooltip()
- ;
-
- var margin = {top: 30, right: 30, bottom: 30, left: 60}
- , marginTop = null
- , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
- , width = null
- , height = null
- , getX = function(d) { return d.x }
- , getY = function(d) { return d.y }
- , color = nv.utils.defaultColor()
- , showLegend = true
- , focusEnable = true
- , focusShowAxisY = false
- , focusShowAxisX = true
- , focusHeight = 50
- , extent
- , brushExtent = null
- , x
- , x2
- , y1
- , y2
- , y3
- , y4
- , noData = null
- , dispatch = d3.dispatch('brush', 'stateChange', 'changeState')
- , transitionDuration = 0
- , state = nv.utils.state()
- , defaultState = null
- , legendLeftAxisHint = ' (left axis)'
- , legendRightAxisHint = ' (right axis)'
- , switchYAxisOrder = false
- ;
-
- lines.clipEdge(true);
- lines2.interactive(false);
- // We don't want any points emitted for the focus chart's scatter graph.
- lines2.pointActive(function(d) { return false });
- xAxis.orient('bottom').tickPadding(5);
- y1Axis.orient('left');
- y2Axis.orient('right');
- x2Axis.orient('bottom').tickPadding(5);
- y3Axis.orient('left');
- y4Axis.orient('right');
-
- tooltip.headerEnabled(true).headerFormatter(function(d, i) {
- return xAxis.tickFormat()(d, i);
- });
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var getBarsAxis = function() {
- return switchYAxisOrder
- ? { main: y2Axis, focus: y4Axis }
- : { main: y1Axis, focus: y3Axis }
- }
-
- var getLinesAxis = function() {
- return switchYAxisOrder
- ? { main: y1Axis, focus: y3Axis }
- : { main: y2Axis, focus: y4Axis }
- }
-
- var stateGetter = function(data) {
- return function(){
- return {
- active: data.map(function(d) { return !d.disabled })
- };
- }
- };
-
- var stateSetter = function(data) {
- return function(state) {
- if (state.active !== undefined)
- data.forEach(function(series,i) {
- series.disabled = !state.active[i];
- });
- }
- };
-
- var allDisabled = function(data) {
- return data.every(function(series) {
- return series.disabled;
- });
- }
-
- function chart(selection) {
- selection.each(function(data) {
- var container = d3.select(this),
- that = this;
- nv.utils.initSVG(container);
- var availableWidth = nv.utils.availableWidth(width, container, margin),
- availableHeight1 = nv.utils.availableHeight(height, container, margin)
- - (focusEnable ? focusHeight : 0),
- availableHeight2 = focusHeight - margin2.top - margin2.bottom;
-
- chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
- chart.container = this;
-
- state
- .setter(stateSetter(data), chart.update)
- .getter(stateGetter(data))
- .update();
-
- // DEPRECATED set state.disableddisabled
- state.disabled = data.map(function(d) { return !!d.disabled });
-
- if (!defaultState) {
- var key;
- defaultState = {};
- for (key in state) {
- if (state[key] instanceof Array)
- defaultState[key] = state[key].slice(0);
- else
- defaultState[key] = state[key];
- }
- }
-
- // Display No Data message if there's nothing to show.
- if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
- nv.utils.noData(chart, container)
- return chart;
- } else {
- container.selectAll('.nv-noData').remove();
- }
-
- // Setup Scales
- var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
- var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
-
- if (dataBars.length && !switchYAxisOrder) {
- x = bars.xScale();
- } else {
- x = lines.xScale();
- }
-
- x2 = x2Axis.scale();
-
- // select the scales and series based on the position of the yAxis
- y1 = switchYAxisOrder ? lines.yScale() : bars.yScale();
- y2 = switchYAxisOrder ? bars.yScale() : lines.yScale();
- y3 = switchYAxisOrder ? lines2.yScale() : bars2.yScale();
- y4 = switchYAxisOrder ? bars2.yScale() : lines2.yScale();
-
- var series1 = data
- .filter(function(d) { return !d.disabled && (switchYAxisOrder ? !d.bar : d.bar) })
- .map(function(d) {
- return d.values.map(function(d,i) {
- return { x: getX(d,i), y: getY(d,i) }
- })
- });
-
- var series2 = data
- .filter(function(d) { return !d.disabled && (switchYAxisOrder ? d.bar : !d.bar) })
- .map(function(d) {
- return d.values.map(function(d,i) {
- return { x: getX(d,i), y: getY(d,i) }
- })
- });
-
- x.range([0, availableWidth]);
-
- x2 .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
- .range([0, availableWidth]);
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
- var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
- var g = wrap.select('g');
-
- gEnter.append('g').attr('class', 'nv-legendWrap');
-
- // this is the main chart
- var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
- focusEnter.append('g').attr('class', 'nv-x nv-axis');
- focusEnter.append('g').attr('class', 'nv-y1 nv-axis');
- focusEnter.append('g').attr('class', 'nv-y2 nv-axis');
- focusEnter.append('g').attr('class', 'nv-barsWrap');
- focusEnter.append('g').attr('class', 'nv-linesWrap');
-
- // context chart is where you can focus in
- var contextEnter = gEnter.append('g').attr('class', 'nv-context');
- contextEnter.append('g').attr('class', 'nv-x nv-axis');
- contextEnter.append('g').attr('class', 'nv-y1 nv-axis');
- contextEnter.append('g').attr('class', 'nv-y2 nv-axis');
- contextEnter.append('g').attr('class', 'nv-barsWrap');
- contextEnter.append('g').attr('class', 'nv-linesWrap');
- contextEnter.append('g').attr('class', 'nv-brushBackground');
- contextEnter.append('g').attr('class', 'nv-x nv-brush');
-
- //============================================================
- // Legend
- //------------------------------------------------------------
-
- if (!showLegend) {
- g.select('.nv-legendWrap').selectAll('*').remove();
- } else {
- var legendWidth = legend.align() ? availableWidth / 2 : availableWidth;
- var legendXPosition = legend.align() ? legendWidth : 0;
-
- legend.width(legendWidth);
-
- g.select('.nv-legendWrap')
- .datum(data.map(function(series) {
- series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
- if(switchYAxisOrder) {
- series.key = series.originalKey + (series.bar ? legendRightAxisHint : legendLeftAxisHint);
- } else {
- series.key = series.originalKey + (series.bar ? legendLeftAxisHint : legendRightAxisHint);
- }
- return series;
- }))
- .call(legend);
-
- if (!marginTop && legend.height() !== margin.top) {
- margin.top = legend.height();
- // FIXME: shouldn't this be "- (focusEnabled ? focusHeight : 0)"?
- availableHeight1 = nv.utils.availableHeight(height, container, margin) - focusHeight;
- }
-
- g.select('.nv-legendWrap')
- .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')');
- }
-
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- //============================================================
- // Context chart (focus chart) components
- //------------------------------------------------------------
-
- // hide or show the focus context chart
- g.select('.nv-context').style('display', focusEnable ? 'initial' : 'none');
-
- bars2
- .width(availableWidth)
- .height(availableHeight2)
- .color(data.map(function (d, i) {
- return d.color || color(d, i);
- }).filter(function (d, i) {
- return !data[i].disabled && data[i].bar
- }));
- lines2
- .width(availableWidth)
- .height(availableHeight2)
- .color(data.map(function (d, i) {
- return d.color || color(d, i);
- }).filter(function (d, i) {
- return !data[i].disabled && !data[i].bar
- }));
-
- var bars2Wrap = g.select('.nv-context .nv-barsWrap')
- .datum(dataBars.length ? dataBars : [
- {values: []}
- ]);
- var lines2Wrap = g.select('.nv-context .nv-linesWrap')
- .datum(allDisabled(dataLines) ?
- [{values: []}] :
- dataLines.filter(function(dataLine) {
- return !dataLine.disabled;
- }));
-
- g.select('.nv-context')
- .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')');
-
- bars2Wrap.transition().call(bars2);
- lines2Wrap.transition().call(lines2);
-
- // context (focus chart) axis controls
- if (focusShowAxisX) {
- x2Axis
- ._ticks( nv.utils.calcTicksX(availableWidth / 100, data))
- .tickSize(-availableHeight2, 0);
- g.select('.nv-context .nv-x.nv-axis')
- .attr('transform', 'translate(0,' + y3.range()[0] + ')');
- g.select('.nv-context .nv-x.nv-axis').transition()
- .call(x2Axis);
- }
-
- if (focusShowAxisY) {
- y3Axis
- .scale(y3)
- ._ticks( availableHeight2 / 36 )
- .tickSize( -availableWidth, 0);
- y4Axis
- .scale(y4)
- ._ticks( availableHeight2 / 36 )
- .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
-
- g.select('.nv-context .nv-y3.nv-axis')
- .style('opacity', dataBars.length ? 1 : 0)
- .attr('transform', 'translate(0,' + x2.range()[0] + ')');
- g.select('.nv-context .nv-y2.nv-axis')
- .style('opacity', dataLines.length ? 1 : 0)
- .attr('transform', 'translate(' + x2.range()[1] + ',0)');
-
- g.select('.nv-context .nv-y1.nv-axis').transition()
- .call(y3Axis);
- g.select('.nv-context .nv-y2.nv-axis').transition()
- .call(y4Axis);
- }
-
- // Setup Brush
- brush.x(x2).on('brush', onBrush);
-
- if (brushExtent) brush.extent(brushExtent);
-
- var brushBG = g.select('.nv-brushBackground').selectAll('g')
- .data([brushExtent || brush.extent()]);
-
- var brushBGenter = brushBG.enter()
- .append('g');
-
- brushBGenter.append('rect')
- .attr('class', 'left')
- .attr('x', 0)
- .attr('y', 0)
- .attr('height', availableHeight2);
-
- brushBGenter.append('rect')
- .attr('class', 'right')
- .attr('x', 0)
- .attr('y', 0)
- .attr('height', availableHeight2);
-
- var gBrush = g.select('.nv-x.nv-brush')
- .call(brush);
- gBrush.selectAll('rect')
- //.attr('y', -5)
- .attr('height', availableHeight2);
- gBrush.selectAll('.resize').append('path').attr('d', resizePath);
-
- //============================================================
- // Event Handling/Dispatching (in chart's scope)
- //------------------------------------------------------------
-
- legend.dispatch.on('stateChange', function(newState) {
- for (var key in newState)
- state[key] = newState[key];
- dispatch.stateChange(state);
- chart.update();
- });
-
- // Update chart from a state object passed to event handler
- dispatch.on('changeState', function(e) {
- if (typeof e.disabled !== 'undefined') {
- data.forEach(function(series,i) {
- series.disabled = e.disabled[i];
- });
- state.disabled = e.disabled;
- }
- chart.update();
- });
-
- //============================================================
- // Functions
- //------------------------------------------------------------
-
- // Taken from crossfilter (http://square.github.com/crossfilter/)
- function resizePath(d) {
- var e = +(d == 'e'),
- x = e ? 1 : -1,
- y = availableHeight2 / 3;
- return 'M' + (.5 * x) + ',' + y
- + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
- + 'V' + (2 * y - 6)
- + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
- + 'Z'
- + 'M' + (2.5 * x) + ',' + (y + 8)
- + 'V' + (2 * y - 8)
- + 'M' + (4.5 * x) + ',' + (y + 8)
- + 'V' + (2 * y - 8);
- }
-
-
- function updateBrushBG() {
- if (!brush.empty()) brush.extent(brushExtent);
- brushBG
- .data([brush.empty() ? x2.domain() : brushExtent])
- .each(function(d,i) {
- var leftWidth = x2(d[0]) - x2.range()[0],
- rightWidth = x2.range()[1] - x2(d[1]);
- d3.select(this).select('.left')
- .attr('width', leftWidth < 0 ? 0 : leftWidth);
-
- d3.select(this).select('.right')
- .attr('x', x2(d[1]))
- .attr('width', rightWidth < 0 ? 0 : rightWidth);
- });
- }
-
- function onBrush() {
- brushExtent = brush.empty() ? null : brush.extent();
- extent = brush.empty() ? x2.domain() : brush.extent();
- dispatch.brush({extent: extent, brush: brush});
- updateBrushBG();
-
- // Prepare Main (Focus) Bars and Lines
- bars
- .width(availableWidth)
- .height(availableHeight1)
- .color(data.map(function(d,i) {
- return d.color || color(d, i);
- }).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
-
- lines
- .width(availableWidth)
- .height(availableHeight1)
- .color(data.map(function(d,i) {
- return d.color || color(d, i);
- }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
-
- var focusBarsWrap = g.select('.nv-focus .nv-barsWrap')
- .datum(!dataBars.length ? [{values:[]}] :
- dataBars
- .map(function(d,i) {
- return {
- key: d.key,
- values: d.values.filter(function(d,i) {
- return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1];
- })
- }
- })
- );
-
- var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
- .datum(allDisabled(dataLines) ? [{values:[]}] :
- dataLines
- .filter(function(dataLine) { return !dataLine.disabled; })
- .map(function(d,i) {
- return {
- area: d.area,
- fillOpacity: d.fillOpacity,
- strokeWidth: d.strokeWidth,
- key: d.key,
- values: d.values.filter(function(d,i) {
- return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
- })
- }
- })
- );
-
- // Update Main (Focus) X Axis
- if (dataBars.length && !switchYAxisOrder) {
- x = bars.xScale();
- } else {
- x = lines.xScale();
- }
-
- xAxis
- .scale(x)
- ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
- .tickSize(-availableHeight1, 0);
-
- xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]);
-
- g.select('.nv-x.nv-axis').transition().duration(transitionDuration)
- .call(xAxis);
-
- // Update Main (Focus) Bars and Lines
- focusBarsWrap.transition().duration(transitionDuration).call(bars);
- focusLinesWrap.transition().duration(transitionDuration).call(lines);
-
- // Setup and Update Main (Focus) Y Axes
- g.select('.nv-focus .nv-x.nv-axis')
- .attr('transform', 'translate(0,' + y1.range()[0] + ')');
-
- y1Axis
- .scale(y1)
- ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
- .tickSize(-availableWidth, 0);
- y2Axis
- .scale(y2)
- ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) );
-
- // Show the y2 rules only if y1 has none
- if(!switchYAxisOrder) {
- y2Axis.tickSize(dataBars.length ? 0 : -availableWidth, 0);
- } else {
- y2Axis.tickSize(dataLines.length ? 0 : -availableWidth, 0);
- }
-
- // Calculate opacity of the axis
- var barsOpacity = dataBars.length ? 1 : 0;
- var linesOpacity = dataLines.length && !allDisabled(dataLines) ? 1 : 0;
-
- var y1Opacity = switchYAxisOrder ? linesOpacity : barsOpacity;
- var y2Opacity = switchYAxisOrder ? barsOpacity : linesOpacity;
-
- g.select('.nv-focus .nv-y1.nv-axis')
- .style('opacity', y1Opacity);
- g.select('.nv-focus .nv-y2.nv-axis')
- .style('opacity', y2Opacity)
- .attr('transform', 'translate(' + x.range()[1] + ',0)');
-
- g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration)
- .call(y1Axis);
- g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration)
- .call(y2Axis);
- }
-
- onBrush();
-
- });
-
- return chart;
- }
-
- //============================================================
- // Event Handling/Dispatching (out of chart's scope)
- //------------------------------------------------------------
-
- lines.dispatch.on('elementMouseover.tooltip', function(evt) {
- tooltip
- .duration(100)
- .valueFormatter(function(d, i) {
- return getLinesAxis().main.tickFormat()(d, i);
- })
- .data(evt)
- .hidden(false);
- });
-
- lines.dispatch.on('elementMouseout.tooltip', function(evt) {
- tooltip.hidden(true)
- });
-
- bars.dispatch.on('elementMouseover.tooltip', function(evt) {
- evt.value = chart.x()(evt.data);
- evt['series'] = {
- value: chart.y()(evt.data),
- color: evt.color
- };
- tooltip
- .duration(0)
- .valueFormatter(function(d, i) {
- return getBarsAxis().main.tickFormat()(d, i);
- })
- .data(evt)
- .hidden(false);
- });
-
- bars.dispatch.on('elementMouseout.tooltip', function(evt) {
- tooltip.hidden(true);
- });
-
- bars.dispatch.on('elementMousemove.tooltip', function(evt) {
- tooltip();
- });
-
- //============================================================
-
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- // expose chart's sub-components
- chart.dispatch = dispatch;
- chart.legend = legend;
- chart.lines = lines;
- chart.lines2 = lines2;
- chart.bars = bars;
- chart.bars2 = bars2;
- chart.xAxis = xAxis;
- chart.x2Axis = x2Axis;
- chart.y1Axis = y1Axis;
- chart.y2Axis = y2Axis;
- chart.y3Axis = y3Axis;
- chart.y4Axis = y4Axis;
- chart.tooltip = tooltip;
-
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
- brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
- noData: {get: function(){return noData;}, set: function(_){noData=_;}},
- focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}},
- focusHeight: {get: function(){return focusHeight;}, set: function(_){focusHeight=_;}},
- focusShowAxisX: {get: function(){return focusShowAxisX;}, set: function(_){focusShowAxisX=_;}},
- focusShowAxisY: {get: function(){return focusShowAxisY;}, set: function(_){focusShowAxisY=_;}},
- legendLeftAxisHint: {get: function(){return legendLeftAxisHint;}, set: function(_){legendLeftAxisHint=_;}},
- legendRightAxisHint: {get: function(){return legendRightAxisHint;}, set: function(_){legendRightAxisHint=_;}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- if (_.top !== undefined) {
- margin.top = _.top;
- marginTop = _.top;
- }
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- focusMargin: {get: function(){return margin2;}, set: function(_){
- margin2.top = _.top !== undefined ? _.top : margin2.top;
- margin2.right = _.right !== undefined ? _.right : margin2.right;
- margin2.bottom = _.bottom !== undefined ? _.bottom : margin2.bottom;
- margin2.left = _.left !== undefined ? _.left : margin2.left;
- }},
- duration: {get: function(){return transitionDuration;}, set: function(_){
- transitionDuration = _;
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- legend.color(color);
- }},
- x: {get: function(){return getX;}, set: function(_){
- getX = _;
- lines.x(_);
- lines2.x(_);
- bars.x(_);
- bars2.x(_);
- }},
- y: {get: function(){return getY;}, set: function(_){
- getY = _;
- lines.y(_);
- lines2.y(_);
- bars.y(_);
- bars2.y(_);
- }},
- switchYAxisOrder: {get: function(){return switchYAxisOrder;}, set: function(_){
- // Switch the tick format for the yAxis
- if(switchYAxisOrder !== _) {
- var y1 = y1Axis;
- y1Axis = y2Axis;
- y2Axis = y1;
-
- var y3 = y3Axis;
- y3Axis = y4Axis;
- y4Axis = y3;
- }
- switchYAxisOrder=_;
-
- y1Axis.orient('left');
- y2Axis.orient('right');
- y3Axis.orient('left');
- y4Axis.orient('right');
- }}
- });
-
- nv.utils.inheritOptions(chart, lines);
- nv.utils.initOptions(chart);
-
- return chart;
- };
-
- nv.models.multiBar = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var margin = {top: 0, right: 0, bottom: 0, left: 0}
- , width = 960
- , height = 500
- , x = d3.scale.ordinal()
- , y = d3.scale.linear()
- , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
- , container = null
- , getX = function(d) { return d.x }
- , getY = function(d) { return d.y }
- , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
- , clipEdge = true
- , stacked = false
- , stackOffset = 'zero' // options include 'silhouette', 'wiggle', 'expand', 'zero', or a custom function
- , color = nv.utils.defaultColor()
- , hideable = false
- , barColor = null // adding the ability to set the color for each rather than the whole group
- , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
- , duration = 500
- , xDomain
- , yDomain
- , xRange
- , yRange
- , groupSpacing = 0.1
- , fillOpacity = 0.75
- , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
- ;
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var x0, y0 //used to store previous scales
- , renderWatch = nv.utils.renderWatch(dispatch, duration)
- ;
-
- var last_datalength = 0;
-
- function chart(selection) {
- renderWatch.reset();
- selection.each(function(data) {
- var availableWidth = width - margin.left - margin.right,
- availableHeight = height - margin.top - margin.bottom;
-
- container = d3.select(this);
- nv.utils.initSVG(container);
- var nonStackableCount = 0;
- // This function defines the requirements for render complete
- var endFn = function(d, i) {
- if (d.series === data.length - 1 && i === data[0].values.length - 1)
- return true;
- return false;
- };
-
- if(hideable && data.length) hideable = [{
- values: data[0].values.map(function(d) {
- return {
- x: d.x,
- y: 0,
- series: d.series,
- size: 0.01
- };}
- )}];
-
- if (stacked) {
- var parsed = d3.layout.stack()
- .offset(stackOffset)
- .values(function(d){ return d.values })
- .y(getY)
- (!data.length && hideable ? hideable : data);
-
- parsed.forEach(function(series, i){
- // if series is non-stackable, use un-parsed data
- if (series.nonStackable) {
- data[i].nonStackableSeries = nonStackableCount++;
- parsed[i] = data[i];
- } else {
- // don't stack this seires on top of the nonStackable seriees
- if (i > 0 && parsed[i - 1].nonStackable){
- parsed[i].values.map(function(d,j){
- d.y0 -= parsed[i - 1].values[j].y;
- d.y1 = d.y0 + d.y;
- });
- }
- }
- });
- data = parsed;
- }
- //add series index and key to each data point for reference
- data.forEach(function(series, i) {
- series.values.forEach(function(point) {
- point.series = i;
- point.key = series.key;
- });
- });
-
- // HACK for negative value stacking
- if (stacked && data.length > 0) {
- data[0].values.map(function(d,i) {
- var posBase = 0, negBase = 0;
- data.map(function(d, idx) {
- if (!data[idx].nonStackable) {
- var f = d.values[i]
- f.size = Math.abs(f.y);
- if (f.y<0) {
- f.y1 = negBase;
- negBase = negBase - f.size;
- } else
- {
- f.y1 = f.size + posBase;
- posBase = posBase + f.size;
- }
- }
-
- });
- });
- }
- // Setup Scales
- // remap and flatten the data for use in calculating the scales' domains
- var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
- data.map(function(d, idx) {
- return d.values.map(function(d,i) {
- return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1, idx:idx }
- })
- });
-
- x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
- .rangeBands(xRange || [0, availableWidth], groupSpacing);
-
- y.domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) {
- var domain = d.y;
- // increase the domain range if this series is stackable
- if (stacked && !data[d.idx].nonStackable) {
- if (d.y > 0){
- domain = d.y1
- } else {
- domain = d.y1 + d.y
- }
- }
- return domain;
- }).concat(forceY)))
- .range(yRange || [availableHeight, 0]);
-
- // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
- if (x.domain()[0] === x.domain()[1])
- x.domain()[0] ?
- x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
- : x.domain([-1,1]);
-
- if (y.domain()[0] === y.domain()[1])
- y.domain()[0] ?
- y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
- : y.domain([-1,1]);
-
- x0 = x0 || x;
- y0 = y0 || y;
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]);
- var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar');
- var defsEnter = wrapEnter.append('defs');
- var gEnter = wrapEnter.append('g');
- var g = wrap.select('g');
-
- gEnter.append('g').attr('class', 'nv-groups');
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- defsEnter.append('clipPath')
- .attr('id', 'nv-edge-clip-' + id)
- .append('rect');
- wrap.select('#nv-edge-clip-' + id + ' rect')
- .attr('width', availableWidth)
- .attr('height', availableHeight);
-
- g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
-
- var groups = wrap.select('.nv-groups').selectAll('.nv-group')
- .data(function(d) { return d }, function(d,i) { return i });
- groups.enter().append('g')
- .style('stroke-opacity', 1e-6)
- .style('fill-opacity', 1e-6);
-
- var exitTransition = renderWatch
- .transition(groups.exit().selectAll('rect.nv-bar'), 'multibarExit', Math.min(100, duration))
- .attr('y', function(d, i, j) {
- var yVal = y0(0) || 0;
- if (stacked) {
- if (data[d.series] && !data[d.series].nonStackable) {
- yVal = y0(d.y0);
- }
- }
- return yVal;
- })
- .attr('height', 0)
- .remove();
- if (exitTransition.delay)
- exitTransition.delay(function(d,i) {
- var delay = i * (duration / (last_datalength + 1)) - i;
- return delay;
- });
- groups
- .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
- .classed('hover', function(d) { return d.hover })
- .style('fill', function(d,i){ return color(d, i) })
- .style('stroke', function(d,i){ return color(d, i) });
- groups
- .style('stroke-opacity', 1)
- .style('fill-opacity', fillOpacity);
-
- var bars = groups.selectAll('rect.nv-bar')
- .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values });
- bars.exit().remove();
-
- var barsEnter = bars.enter().append('rect')
- .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
- .attr('x', function(d,i,j) {
- return stacked && !data[j].nonStackable ? 0 : (j * x.rangeBand() / data.length )
- })
- .attr('y', function(d,i,j) { return y0(stacked && !data[j].nonStackable ? d.y0 : 0) || 0 })
- .attr('height', 0)
- .attr('width', function(d,i,j) { return x.rangeBand() / (stacked && !data[j].nonStackable ? 1 : data.length) })
- .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
- ;
- bars
- .style('fill', function(d,i,j){ return color(d, j, i); })
- .style('stroke', function(d,i,j){ return color(d, j, i); })
- .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
- d3.select(this).classed('hover', true);
- dispatch.elementMouseover({
- data: d,
- index: i,
- color: d3.select(this).style("fill")
- });
- })
- .on('mouseout', function(d,i) {
- d3.select(this).classed('hover', false);
- dispatch.elementMouseout({
- data: d,
- index: i,
- color: d3.select(this).style("fill")
- });
- })
- .on('mousemove', function(d,i) {
- dispatch.elementMousemove({
- data: d,
- index: i,
- color: d3.select(this).style("fill")
- });
- })
- .on('click', function(d,i) {
- var element = this;
- dispatch.elementClick({
- data: d,
- index: i,
- color: d3.select(this).style("fill"),
- event: d3.event,
- element: element
- });
- d3.event.stopPropagation();
- })
- .on('dblclick', function(d,i) {
- dispatch.elementDblClick({
- data: d,
- index: i,
- color: d3.select(this).style("fill")
- });
- d3.event.stopPropagation();
- });
- bars
- .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
- .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
-
- if (barColor) {
- if (!disabled) disabled = data.map(function() { return true });
- bars
- .style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); })
- .style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); });
- }
-
- var barSelection =
- bars.watchTransition(renderWatch, 'multibar', Math.min(250, duration))
- .delay(function(d,i) {
- return i * duration / data[0].values.length;
- });
- if (stacked){
- barSelection
- .attr('y', function(d,i,j) {
- var yVal = 0;
- // if stackable, stack it on top of the previous series
- if (!data[j].nonStackable) {
- yVal = y(d.y1);
- } else {
- if (getY(d,i) < 0){
- yVal = y(0);
- } else {
- if (y(0) - y(getY(d,i)) < -1){
- yVal = y(0) - 1;
- } else {
- yVal = y(getY(d, i)) || 0;
- }
- }
- }
- return yVal;
- })
- .attr('height', function(d,i,j) {
- if (!data[j].nonStackable) {
- return Math.max(Math.abs(y(d.y+d.y0) - y(d.y0)), 0);
- } else {
- return Math.max(Math.abs(y(getY(d,i)) - y(0)), 0) || 0;
- }
- })
- .attr('x', function(d,i,j) {
- var width = 0;
- if (data[j].nonStackable) {
- width = d.series * x.rangeBand() / data.length;
- if (data.length !== nonStackableCount){
- width = data[j].nonStackableSeries * x.rangeBand()/(nonStackableCount*2);
- }
- }
- return width;
- })
- .attr('width', function(d,i,j){
- if (!data[j].nonStackable) {
- return x.rangeBand();
- } else {
- // if all series are nonStacable, take the full width
- var width = (x.rangeBand() / nonStackableCount);
- // otherwise, nonStackable graph will be only taking the half-width
- // of the x rangeBand
- if (data.length !== nonStackableCount) {
- width = x.rangeBand()/(nonStackableCount*2);
- }
- return width;
- }
- });
- }
- else {
- barSelection
- .attr('x', function(d,i) {
- return d.series * x.rangeBand() / data.length;
- })
- .attr('width', x.rangeBand() / data.length)
- .attr('y', function(d,i) {
- return getY(d,i) < 0 ?
- y(0) :
- y(0) - y(getY(d,i)) < 1 ?
- y(0) - 1 :
- y(getY(d,i)) || 0;
- })
- .attr('height', function(d,i) {
- return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
- });
- }
-
- //store old scales for use in transitions on update
- x0 = x.copy();
- y0 = y.copy();
-
- // keep track of the last data value length for transition calculations
- if (data[0] && data[0].values) {
- last_datalength = data[0].values.length;
- }
-
- });
-
- renderWatch.renderEnd('multibar immediate');
-
- return chart;
- }
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- chart.dispatch = dispatch;
-
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- x: {get: function(){return getX;}, set: function(_){getX=_;}},
- y: {get: function(){return getY;}, set: function(_){getY=_;}},
- xScale: {get: function(){return x;}, set: function(_){x=_;}},
- yScale: {get: function(){return y;}, set: function(_){y=_;}},
- xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
- yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
- xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
- yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
- forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
- stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}},
- stackOffset: {get: function(){return stackOffset;}, set: function(_){stackOffset=_;}},
- clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
- disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}},
- id: {get: function(){return id;}, set: function(_){id=_;}},
- hideable: {get: function(){return hideable;}, set: function(_){hideable=_;}},
- groupSpacing:{get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}},
- fillOpacity: {get: function(){return fillOpacity;}, set: function(_){fillOpacity=_;}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top !== undefined ? _.top : margin.top;
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- duration: {get: function(){return duration;}, set: function(_){
- duration = _;
- renderWatch.reset(duration);
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- }},
- barColor: {get: function(){return barColor;}, set: function(_){
- barColor = _ ? nv.utils.getColor(_) : null;
- }}
- });
-
- nv.utils.initOptions(chart);
-
- return chart;
- };
- nv.models.multiBarChart = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var multibar = nv.models.multiBar()
- , xAxis = nv.models.axis()
- , yAxis = nv.models.axis()
- , interactiveLayer = nv.interactiveGuideline()
- , legend = nv.models.legend()
- , controls = nv.models.legend()
- , tooltip = nv.models.tooltip()
- ;
-
- var margin = {top: 30, right: 20, bottom: 50, left: 60}
- , marginTop = null
- , width = null
- , height = null
- , color = nv.utils.defaultColor()
- , showControls = true
- , controlLabels = {}
- , showLegend = true
- , showXAxis = true
- , showYAxis = true
- , rightAlignYAxis = false
- , reduceXTicks = true // if false a tick will show for every data point
- , staggerLabels = false
- , wrapLabels = false
- , rotateLabels = 0
- , x //can be accessed via chart.xScale()
- , y //can be accessed via chart.yScale()
- , state = nv.utils.state()
- , defaultState = null
- , noData = null
- , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
- , controlWidth = function() { return showControls ? 180 : 0 }
- , duration = 250
- , useInteractiveGuideline = false
- ;
-
- state.stacked = false // DEPRECATED Maintained for backward compatibility
-
- multibar.stacked(false);
- xAxis
- .orient('bottom')
- .tickPadding(7)
- .showMaxMin(false)
- .tickFormat(function(d) { return d })
- ;
- yAxis
- .orient((rightAlignYAxis) ? 'right' : 'left')
- .tickFormat(d3.format(',.1f'))
- ;
-
- tooltip
- .duration(0)
- .valueFormatter(function(d, i) {
- return yAxis.tickFormat()(d, i);
- })
- .headerFormatter(function(d, i) {
- return xAxis.tickFormat()(d, i);
- });
-
- interactiveLayer.tooltip
- .valueFormatter(function(d, i) {
- return d == null ? "N/A" : yAxis.tickFormat()(d, i);
- })
- .headerFormatter(function(d, i) {
- return xAxis.tickFormat()(d, i);
- });
-
- interactiveLayer.tooltip
- .valueFormatter(function (d, i) {
- return d == null ? "N/A" : yAxis.tickFormat()(d, i);
- })
- .headerFormatter(function (d, i) {
- return xAxis.tickFormat()(d, i);
- });
-
- interactiveLayer.tooltip
- .duration(0)
- .valueFormatter(function(d, i) {
- return yAxis.tickFormat()(d, i);
- })
- .headerFormatter(function(d, i) {
- return xAxis.tickFormat()(d, i);
- });
-
- controls.updateState(false);
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var renderWatch = nv.utils.renderWatch(dispatch);
- var stacked = false;
-
- var stateGetter = function(data) {
- return function(){
- return {
- active: data.map(function(d) { return !d.disabled }),
- stacked: stacked
- };
- }
- };
-
- var stateSetter = function(data) {
- return function(state) {
- if (state.stacked !== undefined)
- stacked = state.stacked;
- if (state.active !== undefined)
- data.forEach(function(series,i) {
- series.disabled = !state.active[i];
- });
- }
- };
-
- function chart(selection) {
- renderWatch.reset();
- renderWatch.models(multibar);
- if (showXAxis) renderWatch.models(xAxis);
- if (showYAxis) renderWatch.models(yAxis);
-
- selection.each(function(data) {
- var container = d3.select(this),
- that = this;
- nv.utils.initSVG(container);
- var availableWidth = nv.utils.availableWidth(width, container, margin),
- availableHeight = nv.utils.availableHeight(height, container, margin);
-
- chart.update = function() {
- if (duration === 0)
- container.call(chart);
- else
- container.transition()
- .duration(duration)
- .call(chart);
- };
- chart.container = this;
-
- state
- .setter(stateSetter(data), chart.update)
- .getter(stateGetter(data))
- .update();
-
- // DEPRECATED set state.disableddisabled
- state.disabled = data.map(function(d) { return !!d.disabled });
-
- if (!defaultState) {
- var key;
- defaultState = {};
- for (key in state) {
- if (state[key] instanceof Array)
- defaultState[key] = state[key].slice(0);
- else
- defaultState[key] = state[key];
- }
- }
-
- // Display noData message if there's nothing to show.
- if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
- nv.utils.noData(chart, container)
- return chart;
- } else {
- container.selectAll('.nv-noData').remove();
- }
-
- // Setup Scales
- x = multibar.xScale();
- y = multibar.yScale();
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]);
- var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g');
- var g = wrap.select('g');
-
- gEnter.append('g').attr('class', 'nv-x nv-axis');
- gEnter.append('g').attr('class', 'nv-y nv-axis');
- gEnter.append('g').attr('class', 'nv-barsWrap');
- gEnter.append('g').attr('class', 'nv-legendWrap');
- gEnter.append('g').attr('class', 'nv-controlsWrap');
- gEnter.append('g').attr('class', 'nv-interactive');
-
- // Legend
- if (!showLegend) {
- g.select('.nv-legendWrap').selectAll('*').remove();
- } else {
- legend.width(availableWidth - controlWidth());
-
- g.select('.nv-legendWrap')
- .datum(data)
- .call(legend);
-
- if (!marginTop && legend.height() !== margin.top) {
- margin.top = legend.height();
- availableHeight = nv.utils.availableHeight(height, container, margin);
- }
-
- g.select('.nv-legendWrap')
- .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
- }
-
- // Controls
- if (!showControls) {
- g.select('.nv-controlsWrap').selectAll('*').remove();
- } else {
- var controlsData = [
- { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() },
- { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() }
- ];
-
- controls.width(controlWidth()).color(['#444', '#444', '#444']);
- g.select('.nv-controlsWrap')
- .datum(controlsData)
- .attr('transform', 'translate(0,' + (-margin.top) +')')
- .call(controls);
- }
-
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
- if (rightAlignYAxis) {
- g.select(".nv-y.nv-axis")
- .attr("transform", "translate(" + availableWidth + ",0)");
- }
-
- // Main Chart Component(s)
- multibar
- .disabled(data.map(function(series) { return series.disabled }))
- .width(availableWidth)
- .height(availableHeight)
- .color(data.map(function(d,i) {
- return d.color || color(d, i);
- }).filter(function(d,i) { return !data[i].disabled }));
-
-
- var barsWrap = g.select('.nv-barsWrap')
- .datum(data.filter(function(d) { return !d.disabled }));
-
- barsWrap.call(multibar);
-
- // Setup Axes
- if (showXAxis) {
- xAxis
- .scale(x)
- ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
- .tickSize(-availableHeight, 0);
-
- g.select('.nv-x.nv-axis')
- .attr('transform', 'translate(0,' + y.range()[0] + ')');
- g.select('.nv-x.nv-axis')
- .call(xAxis);
-
- var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g');
-
- xTicks
- .selectAll('line, text')
- .style('opacity', 1)
-
- if (staggerLabels) {
- var getTranslate = function(x,y) {
- return "translate(" + x + "," + y + ")";
- };
-
- var staggerUp = 5, staggerDown = 17; //pixels to stagger by
- // Issue #140
- xTicks
- .selectAll("text")
- .attr('transform', function(d,i,j) {
- return getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown));
- });
-
- var totalInBetweenTicks = d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;
- g.selectAll(".nv-x.nv-axis .nv-axisMaxMin text")
- .attr("transform", function(d,i) {
- return getTranslate(0, (i === 0 || totalInBetweenTicks % 2 !== 0) ? staggerDown : staggerUp);
- });
- }
-
- if (wrapLabels) {
- g.selectAll('.tick text')
- .call(nv.utils.wrapTicks, chart.xAxis.rangeBand())
- }
-
- if (reduceXTicks)
- xTicks
- .filter(function(d,i) {
- return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0;
- })
- .selectAll('text, line')
- .style('opacity', 0);
-
- if(rotateLabels)
- xTicks
- .selectAll('.tick text')
- .attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
- .style('text-anchor', rotateLabels > 0 ? 'start' : 'end');
-
- g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text')
- .style('opacity', 1);
- }
-
- if (showYAxis) {
- yAxis
- .scale(y)
- ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
- .tickSize( -availableWidth, 0);
-
- g.select('.nv-y.nv-axis')
- .call(yAxis);
- }
-
- //Set up interactive layer
- if (useInteractiveGuideline) {
- interactiveLayer
- .width(availableWidth)
- .height(availableHeight)
- .margin({left:margin.left, top:margin.top})
- .svgContainer(container)
- .xScale(x);
- wrap.select(".nv-interactive").call(interactiveLayer);
- }
-
- //============================================================
- // Event Handling/Dispatching (in chart's scope)
- //------------------------------------------------------------
-
- legend.dispatch.on('stateChange', function(newState) {
- for (var key in newState)
- state[key] = newState[key];
- dispatch.stateChange(state);
- chart.update();
- });
-
- controls.dispatch.on('legendClick', function(d,i) {
- if (!d.disabled) return;
- controlsData = controlsData.map(function(s) {
- s.disabled = true;
- return s;
- });
- d.disabled = false;
-
- switch (d.key) {
- case 'Grouped':
- case controlLabels.grouped:
- multibar.stacked(false);
- break;
- case 'Stacked':
- case controlLabels.stacked:
- multibar.stacked(true);
- break;
- }
-
- state.stacked = multibar.stacked();
- dispatch.stateChange(state);
- chart.update();
- });
-
- // Update chart from a state object passed to event handler
- dispatch.on('changeState', function(e) {
- if (typeof e.disabled !== 'undefined') {
- data.forEach(function(series,i) {
- series.disabled = e.disabled[i];
- });
- state.disabled = e.disabled;
- }
- if (typeof e.stacked !== 'undefined') {
- multibar.stacked(e.stacked);
- state.stacked = e.stacked;
- stacked = e.stacked;
- }
- chart.update();
- });
-
- if (useInteractiveGuideline) {
- interactiveLayer.dispatch.on('elementMousemove', function(e) {
- if (e.pointXValue == undefined) return;
-
- var singlePoint, pointIndex, pointXLocation, xValue, allData = [];
- data
- .filter(function(series, i) {
- series.seriesIndex = i;
- return !series.disabled;
- })
- .forEach(function(series,i) {
- pointIndex = x.domain().indexOf(e.pointXValue)
-
- var point = series.values[pointIndex];
- if (point === undefined) return;
-
- xValue = point.x;
- if (singlePoint === undefined) singlePoint = point;
- if (pointXLocation === undefined) pointXLocation = e.mouseX
- allData.push({
- key: series.key,
- value: chart.y()(point, pointIndex),
- color: color(series,series.seriesIndex),
- data: series.values[pointIndex]
- });
- });
-
- interactiveLayer.tooltip
- .data({
- value: xValue,
- index: pointIndex,
- series: allData
- })();
-
- interactiveLayer.renderGuideLine(pointXLocation);
- });
-
- interactiveLayer.dispatch.on("elementMouseout",function(e) {
- interactiveLayer.tooltip.hidden(true);
- });
- }
- else {
- multibar.dispatch.on('elementMouseover.tooltip', function(evt) {
- evt.value = chart.x()(evt.data);
- evt['series'] = {
- key: evt.data.key,
- value: chart.y()(evt.data),
- color: evt.color
- };
- tooltip.data(evt).hidden(false);
- });
-
- multibar.dispatch.on('elementMouseout.tooltip', function(evt) {
- tooltip.hidden(true);
- });
-
- multibar.dispatch.on('elementMousemove.tooltip', function(evt) {
- tooltip();
- });
- }
- });
-
- renderWatch.renderEnd('multibarchart immediate');
- return chart;
- }
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- // expose chart's sub-components
- chart.dispatch = dispatch;
- chart.multibar = multibar;
- chart.legend = legend;
- chart.controls = controls;
- chart.xAxis = xAxis;
- chart.yAxis = yAxis;
- chart.state = state;
- chart.tooltip = tooltip;
- chart.interactiveLayer = interactiveLayer;
-
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
- showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
- controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
- showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
- showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
- defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
- noData: {get: function(){return noData;}, set: function(_){noData=_;}},
- reduceXTicks: {get: function(){return reduceXTicks;}, set: function(_){reduceXTicks=_;}},
- rotateLabels: {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
- staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
- wrapLabels: {get: function(){return wrapLabels;}, set: function(_){wrapLabels=!!_;}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- if (_.top !== undefined) {
- margin.top = _.top;
- marginTop = _.top;
- }
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- duration: {get: function(){return duration;}, set: function(_){
- duration = _;
- multibar.duration(duration);
- xAxis.duration(duration);
- yAxis.duration(duration);
- renderWatch.reset(duration);
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- legend.color(color);
- }},
- rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
- rightAlignYAxis = _;
- yAxis.orient( rightAlignYAxis ? 'right' : 'left');
- }},
- useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
- useInteractiveGuideline = _;
- }},
- barColor: {get: function(){return multibar.barColor;}, set: function(_){
- multibar.barColor(_);
- legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();})
- }}
- });
-
- nv.utils.inheritOptions(chart, multibar);
- nv.utils.initOptions(chart);
-
- return chart;
- };
-
- nv.models.multiBarHorizontal = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var margin = {top: 0, right: 0, bottom: 0, left: 0}
- , width = 960
- , height = 500
- , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
- , container = null
- , x = d3.scale.ordinal()
- , y = d3.scale.linear()
- , getX = function(d) { return d.x }
- , getY = function(d) { return d.y }
- , getYerr = function(d) { return d.yErr }
- , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
- , color = nv.utils.defaultColor()
- , barColor = null // adding the ability to set the color for each rather than the whole group
- , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
- , stacked = false
- , showValues = false
- , showBarLabels = false
- , valuePadding = 60
- , groupSpacing = 0.1
- , fillOpacity = 0.75
- , valueFormat = d3.format(',.2f')
- , delay = 1200
- , xDomain
- , yDomain
- , xRange
- , yRange
- , duration = 250
- , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
- ;
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var x0, y0; //used to store previous scales
- var renderWatch = nv.utils.renderWatch(dispatch, duration);
-
- function chart(selection) {
- renderWatch.reset();
- selection.each(function(data) {
- var availableWidth = width - margin.left - margin.right,
- availableHeight = height - margin.top - margin.bottom;
-
- container = d3.select(this);
- nv.utils.initSVG(container);
-
- if (stacked)
- data = d3.layout.stack()
- .offset('zero')
- .values(function(d){ return d.values })
- .y(getY)
- (data);
-
- //add series index and key to each data point for reference
- data.forEach(function(series, i) {
- series.values.forEach(function(point) {
- point.series = i;
- point.key = series.key;
- });
- });
-
- // HACK for negative value stacking
- if (stacked)
- data[0].values.map(function(d,i) {
- var posBase = 0, negBase = 0;
- data.map(function(d) {
- var f = d.values[i]
- f.size = Math.abs(f.y);
- if (f.y<0) {
- f.y1 = negBase - f.size;
- negBase = negBase - f.size;
- } else
- {
- f.y1 = posBase;
- posBase = posBase + f.size;
- }
- });
- });
-
- // Setup Scales
- // remap and flatten the data for use in calculating the scales' domains
- var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
- data.map(function(d) {
- return d.values.map(function(d,i) {
- return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
- })
- });
-
- x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
- .rangeBands(xRange || [0, availableHeight], groupSpacing);
-
- y.domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return stacked ? (d.y > 0 ? d.y1 + d.y : d.y1 ) : d.y }).concat(forceY)))
-
- if (showValues && !stacked)
- y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]);
- else
- y.range(yRange || [0, availableWidth]);
-
- x0 = x0 || x;
- y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]);
-
- // Setup containers and skeleton of chart
- var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]);
- var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal');
- var defsEnter = wrapEnter.append('defs');
- var gEnter = wrapEnter.append('g');
- var g = wrap.select('g');
-
- gEnter.append('g').attr('class', 'nv-groups');
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- var groups = wrap.select('.nv-groups').selectAll('.nv-group')
- .data(function(d) { return d }, function(d,i) { return i });
- groups.enter().append('g')
- .style('stroke-opacity', 1e-6)
- .style('fill-opacity', 1e-6);
- groups.exit().watchTransition(renderWatch, 'multibarhorizontal: exit groups')
- .style('stroke-opacity', 1e-6)
- .style('fill-opacity', 1e-6)
- .remove();
- groups
- .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
- .classed('hover', function(d) { return d.hover })
- .style('fill', function(d,i){ return color(d, i) })
- .style('stroke', function(d,i){ return color(d, i) });
- groups.watchTransition(renderWatch, 'multibarhorizontal: groups')
- .style('stroke-opacity', 1)
- .style('fill-opacity', fillOpacity);
-
- var bars = groups.selectAll('g.nv-bar')
- .data(function(d) { return d.values });
- bars.exit().remove();
-
- var barsEnter = bars.enter().append('g')
- .attr('transform', function(d,i,j) {
- return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')'
- });
-
- barsEnter.append('rect')
- .attr('width', 0)
- .attr('height', x.rangeBand() / (stacked ? 1 : data.length) )
-
- bars
- .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
- d3.select(this).classed('hover', true);
- dispatch.elementMouseover({
- data: d,
- index: i,
- color: d3.select(this).style("fill")
- });
- })
- .on('mouseout', function(d,i) {
- d3.select(this).classed('hover', false);
- dispatch.elementMouseout({
- data: d,
- index: i,
- color: d3.select(this).style("fill")
- });
- })
- .on('mouseout', function(d,i) {
- dispatch.elementMouseout({
- data: d,
- index: i,
- color: d3.select(this).style("fill")
- });
- })
- .on('mousemove', function(d,i) {
- dispatch.elementMousemove({
- data: d,
- index: i,
- color: d3.select(this).style("fill")
- });
- })
- .on('click', function(d,i) {
- var element = this;
- dispatch.elementClick({
- data: d,
- index: i,
- color: d3.select(this).style("fill"),
- event: d3.event,
- element: element
- });
- d3.event.stopPropagation();
- })
- .on('dblclick', function(d,i) {
- dispatch.elementDblClick({
- data: d,
- index: i,
- color: d3.select(this).style("fill")
- });
- d3.event.stopPropagation();
- });
-
- if (getYerr(data[0],0)) {
- barsEnter.append('polyline');
-
- bars.select('polyline')
- .attr('fill', 'none')
- .attr('points', function(d,i) {
- var xerr = getYerr(d,i)
- , mid = 0.8 * x.rangeBand() / ((stacked ? 1 : data.length) * 2);
- xerr = xerr.length ? xerr : [-Math.abs(xerr), Math.abs(xerr)];
- xerr = xerr.map(function(e) { return y(e) - y(0); });
- var a = [[xerr[0],-mid], [xerr[0],mid], [xerr[0],0], [xerr[1],0], [xerr[1],-mid], [xerr[1],mid]];
- return a.map(function (path) { return path.join(',') }).join(' ');
- })
- .attr('transform', function(d,i) {
- var mid = x.rangeBand() / ((stacked ? 1 : data.length) * 2);
- return 'translate(' + (getY(d,i) < 0 ? 0 : y(getY(d,i)) - y(0)) + ', ' + mid + ')'
- });
- }
-
- barsEnter.append('text');
-
- if (showValues && !stacked) {
- bars.select('text')
- .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' })
- .attr('y', x.rangeBand() / (data.length * 2))
- .attr('dy', '.32em')
- .text(function(d,i) {
- var t = valueFormat(getY(d,i))
- , yerr = getYerr(d,i);
- if (yerr === undefined)
- return t;
- if (!yerr.length)
- return t + '±' + valueFormat(Math.abs(yerr));
- return t + '+' + valueFormat(Math.abs(yerr[1])) + '-' + valueFormat(Math.abs(yerr[0]));
- });
- bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
- .select('text')
- .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 })
- } else {
- bars.selectAll('text').text('');
- }
-
- if (showBarLabels && !stacked) {
- barsEnter.append('text').classed('nv-bar-label',true);
- bars.select('text.nv-bar-label')
- .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'start' : 'end' })
- .attr('y', x.rangeBand() / (data.length * 2))
- .attr('dy', '.32em')
- .text(function(d,i) { return getX(d,i) });
- bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
- .select('text.nv-bar-label')
- .attr('x', function(d,i) { return getY(d,i) < 0 ? y(0) - y(getY(d,i)) + 4 : -4 });
- }
- else {
- bars.selectAll('text.nv-bar-label').text('');
- }
-
- bars
- .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
-
- if (barColor) {
- if (!disabled) disabled = data.map(function() { return true });
- bars
- .style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); })
- .style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); });
- }
-
- if (stacked)
- bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
- .attr('transform', function(d,i) {
- return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')'
- })
- .select('rect')
- .attr('width', function(d,i) {
- return Math.abs(y(getY(d,i) + d.y0) - y(d.y0)) || 0
- })
- .attr('height', x.rangeBand() );
- else
- bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
- .attr('transform', function(d,i) {
- //TODO: stacked must be all positive or all negative, not both?
- return 'translate(' +
- (getY(d,i) < 0 ? y(getY(d,i)) : y(0))
- + ',' +
- (d.series * x.rangeBand() / data.length
- +
- x(getX(d,i)) )
- + ')'
- })
- .select('rect')
- .attr('height', x.rangeBand() / data.length )
- .attr('width', function(d,i) {
- return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0
- });
-
- //store old scales for use in transitions on update
- x0 = x.copy();
- y0 = y.copy();
-
- });
-
- renderWatch.renderEnd('multibarHorizontal immediate');
- return chart;
- }
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- chart.dispatch = dispatch;
-
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- x: {get: function(){return getX;}, set: function(_){getX=_;}},
- y: {get: function(){return getY;}, set: function(_){getY=_;}},
- yErr: {get: function(){return getYerr;}, set: function(_){getYerr=_;}},
- xScale: {get: function(){return x;}, set: function(_){x=_;}},
- yScale: {get: function(){return y;}, set: function(_){y=_;}},
- xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
- yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
- xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
- yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
- forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
- stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}},
- showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}},
- // this shows the group name, seems pointless?
- //showBarLabels: {get: function(){return showBarLabels;}, set: function(_){showBarLabels=_;}},
- disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}},
- id: {get: function(){return id;}, set: function(_){id=_;}},
- valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
- valuePadding: {get: function(){return valuePadding;}, set: function(_){valuePadding=_;}},
- groupSpacing: {get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}},
- fillOpacity: {get: function(){return fillOpacity;}, set: function(_){fillOpacity=_;}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top !== undefined ? _.top : margin.top;
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- duration: {get: function(){return duration;}, set: function(_){
- duration = _;
- renderWatch.reset(duration);
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- }},
- barColor: {get: function(){return barColor;}, set: function(_){
- barColor = _ ? nv.utils.getColor(_) : null;
- }}
- });
-
- nv.utils.initOptions(chart);
-
- return chart;
- };
-
- nv.models.multiBarHorizontalChart = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var multibar = nv.models.multiBarHorizontal()
- , xAxis = nv.models.axis()
- , yAxis = nv.models.axis()
- , legend = nv.models.legend().height(30)
- , controls = nv.models.legend().height(30)
- , tooltip = nv.models.tooltip()
- ;
-
- var margin = {top: 30, right: 20, bottom: 50, left: 60}
- , marginTop = null
- , width = null
- , height = null
- , color = nv.utils.defaultColor()
- , showControls = true
- , controlLabels = {}
- , showLegend = true
- , showXAxis = true
- , showYAxis = true
- , stacked = false
- , x //can be accessed via chart.xScale()
- , y //can be accessed via chart.yScale()
- , state = nv.utils.state()
- , defaultState = null
- , noData = null
- , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd')
- , controlWidth = function() { return showControls ? 180 : 0 }
- , duration = 250
- ;
-
- state.stacked = false; // DEPRECATED Maintained for backward compatibility
-
- multibar.stacked(stacked);
-
- xAxis
- .orient('left')
- .tickPadding(5)
- .showMaxMin(false)
- .tickFormat(function(d) { return d })
- ;
- yAxis
- .orient('bottom')
- .tickFormat(d3.format(',.1f'))
- ;
-
- tooltip
- .duration(0)
- .valueFormatter(function(d, i) {
- return yAxis.tickFormat()(d, i);
- })
- .headerFormatter(function(d, i) {
- return xAxis.tickFormat()(d, i);
- });
-
- controls.updateState(false);
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var stateGetter = function(data) {
- return function(){
- return {
- active: data.map(function(d) { return !d.disabled }),
- stacked: stacked
- };
- }
- };
-
- var stateSetter = function(data) {
- return function(state) {
- if (state.stacked !== undefined)
- stacked = state.stacked;
- if (state.active !== undefined)
- data.forEach(function(series,i) {
- series.disabled = !state.active[i];
- });
- }
- };
-
- var renderWatch = nv.utils.renderWatch(dispatch, duration);
-
- function chart(selection) {
- renderWatch.reset();
- renderWatch.models(multibar);
- if (showXAxis) renderWatch.models(xAxis);
- if (showYAxis) renderWatch.models(yAxis);
-
- selection.each(function(data) {
- var container = d3.select(this),
- that = this;
- nv.utils.initSVG(container);
- var availableWidth = nv.utils.availableWidth(width, container, margin),
- availableHeight = nv.utils.availableHeight(height, container, margin);
-
- chart.update = function() { container.transition().duration(duration).call(chart) };
- chart.container = this;
-
- stacked = multibar.stacked();
-
- state
- .setter(stateSetter(data), chart.update)
- .getter(stateGetter(data))
- .update();
-
- // DEPRECATED set state.disableddisabled
- state.disabled = data.map(function(d) { return !!d.disabled });
-
- if (!defaultState) {
- var key;
- defaultState = {};
- for (key in state) {
- if (state[key] instanceof Array)
- defaultState[key] = state[key].slice(0);
- else
- defaultState[key] = state[key];
- }
- }
-
- // Display No Data message if there's nothing to show.
- if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
- nv.utils.noData(chart, container)
- return chart;
- } else {
- container.selectAll('.nv-noData').remove();
- }
-
- // Setup Scales
- x = multibar.xScale();
- y = multibar.yScale().clamp(true);
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]);
- var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g');
- var g = wrap.select('g');
-
- gEnter.append('g').attr('class', 'nv-x nv-axis');
- gEnter.append('g').attr('class', 'nv-y nv-axis')
- .append('g').attr('class', 'nv-zeroLine')
- .append('line');
- gEnter.append('g').attr('class', 'nv-barsWrap');
- gEnter.append('g').attr('class', 'nv-legendWrap');
- gEnter.append('g').attr('class', 'nv-controlsWrap');
-
- // Legend
- if (!showLegend) {
- g.select('.nv-legendWrap').selectAll('*').remove();
- } else {
- legend.width(availableWidth - controlWidth());
-
- g.select('.nv-legendWrap')
- .datum(data)
- .call(legend);
-
- if (!marginTop && legend.height() !== margin.top) {
- margin.top = legend.height();
- availableHeight = nv.utils.availableHeight(height, container, margin);
- }
-
- g.select('.nv-legendWrap')
- .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
- }
-
- // Controls
- if (!showControls) {
- g.select('.nv-controlsWrap').selectAll('*').remove();
- } else {
- var controlsData = [
- { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() },
- { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() }
- ];
-
- controls.width(controlWidth()).color(['#444', '#444', '#444']);
- g.select('.nv-controlsWrap')
- .datum(controlsData)
- .attr('transform', 'translate(0,' + (-margin.top) +')')
- .call(controls);
- }
-
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- // Main Chart Component(s)
- multibar
- .disabled(data.map(function(series) { return series.disabled }))
- .width(availableWidth)
- .height(availableHeight)
- .color(data.map(function(d,i) {
- return d.color || color(d, i);
- }).filter(function(d,i) { return !data[i].disabled }));
-
- var barsWrap = g.select('.nv-barsWrap')
- .datum(data.filter(function(d) { return !d.disabled }));
-
- barsWrap.transition().call(multibar);
-
- // Setup Axes
- if (showXAxis) {
- xAxis
- .scale(x)
- ._ticks( nv.utils.calcTicksY(availableHeight/24, data) )
- .tickSize(-availableWidth, 0);
-
- g.select('.nv-x.nv-axis').call(xAxis);
-
- var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
-
- xTicks
- .selectAll('line, text');
- }
-
- if (showYAxis) {
- yAxis
- .scale(y)
- ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
- .tickSize( -availableHeight, 0);
-
- g.select('.nv-y.nv-axis')
- .attr('transform', 'translate(0,' + availableHeight + ')');
- g.select('.nv-y.nv-axis').call(yAxis);
- }
-
- // Zero line
- g.select(".nv-zeroLine line")
- .attr("x1", y(0))
- .attr("x2", y(0))
- .attr("y1", 0)
- .attr("y2", -availableHeight)
- ;
-
- //============================================================
- // Event Handling/Dispatching (in chart's scope)
- //------------------------------------------------------------
-
- legend.dispatch.on('stateChange', function(newState) {
- for (var key in newState)
- state[key] = newState[key];
- dispatch.stateChange(state);
- chart.update();
- });
-
- controls.dispatch.on('legendClick', function(d,i) {
- if (!d.disabled) return;
- controlsData = controlsData.map(function(s) {
- s.disabled = true;
- return s;
- });
- d.disabled = false;
-
- switch (d.key) {
- case 'Grouped':
- case controlLabels.grouped:
- multibar.stacked(false);
- break;
- case 'Stacked':
- case controlLabels.stacked:
- multibar.stacked(true);
- break;
- }
-
- state.stacked = multibar.stacked();
- dispatch.stateChange(state);
- stacked = multibar.stacked();
-
- chart.update();
- });
-
- // Update chart from a state object passed to event handler
- dispatch.on('changeState', function(e) {
-
- if (typeof e.disabled !== 'undefined') {
- data.forEach(function(series,i) {
- series.disabled = e.disabled[i];
- });
-
- state.disabled = e.disabled;
- }
-
- if (typeof e.stacked !== 'undefined') {
- multibar.stacked(e.stacked);
- state.stacked = e.stacked;
- stacked = e.stacked;
- }
-
- chart.update();
- });
- });
- renderWatch.renderEnd('multibar horizontal chart immediate');
- return chart;
- }
-
- //============================================================
- // Event Handling/Dispatching (out of chart's scope)
- //------------------------------------------------------------
-
- multibar.dispatch.on('elementMouseover.tooltip', function(evt) {
- evt.value = chart.x()(evt.data);
- evt['series'] = {
- key: evt.data.key,
- value: chart.y()(evt.data),
- color: evt.color
- };
- tooltip.data(evt).hidden(false);
- });
-
- multibar.dispatch.on('elementMouseout.tooltip', function(evt) {
- tooltip.hidden(true);
- });
-
- multibar.dispatch.on('elementMousemove.tooltip', function(evt) {
- tooltip();
- });
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- // expose chart's sub-components
- chart.dispatch = dispatch;
- chart.multibar = multibar;
- chart.legend = legend;
- chart.controls = controls;
- chart.xAxis = xAxis;
- chart.yAxis = yAxis;
- chart.state = state;
- chart.tooltip = tooltip;
-
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
- showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
- controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
- showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
- showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
- defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
- noData: {get: function(){return noData;}, set: function(_){noData=_;}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- if (_.top !== undefined) {
- margin.top = _.top;
- marginTop = _.top;
- }
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- duration: {get: function(){return duration;}, set: function(_){
- duration = _;
- renderWatch.reset(duration);
- multibar.duration(duration);
- xAxis.duration(duration);
- yAxis.duration(duration);
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- legend.color(color);
- }},
- barColor: {get: function(){return multibar.barColor;}, set: function(_){
- multibar.barColor(_);
- legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();})
- }}
- });
-
- nv.utils.inheritOptions(chart, multibar);
- nv.utils.initOptions(chart);
-
- return chart;
- };
- nv.models.multiChart = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var margin = {top: 30, right: 20, bottom: 50, left: 60},
- marginTop = null,
- color = nv.utils.defaultColor(),
- width = null,
- height = null,
- showLegend = true,
- noData = null,
- yDomain1,
- yDomain2,
- getX = function(d) { return d.x },
- getY = function(d) { return d.y},
- interpolate = 'linear',
- useVoronoi = true,
- interactiveLayer = nv.interactiveGuideline(),
- useInteractiveGuideline = false,
- legendRightAxisHint = ' (right axis)',
- duration = 250
- ;
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var x = d3.scale.linear(),
- yScale1 = d3.scale.linear(),
- yScale2 = d3.scale.linear(),
-
- lines1 = nv.models.line().yScale(yScale1).duration(duration),
- lines2 = nv.models.line().yScale(yScale2).duration(duration),
-
- scatters1 = nv.models.scatter().yScale(yScale1).duration(duration),
- scatters2 = nv.models.scatter().yScale(yScale2).duration(duration),
-
- bars1 = nv.models.multiBar().stacked(false).yScale(yScale1).duration(duration),
- bars2 = nv.models.multiBar().stacked(false).yScale(yScale2).duration(duration),
-
- stack1 = nv.models.stackedArea().yScale(yScale1).duration(duration),
- stack2 = nv.models.stackedArea().yScale(yScale2).duration(duration),
-
- xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5).duration(duration),
- yAxis1 = nv.models.axis().scale(yScale1).orient('left').duration(duration),
- yAxis2 = nv.models.axis().scale(yScale2).orient('right').duration(duration),
-
- legend = nv.models.legend().height(30),
- tooltip = nv.models.tooltip(),
- dispatch = d3.dispatch();
-
- var charts = [lines1, lines2, scatters1, scatters2, bars1, bars2, stack1, stack2];
-
- function chart(selection) {
- selection.each(function(data) {
- var container = d3.select(this),
- that = this;
- nv.utils.initSVG(container);
-
- chart.update = function() { container.transition().call(chart); };
- chart.container = this;
-
- var availableWidth = nv.utils.availableWidth(width, container, margin),
- availableHeight = nv.utils.availableHeight(height, container, margin);
-
- var dataLines1 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 1});
- var dataLines2 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 2});
- var dataScatters1 = data.filter(function(d) {return d.type == 'scatter' && d.yAxis == 1});
- var dataScatters2 = data.filter(function(d) {return d.type == 'scatter' && d.yAxis == 2});
- var dataBars1 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 1});
- var dataBars2 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 2});
- var dataStack1 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 1});
- var dataStack2 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 2});
-
- // Display noData message if there's nothing to show.
- if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
- nv.utils.noData(chart, container);
- return chart;
- } else {
- container.selectAll('.nv-noData').remove();
- }
-
- var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1})
- .map(function(d) {
- return d.values.map(function(d,i) {
- return { x: getX(d), y: getY(d) }
- })
- });
-
- var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2})
- .map(function(d) {
- return d.values.map(function(d,i) {
- return { x: getX(d), y: getY(d) }
- })
- });
-
- x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x }))
- .range([0, availableWidth]);
-
- var wrap = container.selectAll('g.wrap.multiChart').data([data]);
- var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g');
-
- gEnter.append('g').attr('class', 'nv-x nv-axis');
- gEnter.append('g').attr('class', 'nv-y1 nv-axis');
- gEnter.append('g').attr('class', 'nv-y2 nv-axis');
- gEnter.append('g').attr('class', 'stack1Wrap');
- gEnter.append('g').attr('class', 'stack2Wrap');
- gEnter.append('g').attr('class', 'bars1Wrap');
- gEnter.append('g').attr('class', 'bars2Wrap');
- gEnter.append('g').attr('class', 'scatters1Wrap');
- gEnter.append('g').attr('class', 'scatters2Wrap');
- gEnter.append('g').attr('class', 'lines1Wrap');
- gEnter.append('g').attr('class', 'lines2Wrap');
- gEnter.append('g').attr('class', 'legendWrap');
- gEnter.append('g').attr('class', 'nv-interactive');
-
- var g = wrap.select('g');
-
- var color_array = data.map(function(d,i) {
- return data[i].color || color(d, i);
- });
-
- // Legend
- if (!showLegend) {
- g.select('.legendWrap').selectAll('*').remove();
- } else {
- var legendWidth = legend.align() ? availableWidth / 2 : availableWidth;
- var legendXPosition = legend.align() ? legendWidth : 0;
-
- legend.width(legendWidth);
- legend.color(color_array);
-
- g.select('.legendWrap')
- .datum(data.map(function(series) {
- series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
- series.key = series.originalKey + (series.yAxis == 1 ? '' : legendRightAxisHint);
- return series;
- }))
- .call(legend);
-
- if (!marginTop && legend.height() !== margin.top) {
- margin.top = legend.height();
- availableHeight = nv.utils.availableHeight(height, container, margin);
- }
-
- g.select('.legendWrap')
- .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')');
- }
-
- lines1
- .width(availableWidth)
- .height(availableHeight)
- .interpolate(interpolate)
- .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'}));
- lines2
- .width(availableWidth)
- .height(availableHeight)
- .interpolate(interpolate)
- .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'}));
- scatters1
- .width(availableWidth)
- .height(availableHeight)
- .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'scatter'}));
- scatters2
- .width(availableWidth)
- .height(availableHeight)
- .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'scatter'}));
- bars1
- .width(availableWidth)
- .height(availableHeight)
- .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'}));
- bars2
- .width(availableWidth)
- .height(availableHeight)
- .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'}));
- stack1
- .width(availableWidth)
- .height(availableHeight)
- .interpolate(interpolate)
- .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'}));
- stack2
- .width(availableWidth)
- .height(availableHeight)
- .interpolate(interpolate)
- .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'}));
-
- g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- var lines1Wrap = g.select('.lines1Wrap')
- .datum(dataLines1.filter(function(d){return !d.disabled}));
- var scatters1Wrap = g.select('.scatters1Wrap')
- .datum(dataScatters1.filter(function(d){return !d.disabled}));
- var bars1Wrap = g.select('.bars1Wrap')
- .datum(dataBars1.filter(function(d){return !d.disabled}));
- var stack1Wrap = g.select('.stack1Wrap')
- .datum(dataStack1.filter(function(d){return !d.disabled}));
- var lines2Wrap = g.select('.lines2Wrap')
- .datum(dataLines2.filter(function(d){return !d.disabled}));
- var scatters2Wrap = g.select('.scatters2Wrap')
- .datum(dataScatters2.filter(function(d){return !d.disabled}));
- var bars2Wrap = g.select('.bars2Wrap')
- .datum(dataBars2.filter(function(d){return !d.disabled}));
- var stack2Wrap = g.select('.stack2Wrap')
- .datum(dataStack2.filter(function(d){return !d.disabled}));
-
- var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){
- return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
- }).concat([{x:0, y:0}]) : [];
- var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){
- return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
- }).concat([{x:0, y:0}]) : [];
-
- yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } ))
- .range([0, availableHeight]);
-
- yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } ))
- .range([0, availableHeight]);
-
- lines1.yDomain(yScale1.domain());
- scatters1.yDomain(yScale1.domain());
- bars1.yDomain(yScale1.domain());
- stack1.yDomain(yScale1.domain());
-
- lines2.yDomain(yScale2.domain());
- scatters2.yDomain(yScale2.domain());
- bars2.yDomain(yScale2.domain());
- stack2.yDomain(yScale2.domain());
-
- if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);}
- if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);}
-
- if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);}
- if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);}
-
- if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);}
- if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);}
-
- if(dataScatters1.length){d3.transition(scatters1Wrap).call(scatters1);}
- if(dataScatters2.length){d3.transition(scatters2Wrap).call(scatters2);}
-
- xAxis
- ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
- .tickSize(-availableHeight, 0);
-
- g.select('.nv-x.nv-axis')
- .attr('transform', 'translate(0,' + availableHeight + ')');
- d3.transition(g.select('.nv-x.nv-axis'))
- .call(xAxis);
-
- yAxis1
- ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
- .tickSize( -availableWidth, 0);
-
-
- d3.transition(g.select('.nv-y1.nv-axis'))
- .call(yAxis1);
-
- yAxis2
- ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
- .tickSize( -availableWidth, 0);
-
- d3.transition(g.select('.nv-y2.nv-axis'))
- .call(yAxis2);
-
- g.select('.nv-y1.nv-axis')
- .classed('nv-disabled', series1.length ? false : true)
- .attr('transform', 'translate(' + x.range()[0] + ',0)');
-
- g.select('.nv-y2.nv-axis')
- .classed('nv-disabled', series2.length ? false : true)
- .attr('transform', 'translate(' + x.range()[1] + ',0)');
-
- legend.dispatch.on('stateChange', function(newState) {
- chart.update();
- });
-
- if(useInteractiveGuideline){
- interactiveLayer
- .width(availableWidth)
- .height(availableHeight)
- .margin({left:margin.left, top:margin.top})
- .svgContainer(container)
- .xScale(x);
- wrap.select(".nv-interactive").call(interactiveLayer);
- }
-
- //============================================================
- // Event Handling/Dispatching
- //------------------------------------------------------------
-
- function mouseover_line(evt) {
- var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1;
- evt.value = evt.point.x;
- evt.series = {
- value: evt.point.y,
- color: evt.point.color,
- key: evt.series.key
- };
- tooltip
- .duration(0)
- .headerFormatter(function(d, i) {
- return xAxis.tickFormat()(d, i);
- })
- .valueFormatter(function(d, i) {
- return yaxis.tickFormat()(d, i);
- })
- .data(evt)
- .hidden(false);
- }
-
- function mouseover_scatter(evt) {
- var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1;
- evt.value = evt.point.x;
- evt.series = {
- value: evt.point.y,
- color: evt.point.color,
- key: evt.series.key
- };
- tooltip
- .duration(100)
- .headerFormatter(function(d, i) {
- return xAxis.tickFormat()(d, i);
- })
- .valueFormatter(function(d, i) {
- return yaxis.tickFormat()(d, i);
- })
- .data(evt)
- .hidden(false);
- }
-
- function mouseover_stack(evt) {
- var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1;
- evt.point['x'] = stack1.x()(evt.point);
- evt.point['y'] = stack1.y()(evt.point);
- tooltip
- .duration(0)
- .headerFormatter(function(d, i) {
- return xAxis.tickFormat()(d, i);
- })
- .valueFormatter(function(d, i) {
- return yaxis.tickFormat()(d, i);
- })
- .data(evt)
- .hidden(false);
- }
-
- function mouseover_bar(evt) {
- var yaxis = data[evt.data.series].yAxis === 2 ? yAxis2 : yAxis1;
-
- evt.value = bars1.x()(evt.data);
- evt['series'] = {
- value: bars1.y()(evt.data),
- color: evt.color,
- key: evt.data.key
- };
- tooltip
- .duration(0)
- .headerFormatter(function(d, i) {
- return xAxis.tickFormat()(d, i);
- })
- .valueFormatter(function(d, i) {
- return yaxis.tickFormat()(d, i);
- })
- .data(evt)
- .hidden(false);
- }
-
-
-
- function clearHighlights() {
- for(var i=0, il=charts.length; i < il; i++){
- var chart = charts[i];
- try {
- chart.clearHighlights();
- } catch(e){}
- }
- }
-
- function highlightPoint(serieIndex, pointIndex, b){
- for(var i=0, il=charts.length; i < il; i++){
- var chart = charts[i];
- try {
- chart.highlightPoint(serieIndex, pointIndex, b);
- } catch(e){}
- }
- }
-
- if(useInteractiveGuideline){
- interactiveLayer.dispatch.on('elementMousemove', function(e) {
- clearHighlights();
- var singlePoint, pointIndex, pointXLocation, allData = [];
- data
- .filter(function(series, i) {
- series.seriesIndex = i;
- return !series.disabled;
- })
- .forEach(function(series,i) {
- var extent = x.domain();
- var currentValues = series.values.filter(function(d,i) {
- return chart.x()(d,i) >= extent[0] && chart.x()(d,i) <= extent[1];
- });
-
- pointIndex = nv.interactiveBisect(currentValues, e.pointXValue, chart.x());
- var point = currentValues[pointIndex];
- var pointYValue = chart.y()(point, pointIndex);
- if (pointYValue !== null) {
- highlightPoint(i, pointIndex, true);
- }
- if (point === undefined) return;
- if (singlePoint === undefined) singlePoint = point;
- if (pointXLocation === undefined) pointXLocation = x(chart.x()(point,pointIndex));
- allData.push({
- key: series.key,
- value: pointYValue,
- color: color(series,series.seriesIndex),
- data: point,
- yAxis: series.yAxis == 2 ? yAxis2 : yAxis1
- });
- });
-
- var defaultValueFormatter = function(d,i) {
- var yAxis = allData[i].yAxis;
- return d == null ? "N/A" : yAxis.tickFormat()(d);
- };
-
- interactiveLayer.tooltip
- .headerFormatter(function(d, i) {
- return xAxis.tickFormat()(d, i);
- })
- .valueFormatter(interactiveLayer.tooltip.valueFormatter() || defaultValueFormatter)
- .data({
- value: chart.x()( singlePoint,pointIndex ),
- index: pointIndex,
- series: allData
- })();
-
- interactiveLayer.renderGuideLine(pointXLocation);
- });
-
- interactiveLayer.dispatch.on("elementMouseout",function(e) {
- clearHighlights();
- });
- } else {
- lines1.dispatch.on('elementMouseover.tooltip', mouseover_line);
- lines2.dispatch.on('elementMouseover.tooltip', mouseover_line);
- lines1.dispatch.on('elementMouseout.tooltip', function(evt) {
- tooltip.hidden(true)
- });
- lines2.dispatch.on('elementMouseout.tooltip', function(evt) {
- tooltip.hidden(true)
- });
-
- scatters1.dispatch.on('elementMouseover.tooltip', mouseover_scatter);
- scatters2.dispatch.on('elementMouseover.tooltip', mouseover_scatter);
- scatters1.dispatch.on('elementMouseout.tooltip', function(evt) {
- tooltip.hidden(true)
- });
- scatters2.dispatch.on('elementMouseout.tooltip', function(evt) {
- tooltip.hidden(true)
- });
-
- stack1.dispatch.on('elementMouseover.tooltip', mouseover_stack);
- stack2.dispatch.on('elementMouseover.tooltip', mouseover_stack);
- stack1.dispatch.on('elementMouseout.tooltip', function(evt) {
- tooltip.hidden(true)
- });
- stack2.dispatch.on('elementMouseout.tooltip', function(evt) {
- tooltip.hidden(true)
- });
-
- bars1.dispatch.on('elementMouseover.tooltip', mouseover_bar);
- bars2.dispatch.on('elementMouseover.tooltip', mouseover_bar);
-
- bars1.dispatch.on('elementMouseout.tooltip', function(evt) {
- tooltip.hidden(true);
- });
- bars2.dispatch.on('elementMouseout.tooltip', function(evt) {
- tooltip.hidden(true);
- });
- bars1.dispatch.on('elementMousemove.tooltip', function(evt) {
- tooltip();
- });
- bars2.dispatch.on('elementMousemove.tooltip', function(evt) {
- tooltip();
- });
- }
- });
-
- return chart;
- }
-
- //============================================================
- // Global getters and setters
- //------------------------------------------------------------
-
- chart.dispatch = dispatch;
- chart.legend = legend;
- chart.lines1 = lines1;
- chart.lines2 = lines2;
- chart.scatters1 = scatters1;
- chart.scatters2 = scatters2;
- chart.bars1 = bars1;
- chart.bars2 = bars2;
- chart.stack1 = stack1;
- chart.stack2 = stack2;
- chart.xAxis = xAxis;
- chart.yAxis1 = yAxis1;
- chart.yAxis2 = yAxis2;
- chart.tooltip = tooltip;
- chart.interactiveLayer = interactiveLayer;
-
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
- yDomain1: {get: function(){return yDomain1;}, set: function(_){yDomain1=_;}},
- yDomain2: {get: function(){return yDomain2;}, set: function(_){yDomain2=_;}},
- noData: {get: function(){return noData;}, set: function(_){noData=_;}},
- interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
- legendRightAxisHint: {get: function(){return legendRightAxisHint;}, set: function(_){legendRightAxisHint=_;}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- if (_.top !== undefined) {
- margin.top = _.top;
- marginTop = _.top;
- }
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- }},
- x: {get: function(){return getX;}, set: function(_){
- getX = _;
- lines1.x(_);
- lines2.x(_);
- scatters1.x(_);
- scatters2.x(_);
- bars1.x(_);
- bars2.x(_);
- stack1.x(_);
- stack2.x(_);
- }},
- y: {get: function(){return getY;}, set: function(_){
- getY = _;
- lines1.y(_);
- lines2.y(_);
- scatters1.y(_);
- scatters2.y(_);
- stack1.y(_);
- stack2.y(_);
- bars1.y(_);
- bars2.y(_);
- }},
- useVoronoi: {get: function(){return useVoronoi;}, set: function(_){
- useVoronoi=_;
- lines1.useVoronoi(_);
- lines2.useVoronoi(_);
- stack1.useVoronoi(_);
- stack2.useVoronoi(_);
- }},
-
- useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
- useInteractiveGuideline = _;
- if (useInteractiveGuideline) {
- lines1.interactive(false);
- lines1.useVoronoi(false);
- lines2.interactive(false);
- lines2.useVoronoi(false);
- stack1.interactive(false);
- stack1.useVoronoi(false);
- stack2.interactive(false);
- stack2.useVoronoi(false);
- scatters1.interactive(false);
- scatters2.interactive(false);
- }
- }},
-
- duration: {get: function(){return duration;}, set: function(_) {
- duration = _;
- [lines1, lines2, stack1, stack2, scatters1, scatters2, xAxis, yAxis1, yAxis2].forEach(function(model){
- model.duration(duration);
- });
- }}
- });
-
- nv.utils.initOptions(chart);
-
- return chart;
- };
-
- nv.models.ohlcBar = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var margin = {top: 0, right: 0, bottom: 0, left: 0}
- , width = null
- , height = null
- , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
- , container = null
- , x = d3.scale.linear()
- , y = d3.scale.linear()
- , getX = function(d) { return d.x }
- , getY = function(d) { return d.y }
- , getOpen = function(d) { return d.open }
- , getClose = function(d) { return d.close }
- , getHigh = function(d) { return d.high }
- , getLow = function(d) { return d.low }
- , forceX = []
- , forceY = []
- , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
- , clipEdge = true
- , color = nv.utils.defaultColor()
- , interactive = false
- , xDomain
- , yDomain
- , xRange
- , yRange
- , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove')
- ;
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- function chart(selection) {
- selection.each(function(data) {
- container = d3.select(this);
- var availableWidth = nv.utils.availableWidth(width, container, margin),
- availableHeight = nv.utils.availableHeight(height, container, margin);
-
- nv.utils.initSVG(container);
-
- // ohlc bar width.
- var w = (availableWidth / data[0].values.length) * .9;
-
- // Setup Scales
- x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
-
- if (padData)
- x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
- else
- x.range(xRange || [5 + w/2, availableWidth - w/2 - 5]);
-
- y.domain(yDomain || [
- d3.min(data[0].values.map(getLow).concat(forceY)),
- d3.max(data[0].values.map(getHigh).concat(forceY))
- ]
- ).range(yRange || [availableHeight, 0]);
-
- // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
- if (x.domain()[0] === x.domain()[1])
- x.domain()[0] ?
- x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
- : x.domain([-1,1]);
-
- if (y.domain()[0] === y.domain()[1])
- y.domain()[0] ?
- y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
- : y.domain([-1,1]);
-
- // Setup containers and skeleton of chart
- var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]);
- var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar');
- var defsEnter = wrapEnter.append('defs');
- var gEnter = wrapEnter.append('g');
- var g = wrap.select('g');
-
- gEnter.append('g').attr('class', 'nv-ticks');
-
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- container
- .on('click', function(d,i) {
- dispatch.chartClick({
- data: d,
- index: i,
- pos: d3.event,
- id: id
- });
- });
-
- defsEnter.append('clipPath')
- .attr('id', 'nv-chart-clip-path-' + id)
- .append('rect');
-
- wrap.select('#nv-chart-clip-path-' + id + ' rect')
- .attr('width', availableWidth)
- .attr('height', availableHeight);
-
- g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
-
- var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
- .data(function(d) { return d });
- ticks.exit().remove();
-
- ticks.enter().append('path')
- .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
- .attr('d', function(d,i) {
- return 'm0,0l0,'
- + (y(getOpen(d,i))
- - y(getHigh(d,i)))
- + 'l'
- + (-w/2)
- + ',0l'
- + (w/2)
- + ',0l0,'
- + (y(getLow(d,i)) - y(getOpen(d,i)))
- + 'l0,'
- + (y(getClose(d,i))
- - y(getLow(d,i)))
- + 'l'
- + (w/2)
- + ',0l'
- + (-w/2)
- + ',0z';
- })
- .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
- .attr('fill', function(d,i) { return color[0]; })
- .attr('stroke', function(d,i) { return color[0]; })
- .attr('x', 0 )
- .attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) })
- .attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) });
-
- // the bar colors are controlled by CSS currently
- ticks.attr('class', function(d,i,j) {
- return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i;
- });
-
- d3.transition(ticks)
- .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
- .attr('d', function(d,i) {
- var w = (availableWidth / data[0].values.length) * .9;
- return 'm0,0l0,'
- + (y(getOpen(d,i))
- - y(getHigh(d,i)))
- + 'l'
- + (-w/2)
- + ',0l'
- + (w/2)
- + ',0l0,'
- + (y(getLow(d,i))
- - y(getOpen(d,i)))
- + 'l0,'
- + (y(getClose(d,i))
- - y(getLow(d,i)))
- + 'l'
- + (w/2)
- + ',0l'
- + (-w/2)
- + ',0z';
- });
- });
-
- return chart;
- }
-
-
- //Create methods to allow outside functions to highlight a specific bar.
- chart.highlightPoint = function(pointIndex, isHoverOver) {
- chart.clearHighlights();
- container.select(".nv-ohlcBar .nv-tick-0-" + pointIndex)
- .classed("hover", isHoverOver)
- ;
- };
-
- chart.clearHighlights = function() {
- container.select(".nv-ohlcBar .nv-tick.hover")
- .classed("hover", false)
- ;
- };
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- chart.dispatch = dispatch;
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- xScale: {get: function(){return x;}, set: function(_){x=_;}},
- yScale: {get: function(){return y;}, set: function(_){y=_;}},
- xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
- yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
- xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
- yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
- forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
- forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
- padData: {get: function(){return padData;}, set: function(_){padData=_;}},
- clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
- id: {get: function(){return id;}, set: function(_){id=_;}},
- interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
-
- x: {get: function(){return getX;}, set: function(_){getX=_;}},
- y: {get: function(){return getY;}, set: function(_){getY=_;}},
- open: {get: function(){return getOpen();}, set: function(_){getOpen=_;}},
- close: {get: function(){return getClose();}, set: function(_){getClose=_;}},
- high: {get: function(){return getHigh;}, set: function(_){getHigh=_;}},
- low: {get: function(){return getLow;}, set: function(_){getLow=_;}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top != undefined ? _.top : margin.top;
- margin.right = _.right != undefined ? _.right : margin.right;
- margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
- margin.left = _.left != undefined ? _.left : margin.left;
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- }}
- });
-
- nv.utils.initOptions(chart);
- return chart;
- };
- // Code adapted from Jason Davies' "Parallel Coordinates"
- // http://bl.ocks.org/jasondavies/1341281
- nv.models.parallelCoordinates = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var margin = {top: 30, right: 0, bottom: 10, left: 0}
- , width = null
- , height = null
- , availableWidth = null
- , availableHeight = null
- , x = d3.scale.ordinal()
- , y = {}
- , undefinedValuesLabel = "undefined values"
- , dimensionData = []
- , enabledDimensions = []
- , dimensionNames = []
- , displayBrush = true
- , color = nv.utils.defaultColor()
- , filters = []
- , active = []
- , dragging = []
- , axisWithUndefinedValues = []
- , lineTension = 1
- , foreground
- , background
- , dimensions
- , line = d3.svg.line()
- , axis = d3.svg.axis()
- , dispatch = d3.dispatch('brushstart', 'brush', 'brushEnd', 'dimensionsOrder', "stateChange", 'elementClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd', 'activeChanged')
- ;
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var renderWatch = nv.utils.renderWatch(dispatch);
-
- function chart(selection) {
- renderWatch.reset();
- selection.each(function(data) {
- var container = d3.select(this);
- availableWidth = nv.utils.availableWidth(width, container, margin);
- availableHeight = nv.utils.availableHeight(height, container, margin);
-
- nv.utils.initSVG(container);
-
- //Convert old data to new format (name, values)
- if (data[0].values === undefined) {
- var newData = [];
- data.forEach(function (d) {
- var val = {};
- var key = Object.keys(d);
- key.forEach(function (k) { if (k !== "name") val[k] = d[k] });
- newData.push({ key: d.name, values: val });
- });
- data = newData;
- }
-
- var dataValues = data.map(function (d) {return d.values});
- if (active.length === 0) {
- active = data;
- }; //set all active before first brush call
-
- dimensionNames = dimensionData.sort(function (a, b) { return a.currentPosition - b.currentPosition; }).map(function (d) { return d.key });
- enabledDimensions = dimensionData.filter(function (d) { return !d.disabled; });
-
- // Setup Scales
- x.rangePoints([0, availableWidth], 1).domain(enabledDimensions.map(function (d) { return d.key; }));
-
- //Set as true if all values on an axis are missing.
- // Extract the list of dimensions and create a scale for each.
- var oldDomainMaxValue = {};
- var displayMissingValuesline = false;
- var currentTicks = [];
-
- dimensionNames.forEach(function(d) {
- var extent = d3.extent(dataValues, function (p) { return +p[d]; });
- var min = extent[0];
- var max = extent[1];
- var onlyUndefinedValues = false;
- //If there is no values to display on an axis, set the extent to 0
- if (isNaN(min) || isNaN(max)) {
- onlyUndefinedValues = true;
- min = 0;
- max = 0;
- }
- //Scale axis if there is only one value
- if (min === max) {
- min = min - 1;
- max = max + 1;
- }
- var f = filters.filter(function (k) { return k.dimension == d; });
- if (f.length !== 0) {
- //If there is only NaN values, keep the existing domain.
- if (onlyUndefinedValues) {
- min = y[d].domain()[0];
- max = y[d].domain()[1];
- }
- //If the brush extent is > max (< min), keep the extent value.
- else if (!f[0].hasOnlyNaN && displayBrush) {
- min = min > f[0].extent[0] ? f[0].extent[0] : min;
- max = max < f[0].extent[1] ? f[0].extent[1] : max;
- }
- //If there is NaN values brushed be sure the brush extent is on the domain.
- else if (f[0].hasNaN) {
- max = max < f[0].extent[1] ? f[0].extent[1] : max;
- oldDomainMaxValue[d] = y[d].domain()[1];
- displayMissingValuesline = true;
- }
- }
- //Use 90% of (availableHeight - 12) for the axis range, 12 reprensenting the space necessary to display "undefined values" text.
- //The remaining 10% are used to display the missingValue line.
- y[d] = d3.scale.linear()
- .domain([min, max])
- .range([(availableHeight - 12) * 0.9, 0]);
-
- axisWithUndefinedValues = [];
- y[d].brush = d3.svg.brush().y(y[d]).on('brushstart', brushstart).on('brush', brush).on('brushend', brushend);
- });
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-wrap.nv-parallelCoordinates').data([data]);
- var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-parallelCoordinates');
- var gEnter = wrapEnter.append('g');
- var g = wrap.select('g');
-
- gEnter.append('g').attr('class', 'nv-parallelCoordinates background');
- gEnter.append('g').attr('class', 'nv-parallelCoordinates foreground');
- gEnter.append('g').attr('class', 'nv-parallelCoordinates missingValuesline');
-
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- line.interpolate('cardinal').tension(lineTension);
- axis.orient('left');
- var axisDrag = d3.behavior.drag()
- .on('dragstart', dragStart)
- .on('drag', dragMove)
- .on('dragend', dragEnd);
-
- //Add missing value line at the bottom of the chart
- var missingValuesline, missingValueslineText;
- var step = x.range()[1] - x.range()[0];
- step = isNaN(step) ? x.range()[0] : step;
- if (!isNaN(step)) {
- var lineData = [0 + step / 2, availableHeight - 12, availableWidth - step / 2, availableHeight - 12];
- missingValuesline = wrap.select('.missingValuesline').selectAll('line').data([lineData]);
- missingValuesline.enter().append('line');
- missingValuesline.exit().remove();
- missingValuesline.attr("x1", function(d) { return d[0]; })
- .attr("y1", function(d) { return d[1]; })
- .attr("x2", function(d) { return d[2]; })
- .attr("y2", function(d) { return d[3]; });
-
- //Add the text "undefined values" under the missing value line
- missingValueslineText = wrap.select('.missingValuesline').selectAll('text').data([undefinedValuesLabel]);
- missingValueslineText.append('text').data([undefinedValuesLabel]);
- missingValueslineText.enter().append('text');
- missingValueslineText.exit().remove();
- missingValueslineText.attr("y", availableHeight)
- //To have the text right align with the missingValues line, substract 92 representing the text size.
- .attr("x", availableWidth - 92 - step / 2)
- .text(function(d) { return d; });
- }
- // Add grey background lines for context.
- background = wrap.select('.background').selectAll('path').data(data);
- background.enter().append('path');
- background.exit().remove();
- background.attr('d', path);
-
- // Add blue foreground lines for focus.
- foreground = wrap.select('.foreground').selectAll('path').data(data);
- foreground.enter().append('path')
- foreground.exit().remove();
- foreground.attr('d', path)
- .style("stroke-width", function (d, i) {
- if (isNaN(d.strokeWidth)) { d.strokeWidth = 1;} return d.strokeWidth;})
- .attr('stroke', function (d, i) { return d.color || color(d, i); });
- foreground.on("mouseover", function (d, i) {
- d3.select(this).classed('hover', true).style("stroke-width", d.strokeWidth + 2 + "px").style("stroke-opacity", 1);
- dispatch.elementMouseover({
- label: d.name,
- color: d.color || color(d, i),
- values: d.values,
- dimensions: enabledDimensions
- });
-
- });
- foreground.on("mouseout", function (d, i) {
- d3.select(this).classed('hover', false).style("stroke-width", d.strokeWidth + "px").style("stroke-opacity", 0.7);
- dispatch.elementMouseout({
- label: d.name,
- index: i
- });
- });
- foreground.on('mousemove', function (d, i) {
- dispatch.elementMousemove();
- });
- foreground.on('click', function (d) {
- dispatch.elementClick({
- id: d.id
- });
- });
- // Add a group element for each dimension.
- dimensions = g.selectAll('.dimension').data(enabledDimensions);
- var dimensionsEnter = dimensions.enter().append('g').attr('class', 'nv-parallelCoordinates dimension');
-
- dimensions.attr('transform', function(d) { return 'translate(' + x(d.key) + ',0)'; });
- dimensionsEnter.append('g').attr('class', 'nv-axis');
-
- // Add an axis and title.
- dimensionsEnter.append('text')
- .attr('class', 'nv-label')
- .style("cursor", "move")
- .attr('dy', '-1em')
- .attr('text-anchor', 'middle')
- .on("mouseover", function(d, i) {
- dispatch.elementMouseover({
- label: d.tooltip || d.key,
- color: d.color
- });
- })
- .on("mouseout", function(d, i) {
- dispatch.elementMouseout({
- label: d.tooltip
- });
- })
- .on('mousemove', function (d, i) {
- dispatch.elementMousemove();
- })
- .call(axisDrag);
-
- dimensionsEnter.append('g').attr('class', 'nv-brushBackground');
- dimensions.exit().remove();
- dimensions.select('.nv-label').text(function (d) { return d.key });
-
- // Add and store a brush for each axis.
- restoreBrush(displayBrush);
-
- var actives = dimensionNames.filter(function (p) { return !y[p].brush.empty(); }),
- extents = actives.map(function (p) { return y[p].brush.extent(); });
- var formerActive = active.slice(0);
-
- //Restore active values
- active = [];
- foreground.style("display", function (d) {
- var isActive = actives.every(function (p, i) {
- if ((isNaN(d.values[p]) || isNaN(parseFloat(d.values[p]))) && extents[i][0] == y[p].brush.y().domain()[0]) {
- return true;
- }
- return (extents[i][0] <= d.values[p] && d.values[p] <= extents[i][1]) && !isNaN(parseFloat(d.values[p]));
- });
- if (isActive)
- active.push(d);
- return !isActive ? "none" : null;
-
- });
-
- if (filters.length > 0 || !nv.utils.arrayEquals(active, formerActive)) {
- dispatch.activeChanged(active);
- }
-
- // Returns the path for a given data point.
- function path(d) {
- return line(enabledDimensions.map(function (p) {
- //If value if missing, put the value on the missing value line
- if (isNaN(d.values[p.key]) || isNaN(parseFloat(d.values[p.key])) || displayMissingValuesline) {
- var domain = y[p.key].domain();
- var range = y[p.key].range();
- var min = domain[0] - (domain[1] - domain[0]) / 9;
-
- //If it's not already the case, allow brush to select undefined values
- if (axisWithUndefinedValues.indexOf(p.key) < 0) {
-
- var newscale = d3.scale.linear().domain([min, domain[1]]).range([availableHeight - 12, range[1]]);
- y[p.key].brush.y(newscale);
- axisWithUndefinedValues.push(p.key);
- }
- if (isNaN(d.values[p.key]) || isNaN(parseFloat(d.values[p.key]))) {
- return [x(p.key), y[p.key](min)];
- }
- }
-
- //If parallelCoordinate contain missing values show the missing values line otherwise, hide it.
- if (missingValuesline !== undefined) {
- if (axisWithUndefinedValues.length > 0 || displayMissingValuesline) {
- missingValuesline.style("display", "inline");
- missingValueslineText.style("display", "inline");
- } else {
- missingValuesline.style("display", "none");
- missingValueslineText.style("display", "none");
- }
- }
- return [x(p.key), y[p.key](d.values[p.key])];
- }));
- }
-
- function restoreBrush(visible) {
- filters.forEach(function (f) {
- //If filter brushed NaN values, keep the brush on the bottom of the axis.
- var brushDomain = y[f.dimension].brush.y().domain();
- if (f.hasOnlyNaN) {
- f.extent[1] = (y[f.dimension].domain()[1] - brushDomain[0]) * (f.extent[1] - f.extent[0]) / (oldDomainMaxValue[f.dimension] - f.extent[0]) + brushDomain[0];
- }
- if (f.hasNaN) {
- f.extent[0] = brushDomain[0];
- }
- if (visible)
- y[f.dimension].brush.extent(f.extent);
- });
-
- dimensions.select('.nv-brushBackground')
- .each(function (d) {
- d3.select(this).call(y[d.key].brush);
-
- })
- .selectAll('rect')
- .attr('x', -8)
- .attr('width', 16);
-
- updateTicks();
- }
-
- // Handles a brush event, toggling the display of foreground lines.
- function brushstart() {
- //If brush aren't visible, show it before brushing again.
- if (displayBrush === false) {
- displayBrush = true;
- restoreBrush(true);
- }
- }
-
- // Handles a brush event, toggling the display of foreground lines.
- function brush() {
- actives = dimensionNames.filter(function (p) { return !y[p].brush.empty(); });
- extents = actives.map(function(p) { return y[p].brush.extent(); });
-
- filters = []; //erase current filters
- actives.forEach(function(d,i) {
- filters[i] = {
- dimension: d,
- extent: extents[i],
- hasNaN: false,
- hasOnlyNaN: false
- }
- });
-
- active = []; //erase current active list
- foreground.style('display', function(d) {
- var isActive = actives.every(function(p, i) {
- if ((isNaN(d.values[p]) || isNaN(parseFloat(d.values[p]))) && extents[i][0] == y[p].brush.y().domain()[0]) return true;
- return (extents[i][0] <= d.values[p] && d.values[p] <= extents[i][1]) && !isNaN(parseFloat(d.values[p]));
- });
- if (isActive) active.push(d);
- return isActive ? null : 'none';
- });
-
- updateTicks();
-
- dispatch.brush({
- filters: filters,
- active: active
- });
- }
- function brushend() {
- var hasActiveBrush = actives.length > 0 ? true : false;
- filters.forEach(function (f) {
- if (f.extent[0] === y[f.dimension].brush.y().domain()[0] && axisWithUndefinedValues.indexOf(f.dimension) >= 0)
- f.hasNaN = true;
- if (f.extent[1] < y[f.dimension].domain()[0])
- f.hasOnlyNaN = true;
- });
- dispatch.brushEnd(active, hasActiveBrush);
- }
- function updateTicks() {
- dimensions.select('.nv-axis')
- .each(function (d, i) {
- var f = filters.filter(function (k) { return k.dimension == d.key; });
- currentTicks[d.key] = y[d.key].domain();
-
- //If brush are available, display brush extent
- if (f.length != 0 && displayBrush)
- {
- currentTicks[d.key] = [];
- if (f[0].extent[1] > y[d.key].domain()[0])
- currentTicks[d.key] = [f[0].extent[1]];
- if (f[0].extent[0] >= y[d.key].domain()[0])
- currentTicks[d.key].push(f[0].extent[0]);
- }
-
- d3.select(this).call(axis.scale(y[d.key]).tickFormat(d.format).tickValues(currentTicks[d.key]));
- });
- }
- function dragStart(d) {
- dragging[d.key] = this.parentNode.__origin__ = x(d.key);
- background.attr("visibility", "hidden");
- }
- function dragMove(d) {
- dragging[d.key] = Math.min(availableWidth, Math.max(0, this.parentNode.__origin__ += d3.event.x));
- foreground.attr("d", path);
- enabledDimensions.sort(function (a, b) { return dimensionPosition(a.key) - dimensionPosition(b.key); });
- enabledDimensions.forEach(function (d, i) { return d.currentPosition = i; });
- x.domain(enabledDimensions.map(function (d) { return d.key; }));
- dimensions.attr("transform", function(d) { return "translate(" + dimensionPosition(d.key) + ")"; });
- }
- function dragEnd(d, i) {
- delete this.parentNode.__origin__;
- delete dragging[d.key];
- d3.select(this.parentNode).attr("transform", "translate(" + x(d.key) + ")");
- foreground
- .attr("d", path);
- background
- .attr("d", path)
- .attr("visibility", null);
-
- dispatch.dimensionsOrder(enabledDimensions);
- }
- function dimensionPosition(d) {
- var v = dragging[d];
- return v == null ? x(d) : v;
- }
- });
- return chart;
- }
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- chart.dispatch = dispatch;
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width= _;}},
- height: {get: function(){return height;}, set: function(_){height= _;}},
- dimensionData: { get: function () { return dimensionData; }, set: function (_) { dimensionData = _; } },
- displayBrush: { get: function () { return displayBrush; }, set: function (_) { displayBrush = _; } },
- filters: { get: function () { return filters; }, set: function (_) { filters = _; } },
- active: { get: function () { return active; }, set: function (_) { active = _; } },
- lineTension: {get: function(){return lineTension;}, set: function(_){lineTension = _;}},
- undefinedValuesLabel : {get: function(){return undefinedValuesLabel;}, set: function(_){undefinedValuesLabel=_;}},
-
- // deprecated options
- dimensions: {get: function () { return dimensionData.map(function (d){return d.key}); }, set: function (_) {
- // deprecated after 1.8.1
- nv.deprecated('dimensions', 'use dimensionData instead');
- if (dimensionData.length === 0) {
- _.forEach(function (k) { dimensionData.push({ key: k }) })
- } else {
- _.forEach(function (k, i) { dimensionData[i].key= k })
- }
- }},
- dimensionNames: {get: function () { return dimensionData.map(function (d){return d.key}); }, set: function (_) {
- // deprecated after 1.8.1
- nv.deprecated('dimensionNames', 'use dimensionData instead');
- dimensionNames = [];
- if (dimensionData.length === 0) {
- _.forEach(function (k) { dimensionData.push({ key: k }) })
- } else {
- _.forEach(function (k, i) { dimensionData[i].key = k })
- }
-
- }},
- dimensionFormats: {get: function () { return dimensionData.map(function (d) { return d.format }); }, set: function (_) {
- // deprecated after 1.8.1
- nv.deprecated('dimensionFormats', 'use dimensionData instead');
- if (dimensionData.length === 0) {
- _.forEach(function (f) { dimensionData.push({ format: f }) })
- } else {
- _.forEach(function (f, i) { dimensionData[i].format = f })
- }
-
- }},
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top !== undefined ? _.top : margin.top;
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- }}
- });
- nv.utils.initOptions(chart);
- return chart;
- };
- nv.models.parallelCoordinatesChart = function () {
- "use strict";
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var parallelCoordinates = nv.models.parallelCoordinates()
- var legend = nv.models.legend()
- var tooltip = nv.models.tooltip();
- var dimensionTooltip = nv.models.tooltip();
-
- var margin = { top: 0, right: 0, bottom: 0, left: 0 }
- , marginTop = null
- , width = null
- , height = null
- , showLegend = true
- , color = nv.utils.defaultColor()
- , state = nv.utils.state()
- , dimensionData = []
- , displayBrush = true
- , defaultState = null
- , noData = null
- , nanValue = "undefined"
- , dispatch = d3.dispatch('dimensionsOrder', 'brushEnd', 'stateChange', 'changeState', 'renderEnd')
- , controlWidth = function () { return showControls ? 180 : 0 }
- ;
-
- //============================================================
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var renderWatch = nv.utils.renderWatch(dispatch);
-
- var stateGetter = function(data) {
- return function() {
- return {
- active: data.map(function(d) { return !d.disabled })
- };
- }
- };
-
- var stateSetter = function(data) {
- return function(state) {
- if(state.active !== undefined) {
- data.forEach(function(series, i) {
- series.disabled = !state.active[i];
- });
- }
- }
- };
-
- tooltip.contentGenerator(function(data) {
- var str = '<table><thead><tr><td class="legend-color-guide"><div style="background-color:' + data.color + '"></div></td><td><strong>' + data.key + '</strong></td></tr></thead>';
- if(data.series.length !== 0)
- {
- str = str + '<tbody><tr><td height ="10px"></td></tr>';
- data.series.forEach(function(d){
- str = str + '<tr><td class="legend-color-guide"><div style="background-color:' + d.color + '"></div></td><td class="key">' + d.key + '</td><td class="value">' + d.value + '</td></tr>';
- });
- str = str + '</tbody>';
- }
- str = str + '</table>';
- return str;
- });
-
- //============================================================
- // Chart function
- //------------------------------------------------------------
-
- function chart(selection) {
- renderWatch.reset();
- renderWatch.models(parallelCoordinates);
-
- selection.each(function(data) {
- var container = d3.select(this);
- nv.utils.initSVG(container);
-
- var that = this;
-
- var availableWidth = nv.utils.availableWidth(width, container, margin),
- availableHeight = nv.utils.availableHeight(height, container, margin);
-
- chart.update = function() { container.call(chart); };
- chart.container = this;
-
- state.setter(stateSetter(dimensionData), chart.update)
- .getter(stateGetter(dimensionData))
- .update();
-
- //set state.disabled
- state.disabled = dimensionData.map(function (d) { return !!d.disabled });
-
- //Keep dimensions position in memory
- dimensionData = dimensionData.map(function (d) {d.disabled = !!d.disabled; return d});
- dimensionData.forEach(function (d, i) {
- d.originalPosition = isNaN(d.originalPosition) ? i : d.originalPosition;
- d.currentPosition = isNaN(d.currentPosition) ? i : d.currentPosition;
- });
-
- if (!defaultState) {
- var key;
- defaultState = {};
- for(key in state) {
- if(state[key] instanceof Array)
- defaultState[key] = state[key].slice(0);
- else
- defaultState[key] = state[key];
- }
- }
-
- // Display No Data message if there's nothing to show.
- if(!data || !data.length) {
- nv.utils.noData(chart, container);
- return chart;
- } else {
- container.selectAll('.nv-noData').remove();
- }
-
- //------------------------------------------------------------
- // Setup containers and skeleton of chart
-
- var wrap = container.selectAll('g.nv-wrap.nv-parallelCoordinatesChart').data([data]);
- var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-parallelCoordinatesChart').append('g');
-
- var g = wrap.select('g');
-
- gEnter.append('g').attr('class', 'nv-parallelCoordinatesWrap');
- gEnter.append('g').attr('class', 'nv-legendWrap');
-
- g.select("rect")
- .attr("width", availableWidth)
- .attr("height", (availableHeight > 0) ? availableHeight : 0);
-
- // Legend
- if (!showLegend) {
- g.select('.nv-legendWrap').selectAll('*').remove();
- } else {
- legend.width(availableWidth)
- .color(function (d) { return "rgb(188,190,192)"; });
-
- g.select('.nv-legendWrap')
- .datum(dimensionData.sort(function (a, b) { return a.originalPosition - b.originalPosition; }))
- .call(legend);
-
- if (!marginTop && legend.height() !== margin.top) {
- margin.top = legend.height();
- availableHeight = nv.utils.availableHeight(height, container, margin);
- }
- wrap.select('.nv-legendWrap')
- .attr('transform', 'translate( 0 ,' + (-margin.top) + ')');
- }
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- // Main Chart Component(s)
- parallelCoordinates
- .width(availableWidth)
- .height(availableHeight)
- .dimensionData(dimensionData)
- .displayBrush(displayBrush);
-
- var parallelCoordinatesWrap = g.select('.nv-parallelCoordinatesWrap ')
- .datum(data);
-
- parallelCoordinatesWrap.transition().call(parallelCoordinates);
-
- //============================================================
- // Event Handling/Dispatching (in chart's scope)
- //------------------------------------------------------------
- //Display reset brush button
- parallelCoordinates.dispatch.on('brushEnd', function (active, hasActiveBrush) {
- if (hasActiveBrush) {
- displayBrush = true;
- dispatch.brushEnd(active);
- } else {
-
- displayBrush = false;
- }
- });
-
- legend.dispatch.on('stateChange', function(newState) {
- for(var key in newState) {
- state[key] = newState[key];
- }
- dispatch.stateChange(state);
- chart.update();
- });
-
- //Update dimensions order and display reset sorting button
- parallelCoordinates.dispatch.on('dimensionsOrder', function (e) {
- dimensionData.sort(function (a, b) { return a.currentPosition - b.currentPosition; });
- var isSorted = false;
- dimensionData.forEach(function (d, i) {
- d.currentPosition = i;
- if (d.currentPosition !== d.originalPosition)
- isSorted = true;
- });
- dispatch.dimensionsOrder(dimensionData, isSorted);
- });
-
- // Update chart from a state object passed to event handler
- dispatch.on('changeState', function (e) {
-
- if (typeof e.disabled !== 'undefined') {
- dimensionData.forEach(function (series, i) {
- series.disabled = e.disabled[i];
- });
- state.disabled = e.disabled;
- }
- chart.update();
- });
- });
-
- renderWatch.renderEnd('parraleleCoordinateChart immediate');
- return chart;
- }
-
- //============================================================
- // Event Handling/Dispatching (out of chart's scope)
- //------------------------------------------------------------
-
- parallelCoordinates.dispatch.on('elementMouseover.tooltip', function (evt) {
- var tp = {
- key: evt.label,
- color: evt.color,
- series: []
- }
- if(evt.values){
- Object.keys(evt.values).forEach(function (d) {
- var dim = evt.dimensions.filter(function (dd) {return dd.key === d;})[0];
- if(dim){
- var v;
- if (isNaN(evt.values[d]) || isNaN(parseFloat(evt.values[d]))) {
- v = nanValue;
- } else {
- v = dim.format(evt.values[d]);
- }
- tp.series.push({ idx: dim.currentPosition, key: d, value: v, color: dim.color });
- }
- });
- tp.series.sort(function(a,b) {return a.idx - b.idx});
- }
- tooltip.data(tp).hidden(false);
- });
-
- parallelCoordinates.dispatch.on('elementMouseout.tooltip', function(evt) {
- tooltip.hidden(true)
- });
-
- parallelCoordinates.dispatch.on('elementMousemove.tooltip', function () {
- tooltip();
- });
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- // expose chart's sub-components
- chart.dispatch = dispatch;
- chart.parallelCoordinates = parallelCoordinates;
- chart.legend = legend;
- chart.tooltip = tooltip;
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: { get: function () { return width; }, set: function (_) { width = _; } },
- height: { get: function () { return height; }, set: function (_) { height = _; } },
- showLegend: { get: function () { return showLegend; }, set: function (_) { showLegend = _; } },
- defaultState: { get: function () { return defaultState; }, set: function (_) { defaultState = _; } },
- dimensionData: { get: function () { return dimensionData; }, set: function (_) { dimensionData = _; } },
- displayBrush: { get: function () { return displayBrush; }, set: function (_) { displayBrush = _; } },
- noData: { get: function () { return noData; }, set: function (_) { noData = _; } },
- nanValue: { get: function () { return nanValue; }, set: function (_) { nanValue = _; } },
-
- // options that require extra logic in the setter
- margin: {
- get: function () { return margin; },
- set: function (_) {
- if (_.top !== undefined) {
- margin.top = _.top;
- marginTop = _.top;
- }
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }
- },
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- legend.color(color);
- parallelCoordinates.color(color);
- }}
- });
-
- nv.utils.inheritOptions(chart, parallelCoordinates);
- nv.utils.initOptions(chart);
-
- return chart;
- };
- nv.models.pie = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var margin = {top: 0, right: 0, bottom: 0, left: 0}
- , width = 500
- , height = 500
- , getX = function(d) { return d.x }
- , getY = function(d) { return d.y }
- , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
- , container = null
- , color = nv.utils.defaultColor()
- , valueFormat = d3.format(',.2f')
- , showLabels = true
- , labelsOutside = false
- , labelType = "key"
- , labelThreshold = .02 //if slice percentage is under this, don't show label
- , donut = false
- , title = false
- , growOnHover = true
- , titleOffset = 0
- , labelSunbeamLayout = false
- , startAngle = false
- , padAngle = false
- , endAngle = false
- , cornerRadius = 0
- , donutRatio = 0.5
- , duration = 250
- , arcsRadius = []
- , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
- ;
-
- var arcs = [];
- var arcsOver = [];
-
- //============================================================
- // chart function
- //------------------------------------------------------------
-
- var renderWatch = nv.utils.renderWatch(dispatch);
-
- function chart(selection) {
- renderWatch.reset();
- selection.each(function(data) {
- var availableWidth = width - margin.left - margin.right
- , availableHeight = height - margin.top - margin.bottom
- , radius = Math.min(availableWidth, availableHeight) / 2
- , arcsRadiusOuter = []
- , arcsRadiusInner = []
- ;
-
- container = d3.select(this)
- if (arcsRadius.length === 0) {
- var outer = radius - radius / 5;
- var inner = donutRatio * radius;
- for (var i = 0; i < data[0].length; i++) {
- arcsRadiusOuter.push(outer);
- arcsRadiusInner.push(inner);
- }
- } else {
- if(growOnHover){
- arcsRadiusOuter = arcsRadius.map(function (d) { return (d.outer - d.outer / 5) * radius; });
- arcsRadiusInner = arcsRadius.map(function (d) { return (d.inner - d.inner / 5) * radius; });
- donutRatio = d3.min(arcsRadius.map(function (d) { return (d.inner - d.inner / 5); }));
- } else {
- arcsRadiusOuter = arcsRadius.map(function (d) { return d.outer * radius; });
- arcsRadiusInner = arcsRadius.map(function (d) { return d.inner * radius; });
- donutRatio = d3.min(arcsRadius.map(function (d) { return d.inner; }));
- }
- }
- nv.utils.initSVG(container);
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('.nv-wrap.nv-pie').data(data);
- var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id);
- var gEnter = wrapEnter.append('g');
- var g = wrap.select('g');
- var g_pie = gEnter.append('g').attr('class', 'nv-pie');
- gEnter.append('g').attr('class', 'nv-pieLabels');
-
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
- g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
- g.select('.nv-pieLabels').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
-
- //
- container.on('click', function(d,i) {
- dispatch.chartClick({
- data: d,
- index: i,
- pos: d3.event,
- id: id
- });
- });
-
- arcs = [];
- arcsOver = [];
- for (var i = 0; i < data[0].length; i++) {
-
- var arc = d3.svg.arc().outerRadius(arcsRadiusOuter[i]);
- var arcOver = d3.svg.arc().outerRadius(arcsRadiusOuter[i] + 5);
-
- if (startAngle !== false) {
- arc.startAngle(startAngle);
- arcOver.startAngle(startAngle);
- }
- if (endAngle !== false) {
- arc.endAngle(endAngle);
- arcOver.endAngle(endAngle);
- }
- if (donut) {
- arc.innerRadius(arcsRadiusInner[i]);
- arcOver.innerRadius(arcsRadiusInner[i]);
- }
-
- if (arc.cornerRadius && cornerRadius) {
- arc.cornerRadius(cornerRadius);
- arcOver.cornerRadius(cornerRadius);
- }
-
- arcs.push(arc);
- arcsOver.push(arcOver);
- }
-
- // Setup the Pie chart and choose the data element
- var pie = d3.layout.pie()
- .sort(null)
- .value(function(d) { return d.disabled ? 0 : getY(d) });
-
- // padAngle added in d3 3.5
- if (pie.padAngle && padAngle) {
- pie.padAngle(padAngle);
- }
-
- // if title is specified and donut, put it in the middle
- if (donut && title) {
- g_pie.append("text").attr('class', 'nv-pie-title');
-
- wrap.select('.nv-pie-title')
- .style("text-anchor", "middle")
- .text(function (d) {
- return title;
- })
- .style("font-size", (Math.min(availableWidth, availableHeight)) * donutRatio * 2 / (title.length + 2) + "px")
- .attr("dy", "0.35em") // trick to vertically center text
- .attr('transform', function(d, i) {
- return 'translate(0, '+ titleOffset + ')';
- });
- }
-
- var slices = wrap.select('.nv-pie').selectAll('.nv-slice').data(pie);
- var pieLabels = wrap.select('.nv-pieLabels').selectAll('.nv-label').data(pie);
-
- slices.exit().remove();
- pieLabels.exit().remove();
-
- var ae = slices.enter().append('g');
- ae.attr('class', 'nv-slice');
- ae.on('mouseover', function(d, i) {
- d3.select(this).classed('hover', true);
- if (growOnHover) {
- d3.select(this).select("path").transition()
- .duration(70)
- .attr("d", arcsOver[i]);
- }
- dispatch.elementMouseover({
- data: d.data,
- index: i,
- color: d3.select(this).style("fill"),
- percent: (d.endAngle - d.startAngle) / (2 * Math.PI)
- });
- });
- ae.on('mouseout', function(d, i) {
- d3.select(this).classed('hover', false);
- if (growOnHover) {
- d3.select(this).select("path").transition()
- .duration(50)
- .attr("d", arcs[i]);
- }
- dispatch.elementMouseout({data: d.data, index: i});
- });
- ae.on('mousemove', function(d, i) {
- dispatch.elementMousemove({data: d.data, index: i});
- });
- ae.on('click', function(d, i) {
- var element = this;
- dispatch.elementClick({
- data: d.data,
- index: i,
- color: d3.select(this).style("fill"),
- event: d3.event,
- element: element
- });
- });
- ae.on('dblclick', function(d, i) {
- dispatch.elementDblClick({
- data: d.data,
- index: i,
- color: d3.select(this).style("fill")
- });
- });
-
- slices.attr('fill', function(d,i) { return color(d.data, i); });
- slices.attr('stroke', function(d,i) { return color(d.data, i); });
-
- var paths = ae.append('path').each(function(d) {
- this._current = d;
- });
-
- slices.select('path')
- .transition()
- .duration(duration)
- .attr('d', function (d, i) { return arcs[i](d); })
- .attrTween('d', arcTween);
-
- if (showLabels) {
- // This does the normal label
- var labelsArc = [];
- for (var i = 0; i < data[0].length; i++) {
- labelsArc.push(arcs[i]);
-
- if (labelsOutside) {
- if (donut) {
- labelsArc[i] = d3.svg.arc().outerRadius(arcs[i].outerRadius());
- if (startAngle !== false) labelsArc[i].startAngle(startAngle);
- if (endAngle !== false) labelsArc[i].endAngle(endAngle);
- }
- } else if (!donut) {
- labelsArc[i].innerRadius(0);
- }
- }
-
- pieLabels.enter().append("g").classed("nv-label",true).each(function(d,i) {
- var group = d3.select(this);
-
- group.attr('transform', function (d, i) {
- if (labelSunbeamLayout) {
- d.outerRadius = arcsRadiusOuter[i] + 10; // Set Outer Coordinate
- d.innerRadius = arcsRadiusOuter[i] + 15; // Set Inner Coordinate
- var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
- if ((d.startAngle + d.endAngle) / 2 < Math.PI) {
- rotateAngle -= 90;
- } else {
- rotateAngle += 90;
- }
- return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')';
- } else {
- d.outerRadius = radius + 10; // Set Outer Coordinate
- d.innerRadius = radius + 15; // Set Inner Coordinate
- return 'translate(' + labelsArc[i].centroid(d) + ')'
- }
- });
-
- group.append('rect')
- .style('stroke', '#fff')
- .style('fill', '#fff')
- .attr("rx", 3)
- .attr("ry", 3);
-
- group.append('text')
- .style('text-anchor', labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle') //center the text on it's origin or begin/end if orthogonal aligned
- .style('fill', '#000')
- });
-
- var labelLocationHash = {};
- var avgHeight = 14;
- var avgWidth = 140;
- var createHashKey = function(coordinates) {
- return Math.floor(coordinates[0]/avgWidth) * avgWidth + ',' + Math.floor(coordinates[1]/avgHeight) * avgHeight;
- };
- var getSlicePercentage = function(d) {
- return (d.endAngle - d.startAngle) / (2 * Math.PI);
- };
-
- pieLabels.watchTransition(renderWatch, 'pie labels').attr('transform', function (d, i) {
- if (labelSunbeamLayout) {
- d.outerRadius = arcsRadiusOuter[i] + 10; // Set Outer Coordinate
- d.innerRadius = arcsRadiusOuter[i] + 15; // Set Inner Coordinate
- var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
- if ((d.startAngle + d.endAngle) / 2 < Math.PI) {
- rotateAngle -= 90;
- } else {
- rotateAngle += 90;
- }
- return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')';
- } else {
- d.outerRadius = radius + 10; // Set Outer Coordinate
- d.innerRadius = radius + 15; // Set Inner Coordinate
-
- /*
- Overlapping pie labels are not good. What this attempts to do is, prevent overlapping.
- Each label location is hashed, and if a hash collision occurs, we assume an overlap.
- Adjust the label's y-position to remove the overlap.
- */
- var center = labelsArc[i].centroid(d);
- var percent = getSlicePercentage(d);
- if (d.value && percent >= labelThreshold) {
- var hashKey = createHashKey(center);
- if (labelLocationHash[hashKey]) {
- center[1] -= avgHeight;
- }
- labelLocationHash[createHashKey(center)] = true;
- }
- return 'translate(' + center + ')'
- }
- });
-
- pieLabels.select(".nv-label text")
- .style('text-anchor', function(d,i) {
- //center the text on it's origin or begin/end if orthogonal aligned
- return labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle';
- })
- .text(function(d, i) {
- var percent = getSlicePercentage(d);
- var label = '';
- if (!d.value || percent < labelThreshold) return '';
-
- if(typeof labelType === 'function') {
- label = labelType(d, i, {
- 'key': getX(d.data),
- 'value': getY(d.data),
- 'percent': valueFormat(percent)
- });
- } else {
- switch (labelType) {
- case 'key':
- label = getX(d.data);
- break;
- case 'value':
- label = valueFormat(getY(d.data));
- break;
- case 'percent':
- label = d3.format('%')(percent);
- break;
- }
- }
- return label;
- })
- ;
- }
-
-
- // Computes the angle of an arc, converting from radians to degrees.
- function angle(d) {
- var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
- return a > 90 ? a - 180 : a;
- }
-
- function arcTween(a, idx) {
- a.endAngle = isNaN(a.endAngle) ? 0 : a.endAngle;
- a.startAngle = isNaN(a.startAngle) ? 0 : a.startAngle;
- if (!donut) a.innerRadius = 0;
- var i = d3.interpolate(this._current, a);
- this._current = i(0);
- return function (t) {
- return arcs[idx](i(t));
- };
- }
- });
-
- renderWatch.renderEnd('pie immediate');
- return chart;
- }
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- chart.dispatch = dispatch;
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- arcsRadius: { get: function () { return arcsRadius; }, set: function (_) { arcsRadius = _; } },
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}},
- title: {get: function(){return title;}, set: function(_){title=_;}},
- titleOffset: {get: function(){return titleOffset;}, set: function(_){titleOffset=_;}},
- labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_;}},
- valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
- x: {get: function(){return getX;}, set: function(_){getX=_;}},
- id: {get: function(){return id;}, set: function(_){id=_;}},
- endAngle: {get: function(){return endAngle;}, set: function(_){endAngle=_;}},
- startAngle: {get: function(){return startAngle;}, set: function(_){startAngle=_;}},
- padAngle: {get: function(){return padAngle;}, set: function(_){padAngle=_;}},
- cornerRadius: {get: function(){return cornerRadius;}, set: function(_){cornerRadius=_;}},
- donutRatio: {get: function(){return donutRatio;}, set: function(_){donutRatio=_;}},
- labelsOutside: {get: function(){return labelsOutside;}, set: function(_){labelsOutside=_;}},
- labelSunbeamLayout: {get: function(){return labelSunbeamLayout;}, set: function(_){labelSunbeamLayout=_;}},
- donut: {get: function(){return donut;}, set: function(_){donut=_;}},
- growOnHover: {get: function(){return growOnHover;}, set: function(_){growOnHover=_;}},
-
- // depreciated after 1.7.1
- pieLabelsOutside: {get: function(){return labelsOutside;}, set: function(_){
- labelsOutside=_;
- nv.deprecated('pieLabelsOutside', 'use labelsOutside instead');
- }},
- // depreciated after 1.7.1
- donutLabelsOutside: {get: function(){return labelsOutside;}, set: function(_){
- labelsOutside=_;
- nv.deprecated('donutLabelsOutside', 'use labelsOutside instead');
- }},
- // deprecated after 1.7.1
- labelFormat: {get: function(){ return valueFormat;}, set: function(_) {
- valueFormat=_;
- nv.deprecated('labelFormat','use valueFormat instead');
- }},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
- margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
- margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
- margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
- }},
- duration: {get: function(){return duration;}, set: function(_){
- duration = _;
- renderWatch.reset(duration);
- }},
- y: {get: function(){return getY;}, set: function(_){
- getY=d3.functor(_);
- }},
- color: {get: function(){return color;}, set: function(_){
- color=nv.utils.getColor(_);
- }},
- labelType: {get: function(){return labelType;}, set: function(_){
- labelType= _ || 'key';
- }}
- });
-
- nv.utils.initOptions(chart);
- return chart;
- };
- nv.models.pieChart = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var pie = nv.models.pie();
- var legend = nv.models.legend();
- var tooltip = nv.models.tooltip();
-
- var margin = {top: 30, right: 20, bottom: 20, left: 20}
- , marginTop = null
- , width = null
- , height = null
- , showTooltipPercent = false
- , showLegend = true
- , legendPosition = "top"
- , color = nv.utils.defaultColor()
- , state = nv.utils.state()
- , defaultState = null
- , noData = null
- , duration = 250
- , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd')
- ;
-
- tooltip
- .duration(0)
- .headerEnabled(false)
- .valueFormatter(function(d, i) {
- return pie.valueFormat()(d, i);
- });
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var renderWatch = nv.utils.renderWatch(dispatch);
-
- var stateGetter = function(data) {
- return function(){
- return {
- active: data.map(function(d) { return !d.disabled })
- };
- }
- };
-
- var stateSetter = function(data) {
- return function(state) {
- if (state.active !== undefined) {
- data.forEach(function (series, i) {
- series.disabled = !state.active[i];
- });
- }
- }
- };
-
- //============================================================
- // Chart function
- //------------------------------------------------------------
-
- function chart(selection) {
- renderWatch.reset();
- renderWatch.models(pie);
-
- selection.each(function(data) {
- var container = d3.select(this);
- nv.utils.initSVG(container);
-
- var that = this;
- var availableWidth = nv.utils.availableWidth(width, container, margin),
- availableHeight = nv.utils.availableHeight(height, container, margin);
-
- chart.update = function() { container.transition().call(chart); };
- chart.container = this;
-
- state.setter(stateSetter(data), chart.update)
- .getter(stateGetter(data))
- .update();
-
- //set state.disabled
- state.disabled = data.map(function(d) { return !!d.disabled });
-
- if (!defaultState) {
- var key;
- defaultState = {};
- for (key in state) {
- if (state[key] instanceof Array)
- defaultState[key] = state[key].slice(0);
- else
- defaultState[key] = state[key];
- }
- }
-
- // Display No Data message if there's nothing to show.
- if (!data || !data.length) {
- nv.utils.noData(chart, container);
- return chart;
- } else {
- container.selectAll('.nv-noData').remove();
- }
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]);
- var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g');
- var g = wrap.select('g');
-
- gEnter.append('g').attr('class', 'nv-pieWrap');
- gEnter.append('g').attr('class', 'nv-legendWrap');
-
- // Legend
- if (!showLegend) {
- g.select('.nv-legendWrap').selectAll('*').remove();
- } else {
- if (legendPosition === "top") {
- legend.width( availableWidth ).key(pie.x());
-
- wrap.select('.nv-legendWrap')
- .datum(data)
- .call(legend);
-
- if (!marginTop && legend.height() !== margin.top) {
- margin.top = legend.height();
- availableHeight = nv.utils.availableHeight(height, container, margin);
- }
-
- wrap.select('.nv-legendWrap')
- .attr('transform', 'translate(0,' + (-margin.top) +')');
- } else if (legendPosition === "right") {
- var legendWidth = nv.models.legend().width();
- if (availableWidth / 2 < legendWidth) {
- legendWidth = (availableWidth / 2)
- }
- legend.height(availableHeight).key(pie.x());
- legend.width(legendWidth);
- availableWidth -= legend.width();
-
- wrap.select('.nv-legendWrap')
- .datum(data)
- .call(legend)
- .attr('transform', 'translate(' + (availableWidth) +',0)');
- }
- }
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- // Main Chart Component(s)
- pie.width(availableWidth).height(availableHeight);
- var pieWrap = g.select('.nv-pieWrap').datum([data]);
- d3.transition(pieWrap).call(pie);
-
- //============================================================
- // Event Handling/Dispatching (in chart's scope)
- //------------------------------------------------------------
-
- legend.dispatch.on('stateChange', function(newState) {
- for (var key in newState) {
- state[key] = newState[key];
- }
- dispatch.stateChange(state);
- chart.update();
- });
-
- // Update chart from a state object passed to event handler
- dispatch.on('changeState', function(e) {
- if (typeof e.disabled !== 'undefined') {
- data.forEach(function(series,i) {
- series.disabled = e.disabled[i];
- });
- state.disabled = e.disabled;
- }
- chart.update();
- });
- });
-
- renderWatch.renderEnd('pieChart immediate');
- return chart;
- }
-
- //============================================================
- // Event Handling/Dispatching (out of chart's scope)
- //------------------------------------------------------------
-
- pie.dispatch.on('elementMouseover.tooltip', function(evt) {
- evt['series'] = {
- key: chart.x()(evt.data),
- value: chart.y()(evt.data),
- color: evt.color,
- percent: evt.percent
- };
- if (!showTooltipPercent) {
- delete evt.percent;
- delete evt.series.percent;
- }
- tooltip.data(evt).hidden(false);
- });
-
- pie.dispatch.on('elementMouseout.tooltip', function(evt) {
- tooltip.hidden(true);
- });
-
- pie.dispatch.on('elementMousemove.tooltip', function(evt) {
- tooltip();
- });
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- // expose chart's sub-components
- chart.legend = legend;
- chart.dispatch = dispatch;
- chart.pie = pie;
- chart.tooltip = tooltip;
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- // use Object get/set functionality to map between vars and chart functions
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- noData: {get: function(){return noData;}, set: function(_){noData=_;}},
- showTooltipPercent: {get: function(){return showTooltipPercent;}, set: function(_){showTooltipPercent=_;}},
- showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
- legendPosition: {get: function(){return legendPosition;}, set: function(_){legendPosition=_;}},
- defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
-
- // options that require extra logic in the setter
- color: {get: function(){return color;}, set: function(_){
- color = _;
- legend.color(color);
- pie.color(color);
- }},
- duration: {get: function(){return duration;}, set: function(_){
- duration = _;
- renderWatch.reset(duration);
- pie.duration(duration);
- }},
- margin: {get: function(){return margin;}, set: function(_){
- if (_.top !== undefined) {
- margin.top = _.top;
- marginTop = _.top;
- }
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }}
- });
- nv.utils.inheritOptions(chart, pie);
- nv.utils.initOptions(chart);
- return chart;
- };
- nv.models.sankey = function() {
- 'use strict';
-
- // Sources:
- // - https://bost.ocks.org/mike/sankey/
- // - https://github.com/soxofaan/d3-plugin-captain-sankey
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var sankey = {},
- nodeWidth = 24,
- nodePadding = 8,
- size = [1, 1],
- nodes = [],
- links = [],
- sinksRight = true;
-
- var layout = function(iterations) {
- computeNodeLinks();
- computeNodeValues();
- computeNodeBreadths();
- computeNodeDepths(iterations);
- };
-
- var relayout = function() {
- computeLinkDepths();
- };
-
- // SVG path data generator, to be used as 'd' attribute on 'path' element selection.
- var link = function() {
- var curvature = .5;
-
- function link(d) {
-
- var x0 = d.source.x + d.source.dx,
- x1 = d.target.x,
- xi = d3.interpolateNumber(x0, x1),
- x2 = xi(curvature),
- x3 = xi(1 - curvature),
- y0 = d.source.y + d.sy + d.dy / 2,
- y1 = d.target.y + d.ty + d.dy / 2;
- var linkPath = 'M' + x0 + ',' + y0
- + 'C' + x2 + ',' + y0
- + ' ' + x3 + ',' + y1
- + ' ' + x1 + ',' + y1;
- return linkPath;
- }
-
- link.curvature = function(_) {
- if (!arguments.length) return curvature;
- curvature = +_;
- return link;
- };
-
- return link;
- };
-
- // Y-position of the middle of a node.
- var center = function(node) {
- return node.y + node.dy / 2;
- };
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- // Populate the sourceLinks and targetLinks for each node.
- // Also, if the source and target are not objects, assume they are indices.
- function computeNodeLinks() {
- nodes.forEach(function(node) {
- // Links that have this node as source.
- node.sourceLinks = [];
- // Links that have this node as target.
- node.targetLinks = [];
- });
- links.forEach(function(link) {
- var source = link.source,
- target = link.target;
- if (typeof source === 'number') source = link.source = nodes[link.source];
- if (typeof target === 'number') target = link.target = nodes[link.target];
- source.sourceLinks.push(link);
- target.targetLinks.push(link);
- });
- }
-
- // Compute the value (size) of each node by summing the associated links.
- function computeNodeValues() {
- nodes.forEach(function(node) {
- node.value = Math.max(
- d3.sum(node.sourceLinks, value),
- d3.sum(node.targetLinks, value)
- );
- });
- }
-
- // Iteratively assign the breadth (x-position) for each node.
- // Nodes are assigned the maximum breadth of incoming neighbors plus one;
- // nodes with no incoming links are assigned breadth zero, while
- // nodes with no outgoing links are assigned the maximum breadth.
- function computeNodeBreadths() {
- //
- var remainingNodes = nodes,
- nextNodes,
- x = 0;
-
- // Work from left to right.
- // Keep updating the breath (x-position) of nodes that are target of recently updated nodes.
- //
- while (remainingNodes.length && x < nodes.length) {
- nextNodes = [];
- remainingNodes.forEach(function(node) {
- node.x = x;
- node.dx = nodeWidth;
- node.sourceLinks.forEach(function(link) {
- if (nextNodes.indexOf(link.target) < 0) {
- nextNodes.push(link.target);
- }
- });
- });
- remainingNodes = nextNodes;
- ++x;
- //
- }
-
- // Optionally move pure sinks always to the right.
- if (sinksRight) {
- moveSinksRight(x);
- }
-
- scaleNodeBreadths((size[0] - nodeWidth) / (x - 1));
- }
-
- function moveSourcesRight() {
- nodes.forEach(function(node) {
- if (!node.targetLinks.length) {
- node.x = d3.min(node.sourceLinks, function(d) { return d.target.x; }) - 1;
- }
- });
- }
-
- function moveSinksRight(x) {
- nodes.forEach(function(node) {
- if (!node.sourceLinks.length) {
- node.x = x - 1;
- }
- });
- }
-
- function scaleNodeBreadths(kx) {
- nodes.forEach(function(node) {
- node.x *= kx;
- });
- }
-
- // Compute the depth (y-position) for each node.
- function computeNodeDepths(iterations) {
- // Group nodes by breath.
- var nodesByBreadth = d3.nest()
- .key(function(d) { return d.x; })
- .sortKeys(d3.ascending)
- .entries(nodes)
- .map(function(d) { return d.values; });
-
- //
- initializeNodeDepth();
- resolveCollisions();
- computeLinkDepths();
- for (var alpha = 1; iterations > 0; --iterations) {
- relaxRightToLeft(alpha *= .99);
- resolveCollisions();
- computeLinkDepths();
- relaxLeftToRight(alpha);
- resolveCollisions();
- computeLinkDepths();
- }
-
- function initializeNodeDepth() {
- // Calculate vertical scaling factor.
- var ky = d3.min(nodesByBreadth, function(nodes) {
- return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value);
- });
-
- nodesByBreadth.forEach(function(nodes) {
- nodes.forEach(function(node, i) {
- node.y = i;
- node.dy = node.value * ky;
- });
- });
-
- links.forEach(function(link) {
- link.dy = link.value * ky;
- });
- }
-
- function relaxLeftToRight(alpha) {
- nodesByBreadth.forEach(function(nodes, breadth) {
- nodes.forEach(function(node) {
- if (node.targetLinks.length) {
- // Value-weighted average of the y-position of source node centers linked to this node.
- var y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value);
- node.y += (y - center(node)) * alpha;
- }
- });
- });
-
- function weightedSource(link) {
- return (link.source.y + link.sy + link.dy / 2) * link.value;
- }
- }
-
- function relaxRightToLeft(alpha) {
- nodesByBreadth.slice().reverse().forEach(function(nodes) {
- nodes.forEach(function(node) {
- if (node.sourceLinks.length) {
- // Value-weighted average of the y-positions of target nodes linked to this node.
- var y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value);
- node.y += (y - center(node)) * alpha;
- }
- });
- });
-
- function weightedTarget(link) {
- return (link.target.y + link.ty + link.dy / 2) * link.value;
- }
- }
-
- function resolveCollisions() {
- nodesByBreadth.forEach(function(nodes) {
- var node,
- dy,
- y0 = 0,
- n = nodes.length,
- i;
-
- // Push any overlapping nodes down.
- nodes.sort(ascendingDepth);
- for (i = 0; i < n; ++i) {
- node = nodes[i];
- dy = y0 - node.y;
- if (dy > 0) node.y += dy;
- y0 = node.y + node.dy + nodePadding;
- }
-
- // If the bottommost node goes outside the bounds, push it back up.
- dy = y0 - nodePadding - size[1];
- if (dy > 0) {
- y0 = node.y -= dy;
-
- // Push any overlapping nodes back up.
- for (i = n - 2; i >= 0; --i) {
- node = nodes[i];
- dy = node.y + node.dy + nodePadding - y0;
- if (dy > 0) node.y -= dy;
- y0 = node.y;
- }
- }
- });
- }
-
- function ascendingDepth(a, b) {
- return a.y - b.y;
- }
- }
-
- // Compute y-offset of the source endpoint (sy) and target endpoints (ty) of links,
- // relative to the source/target node's y-position.
- function computeLinkDepths() {
- nodes.forEach(function(node) {
- node.sourceLinks.sort(ascendingTargetDepth);
- node.targetLinks.sort(ascendingSourceDepth);
- });
- nodes.forEach(function(node) {
- var sy = 0, ty = 0;
- node.sourceLinks.forEach(function(link) {
- link.sy = sy;
- sy += link.dy;
- });
- node.targetLinks.forEach(function(link) {
- link.ty = ty;
- ty += link.dy;
- });
- });
-
- function ascendingSourceDepth(a, b) {
- return a.source.y - b.source.y;
- }
-
- function ascendingTargetDepth(a, b) {
- return a.target.y - b.target.y;
- }
- }
-
- // Value property accessor.
- function value(x) {
- return x.value;
- }
-
- sankey.options = nv.utils.optionsFunc.bind(sankey);
- sankey._options = Object.create({}, {
- nodeWidth: {get: function(){return nodeWidth;}, set: function(_){nodeWidth=+_;}},
- nodePadding: {get: function(){return nodePadding;}, set: function(_){nodePadding=_;}},
- nodes: {get: function(){return nodes;}, set: function(_){nodes=_;}},
- links: {get: function(){return links ;}, set: function(_){links=_;}},
- size: {get: function(){return size;}, set: function(_){size=_;}},
- sinksRight: {get: function(){return sinksRight;}, set: function(_){sinksRight=_;}},
-
- layout: {get: function(){layout(32);}, set: function(_){layout(_);}},
- relayout: {get: function(){relayout();}, set: function(_){}},
- center: {get: function(){return center();}, set: function(_){
- if(typeof _ === 'function'){
- center=_;
- }
- }},
- link: {get: function(){return link();}, set: function(_){
- if(typeof _ === 'function'){
- link=_;
- }
- return link();
- }}
- });
-
- nv.utils.initOptions(sankey);
-
- return sankey;
- };
- nv.models.sankeyChart = function() {
- "use strict";
-
- // Sources:
- // - https://bost.ocks.org/mike/sankey/
- // - https://github.com/soxofaan/d3-plugin-captain-sankey
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var margin = {top: 5, right: 0, bottom: 5, left: 0}
- , sankey = nv.models.sankey()
- , width = 600
- , height = 400
- , nodeWidth = 36
- , nodePadding = 40
- , units = 'units'
- , center = undefined
- ;
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var formatNumber = d3.format(',.0f'); // zero decimal places
- var format = function(d) {
- return formatNumber(d) + ' ' + units;
- };
- var color = d3.scale.category20();
- var linkTitle = function(d){
- return d.source.name + ' → ' + d.target.name + '\n' + format(d.value);
- };
- var nodeFillColor = function(d){
- return d.color = color(d.name.replace(/ .*/, ''));
- };
- var nodeStrokeColor = function(d){
- return d3.rgb(d.color).darker(2);
- };
- var nodeTitle = function(d){
- return d.name + '\n' + format(d.value);
- };
-
- var showError = function(element, message) {
- element.append('text')
- .attr('x', 0)
- .attr('y', 0)
- .attr('class', 'nvd3-sankey-chart-error')
- .attr('text-anchor', 'middle')
- .text(message);
- };
-
- function chart(selection) {
- selection.each(function(data) {
-
- var testData = {
- nodes:
- [
- {'node': 1, 'name': 'Test 1'},
- {'node': 2, 'name': 'Test 2'},
- {'node': 3, 'name': 'Test 3'},
- {'node': 4, 'name': 'Test 4'},
- {'node': 5, 'name': 'Test 5'},
- {'node': 6, 'name': 'Test 6'}
- ],
- links:
- [
- {'source': 0, 'target': 1, 'value': 2295},
- {'source': 0, 'target': 5, 'value': 1199},
- {'source': 1, 'target': 2, 'value': 1119},
- {'source': 1, 'target': 5, 'value': 1176},
- {'source': 2, 'target': 3, 'value': 487},
- {'source': 2, 'target': 5, 'value': 632},
- {'source': 3, 'target': 4, 'value': 301},
- {'source': 3, 'target': 5, 'value': 186}
- ]
- };
-
- // Error handling
- var isDataValid = false;
- var dataAvailable = false;
-
- // check if data is valid
- if(
- (typeof data['nodes'] === 'object' && data['nodes'].length) >= 0 &&
- (typeof data['links'] === 'object' && data['links'].length) >= 0
- ){
- isDataValid = true;
- }
-
- // check if data is available
- if(
- data['nodes'] && data['nodes'].length > 0 &&
- data['links'] && data['links'].length > 0
- ) {
- dataAvailable = true;
- }
-
- // show error
- if(!isDataValid) {
- console.error('NVD3 Sankey chart error:', 'invalid data format for', data);
- console.info('Valid data format is: ', testData, JSON.stringify(testData));
- showError(selection, 'Error loading chart, data is invalid');
- return false;
- }
-
- // TODO use nv.utils.noData
- if(!dataAvailable) {
- showError(selection, 'No data available');
- return false;
- }
-
- // No errors, continue
-
- // append the svg canvas to the page
- var svg = selection.append('svg')
- .attr('width', width)
- .attr('height', height)
- .append('g')
- .attr('class', 'nvd3 nv-wrap nv-sankeyChart');
-
- // Set the sankey diagram properties
- sankey
- .nodeWidth(nodeWidth)
- .nodePadding(nodePadding)
- .size([width, height]);
-
- var path = sankey.link();
-
- sankey
- .nodes(data.nodes)
- .links(data.links)
- .layout(32)
- .center(center);
-
- // add in the links
- var link = svg.append('g').selectAll('.link')
- .data(data.links)
- .enter().append('path')
- .attr('class', 'link')
- .attr('d', path)
- .style('stroke-width', function(d) { return Math.max(1, d.dy); })
- .sort(function(a,b) { return b.dy - a.dy; });
-
- // add the link titles
- link.append('title')
- .text(linkTitle);
-
- // add in the nodes
- var node = svg.append('g').selectAll('.node')
- .data(data.nodes)
- .enter().append('g')
- .attr('class', 'node')
- .attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; })
- .call(
- d3.behavior
- .drag()
- .origin(function(d) { return d; })
- .on('dragstart', function() {
- this.parentNode.appendChild(this);
- })
- .on('drag', dragmove)
- );
-
- // add the rectangles for the nodes
- node.append('rect')
- .attr('height', function(d) { return d.dy; })
- .attr('width', sankey.nodeWidth())
- .style('fill', nodeFillColor)
- .style('stroke', nodeStrokeColor)
- .append('title')
- .text(nodeTitle);
-
- // add in the title for the nodes
- node.append('text')
- .attr('x', -6)
- .attr('y', function(d) { return d.dy / 2; })
- .attr('dy', '.35em')
- .attr('text-anchor', 'end')
- .attr('transform', null)
- .text(function(d) { return d.name; })
- .filter(function(d) { return d.x < width / 2; })
- .attr('x', 6 + sankey.nodeWidth())
- .attr('text-anchor', 'start');
-
- // the function for moving the nodes
- function dragmove(d) {
- d3.select(this).attr('transform',
- 'translate(' + d.x + ',' + (
- d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))
- ) + ')');
- sankey.relayout();
- link.attr('d', path);
- }
- });
-
- return chart;
- }
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- units: {get: function(){return units;}, set: function(_){units=_;}},
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- format: {get: function(){return format;}, set: function(_){format=_;}},
- linkTitle: {get: function(){return linkTitle;}, set: function(_){linkTitle=_;}},
- nodeWidth: {get: function(){return nodeWidth;}, set: function(_){nodeWidth=_;}},
- nodePadding: {get: function(){return nodePadding;}, set: function(_){nodePadding=_;}},
- center: {get: function(){return center}, set: function(_){center=_}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top !== undefined ? _.top : margin.top;
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- nodeStyle: {get: function(){return {};}, set: function(_){
- nodeFillColor = _.fillColor !== undefined ? _.fillColor : nodeFillColor;
- nodeStrokeColor = _.strokeColor !== undefined ? _.strokeColor : nodeStrokeColor;
- nodeTitle = _.title !== undefined ? _.title : nodeTitle;
- }}
-
- });
-
- nv.utils.initOptions(chart);
-
- return chart;
- };
-
- nv.models.scatter = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var margin = {top: 0, right: 0, bottom: 0, left: 0}
- , width = null
- , height = null
- , color = nv.utils.defaultColor() // chooses color
- , pointBorderColor = null
- , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one
- , container = null
- , x = d3.scale.linear()
- , y = d3.scale.linear()
- , z = d3.scale.linear() //linear because d3.svg.shape.size is treated as area
- , getX = function(d) { return d.x } // accessor to get the x value
- , getY = function(d) { return d.y } // accessor to get the y value
- , getSize = function(d) { return d.size || 1} // accessor to get the point size
- , getShape = function(d) { return d.shape || 'circle' } // accessor to get point shape
- , forceX = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
- , forceY = [] // List of numbers to Force into the Y scale
- , forceSize = [] // List of numbers to Force into the Size scale
- , interactive = true // If true, plots a voronoi overlay for advanced point intersection
- , pointActive = function(d) { return !d.notActive } // any points that return false will be filtered out
- , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
- , padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding
- , clipEdge = false // if true, masks points within x and y scale
- , clipVoronoi = true // if true, masks each point with a circle... can turn off to slightly increase performance
- , showVoronoi = false // display the voronoi areas
- , clipRadius = function() { return 25 } // function to get the radius for voronoi point clips
- , xDomain = null // Override x domain (skips the calculation from data)
- , yDomain = null // Override y domain
- , xRange = null // Override x range
- , yRange = null // Override y range
- , sizeDomain = null // Override point size domain
- , sizeRange = null
- , singlePoint = false
- , dispatch = d3.dispatch('elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
- , useVoronoi = true
- , duration = 250
- , interactiveUpdateDelay = 300
- , showLabels = false
- ;
-
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var x0, y0, z0 // used to store previous scales
- , width0
- , height0
- , timeoutID
- , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips
- , renderWatch = nv.utils.renderWatch(dispatch, duration)
- , _sizeRange_def = [16, 256]
- , _cache = {}
- ;
-
- function getCache(d) {
- var key, val;
- key = d[0].series + ':' + d[1];
- val = _cache[key] = _cache[key] || {};
- return val;
- }
-
- function delCache(d) {
- var key, val;
- key = d[0].series + ':' + d[1];
- delete _cache[key];
- }
-
- function getDiffs(d) {
- var i, key, val,
- cache = getCache(d),
- diffs = false;
- for (i = 1; i < arguments.length; i += 2) {
- key = arguments[i];
- val = arguments[i + 1](d[0], d[1]);
- if (cache[key] !== val || !cache.hasOwnProperty(key)) {
- cache[key] = val;
- diffs = true;
- }
- }
- return diffs;
- }
-
- function chart(selection) {
- renderWatch.reset();
- selection.each(function(data) {
- container = d3.select(this);
- var availableWidth = nv.utils.availableWidth(width, container, margin),
- availableHeight = nv.utils.availableHeight(height, container, margin);
-
- nv.utils.initSVG(container);
-
- //add series index to each data point for reference
- data.forEach(function(series, i) {
- series.values.forEach(function(point) {
- point.series = i;
- });
- });
-
- // Setup Scales
- var logScale = chart.yScale().name === d3.scale.log().name ? true : false;
- // remap and flatten the data for use in calculating the scales' domains
- var seriesData = (xDomain && yDomain && sizeDomain) ? [] : // if we know xDomain and yDomain and sizeDomain, no need to calculate.... if Size is constant remember to set sizeDomain to speed up performance
- d3.merge(
- data.map(function(d) {
- return d.values.map(function(d,i) {
- return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) }
- })
- })
- );
-
- x .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX)))
-
- if (padData && data[0])
- x.range(xRange || [(availableWidth * padDataOuter + availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length) ]);
- //x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
- else
- x.range(xRange || [0, availableWidth]);
-
- if (logScale) {
- var min = d3.min(seriesData.map(function(d) { if (d.y !== 0) return d.y; }));
- y.clamp(true)
- .domain(yDomain || d3.extent(seriesData.map(function(d) {
- if (d.y !== 0) return d.y;
- else return min * 0.1;
- }).concat(forceY)))
- .range(yRange || [availableHeight, 0]);
- } else {
- y.domain(yDomain || d3.extent(seriesData.map(function (d) { return d.y;}).concat(forceY)))
- .range(yRange || [availableHeight, 0]);
- }
-
- z .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize)))
- .range(sizeRange || _sizeRange_def);
-
- // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
- singlePoint = x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1];
-
- if (x.domain()[0] === x.domain()[1])
- x.domain()[0] ?
- x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
- : x.domain([-1,1]);
-
- if (y.domain()[0] === y.domain()[1])
- y.domain()[0] ?
- y.domain([y.domain()[0] - y.domain()[0] * 0.01, y.domain()[1] + y.domain()[1] * 0.01])
- : y.domain([-1,1]);
-
- if ( isNaN(x.domain()[0])) {
- x.domain([-1,1]);
- }
-
- if ( isNaN(y.domain()[0])) {
- y.domain([-1,1]);
- }
-
- x0 = x0 || x;
- y0 = y0 || y;
- z0 = z0 || z;
-
- var scaleDiff = x(1) !== x0(1) || y(1) !== y0(1) || z(1) !== z0(1);
-
- width0 = width0 || width;
- height0 = height0 || height;
-
- var sizeDiff = width0 !== width || height0 !== height;
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]);
- var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id);
- var defsEnter = wrapEnter.append('defs');
- var gEnter = wrapEnter.append('g');
- var g = wrap.select('g');
-
- wrap.classed('nv-single-point', singlePoint);
- gEnter.append('g').attr('class', 'nv-groups');
- gEnter.append('g').attr('class', 'nv-point-paths');
- wrapEnter.append('g').attr('class', 'nv-point-clips');
-
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- defsEnter.append('clipPath')
- .attr('id', 'nv-edge-clip-' + id)
- .append('rect')
- .attr('transform', 'translate( -10, -10)');
-
- wrap.select('#nv-edge-clip-' + id + ' rect')
- .attr('width', availableWidth + 20)
- .attr('height', (availableHeight > 0) ? availableHeight + 20 : 0);
-
- g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
-
- function updateInteractiveLayer() {
- // Always clear needs-update flag regardless of whether or not
- // we will actually do anything (avoids needless invocations).
- needsUpdate = false;
-
- if (!interactive) return false;
-
- // inject series and point index for reference into voronoi
- if (useVoronoi === true) {
- var vertices = d3.merge(data.map(function(group, groupIndex) {
- return group.values
- .map(function(point, pointIndex) {
- // *Adding noise to make duplicates very unlikely
- // *Injecting series and point index for reference
- /* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi.
- */
- var pX = getX(point,pointIndex);
- var pY = getY(point,pointIndex);
-
- return [nv.utils.NaNtoZero(x(pX))+ Math.random() * 1e-4,
- nv.utils.NaNtoZero(y(pY))+ Math.random() * 1e-4,
- groupIndex,
- pointIndex, point]; //temp hack to add noise until I think of a better way so there are no duplicates
- })
- .filter(function(pointArray, pointIndex) {
- return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct!
- })
- })
- );
-
- if (vertices.length == 0) return false; // No active points, we're done
- if (vertices.length < 3) {
- // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work
- vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]);
- vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]);
- vertices.push([x.range()[0] - 20, y.range()[0] + 20, null, null]);
- vertices.push([x.range()[1] + 20, y.range()[1] - 20, null, null]);
- }
-
- // keep voronoi sections from going more than 10 outside of graph
- // to avoid overlap with other things like legend etc
- var bounds = d3.geom.polygon([
- [-10,-10],
- [-10,height + 10],
- [width + 10,height + 10],
- [width + 10,-10]
- ]);
-
- var voronoi = d3.geom.voronoi(vertices).map(function(d, i) {
- return {
- 'data': bounds.clip(d),
- 'series': vertices[i][2],
- 'point': vertices[i][3]
- }
- });
-
- // nuke all voronoi paths on reload and recreate them
- wrap.select('.nv-point-paths').selectAll('path').remove();
- var pointPaths = wrap.select('.nv-point-paths').selectAll('path').data(voronoi);
- var vPointPaths = pointPaths
- .enter().append("svg:path")
- .attr("d", function(d) {
- if (!d || !d.data || d.data.length === 0)
- return 'M 0 0';
- else
- return "M" + d.data.join(",") + "Z";
- })
- .attr("id", function(d,i) {
- return "nv-path-"+i; })
- .attr("clip-path", function(d,i) { return "url(#nv-clip-"+id+"-"+i+")"; })
- ;
-
- // good for debugging point hover issues
- if (showVoronoi) {
- vPointPaths.style("fill", d3.rgb(230, 230, 230))
- .style('fill-opacity', 0.4)
- .style('stroke-opacity', 1)
- .style("stroke", d3.rgb(200,200,200));
- }
-
- if (clipVoronoi) {
- // voronoi sections are already set to clip,
- // just create the circles with the IDs they expect
- wrap.select('.nv-point-clips').selectAll('*').remove(); // must do * since it has sub-dom
- var pointClips = wrap.select('.nv-point-clips').selectAll('clipPath').data(vertices);
- var vPointClips = pointClips
- .enter().append("svg:clipPath")
- .attr("id", function(d, i) { return "nv-clip-"+id+"-"+i;})
- .append("svg:circle")
- .attr('cx', function(d) { return d[0]; })
- .attr('cy', function(d) { return d[1]; })
- .attr('r', clipRadius);
- }
-
- var mouseEventCallback = function(el, d, mDispatch) {
- if (needsUpdate) return 0;
- var series = data[d.series];
- if (series === undefined) return;
- var point = series.values[d.point];
- point['color'] = color(series, d.series);
-
- // standardize attributes for tooltip.
- point['x'] = getX(point);
- point['y'] = getY(point);
-
- // can't just get box of event node since it's actually a voronoi polygon
- var box = container.node().getBoundingClientRect();
- var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
- var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
-
- var pos = {
- left: x(getX(point, d.point)) + box.left + scrollLeft + margin.left + 10,
- top: y(getY(point, d.point)) + box.top + scrollTop + margin.top + 10
- };
-
- mDispatch({
- point: point,
- series: series,
- pos: pos,
- relativePos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top],
- seriesIndex: d.series,
- pointIndex: d.point,
- event: d3.event,
- element: el
- });
- };
-
- pointPaths
- .on('click', function(d) {
- mouseEventCallback(this, d, dispatch.elementClick);
- })
- .on('dblclick', function(d) {
- mouseEventCallback(this, d, dispatch.elementDblClick);
- })
- .on('mouseover', function(d) {
- mouseEventCallback(this, d, dispatch.elementMouseover);
- })
- .on('mouseout', function(d, i) {
- mouseEventCallback(this, d, dispatch.elementMouseout);
- });
-
- } else {
- // add event handlers to points instead voronoi paths
- wrap.select('.nv-groups').selectAll('.nv-group')
- .selectAll('.nv-point')
- //.data(dataWithPoints)
- //.style('pointer-events', 'auto') // recativate events, disabled by css
- .on('click', function(d,i) {
- //nv.log('test', d, i);
- if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
- var series = data[d.series],
- point = series.values[i];
- var element = this;
- dispatch.elementClick({
- point: point,
- series: series,
- pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], //TODO: make this pos base on the page
- relativePos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
- seriesIndex: d.series,
- pointIndex: i,
- event: d3.event,
- element: element
- });
- })
- .on('dblclick', function(d,i) {
- if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
- var series = data[d.series],
- point = series.values[i];
-
- dispatch.elementDblClick({
- point: point,
- series: series,
- pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],//TODO: make this pos base on the page
- relativePos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
- seriesIndex: d.series,
- pointIndex: i
- });
- })
- .on('mouseover', function(d,i) {
- if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
- var series = data[d.series],
- point = series.values[i];
-
- dispatch.elementMouseover({
- point: point,
- series: series,
- pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],//TODO: make this pos base on the page
- relativePos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
- seriesIndex: d.series,
- pointIndex: i,
- color: color(d, i)
- });
- })
- .on('mouseout', function(d,i) {
- if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
- var series = data[d.series],
- point = series.values[i];
-
- dispatch.elementMouseout({
- point: point,
- series: series,
- pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],//TODO: make this pos base on the page
- relativePos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
- seriesIndex: d.series,
- pointIndex: i,
- color: color(d, i)
- });
- });
- }
- }
-
- needsUpdate = true;
- var groups = wrap.select('.nv-groups').selectAll('.nv-group')
- .data(function(d) { return d }, function(d) { return d.key });
- groups.enter().append('g')
- .style('stroke-opacity', 1e-6)
- .style('fill-opacity', 1e-6);
- groups.exit()
- .remove();
- groups
- .attr('class', function(d,i) {
- return (d.classed || '') + ' nv-group nv-series-' + i;
- })
- .classed('nv-noninteractive', !interactive)
- .classed('hover', function(d) { return d.hover });
- groups.watchTransition(renderWatch, 'scatter: groups')
- .style('fill', function(d,i) { return color(d, i) })
- .style('stroke', function(d,i) { return d.pointBorderColor || pointBorderColor || color(d, i) })
- .style('stroke-opacity', 1)
- .style('fill-opacity', .5);
-
- // create the points, maintaining their IDs from the original data set
- var points = groups.selectAll('path.nv-point')
- .data(function(d) {
- return d.values.map(
- function (point, pointIndex) {
- return [point, pointIndex]
- }).filter(
- function(pointArray, pointIndex) {
- return pointActive(pointArray[0], pointIndex)
- })
- });
- points.enter().append('path')
- .attr('class', function (d) {
- return 'nv-point nv-point-' + d[1];
- })
- .style('fill', function (d) { return d.color })
- .style('stroke', function (d) { return d.color })
- .attr('transform', function(d) {
- return 'translate(' + nv.utils.NaNtoZero(x0(getX(d[0],d[1]))) + ',' + nv.utils.NaNtoZero(y0(getY(d[0],d[1]))) + ')'
- })
- .attr('d',
- nv.utils.symbol()
- .type(function(d) { return getShape(d[0]); })
- .size(function(d) { return z(getSize(d[0],d[1])) })
- );
- points.exit().each(delCache).remove();
- groups.exit().selectAll('path.nv-point')
- .watchTransition(renderWatch, 'scatter exit')
- .attr('transform', function(d) {
- return 'translate(' + nv.utils.NaNtoZero(x(getX(d[0],d[1]))) + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')'
- })
- .remove();
- // Update points position only if "x" or "y" have changed
- points.filter(function (d) { return scaleDiff || sizeDiff || getDiffs(d, 'x', getX, 'y', getY); })
- .watchTransition(renderWatch, 'scatter points')
- .attr('transform', function(d) {
- //nv.log(d, getX(d[0],d[1]), x(getX(d[0],d[1])));
- return 'translate(' + nv.utils.NaNtoZero(x(getX(d[0],d[1]))) + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')'
- });
- // Update points appearance only if "shape" or "size" have changed
- points.filter(function (d) { return scaleDiff || sizeDiff || getDiffs(d, 'shape', getShape, 'size', getSize); })
- .watchTransition(renderWatch, 'scatter points')
- .attr('d',
- nv.utils.symbol()
- .type(function(d) { return getShape(d[0]); })
- .size(function(d) { return z(getSize(d[0],d[1])) })
- );
-
- // add label a label to scatter chart
- if(showLabels)
- {
- var titles = groups.selectAll('.nv-label')
- .data(function(d) {
- return d.values.map(
- function (point, pointIndex) {
- return [point, pointIndex]
- }).filter(
- function(pointArray, pointIndex) {
- return pointActive(pointArray[0], pointIndex)
- })
- });
-
- titles.enter().append('text')
- .style('fill', function (d,i) {
- return d.color })
- .style('stroke-opacity', 0)
- .style('fill-opacity', 1)
- .attr('transform', function(d) {
- var dx = nv.utils.NaNtoZero(x0(getX(d[0],d[1]))) + Math.sqrt(z(getSize(d[0],d[1]))/Math.PI) + 2;
- return 'translate(' + dx + ',' + nv.utils.NaNtoZero(y0(getY(d[0],d[1]))) + ')';
- })
- .text(function(d,i){
- return d[0].label;});
-
- titles.exit().remove();
- groups.exit().selectAll('path.nv-label')
- .watchTransition(renderWatch, 'scatter exit')
- .attr('transform', function(d) {
- var dx = nv.utils.NaNtoZero(x(getX(d[0],d[1])))+ Math.sqrt(z(getSize(d[0],d[1]))/Math.PI)+2;
- return 'translate(' + dx + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')';
- })
- .remove();
- titles.each(function(d) {
- d3.select(this)
- .classed('nv-label', true)
- .classed('nv-label-' + d[1], false)
- .classed('hover',false);
- });
- titles.watchTransition(renderWatch, 'scatter labels')
- .attr('transform', function(d) {
- var dx = nv.utils.NaNtoZero(x(getX(d[0],d[1])))+ Math.sqrt(z(getSize(d[0],d[1]))/Math.PI)+2;
- return 'translate(' + dx + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')'
- });
- }
-
- // Delay updating the invisible interactive layer for smoother animation
- if( interactiveUpdateDelay )
- {
- clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer
- timeoutID = setTimeout(updateInteractiveLayer, interactiveUpdateDelay );
- }
- else
- {
- updateInteractiveLayer();
- }
-
- //store old scales for use in transitions on update
- x0 = x.copy();
- y0 = y.copy();
- z0 = z.copy();
-
- width0 = width;
- height0 = height;
-
- });
- renderWatch.renderEnd('scatter immediate');
- return chart;
- }
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- chart.dispatch = dispatch;
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- // utility function calls provided by this chart
- chart._calls = new function() {
- this.clearHighlights = function () {
- nv.dom.write(function() {
- container.selectAll(".nv-point.hover").classed("hover", false);
- });
- return null;
- };
- this.highlightPoint = function (seriesIndex, pointIndex, isHoverOver) {
- nv.dom.write(function() {
- container.select('.nv-groups')
- .selectAll(".nv-series-" + seriesIndex)
- .selectAll(".nv-point-" + pointIndex)
- .classed("hover", isHoverOver);
- });
- };
- };
-
- // trigger calls from events too
- dispatch.on('elementMouseover.point', function(d) {
- if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,true);
- });
-
- dispatch.on('elementMouseout.point', function(d) {
- if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,false);
- });
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- xScale: {get: function(){return x;}, set: function(_){x=_;}},
- yScale: {get: function(){return y;}, set: function(_){y=_;}},
- pointScale: {get: function(){return z;}, set: function(_){z=_;}},
- xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
- yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
- pointDomain: {get: function(){return sizeDomain;}, set: function(_){sizeDomain=_;}},
- xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
- yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
- pointRange: {get: function(){return sizeRange;}, set: function(_){sizeRange=_;}},
- forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
- forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
- forcePoint: {get: function(){return forceSize;}, set: function(_){forceSize=_;}},
- interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
- pointActive: {get: function(){return pointActive;}, set: function(_){pointActive=_;}},
- padDataOuter: {get: function(){return padDataOuter;}, set: function(_){padDataOuter=_;}},
- padData: {get: function(){return padData;}, set: function(_){padData=_;}},
- clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
- clipVoronoi: {get: function(){return clipVoronoi;}, set: function(_){clipVoronoi=_;}},
- clipRadius: {get: function(){return clipRadius;}, set: function(_){clipRadius=_;}},
- showVoronoi: {get: function(){return showVoronoi;}, set: function(_){showVoronoi=_;}},
- id: {get: function(){return id;}, set: function(_){id=_;}},
- interactiveUpdateDelay: {get:function(){return interactiveUpdateDelay;}, set: function(_){interactiveUpdateDelay=_;}},
- showLabels: {get: function(){return showLabels;}, set: function(_){ showLabels = _;}},
- pointBorderColor: {get: function(){return pointBorderColor;}, set: function(_){pointBorderColor=_;}},
-
- // simple functor options
- x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}},
- y: {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}},
- pointSize: {get: function(){return getSize;}, set: function(_){getSize = d3.functor(_);}},
- pointShape: {get: function(){return getShape;}, set: function(_){getShape = d3.functor(_);}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top !== undefined ? _.top : margin.top;
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- duration: {get: function(){return duration;}, set: function(_){
- duration = _;
- renderWatch.reset(duration);
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- }},
- useVoronoi: {get: function(){return useVoronoi;}, set: function(_){
- useVoronoi = _;
- if (useVoronoi === false) {
- clipVoronoi = false;
- }
- }}
- });
-
- nv.utils.initOptions(chart);
- return chart;
- };
-
- nv.models.scatterChart = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var scatter = nv.models.scatter()
- , xAxis = nv.models.axis()
- , yAxis = nv.models.axis()
- , legend = nv.models.legend()
- , distX = nv.models.distribution()
- , distY = nv.models.distribution()
- , tooltip = nv.models.tooltip()
- ;
-
- var margin = {top: 30, right: 20, bottom: 50, left: 75}
- , marginTop = null
- , width = null
- , height = null
- , container = null
- , color = nv.utils.defaultColor()
- , x = scatter.xScale()
- , y = scatter.yScale()
- , showDistX = false
- , showDistY = false
- , showLegend = true
- , showXAxis = true
- , showYAxis = true
- , rightAlignYAxis = false
- , state = nv.utils.state()
- , defaultState = null
- , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
- , noData = null
- , duration = 250
- , showLabels = false
- ;
-
- scatter.xScale(x).yScale(y);
- xAxis.orient('bottom').tickPadding(10);
- yAxis
- .orient((rightAlignYAxis) ? 'right' : 'left')
- .tickPadding(10)
- ;
- distX.axis('x');
- distY.axis('y');
- tooltip
- .headerFormatter(function(d, i) {
- return xAxis.tickFormat()(d, i);
- })
- .valueFormatter(function(d, i) {
- return yAxis.tickFormat()(d, i);
- });
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var x0, y0
- , renderWatch = nv.utils.renderWatch(dispatch, duration);
-
- var stateGetter = function(data) {
- return function(){
- return {
- active: data.map(function(d) { return !d.disabled })
- };
- }
- };
-
- var stateSetter = function(data) {
- return function(state) {
- if (state.active !== undefined)
- data.forEach(function(series,i) {
- series.disabled = !state.active[i];
- });
- }
- };
-
- function chart(selection) {
- renderWatch.reset();
- renderWatch.models(scatter);
- if (showXAxis) renderWatch.models(xAxis);
- if (showYAxis) renderWatch.models(yAxis);
- if (showDistX) renderWatch.models(distX);
- if (showDistY) renderWatch.models(distY);
-
- selection.each(function(data) {
- var that = this;
-
- container = d3.select(this);
- nv.utils.initSVG(container);
-
- var availableWidth = nv.utils.availableWidth(width, container, margin),
- availableHeight = nv.utils.availableHeight(height, container, margin);
-
- chart.update = function() {
- if (duration === 0)
- container.call(chart);
- else
- container.transition().duration(duration).call(chart);
- };
- chart.container = this;
-
- state
- .setter(stateSetter(data), chart.update)
- .getter(stateGetter(data))
- .update();
-
- // DEPRECATED set state.disableddisabled
- state.disabled = data.map(function(d) { return !!d.disabled });
-
- if (!defaultState) {
- var key;
- defaultState = {};
- for (key in state) {
- if (state[key] instanceof Array)
- defaultState[key] = state[key].slice(0);
- else
- defaultState[key] = state[key];
- }
- }
-
- // Display noData message if there's nothing to show.
- if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
- nv.utils.noData(chart, container);
- renderWatch.renderEnd('scatter immediate');
- return chart;
- } else {
- container.selectAll('.nv-noData').remove();
- }
-
- // Setup Scales
- x = scatter.xScale();
- y = scatter.yScale();
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
- var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
- var gEnter = wrapEnter.append('g');
- var g = wrap.select('g');
-
- // background for pointer events
- gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none");
-
- gEnter.append('g').attr('class', 'nv-x nv-axis');
- gEnter.append('g').attr('class', 'nv-y nv-axis');
- gEnter.append('g').attr('class', 'nv-scatterWrap');
- gEnter.append('g').attr('class', 'nv-regressionLinesWrap');
- gEnter.append('g').attr('class', 'nv-distWrap');
- gEnter.append('g').attr('class', 'nv-legendWrap');
-
- if (rightAlignYAxis) {
- g.select(".nv-y.nv-axis")
- .attr("transform", "translate(" + availableWidth + ",0)");
- }
-
- // Legend
- if (!showLegend) {
- g.select('.nv-legendWrap').selectAll('*').remove();
- } else {
- var legendWidth = availableWidth;
- legend.width(legendWidth);
-
- wrap.select('.nv-legendWrap')
- .datum(data)
- .call(legend);
-
- if (!marginTop && legend.height() !== margin.top) {
- margin.top = legend.height();
- availableHeight = nv.utils.availableHeight(height, container, margin);
- }
-
- wrap.select('.nv-legendWrap')
- .attr('transform', 'translate(0' + ',' + (-margin.top) +')');
- }
-
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- // Main Chart Component(s)
- scatter
- .width(availableWidth)
- .height(availableHeight)
- .color(data.map(function(d,i) {
- d.color = d.color || color(d, i);
- return d.color;
- }).filter(function(d,i) { return !data[i].disabled }))
- .showLabels(showLabels);
-
- wrap.select('.nv-scatterWrap')
- .datum(data.filter(function(d) { return !d.disabled }))
- .call(scatter);
-
-
- wrap.select('.nv-regressionLinesWrap')
- .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')');
-
- var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines')
- .data(function (d) {
- return d;
- });
-
- regWrap.enter().append('g').attr('class', 'nv-regLines');
-
- var regLine = regWrap.selectAll('.nv-regLine')
- .data(function (d) {
- return [d]
- });
-
- regLine.enter()
- .append('line').attr('class', 'nv-regLine')
- .style('stroke-opacity', 0);
-
- // don't add lines unless we have slope and intercept to use
- regLine.filter(function(d) {
- return d.intercept && d.slope;
- })
- .watchTransition(renderWatch, 'scatterPlusLineChart: regline')
- .attr('x1', x.range()[0])
- .attr('x2', x.range()[1])
- .attr('y1', function (d, i) {
- return y(x.domain()[0] * d.slope + d.intercept)
- })
- .attr('y2', function (d, i) {
- return y(x.domain()[1] * d.slope + d.intercept)
- })
- .style('stroke', function (d, i, j) {
- return color(d, j)
- })
- .style('stroke-opacity', function (d, i) {
- return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1
- });
-
- // Setup Axes
- if (showXAxis) {
- xAxis
- .scale(x)
- ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
- .tickSize( -availableHeight , 0);
-
- g.select('.nv-x.nv-axis')
- .attr('transform', 'translate(0,' + y.range()[0] + ')')
- .call(xAxis);
- }
-
- if (showYAxis) {
- yAxis
- .scale(y)
- ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
- .tickSize( -availableWidth, 0);
-
- g.select('.nv-y.nv-axis')
- .call(yAxis);
- }
-
- // Setup Distribution
- if (showDistX) {
- distX
- .getData(scatter.x())
- .scale(x)
- .width(availableWidth)
- .color(data.map(function(d,i) {
- return d.color || color(d, i);
- }).filter(function(d,i) { return !data[i].disabled }));
- gEnter.select('.nv-distWrap').append('g')
- .attr('class', 'nv-distributionX');
- g.select('.nv-distributionX')
- .attr('transform', 'translate(0,' + y.range()[0] + ')')
- .datum(data.filter(function(d) { return !d.disabled }))
- .call(distX);
- }
-
- if (showDistY) {
- distY
- .getData(scatter.y())
- .scale(y)
- .width(availableHeight)
- .color(data.map(function(d,i) {
- return d.color || color(d, i);
- }).filter(function(d,i) { return !data[i].disabled }));
- gEnter.select('.nv-distWrap').append('g')
- .attr('class', 'nv-distributionY');
- g.select('.nv-distributionY')
- .attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
- .datum(data.filter(function(d) { return !d.disabled }))
- .call(distY);
- }
-
- //============================================================
- // Event Handling/Dispatching (in chart's scope)
- //------------------------------------------------------------
-
- legend.dispatch.on('stateChange', function(newState) {
- for (var key in newState)
- state[key] = newState[key];
- dispatch.stateChange(state);
- chart.update();
- });
-
- // Update chart from a state object passed to event handler
- dispatch.on('changeState', function(e) {
- if (typeof e.disabled !== 'undefined') {
- data.forEach(function(series,i) {
- series.disabled = e.disabled[i];
- });
- state.disabled = e.disabled;
- }
- chart.update();
- });
-
- // mouseover needs availableHeight so we just keep scatter mouse events inside the chart block
- scatter.dispatch.on('elementMouseout.tooltip', function(evt) {
- tooltip.hidden(true);
- container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex)
- .attr('y1', 0);
- container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
- .attr('x2', distY.size());
- });
-
- scatter.dispatch.on('elementMouseover.tooltip', function(evt) {
- container.select('.nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex)
- .attr('y1', evt.relativePos[1] - availableHeight);
- container.select('.nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
- .attr('x2', evt.relativePos[0] + distX.size());
- tooltip.data(evt).hidden(false);
- });
-
- //store old scales for use in transitions on update
- x0 = x.copy();
- y0 = y.copy();
-
- });
-
- renderWatch.renderEnd('scatter with line immediate');
- return chart;
- }
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- // expose chart's sub-components
- chart.dispatch = dispatch;
- chart.scatter = scatter;
- chart.legend = legend;
- chart.xAxis = xAxis;
- chart.yAxis = yAxis;
- chart.distX = distX;
- chart.distY = distY;
- chart.tooltip = tooltip;
-
- chart.options = nv.utils.optionsFunc.bind(chart);
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- container: {get: function(){return container;}, set: function(_){container=_;}},
- showDistX: {get: function(){return showDistX;}, set: function(_){showDistX=_;}},
- showDistY: {get: function(){return showDistY;}, set: function(_){showDistY=_;}},
- showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
- showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
- showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
- defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
- noData: {get: function(){return noData;}, set: function(_){noData=_;}},
- duration: {get: function(){return duration;}, set: function(_){duration=_;}},
- showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- if (_.top !== undefined) {
- margin.top = _.top;
- marginTop = _.top;
- }
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
- rightAlignYAxis = _;
- yAxis.orient( (_) ? 'right' : 'left');
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- legend.color(color);
- distX.color(color);
- distY.color(color);
- }}
- });
-
- nv.utils.inheritOptions(chart, scatter);
- nv.utils.initOptions(chart);
- return chart;
- };
-
- nv.models.sparkline = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var margin = {top: 2, right: 0, bottom: 2, left: 0}
- , width = 400
- , height = 32
- , container = null
- , animate = true
- , x = d3.scale.linear()
- , y = d3.scale.linear()
- , getX = function(d) { return d.x }
- , getY = function(d) { return d.y }
- , color = nv.utils.getColor(['#000'])
- , xDomain
- , yDomain
- , xRange
- , yRange
- , showMinMaxPoints = true
- , showCurrentPoint = true
- , dispatch = d3.dispatch('renderEnd')
- ;
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var renderWatch = nv.utils.renderWatch(dispatch);
-
- function chart(selection) {
- renderWatch.reset();
- selection.each(function(data) {
- var availableWidth = width - margin.left - margin.right,
- availableHeight = height - margin.top - margin.bottom;
-
- container = d3.select(this);
- nv.utils.initSVG(container);
-
- // Setup Scales
- x .domain(xDomain || d3.extent(data, getX ))
- .range(xRange || [0, availableWidth]);
-
- y .domain(yDomain || d3.extent(data, getY ))
- .range(yRange || [availableHeight, 0]);
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]);
- var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline');
- var gEnter = wrapEnter.append('g');
- var g = wrap.select('g');
-
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
-
- var paths = wrap.selectAll('path')
- .data(function(d) { return [d] });
- paths.enter().append('path');
- paths.exit().remove();
- paths
- .style('stroke', function(d,i) { return d.color || color(d, i) })
- .attr('d', d3.svg.line()
- .x(function(d,i) { return x(getX(d,i)) })
- .y(function(d,i) { return y(getY(d,i)) })
- );
-
- // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent)
- var points = wrap.selectAll('circle.nv-point')
- .data(function(data) {
- var yValues = data.map(function(d, i) { return getY(d,i); });
- function pointIndex(index) {
- if (index != -1) {
- var result = data[index];
- result.pointIndex = index;
- return result;
- } else {
- return null;
- }
- }
- var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])),
- minPoint = pointIndex(yValues.indexOf(y.domain()[0])),
- currentPoint = pointIndex(yValues.length - 1);
- return [(showMinMaxPoints ? minPoint : null), (showMinMaxPoints ? maxPoint : null), (showCurrentPoint ? currentPoint : null)].filter(function (d) {return d != null;});
- });
- points.enter().append('circle');
- points.exit().remove();
- points
- .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) })
- .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) })
- .attr('r', 2)
- .attr('class', function(d,i) {
- return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' :
- getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue'
- });
- });
-
- renderWatch.renderEnd('sparkline immediate');
- return chart;
- }
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
- yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
- xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
- yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
- xScale: {get: function(){return x;}, set: function(_){x=_;}},
- yScale: {get: function(){return y;}, set: function(_){y=_;}},
- animate: {get: function(){return animate;}, set: function(_){animate=_;}},
- showMinMaxPoints: {get: function(){return showMinMaxPoints;}, set: function(_){showMinMaxPoints=_;}},
- showCurrentPoint: {get: function(){return showCurrentPoint;}, set: function(_){showCurrentPoint=_;}},
-
- //functor options
- x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}},
- y: {get: function(){return getY;}, set: function(_){getY=d3.functor(_);}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top !== undefined ? _.top : margin.top;
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- }}
- });
-
- chart.dispatch = dispatch;
- nv.utils.initOptions(chart);
- return chart;
- };
-
- nv.models.sparklinePlus = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var sparkline = nv.models.sparkline();
-
- var margin = {top: 15, right: 100, bottom: 10, left: 50}
- , width = null
- , height = null
- , x
- , y
- , index = []
- , paused = false
- , xTickFormat = d3.format(',r')
- , yTickFormat = d3.format(',.2f')
- , showLastValue = true
- , alignValue = true
- , rightAlignValue = false
- , noData = null
- , dispatch = d3.dispatch('renderEnd')
- ;
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var renderWatch = nv.utils.renderWatch(dispatch);
-
- function chart(selection) {
- renderWatch.reset();
- renderWatch.models(sparkline);
- selection.each(function(data) {
- var container = d3.select(this);
- nv.utils.initSVG(container);
-
- var availableWidth = nv.utils.availableWidth(width, container, margin),
- availableHeight = nv.utils.availableHeight(height, container, margin);
-
- chart.update = function() { container.call(chart); };
- chart.container = this;
-
- // Display No Data message if there's nothing to show.
- if (!data || !data.length) {
- nv.utils.noData(chart, container)
- return chart;
- } else {
- container.selectAll('.nv-noData').remove();
- }
-
- var currentValue = sparkline.y()(data[data.length-1], data.length-1);
-
- // Setup Scales
- x = sparkline.xScale();
- y = sparkline.yScale();
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]);
- var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus');
- var gEnter = wrapEnter.append('g');
- var g = wrap.select('g');
-
- gEnter.append('g').attr('class', 'nv-sparklineWrap');
- gEnter.append('g').attr('class', 'nv-valueWrap');
- gEnter.append('g').attr('class', 'nv-hoverArea');
-
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- // Main Chart Component(s)
- var sparklineWrap = g.select('.nv-sparklineWrap');
-
- sparkline.width(availableWidth).height(availableHeight);
- sparklineWrap.call(sparkline);
-
- if (showLastValue) {
- var valueWrap = g.select('.nv-valueWrap');
- var value = valueWrap.selectAll('.nv-currentValue')
- .data([currentValue]);
-
- value.enter().append('text').attr('class', 'nv-currentValue')
- .attr('dx', rightAlignValue ? -8 : 8)
- .attr('dy', '.9em')
- .style('text-anchor', rightAlignValue ? 'end' : 'start');
-
- value
- .attr('x', availableWidth + (rightAlignValue ? margin.right : 0))
- .attr('y', alignValue ? function (d) {
- return y(d)
- } : 0)
- .style('fill', sparkline.color()(data[data.length - 1], data.length - 1))
- .text(yTickFormat(currentValue));
- }
-
- gEnter.select('.nv-hoverArea').append('rect')
- .on('mousemove', sparklineHover)
- .on('click', function() { paused = !paused })
- .on('mouseout', function() { index = []; updateValueLine(); });
-
- g.select('.nv-hoverArea rect')
- .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' })
- .attr('width', availableWidth + margin.left + margin.right)
- .attr('height', availableHeight + margin.top);
-
- //index is currently global (within the chart), may or may not keep it that way
- function updateValueLine() {
- if (paused) return;
-
- var hoverValue = g.selectAll('.nv-hoverValue').data(index);
-
- var hoverEnter = hoverValue.enter()
- .append('g').attr('class', 'nv-hoverValue')
- .style('stroke-opacity', 0)
- .style('fill-opacity', 0);
-
- hoverValue.exit()
- .transition().duration(250)
- .style('stroke-opacity', 0)
- .style('fill-opacity', 0)
- .remove();
-
- hoverValue
- .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' })
- .transition().duration(250)
- .style('stroke-opacity', 1)
- .style('fill-opacity', 1);
-
- if (!index.length) return;
-
- hoverEnter.append('line')
- .attr('x1', 0)
- .attr('y1', -margin.top)
- .attr('x2', 0)
- .attr('y2', availableHeight);
-
- hoverEnter.append('text').attr('class', 'nv-xValue')
- .attr('x', -6)
- .attr('y', -margin.top)
- .attr('text-anchor', 'end')
- .attr('dy', '.9em');
-
- g.select('.nv-hoverValue .nv-xValue')
- .text(xTickFormat(sparkline.x()(data[index[0]], index[0])));
-
- hoverEnter.append('text').attr('class', 'nv-yValue')
- .attr('x', 6)
- .attr('y', -margin.top)
- .attr('text-anchor', 'start')
- .attr('dy', '.9em');
-
- g.select('.nv-hoverValue .nv-yValue')
- .text(yTickFormat(sparkline.y()(data[index[0]], index[0])));
- }
-
- function sparklineHover() {
- if (paused) return;
-
- var pos = d3.mouse(this)[0] - margin.left;
-
- function getClosestIndex(data, x) {
- var distance = Math.abs(sparkline.x()(data[0], 0) - x);
- var closestIndex = 0;
- for (var i = 0; i < data.length; i++){
- if (Math.abs(sparkline.x()(data[i], i) - x) < distance) {
- distance = Math.abs(sparkline.x()(data[i], i) - x);
- closestIndex = i;
- }
- }
- return closestIndex;
- }
-
- index = [getClosestIndex(data, Math.round(x.invert(pos)))];
- updateValueLine();
- }
-
- });
- renderWatch.renderEnd('sparklinePlus immediate');
- return chart;
- }
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- // expose chart's sub-components
- chart.dispatch = dispatch;
- chart.sparkline = sparkline;
-
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- xTickFormat: {get: function(){return xTickFormat;}, set: function(_){xTickFormat=_;}},
- yTickFormat: {get: function(){return yTickFormat;}, set: function(_){yTickFormat=_;}},
- showLastValue: {get: function(){return showLastValue;}, set: function(_){showLastValue=_;}},
- alignValue: {get: function(){return alignValue;}, set: function(_){alignValue=_;}},
- rightAlignValue: {get: function(){return rightAlignValue;}, set: function(_){rightAlignValue=_;}},
- noData: {get: function(){return noData;}, set: function(_){noData=_;}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top !== undefined ? _.top : margin.top;
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }}
- });
-
- nv.utils.inheritOptions(chart, sparkline);
- nv.utils.initOptions(chart);
-
- return chart;
- };
-
- nv.models.stackedArea = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var margin = {top: 0, right: 0, bottom: 0, left: 0}
- , width = 960
- , height = 500
- , color = nv.utils.defaultColor() // a function that computes the color
- , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one
- , container = null
- , getX = function(d) { return d.x } // accessor to get the x value from a data point
- , getY = function(d) { return d.y } // accessor to get the y value from a data point
- , defined = function(d,i) { return !isNaN(getY(d,i)) && getY(d,i) !== null } // allows a line to be not continuous when it is not defined
- , style = 'stack'
- , offset = 'zero'
- , order = 'default'
- , interpolate = 'linear' // controls the line interpolation
- , clipEdge = false // if true, masks lines within x and y scale
- , x //can be accessed via chart.xScale()
- , y //can be accessed via chart.yScale()
- , scatter = nv.models.scatter()
- , duration = 250
- , dispatch = d3.dispatch('areaClick', 'areaMouseover', 'areaMouseout','renderEnd', 'elementClick', 'elementMouseover', 'elementMouseout')
- ;
-
- scatter
- .pointSize(2.2) // default size
- .pointDomain([2.2, 2.2]) // all the same size by default
- ;
-
- /************************************
- * offset:
- * 'wiggle' (stream)
- * 'zero' (stacked)
- * 'expand' (normalize to 100%)
- * 'silhouette' (simple centered)
- *
- * order:
- * 'inside-out' (stream)
- * 'default' (input order)
- ************************************/
-
- var renderWatch = nv.utils.renderWatch(dispatch, duration);
-
- function chart(selection) {
- renderWatch.reset();
- renderWatch.models(scatter);
- selection.each(function(data) {
- var availableWidth = width - margin.left - margin.right,
- availableHeight = height - margin.top - margin.bottom;
-
- container = d3.select(this);
- nv.utils.initSVG(container);
-
- // Setup Scales
- x = scatter.xScale();
- y = scatter.yScale();
-
- var dataRaw = data;
- // Injecting point index into each point because d3.layout.stack().out does not give index
- data.forEach(function(aseries, i) {
- aseries.seriesIndex = i;
- aseries.values = aseries.values.map(function(d, j) {
- d.index = j;
- d.seriesIndex = i;
- return d;
- });
- });
-
- var dataFiltered = data.filter(function(series) {
- return !series.disabled;
- });
-
- data = d3.layout.stack()
- .order(order)
- .offset(offset)
- .values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion
- .x(getX)
- .y(getY)
- .out(function(d, y0, y) {
- d.display = {
- y: y,
- y0: y0
- };
- })
- (dataFiltered);
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]);
- var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea');
- var defsEnter = wrapEnter.append('defs');
- var gEnter = wrapEnter.append('g');
- var g = wrap.select('g');
-
- gEnter.append('g').attr('class', 'nv-areaWrap');
- gEnter.append('g').attr('class', 'nv-scatterWrap');
-
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- // If the user has not specified forceY, make sure 0 is included in the domain
- // Otherwise, use user-specified values for forceY
- if (scatter.forceY().length == 0) {
- scatter.forceY().push(0);
- }
-
- scatter
- .width(availableWidth)
- .height(availableHeight)
- .x(getX)
- .y(function(d) {
- if (d.display !== undefined) { return d.display.y + d.display.y0; }
- })
- .color(data.map(function(d,i) {
- d.color = d.color || color(d, d.seriesIndex);
- return d.color;
- }));
-
- var scatterWrap = g.select('.nv-scatterWrap')
- .datum(data);
-
- scatterWrap.call(scatter);
-
- defsEnter.append('clipPath')
- .attr('id', 'nv-edge-clip-' + id)
- .append('rect');
-
- wrap.select('#nv-edge-clip-' + id + ' rect')
- .attr('width', availableWidth)
- .attr('height', availableHeight);
-
- g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
-
- var area = d3.svg.area()
- .defined(defined)
- .x(function(d,i) { return x(getX(d,i)) })
- .y0(function(d) {
- return y(d.display.y0)
- })
- .y1(function(d) {
- return y(d.display.y + d.display.y0)
- })
- .interpolate(interpolate);
-
- var zeroArea = d3.svg.area()
- .defined(defined)
- .x(function(d,i) { return x(getX(d,i)) })
- .y0(function(d) { return y(d.display.y0) })
- .y1(function(d) { return y(d.display.y0) });
-
- var path = g.select('.nv-areaWrap').selectAll('path.nv-area')
- .data(function(d) { return d });
-
- path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i })
- .attr('d', function(d,i){
- return zeroArea(d.values, d.seriesIndex);
- })
- .on('mouseover', function(d,i) {
- d3.select(this).classed('hover', true);
- dispatch.areaMouseover({
- point: d,
- series: d.key,
- pos: [d3.event.pageX, d3.event.pageY],
- seriesIndex: d.seriesIndex
- });
- })
- .on('mouseout', function(d,i) {
- d3.select(this).classed('hover', false);
- dispatch.areaMouseout({
- point: d,
- series: d.key,
- pos: [d3.event.pageX, d3.event.pageY],
- seriesIndex: d.seriesIndex
- });
- })
- .on('click', function(d,i) {
- d3.select(this).classed('hover', false);
- dispatch.areaClick({
- point: d,
- series: d.key,
- pos: [d3.event.pageX, d3.event.pageY],
- seriesIndex: d.seriesIndex
- });
- });
-
- path.exit().remove();
- path.style('fill', function(d,i){
- return d.color || color(d, d.seriesIndex)
- })
- .style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) });
- path.watchTransition(renderWatch,'stackedArea path')
- .attr('d', function(d,i) {
- return area(d.values,i)
- });
-
- //============================================================
- // Event Handling/Dispatching (in chart's scope)
- //------------------------------------------------------------
-
- scatter.dispatch.on('elementMouseover.area', function(e) {
- g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true);
- });
- scatter.dispatch.on('elementMouseout.area', function(e) {
- g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false);
- });
-
- //Special offset functions
- chart.d3_stackedOffset_stackPercent = function(stackData) {
- var n = stackData.length, //How many series
- m = stackData[0].length, //how many points per series
- i,
- j,
- o,
- y0 = [];
-
- for (j = 0; j < m; ++j) { //Looping through all points
- for (i = 0, o = 0; i < dataRaw.length; i++) { //looping through all series
- o += getY(dataRaw[i].values[j]); //total y value of all series at a certian point in time.
- }
-
- if (o) for (i = 0; i < n; i++) { //(total y value of all series at point in time i) != 0
- stackData[i][j][1] /= o;
- } else { //(total y value of all series at point in time i) == 0
- for (i = 0; i < n; i++) {
- stackData[i][j][1] = 0;
- }
- }
- }
- for (j = 0; j < m; ++j) y0[j] = 0;
- return y0;
- };
-
- });
-
- renderWatch.renderEnd('stackedArea immediate');
- return chart;
- }
-
- //============================================================
- // Global getters and setters
- //------------------------------------------------------------
-
- chart.dispatch = dispatch;
- chart.scatter = scatter;
-
- scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); });
- scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); });
- scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); });
-
- chart.interpolate = function(_) {
- if (!arguments.length) return interpolate;
- interpolate = _;
- return chart;
- };
-
- chart.duration = function(_) {
- if (!arguments.length) return duration;
- duration = _;
- renderWatch.reset(duration);
- scatter.duration(duration);
- return chart;
- };
-
- chart.dispatch = dispatch;
- chart.scatter = scatter;
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- defined: {get: function(){return defined;}, set: function(_){defined=_;}},
- clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
- offset: {get: function(){return offset;}, set: function(_){offset=_;}},
- order: {get: function(){return order;}, set: function(_){order=_;}},
- interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
-
- // simple functor options
- x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}},
- y: {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top !== undefined ? _.top : margin.top;
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- }},
- style: {get: function(){return style;}, set: function(_){
- style = _;
- switch (style) {
- case 'stack':
- chart.offset('zero');
- chart.order('default');
- break;
- case 'stream':
- chart.offset('wiggle');
- chart.order('inside-out');
- break;
- case 'stream-center':
- chart.offset('silhouette');
- chart.order('inside-out');
- break;
- case 'expand':
- chart.offset('expand');
- chart.order('default');
- break;
- case 'stack_percent':
- chart.offset(chart.d3_stackedOffset_stackPercent);
- chart.order('default');
- break;
- }
- }},
- duration: {get: function(){return duration;}, set: function(_){
- duration = _;
- renderWatch.reset(duration);
- scatter.duration(duration);
- }}
- });
-
- nv.utils.inheritOptions(chart, scatter);
- nv.utils.initOptions(chart);
-
- return chart;
- };
-
- nv.models.stackedAreaChart = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var stacked = nv.models.stackedArea()
- , xAxis = nv.models.axis()
- , yAxis = nv.models.axis()
- , legend = nv.models.legend()
- , controls = nv.models.legend()
- , interactiveLayer = nv.interactiveGuideline()
- , tooltip = nv.models.tooltip()
- , focus = nv.models.focus(nv.models.stackedArea())
- ;
-
- var margin = {top: 10, right: 25, bottom: 50, left: 60}
- , marginTop = null
- , width = null
- , height = null
- , color = nv.utils.defaultColor()
- , showControls = true
- , showLegend = true
- , legendPosition = 'top'
- , showXAxis = true
- , showYAxis = true
- , rightAlignYAxis = false
- , focusEnable = false
- , useInteractiveGuideline = false
- , showTotalInTooltip = true
- , totalLabel = 'TOTAL'
- , x //can be accessed via chart.xScale()
- , y //can be accessed via chart.yScale()
- , state = nv.utils.state()
- , defaultState = null
- , noData = null
- , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd')
- , controlWidth = 250
- , controlOptions = ['Stacked','Stream','Expanded']
- , controlLabels = {}
- , duration = 250
- ;
-
- state.style = stacked.style();
- xAxis.orient('bottom').tickPadding(7);
- yAxis.orient((rightAlignYAxis) ? 'right' : 'left');
-
- tooltip
- .headerFormatter(function(d, i) {
- return xAxis.tickFormat()(d, i);
- })
- .valueFormatter(function(d, i) {
- return yAxis.tickFormat()(d, i);
- });
-
- interactiveLayer.tooltip
- .headerFormatter(function(d, i) {
- return xAxis.tickFormat()(d, i);
- })
- .valueFormatter(function(d, i) {
- return d == null ? "N/A" : yAxis.tickFormat()(d, i);
- });
-
- var oldYTickFormat = null,
- oldValueFormatter = null;
-
- controls.updateState(false);
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var renderWatch = nv.utils.renderWatch(dispatch);
- var style = stacked.style();
-
- var stateGetter = function(data) {
- return function(){
- return {
- active: data.map(function(d) { return !d.disabled }),
- style: stacked.style()
- };
- }
- };
-
- var stateSetter = function(data) {
- return function(state) {
- if (state.style !== undefined)
- style = state.style;
- if (state.active !== undefined)
- data.forEach(function(series,i) {
- series.disabled = !state.active[i];
- });
- }
- };
-
- var percentFormatter = d3.format('%');
-
- function chart(selection) {
- renderWatch.reset();
- renderWatch.models(stacked);
- if (showXAxis) renderWatch.models(xAxis);
- if (showYAxis) renderWatch.models(yAxis);
-
- selection.each(function(data) {
- var container = d3.select(this),
- that = this;
- nv.utils.initSVG(container);
-
- var availableWidth = nv.utils.availableWidth(width, container, margin),
- availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0);
-
- chart.update = function() { container.transition().duration(duration).call(chart); };
- chart.container = this;
-
- state
- .setter(stateSetter(data), chart.update)
- .getter(stateGetter(data))
- .update();
-
- // DEPRECATED set state.disabled
- state.disabled = data.map(function(d) { return !!d.disabled });
-
- if (!defaultState) {
- var key;
- defaultState = {};
- for (key in state) {
- if (state[key] instanceof Array)
- defaultState[key] = state[key].slice(0);
- else
- defaultState[key] = state[key];
- }
- }
-
- // Display No Data message if there's nothing to show.
- if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
- nv.utils.noData(chart, container)
- return chart;
- } else {
- container.selectAll('.nv-noData').remove();
- }
- // Setup Scales
- x = stacked.xScale();
- y = stacked.yScale();
-
- // Setup containers and skeleton of chart
- var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]);
- var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g');
- var g = wrap.select('g');
-
- gEnter.append('g').attr('class', 'nv-legendWrap');
- gEnter.append('g').attr('class', 'nv-controlsWrap');
-
- var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
- focusEnter.append('g').attr('class', 'nv-background').append('rect');
- focusEnter.append('g').attr('class', 'nv-x nv-axis');
- focusEnter.append('g').attr('class', 'nv-y nv-axis');
- focusEnter.append('g').attr('class', 'nv-stackedWrap');
- focusEnter.append('g').attr('class', 'nv-interactive');
-
- // g.select("rect").attr("width",availableWidth).attr("height",availableHeight);
-
- var contextEnter = gEnter.append('g').attr('class', 'nv-focusWrap');
-
- // Legend
- if (!showLegend) {
- g.select('.nv-legendWrap').selectAll('*').remove();
- } else {
- var legendWidth = (showControls && legendPosition === 'top') ? availableWidth - controlWidth : availableWidth;
-
- legend.width(legendWidth);
- g.select('.nv-legendWrap').datum(data).call(legend);
-
- if (legendPosition === 'bottom') {
- // constant from axis.js, plus some margin for better layout
- var xAxisHeight = (showXAxis ? 12 : 0) + 10;
- margin.bottom = Math.max(legend.height() + xAxisHeight, margin.bottom);
- availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0);
- var legendTop = availableHeight + xAxisHeight;
- g.select('.nv-legendWrap')
- .attr('transform', 'translate(0,' + legendTop +')');
- } else if (legendPosition === 'top') {
- if (!marginTop && margin.top != legend.height()) {
- margin.top = legend.height();
- availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0);
- }
-
- g.select('.nv-legendWrap')
- .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')');
- }
- }
-
- // Controls
- if (!showControls) {
- g.select('.nv-controlsWrap').selectAll('*').remove();
- } else {
- var controlsData = [
- {
- key: controlLabels.stacked || 'Stacked',
- metaKey: 'Stacked',
- disabled: stacked.style() != 'stack',
- style: 'stack'
- },
- {
- key: controlLabels.stream || 'Stream',
- metaKey: 'Stream',
- disabled: stacked.style() != 'stream',
- style: 'stream'
- },
- {
- key: controlLabels.expanded || 'Expanded',
- metaKey: 'Expanded',
- disabled: stacked.style() != 'expand',
- style: 'expand'
- },
- {
- key: controlLabels.stack_percent || 'Stack %',
- metaKey: 'Stack_Percent',
- disabled: stacked.style() != 'stack_percent',
- style: 'stack_percent'
- }
- ];
-
- controlWidth = (controlOptions.length/3) * 260;
- controlsData = controlsData.filter(function(d) {
- return controlOptions.indexOf(d.metaKey) !== -1;
- });
-
- controls
- .width( controlWidth )
- .color(['#444', '#444', '#444']);
-
- g.select('.nv-controlsWrap')
- .datum(controlsData)
- .call(controls);
-
- var requiredTop = Math.max(controls.height(), showLegend && (legendPosition === 'top') ? legend.height() : 0);
-
- if ( margin.top != requiredTop ) {
- margin.top = requiredTop;
- availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0);
- }
-
- g.select('.nv-controlsWrap')
- .attr('transform', 'translate(0,' + (-margin.top) +')');
- }
-
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
-
- if (rightAlignYAxis) {
- g.select(".nv-y.nv-axis")
- .attr("transform", "translate(" + availableWidth + ",0)");
- }
-
- //Set up interactive layer
- if (useInteractiveGuideline) {
- interactiveLayer
- .width(availableWidth)
- .height(availableHeight)
- .margin({left: margin.left, top: margin.top})
- .svgContainer(container)
- .xScale(x);
- wrap.select(".nv-interactive").call(interactiveLayer);
- }
-
- g.select('.nv-focus .nv-background rect')
- .attr('width', availableWidth)
- .attr('height', availableHeight);
-
- stacked
- .width(availableWidth)
- .height(availableHeight)
- .color(data.map(function(d,i) {
- return d.color || color(d, i);
- }).filter(function(d,i) { return !data[i].disabled; }));
-
- var stackedWrap = g.select('.nv-focus .nv-stackedWrap')
- .datum(data.filter(function(d) { return !d.disabled; }));
-
- // Setup Axes
- if (showXAxis) {
- xAxis.scale(x)
- ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
- .tickSize( -availableHeight, 0);
- }
-
- if (showYAxis) {
- var ticks;
- if (stacked.offset() === 'wiggle') {
- ticks = 0;
- }
- else {
- ticks = nv.utils.calcTicksY(availableHeight/36, data);
- }
- yAxis.scale(y)
- ._ticks(ticks)
- .tickSize(-availableWidth, 0);
- }
-
- //============================================================
- // Update Axes
- //============================================================
- function updateXAxis() {
- if(showXAxis) {
- g.select('.nv-focus .nv-x.nv-axis')
- .attr('transform', 'translate(0,' + availableHeight + ')')
- .transition()
- .duration(duration)
- .call(xAxis)
- ;
- }
- }
-
- function updateYAxis() {
- if(showYAxis) {
- if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') {
- var currentFormat = yAxis.tickFormat();
-
- if ( !oldYTickFormat || currentFormat !== percentFormatter )
- oldYTickFormat = currentFormat;
-
- //Forces the yAxis to use percentage in 'expand' mode.
- yAxis.tickFormat(percentFormatter);
- }
- else {
- if (oldYTickFormat) {
- yAxis.tickFormat(oldYTickFormat);
- oldYTickFormat = null;
- }
- }
-
- g.select('.nv-focus .nv-y.nv-axis')
- .transition().duration(0)
- .call(yAxis);
- }
- }
-
- //============================================================
- // Update Focus
- //============================================================
- if(!focusEnable) {
- stackedWrap.transition().call(stacked);
- updateXAxis();
- updateYAxis();
- } else {
- focus.width(availableWidth);
- g.select('.nv-focusWrap')
- .attr('transform', 'translate(0,' + ( availableHeight + margin.bottom + focus.margin().top) + ')')
- .datum(data.filter(function(d) { return !d.disabled; }))
- .call(focus);
- var extent = focus.brush.empty() ? focus.xDomain() : focus.brush.extent();
- if(extent !== null){
- onBrush(extent);
- }
- }
-
- //============================================================
- // Event Handling/Dispatching (in chart's scope)
- //------------------------------------------------------------
-
- stacked.dispatch.on('areaClick.toggle', function(e) {
- if (data.filter(function(d) { return !d.disabled }).length === 1)
- data.forEach(function(d) {
- d.disabled = false;
- });
- else
- data.forEach(function(d,i) {
- d.disabled = (i != e.seriesIndex);
- });
-
- state.disabled = data.map(function(d) { return !!d.disabled });
- dispatch.stateChange(state);
-
- chart.update();
- });
-
- legend.dispatch.on('stateChange', function(newState) {
- for (var key in newState)
- state[key] = newState[key];
- dispatch.stateChange(state);
- chart.update();
- });
-
- controls.dispatch.on('legendClick', function(d,i) {
- if (!d.disabled) return;
-
- controlsData = controlsData.map(function(s) {
- s.disabled = true;
- return s;
- });
- d.disabled = false;
-
- stacked.style(d.style);
-
-
- state.style = stacked.style();
- dispatch.stateChange(state);
-
- chart.update();
- });
-
- interactiveLayer.dispatch.on('elementMousemove', function(e) {
- stacked.clearHighlights();
- var singlePoint, pointIndex, pointXLocation, allData = [], valueSum = 0, allNullValues = true;
- data
- .filter(function(series, i) {
- series.seriesIndex = i;
- return !series.disabled;
- })
- .forEach(function(series,i) {
- pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
- var point = series.values[pointIndex];
- var pointYValue = chart.y()(point, pointIndex);
- if (pointYValue != null) {
- stacked.highlightPoint(i, pointIndex, true);
- }
- if (typeof point === 'undefined') return;
- if (typeof singlePoint === 'undefined') singlePoint = point;
- if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
-
- //If we are in 'expand' mode, use the stacked percent value instead of raw value.
- var tooltipValue = (stacked.style() == 'expand') ? point.display.y : chart.y()(point,pointIndex);
- allData.push({
- key: series.key,
- value: tooltipValue,
- color: color(series,series.seriesIndex),
- point: point
- });
-
- if (showTotalInTooltip && stacked.style() != 'expand' && tooltipValue != null) {
- valueSum += tooltipValue;
- allNullValues = false;
- };
- });
-
- allData.reverse();
-
- //Highlight the tooltip entry based on which stack the mouse is closest to.
- if (allData.length > 2) {
- var yValue = chart.yScale().invert(e.mouseY);
- var yDistMax = Infinity, indexToHighlight = null;
- allData.forEach(function(series,i) {
-
- //To handle situation where the stacked area chart is negative, we need to use absolute values
- //when checking if the mouse Y value is within the stack area.
- yValue = Math.abs(yValue);
- var stackedY0 = Math.abs(series.point.display.y0);
- var stackedY = Math.abs(series.point.display.y);
- if ( yValue >= stackedY0 && yValue <= (stackedY + stackedY0))
- {
- indexToHighlight = i;
- return;
- }
- });
- if (indexToHighlight != null)
- allData[indexToHighlight].highlight = true;
- }
-
- //If we are not in 'expand' mode, add a 'Total' row to the tooltip.
- if (showTotalInTooltip && stacked.style() != 'expand' && allData.length >= 2 && !allNullValues) {
- allData.push({
- key: totalLabel,
- value: valueSum,
- total: true
- });
- }
-
- var xValue = chart.x()(singlePoint,pointIndex);
-
- var valueFormatter = interactiveLayer.tooltip.valueFormatter();
- // Keeps track of the tooltip valueFormatter if the chart changes to expanded view
- if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') {
- if ( !oldValueFormatter ) {
- oldValueFormatter = valueFormatter;
- }
- //Forces the tooltip to use percentage in 'expand' mode.
- valueFormatter = d3.format(".1%");
- }
- else {
- if (oldValueFormatter) {
- valueFormatter = oldValueFormatter;
- oldValueFormatter = null;
- }
- }
-
- interactiveLayer.tooltip
- .valueFormatter(valueFormatter)
- .data(
- {
- value: xValue,
- series: allData
- }
- )();
-
- interactiveLayer.renderGuideLine(pointXLocation);
-
- });
-
- interactiveLayer.dispatch.on("elementMouseout",function(e) {
- stacked.clearHighlights();
- });
-
- /* Update `main' graph on brush update. */
- focus.dispatch.on("onBrush", function(extent) {
- onBrush(extent);
- });
-
- // Update chart from a state object passed to event handler
- dispatch.on('changeState', function(e) {
-
- if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
- data.forEach(function(series,i) {
- series.disabled = e.disabled[i];
- });
-
- state.disabled = e.disabled;
- }
-
- if (typeof e.style !== 'undefined') {
- stacked.style(e.style);
- style = e.style;
- }
-
- chart.update();
- });
-
- //============================================================
- // Functions
- //------------------------------------------------------------
-
- function onBrush(extent) {
- // Update Main (Focus)
- var stackedWrap = g.select('.nv-focus .nv-stackedWrap')
- .datum(
- data.filter(function(d) { return !d.disabled; })
- .map(function(d,i) {
- return {
- key: d.key,
- area: d.area,
- classed: d.classed,
- values: d.values.filter(function(d,i) {
- return stacked.x()(d,i) >= extent[0] && stacked.x()(d,i) <= extent[1];
- }),
- disableTooltip: d.disableTooltip
- };
- })
- );
- stackedWrap.transition().duration(duration).call(stacked);
-
- // Update Main (Focus) Axes
- updateXAxis();
- updateYAxis();
- }
-
- });
-
- renderWatch.renderEnd('stacked Area chart immediate');
- return chart;
- }
-
- //============================================================
- // Event Handling/Dispatching (out of chart's scope)
- //------------------------------------------------------------
-
- stacked.dispatch.on('elementMouseover.tooltip', function(evt) {
- evt.point['x'] = stacked.x()(evt.point);
- evt.point['y'] = stacked.y()(evt.point);
- tooltip.data(evt).hidden(false);
- });
-
- stacked.dispatch.on('elementMouseout.tooltip', function(evt) {
- tooltip.hidden(true)
- });
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- // expose chart's sub-components
- chart.dispatch = dispatch;
- chart.stacked = stacked;
- chart.legend = legend;
- chart.controls = controls;
- chart.xAxis = xAxis;
- chart.x2Axis = focus.xAxis;
- chart.yAxis = yAxis;
- chart.y2Axis = focus.yAxis;
- chart.interactiveLayer = interactiveLayer;
- chart.tooltip = tooltip;
- chart.focus = focus;
-
- chart.dispatch = dispatch;
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
- legendPosition: {get: function(){return legendPosition;}, set: function(_){legendPosition=_;}},
- showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
- showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
- defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
- noData: {get: function(){return noData;}, set: function(_){noData=_;}},
- showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
- controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
- controlOptions: {get: function(){return controlOptions;}, set: function(_){controlOptions=_;}},
- showTotalInTooltip: {get: function(){return showTotalInTooltip;}, set: function(_){showTotalInTooltip=_;}},
- totalLabel: {get: function(){return totalLabel;}, set: function(_){totalLabel=_;}},
- focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}},
- focusHeight: {get: function(){return focus.height();}, set: function(_){focus.height(_);}},
- brushExtent: {get: function(){return focus.brushExtent();}, set: function(_){focus.brushExtent(_);}},
-
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- if (_.top !== undefined) {
- margin.top = _.top;
- marginTop = _.top;
- }
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- }},
- focusMargin: {get: function(){return focus.margin}, set: function(_){
- focus.margin.top = _.top !== undefined ? _.top : focus.margin.top;
- focus.margin.right = _.right !== undefined ? _.right : focus.margin.right;
- focus.margin.bottom = _.bottom !== undefined ? _.bottom : focus.margin.bottom;
- focus.margin.left = _.left !== undefined ? _.left : focus.margin.left;
- }},
- duration: {get: function(){return duration;}, set: function(_){
- duration = _;
- renderWatch.reset(duration);
- stacked.duration(duration);
- xAxis.duration(duration);
- yAxis.duration(duration);
- }},
- color: {get: function(){return color;}, set: function(_){
- color = nv.utils.getColor(_);
- legend.color(color);
- stacked.color(color);
- focus.color(color);
- }},
- x: {get: function(){return stacked.x();}, set: function(_){
- stacked.x(_);
- focus.x(_);
- }},
- y: {get: function(){return stacked.y();}, set: function(_){
- stacked.y(_);
- focus.y(_);
- }},
- rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
- rightAlignYAxis = _;
- yAxis.orient( rightAlignYAxis ? 'right' : 'left');
- }},
- useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
- useInteractiveGuideline = !!_;
- chart.interactive(!_);
- chart.useVoronoi(!_);
- stacked.scatter.interactive(!_);
- }}
- });
-
- nv.utils.inheritOptions(chart, stacked);
- nv.utils.initOptions(chart);
-
- return chart;
- };
-
- nv.models.stackedAreaWithFocusChart = function() {
- return nv.models.stackedAreaChart()
- .margin({ bottom: 30 })
- .focusEnable( true );
- };
- // based on http://bl.ocks.org/kerryrodden/477c1bfb081b783f80ad
- nv.models.sunburst = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var margin = {top: 0, right: 0, bottom: 0, left: 0}
- , width = 600
- , height = 600
- , mode = "count"
- , modes = {count: function(d) { return 1; }, value: function(d) { return d.value || d.size }, size: function(d) { return d.value || d.size }}
- , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
- , container = null
- , color = nv.utils.defaultColor()
- , showLabels = false
- , labelFormat = function(d){if(mode === 'count'){return d.name + ' #' + d.value}else{return d.name + ' ' + (d.value || d.size)}}
- , labelThreshold = 0.02
- , sort = function(d1, d2){return d1.name > d2.name;}
- , key = function(d,i){return d.name;}
- , groupColorByParent = true
- , duration = 500
- , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMousemove', 'elementMouseover', 'elementMouseout', 'renderEnd');
-
- //============================================================
- // aux functions and setup
- //------------------------------------------------------------
-
- var x = d3.scale.linear().range([0, 2 * Math.PI]);
- var y = d3.scale.sqrt();
-
- var partition = d3.layout.partition().sort(sort);
-
- var node, availableWidth, availableHeight, radius;
- var prevPositions = {};
-
- var arc = d3.svg.arc()
- .startAngle(function(d) {return Math.max(0, Math.min(2 * Math.PI, x(d.x))) })
- .endAngle(function(d) {return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))) })
- .innerRadius(function(d) {return Math.max(0, y(d.y)) })
- .outerRadius(function(d) {return Math.max(0, y(d.y + d.dy)) });
-
- function rotationToAvoidUpsideDown(d) {
- var centerAngle = computeCenterAngle(d);
- if(centerAngle > 90){
- return 180;
- }
- else {
- return 0;
- }
- }
-
- function computeCenterAngle(d) {
- var startAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x)));
- var endAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
- var centerAngle = (((startAngle + endAngle) / 2) * (180 / Math.PI)) - 90;
- return centerAngle;
- }
-
- function computeNodePercentage(d) {
- var startAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x)));
- var endAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
- return (endAngle - startAngle) / (2 * Math.PI);
- }
-
- function labelThresholdMatched(d) {
- var startAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x)));
- var endAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
-
- var size = endAngle - startAngle;
- return size > labelThreshold;
- }
-
- // When zooming: interpolate the scales.
- function arcTweenZoom(e,i) {
- var xd = d3.interpolate(x.domain(), [node.x, node.x + node.dx]),
- yd = d3.interpolate(y.domain(), [node.y, 1]),
- yr = d3.interpolate(y.range(), [node.y ? 20 : 0, radius]);
-
- if (i === 0) {
- return function() {return arc(e);}
- }
- else {
- return function (t) {
- x.domain(xd(t));
- y.domain(yd(t)).range(yr(t));
- return arc(e);
- }
- };
- }
-
- function arcTweenUpdate(d) {
- var ipo = d3.interpolate({x: d.x0, dx: d.dx0, y: d.y0, dy: d.dy0}, d);
-
- return function (t) {
- var b = ipo(t);
-
- d.x0 = b.x;
- d.dx0 = b.dx;
- d.y0 = b.y;
- d.dy0 = b.dy;
-
- return arc(b);
- };
- }
-
- function updatePrevPosition(node) {
- var k = key(node);
- if(! prevPositions[k]) prevPositions[k] = {};
- var pP = prevPositions[k];
- pP.dx = node.dx;
- pP.x = node.x;
- pP.dy = node.dy;
- pP.y = node.y;
- }
-
- function storeRetrievePrevPositions(nodes) {
- nodes.forEach(function(n){
- var k = key(n);
- var pP = prevPositions[k];
- //console.log(k,n,pP);
- if( pP ){
- n.dx0 = pP.dx;
- n.x0 = pP.x;
- n.dy0 = pP.dy;
- n.y0 = pP.y;
- }
- else {
- n.dx0 = n.dx;
- n.x0 = n.x;
- n.dy0 = n.dy;
- n.y0 = n.y;
- }
- updatePrevPosition(n);
- });
- }
-
- function zoomClick(d) {
- var labels = container.selectAll('text')
- var path = container.selectAll('path')
-
- // fade out all text elements
- labels.transition().attr("opacity",0);
-
- // to allow reference to the new center node
- node = d;
-
- path.transition()
- .duration(duration)
- .attrTween("d", arcTweenZoom)
- .each('end', function(e) {
- // partially taken from here: http://bl.ocks.org/metmajer/5480307
- // check if the animated element's data e lies within the visible angle span given in d
- if(e.x >= d.x && e.x < (d.x + d.dx) ){
- if(e.depth >= d.depth){
- // get a selection of the associated text element
- var parentNode = d3.select(this.parentNode);
- var arcText = parentNode.select('text');
-
- // fade in the text element and recalculate positions
- arcText.transition().duration(duration)
- .text( function(e){return labelFormat(e) })
- .attr("opacity", function(d){
- if(labelThresholdMatched(d)) {
- return 1;
- }
- else {
- return 0;
- }
- })
- .attr("transform", function() {
- var width = this.getBBox().width;
- if(e.depth === 0)
- return "translate(" + (width / 2 * - 1) + ",0)";
- else if(e.depth === d.depth){
- return "translate(" + (y(e.y) + 5) + ",0)";
- }
- else {
- var centerAngle = computeCenterAngle(e);
- var rotation = rotationToAvoidUpsideDown(e);
- if (rotation === 0) {
- return 'rotate('+ centerAngle +')translate(' + (y(e.y) + 5) + ',0)';
- }
- else {
- return 'rotate('+ centerAngle +')translate(' + (y(e.y) + width + 5) + ',0)rotate(' + rotation + ')';
- }
- }
- });
- }
- }
- })
- }
-
- //============================================================
- // chart function
- //------------------------------------------------------------
- var renderWatch = nv.utils.renderWatch(dispatch);
-
- function chart(selection) {
- renderWatch.reset();
-
- selection.each(function(data) {
- container = d3.select(this);
- availableWidth = nv.utils.availableWidth(width, container, margin);
- availableHeight = nv.utils.availableHeight(height, container, margin);
- radius = Math.min(availableWidth, availableHeight) / 2;
-
- y.range([0, radius]);
-
- // Setup containers and skeleton of chart
- var wrap = container.select('g.nvd3.nv-wrap.nv-sunburst');
- if( !wrap[0][0] ) {
- wrap = container.append('g')
- .attr('class', 'nvd3 nv-wrap nv-sunburst nv-chart-' + id)
- .attr('transform', 'translate(' + ((availableWidth / 2) + margin.left + margin.right) + ',' + ((availableHeight / 2) + margin.top + margin.bottom) + ')');
- } else {
- wrap.attr('transform', 'translate(' + ((availableWidth / 2) + margin.left + margin.right) + ',' + ((availableHeight / 2) + margin.top + margin.bottom) + ')');
- }
-
- container.on('click', function (d, i) {
- dispatch.chartClick({
- data: d,
- index: i,
- pos: d3.event,
- id: id
- });
- });
-
- partition.value(modes[mode] || modes["count"]);
-
- //reverse the drawing order so that the labels of inner
- //arcs are drawn on top of the outer arcs.
- var nodes = partition.nodes(data[0]).reverse()
-
- storeRetrievePrevPositions(nodes);
- var cG = wrap.selectAll('.arc-container').data(nodes, key)
-
- //handle new datapoints
- var cGE = cG.enter()
- .append("g")
- .attr("class",'arc-container')
-
- cGE.append("path")
- .attr("d", arc)
- .style("fill", function (d) {
- if (d.color) {
- return d.color;
- }
- else if (groupColorByParent) {
- return color((d.children ? d : d.parent).name);
- }
- else {
- return color(d.name);
- }
- })
- .style("stroke", "#FFF")
- .on("click", function(d,i){
- zoomClick(d);
- dispatch.elementClick({
- data: d,
- index: i
- })
- })
- .on('mouseover', function(d,i){
- d3.select(this).classed('hover', true).style('opacity', 0.8);
- dispatch.elementMouseover({
- data: d,
- color: d3.select(this).style("fill"),
- percent: computeNodePercentage(d)
- });
- })
- .on('mouseout', function(d,i){
- d3.select(this).classed('hover', false).style('opacity', 1);
- dispatch.elementMouseout({
- data: d
- });
- })
- .on('mousemove', function(d,i){
- dispatch.elementMousemove({
- data: d
- });
- });
-
- ///Iterating via each and selecting based on the this
- ///makes it work ... a cG.selectAll('path') doesn't.
- ///Without iteration the data (in the element) didn't update.
- cG.each(function(d){
- d3.select(this).select('path')
- .transition()
- .duration(duration)
- .attrTween('d', arcTweenUpdate);
- });
-
- if(showLabels){
- //remove labels first and add them back
- cG.selectAll('text').remove();
-
- //this way labels are on top of newly added arcs
- cG.append('text')
- .text( function(e){ return labelFormat(e)})
- .transition()
- .duration(duration)
- .attr("opacity", function(d){
- if(labelThresholdMatched(d)) {
- return 1;
- }
- else {
- return 0;
- }
- })
- .attr("transform", function(d) {
- var width = this.getBBox().width;
- if(d.depth === 0){
- return "rotate(0)translate(" + (width / 2 * -1) + ",0)";
- }
- else {
- var centerAngle = computeCenterAngle(d);
- var rotation = rotationToAvoidUpsideDown(d);
- if (rotation === 0) {
- return 'rotate('+ centerAngle +')translate(' + (y(d.y) + 5) + ',0)';
- }
- else {
- return 'rotate('+ centerAngle +')translate(' + (y(d.y) + width + 5) + ',0)rotate(' + rotation + ')';
- }
- }
- });
- }
-
- //zoom out to the center when the data is updated.
- zoomClick(nodes[nodes.length - 1])
-
-
- //remove unmatched elements ...
- cG.exit()
- .transition()
- .duration(duration)
- .attr('opacity',0)
- .each('end',function(d){
- var k = key(d);
- prevPositions[k] = undefined;
- })
- .remove();
- });
-
-
- renderWatch.renderEnd('sunburst immediate');
- return chart;
- }
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- chart.dispatch = dispatch;
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- width: {get: function(){return width;}, set: function(_){width=_;}},
- height: {get: function(){return height;}, set: function(_){height=_;}},
- mode: {get: function(){return mode;}, set: function(_){mode=_;}},
- id: {get: function(){return id;}, set: function(_){id=_;}},
- duration: {get: function(){return duration;}, set: function(_){duration=_;}},
- groupColorByParent: {get: function(){return groupColorByParent;}, set: function(_){groupColorByParent=!!_;}},
- showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=!!_}},
- labelFormat: {get: function(){return labelFormat;}, set: function(_){labelFormat=_}},
- labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_}},
- sort: {get: function(){return sort;}, set: function(_){sort=_}},
- key: {get: function(){return key;}, set: function(_){key=_}},
- // options that require extra logic in the setter
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top != undefined ? _.top : margin.top;
- margin.right = _.right != undefined ? _.right : margin.right;
- margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
- margin.left = _.left != undefined ? _.left : margin.left;
- }},
- color: {get: function(){return color;}, set: function(_){
- color=nv.utils.getColor(_);
- }}
- });
-
- nv.utils.initOptions(chart);
- return chart;
- };
- nv.models.sunburstChart = function() {
- "use strict";
-
- //============================================================
- // Public Variables with Default Settings
- //------------------------------------------------------------
-
- var sunburst = nv.models.sunburst();
- var tooltip = nv.models.tooltip();
-
- var margin = {top: 30, right: 20, bottom: 20, left: 20}
- , width = null
- , height = null
- , color = nv.utils.defaultColor()
- , showTooltipPercent = false
- , id = Math.round(Math.random() * 100000)
- , defaultState = null
- , noData = null
- , duration = 250
- , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd');
-
-
- //============================================================
- // Private Variables
- //------------------------------------------------------------
-
- var renderWatch = nv.utils.renderWatch(dispatch);
-
- tooltip
- .duration(0)
- .headerEnabled(false)
- .valueFormatter(function(d){return d;});
-
- //============================================================
- // Chart function
- //------------------------------------------------------------
-
- function chart(selection) {
- renderWatch.reset();
- renderWatch.models(sunburst);
-
- selection.each(function(data) {
- var container = d3.select(this);
-
- nv.utils.initSVG(container);
-
- var availableWidth = nv.utils.availableWidth(width, container, margin);
- var availableHeight = nv.utils.availableHeight(height, container, margin);
-
- chart.update = function() {
- if (duration === 0) {
- container.call(chart);
- } else {
- container.transition().duration(duration).call(chart);
- }
- };
- chart.container = container;
-
- // Display No Data message if there's nothing to show.
- if (!data || !data.length) {
- nv.utils.noData(chart, container);
- return chart;
- } else {
- container.selectAll('.nv-noData').remove();
- }
-
- sunburst.width(availableWidth).height(availableHeight).margin(margin);
- container.call(sunburst);
- });
-
- renderWatch.renderEnd('sunburstChart immediate');
- return chart;
- }
-
- //============================================================
- // Event Handling/Dispatching (out of chart's scope)
- //------------------------------------------------------------
-
- sunburst.dispatch.on('elementMouseover.tooltip', function(evt) {
- evt.series = {
- key: evt.data.name,
- value: (evt.data.value || evt.data.size),
- color: evt.color,
- percent: evt.percent
- };
- if (!showTooltipPercent) {
- delete evt.percent;
- delete evt.series.percent;
- }
- tooltip.data(evt).hidden(false);
- });
-
- sunburst.dispatch.on('elementMouseout.tooltip', function(evt) {
- tooltip.hidden(true);
- });
-
- sunburst.dispatch.on('elementMousemove.tooltip', function(evt) {
- tooltip();
- });
-
- //============================================================
- // Expose Public Variables
- //------------------------------------------------------------
-
- // expose chart's sub-components
- chart.dispatch = dispatch;
- chart.sunburst = sunburst;
- chart.tooltip = tooltip;
- chart.options = nv.utils.optionsFunc.bind(chart);
-
- // use Object get/set functionality to map between vars and chart functions
- chart._options = Object.create({}, {
- // simple options, just get/set the necessary values
- noData: {get: function(){return noData;}, set: function(_){noData=_;}},
- defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
- showTooltipPercent: {get: function(){return showTooltipPercent;}, set: function(_){showTooltipPercent=_;}},
-
- // options that require extra logic in the setter
- color: {get: function(){return color;}, set: function(_){
- color = _;
- sunburst.color(color);
- }},
- duration: {get: function(){return duration;}, set: function(_){
- duration = _;
- renderWatch.reset(duration);
- sunburst.duration(duration);
- }},
- margin: {get: function(){return margin;}, set: function(_){
- margin.top = _.top !== undefined ? _.top : margin.top;
- margin.right = _.right !== undefined ? _.right : margin.right;
- margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
- margin.left = _.left !== undefined ? _.left : margin.left;
- sunburst.margin(margin);
- }}
- });
- nv.utils.inheritOptions(chart, sunburst);
- nv.utils.initOptions(chart);
- return chart;
-
- };
-
- nv.version = "1.8.5";
- })();
- //# sourceMappingURL=nv.d3.js.map
|