Dashboard sipadu mbip
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

nv.d3.js 622KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399144001440114402144031440414405144061440714408144091441014411144121441314414144151441614417144181441914420144211442214423144241442514426144271442814429144301443114432144331443414435144361443714438144391444014441144421444314444144451444614447144481444914450144511445214453144541445514456144571445814459144601446114462144631446414465144661446714468144691447014471144721447314474144751447614477144781447914480144811448214483144841448514486144871448814489144901449114492144931449414495144961449714498144991450014501145021450314504145051450614507145081450914510145111451214513145141451514516145171451814519145201452114522145231452414525145261452714528145291453014531145321453314534145351453614537145381453914540145411454214543145441454514546145471454814549145501455114552145531455414555145561455714558145591456014561145621456314564145651456614567145681456914570145711457214573145741457514576145771457814579145801458114582145831458414585145861458714588145891459014591145921459314594145951459614597145981459914600146011460214603146041460514606146071460814609146101461114612146131461414615146161461714618146191462014621146221462314624146251462614627146281462914630146311463214633146341463514636146371463814639146401464114642146431464414645146461464714648146491465014651146521465314654146551465614657146581465914660146611466214663146641466514666146671466814669146701467114672146731467414675146761467714678146791468014681146821468314684146851468614687146881468914690146911469214693146941469514696146971469814699147001470114702147031470414705147061470714708147091471014711147121471314714147151471614717147181471914720147211472214723147241472514726147271472814729147301473114732147331473414735147361473714738147391474014741147421474314744147451474614747147481474914750147511475214753147541475514756147571475814759147601476114762147631476414765147661476714768147691477014771147721477314774147751477614777147781477914780147811478214783147841478514786147871478814789147901479114792147931479414795147961479714798147991480014801148021480314804148051480614807148081480914810148111481214813148141481514816148171481814819148201482114822148231482414825148261482714828148291483014831148321483314834148351483614837148381483914840148411484214843148441484514846148471484814849148501485114852148531485414855148561485714858148591486014861148621486314864148651486614867148681486914870148711487214873148741487514876148771487814879148801488114882148831488414885148861488714888148891489014891148921489314894148951489614897148981489914900149011490214903149041490514906149071490814909149101491114912149131491414915149161491714918149191492014921149221492314924149251492614927149281492914930149311493214933149341493514936149371493814939149401494114942149431494414945149461494714948149491495014951149521495314954149551495614957149581495914960149611496214963149641496514966149671496814969149701497114972149731497414975149761497714978149791498014981149821498314984149851498614987149881498914990149911499214993149941499514996149971499814999150001500115002150031500415005150061500715008150091501015011150121501315014150151501615017150181501915020150211502215023150241502515026150271502815029150301503115032150331503415035150361503715038150391504015041150421504315044150451504615047150481504915050150511505215053150541505515056150571505815059150601506115062150631506415065150661506715068150691507015071150721507315074150751507615077150781507915080150811508215083150841508515086150871508815089150901509115092150931509415095150961509715098150991510015101151021510315104151051510615107151081510915110151111511215113151141511515116151171511815119151201512115122151231512415125151261512715128151291513015131151321513315134151351513615137151381513915140151411514215143151441514515146151471514815149151501515115152151531515415155151561515715158151591516015161151621516315164151651516615167151681516915170151711517215173151741517515176151771517815179151801518115182151831518415185151861518715188151891519015191151921519315194151951519615197151981519915200152011520215203152041520515206152071520815209152101521115212152131521415215152161521715218152191522015221152221522315224152251522615227152281522915230152311523215233152341523515236152371523815239152401524115242152431524415245152461524715248152491525015251152521525315254152551525615257152581525915260152611526215263152641526515266152671526815269152701527115272152731527415275152761527715278152791528015281152821528315284152851528615287152881528915290152911529215293152941529515296152971529815299153001530115302153031530415305153061530715308153091531015311153121531315314153151531615317153181531915320153211532215323153241532515326153271532815329153301533115332153331533415335153361533715338153391534015341153421534315344153451534615347153481534915350153511535215353153541535515356153571535815359153601536115362153631536415365153661536715368153691537015371153721537315374153751537615377153781537915380153811538215383153841538515386153871538815389153901539115392153931539415395153961539715398153991540015401154021540315404154051540615407154081540915410154111541215413154141541515416154171541815419154201542115422154231542415425
  1. /* nvd3 version 1.8.5 (https://github.com/novus/nvd3) 2016-12-01 */
  2. (function(){
  3. // set up main nv object
  4. var nv = {};
  5. // the major global objects under the nv namespace
  6. nv.dev = false; //set false when in production
  7. nv.tooltip = nv.tooltip || {}; // For the tooltip system
  8. nv.utils = nv.utils || {}; // Utility subsystem
  9. nv.models = nv.models || {}; //stores all the possible models/components
  10. nv.charts = {}; //stores all the ready to use charts
  11. nv.logs = {}; //stores some statistics and potential error messages
  12. nv.dom = {}; //DOM manipulation functions
  13. // Node/CommonJS - require D3
  14. if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined' && typeof(d3) == 'undefined') {
  15. d3 = require('d3');
  16. }
  17. nv.dispatch = d3.dispatch('render_start', 'render_end');
  18. // Function bind polyfill
  19. // Needed ONLY for phantomJS as it's missing until version 2.0 which is unreleased as of this comment
  20. // https://github.com/ariya/phantomjs/issues/10522
  21. // http://kangax.github.io/compat-table/es5/#Function.prototype.bind
  22. // phantomJS is used for running the test suite
  23. if (!Function.prototype.bind) {
  24. Function.prototype.bind = function (oThis) {
  25. if (typeof this !== "function") {
  26. // closest thing possible to the ECMAScript 5 internal IsCallable function
  27. throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
  28. }
  29. var aArgs = Array.prototype.slice.call(arguments, 1),
  30. fToBind = this,
  31. fNOP = function () {},
  32. fBound = function () {
  33. return fToBind.apply(this instanceof fNOP && oThis
  34. ? this
  35. : oThis,
  36. aArgs.concat(Array.prototype.slice.call(arguments)));
  37. };
  38. fNOP.prototype = this.prototype;
  39. fBound.prototype = new fNOP();
  40. return fBound;
  41. };
  42. }
  43. // Development render timers - disabled if dev = false
  44. if (nv.dev) {
  45. nv.dispatch.on('render_start', function(e) {
  46. nv.logs.startTime = +new Date();
  47. });
  48. nv.dispatch.on('render_end', function(e) {
  49. nv.logs.endTime = +new Date();
  50. nv.logs.totalTime = nv.logs.endTime - nv.logs.startTime;
  51. nv.log('total', nv.logs.totalTime); // used for development, to keep track of graph generation times
  52. });
  53. }
  54. // Logs all arguments, and returns the last so you can test things in place
  55. // Note: in IE8 console.log is an object not a function, and if modernizr is used
  56. // then calling Function.prototype.bind with with anything other than a function
  57. // causes a TypeError to be thrown.
  58. nv.log = function() {
  59. if (nv.dev && window.console && console.log && console.log.apply)
  60. console.log.apply(console, arguments);
  61. else if (nv.dev && window.console && typeof console.log == "function" && Function.prototype.bind) {
  62. var log = Function.prototype.bind.call(console.log, console);
  63. log.apply(console, arguments);
  64. }
  65. return arguments[arguments.length - 1];
  66. };
  67. // print console warning, should be used by deprecated functions
  68. nv.deprecated = function(name, info) {
  69. if (console && console.warn) {
  70. console.warn('nvd3 warning: `' + name + '` has been deprecated. ', info || '');
  71. }
  72. };
  73. // The nv.render function is used to queue up chart rendering
  74. // in non-blocking async functions.
  75. // When all queued charts are done rendering, nv.dispatch.render_end is invoked.
  76. nv.render = function render(step) {
  77. // number of graphs to generate in each timeout loop
  78. step = step || 1;
  79. nv.render.active = true;
  80. nv.dispatch.render_start();
  81. var renderLoop = function() {
  82. var chart, graph;
  83. for (var i = 0; i < step && (graph = nv.render.queue[i]); i++) {
  84. chart = graph.generate();
  85. if (typeof graph.callback == typeof(Function)) graph.callback(chart);
  86. }
  87. nv.render.queue.splice(0, i);
  88. if (nv.render.queue.length) {
  89. setTimeout(renderLoop);
  90. }
  91. else {
  92. nv.dispatch.render_end();
  93. nv.render.active = false;
  94. }
  95. };
  96. setTimeout(renderLoop);
  97. };
  98. nv.render.active = false;
  99. nv.render.queue = [];
  100. /*
  101. Adds a chart to the async rendering queue. This method can take arguments in two forms:
  102. nv.addGraph({
  103. generate: <Function>
  104. callback: <Function>
  105. })
  106. or
  107. nv.addGraph(<generate Function>, <callback Function>)
  108. The generate function should contain code that creates the NVD3 model, sets options
  109. on it, adds data to an SVG element, and invokes the chart model. The generate function
  110. should return the chart model. See examples/lineChart.html for a usage example.
  111. The callback function is optional, and it is called when the generate function completes.
  112. */
  113. nv.addGraph = function(obj) {
  114. if (typeof arguments[0] === typeof(Function)) {
  115. obj = {generate: arguments[0], callback: arguments[1]};
  116. }
  117. nv.render.queue.push(obj);
  118. if (!nv.render.active) {
  119. nv.render();
  120. }
  121. };
  122. // Node/CommonJS exports
  123. if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined') {
  124. module.exports = nv;
  125. }
  126. if (typeof(window) !== 'undefined') {
  127. window.nv = nv;
  128. }
  129. /* Facade for queueing DOM write operations
  130. * with Fastdom (https://github.com/wilsonpage/fastdom)
  131. * if available.
  132. * This could easily be extended to support alternate
  133. * implementations in the future.
  134. */
  135. nv.dom.write = function(callback) {
  136. if (window.fastdom !== undefined) {
  137. return fastdom.mutate(callback);
  138. }
  139. return callback();
  140. };
  141. /* Facade for queueing DOM read operations
  142. * with Fastdom (https://github.com/wilsonpage/fastdom)
  143. * if available.
  144. * This could easily be extended to support alternate
  145. * implementations in the future.
  146. */
  147. nv.dom.read = function(callback) {
  148. if (window.fastdom !== undefined) {
  149. return fastdom.measure(callback);
  150. }
  151. return callback();
  152. };
  153. /* Utility class to handle creation of an interactive layer.
  154. This places a rectangle on top of the chart. When you mouse move over it, it sends a dispatch
  155. containing the X-coordinate. It can also render a vertical line where the mouse is located.
  156. dispatch.elementMousemove is the important event to latch onto. It is fired whenever the mouse moves over
  157. the rectangle. The dispatch is given one object which contains the mouseX/Y location.
  158. It also has 'pointXValue', which is the conversion of mouseX to the x-axis scale.
  159. */
  160. nv.interactiveGuideline = function() {
  161. "use strict";
  162. var margin = { left: 0, top: 0 } //Pass the chart's top and left magins. Used to calculate the mouseX/Y.
  163. , width = null
  164. , height = null
  165. , xScale = d3.scale.linear()
  166. , dispatch = d3.dispatch('elementMousemove', 'elementMouseout', 'elementClick', 'elementDblclick', 'elementMouseDown', 'elementMouseUp')
  167. , showGuideLine = true
  168. , svgContainer = null // Must pass the chart's svg, we'll use its mousemove event.
  169. , tooltip = nv.models.tooltip()
  170. , isMSIE = window.ActiveXObject// Checkt if IE by looking for activeX. (excludes IE11)
  171. ;
  172. tooltip
  173. .duration(0)
  174. .hideDelay(0)
  175. .hidden(false);
  176. function layer(selection) {
  177. selection.each(function(data) {
  178. var container = d3.select(this);
  179. var availableWidth = (width || 960), availableHeight = (height || 400);
  180. var wrap = container.selectAll("g.nv-wrap.nv-interactiveLineLayer")
  181. .data([data]);
  182. var wrapEnter = wrap.enter()
  183. .append("g").attr("class", " nv-wrap nv-interactiveLineLayer");
  184. wrapEnter.append("g").attr("class","nv-interactiveGuideLine");
  185. if (!svgContainer) {
  186. return;
  187. }
  188. function mouseHandler() {
  189. var d3mouse = d3.mouse(this);
  190. var mouseX = d3mouse[0];
  191. var mouseY = d3mouse[1];
  192. var subtractMargin = true;
  193. var mouseOutAnyReason = false;
  194. if (isMSIE) {
  195. /*
  196. D3.js (or maybe SVG.getScreenCTM) has a nasty bug in Internet Explorer 10.
  197. d3.mouse() returns incorrect X,Y mouse coordinates when mouse moving
  198. over a rect in IE 10.
  199. However, d3.event.offsetX/Y also returns the mouse coordinates
  200. relative to the triggering <rect>. So we use offsetX/Y on IE.
  201. */
  202. mouseX = d3.event.offsetX;
  203. mouseY = d3.event.offsetY;
  204. /*
  205. On IE, if you attach a mouse event listener to the <svg> container,
  206. it will actually trigger it for all the child elements (like <path>, <circle>, etc).
  207. When this happens on IE, the offsetX/Y is set to where ever the child element
  208. is located.
  209. As a result, we do NOT need to subtract margins to figure out the mouse X/Y
  210. position under this scenario. Removing the line below *will* cause
  211. the interactive layer to not work right on IE.
  212. */
  213. if(d3.event.target.tagName !== "svg") {
  214. subtractMargin = false;
  215. }
  216. if (d3.event.target.className.baseVal.match("nv-legend")) {
  217. mouseOutAnyReason = true;
  218. }
  219. }
  220. if(subtractMargin) {
  221. mouseX -= margin.left;
  222. mouseY -= margin.top;
  223. }
  224. /* If mouseX/Y is outside of the chart's bounds,
  225. trigger a mouseOut event.
  226. */
  227. if (d3.event.type === 'mouseout'
  228. || mouseX < 0 || mouseY < 0
  229. || mouseX > availableWidth || mouseY > availableHeight
  230. || (d3.event.relatedTarget && d3.event.relatedTarget.ownerSVGElement === undefined)
  231. || mouseOutAnyReason
  232. ) {
  233. if (isMSIE) {
  234. if (d3.event.relatedTarget
  235. && d3.event.relatedTarget.ownerSVGElement === undefined
  236. && (d3.event.relatedTarget.className === undefined
  237. || d3.event.relatedTarget.className.match(tooltip.nvPointerEventsClass))) {
  238. return;
  239. }
  240. }
  241. dispatch.elementMouseout({
  242. mouseX: mouseX,
  243. mouseY: mouseY
  244. });
  245. layer.renderGuideLine(null); //hide the guideline
  246. tooltip.hidden(true);
  247. return;
  248. } else {
  249. tooltip.hidden(false);
  250. }
  251. var scaleIsOrdinal = typeof xScale.rangeBands === 'function';
  252. var pointXValue = undefined;
  253. // Ordinal scale has no invert method
  254. if (scaleIsOrdinal) {
  255. var elementIndex = d3.bisect(xScale.range(), mouseX) - 1;
  256. // Check if mouseX is in the range band
  257. if (xScale.range()[elementIndex] + xScale.rangeBand() >= mouseX) {
  258. pointXValue = xScale.domain()[d3.bisect(xScale.range(), mouseX) - 1];
  259. }
  260. else {
  261. dispatch.elementMouseout({
  262. mouseX: mouseX,
  263. mouseY: mouseY
  264. });
  265. layer.renderGuideLine(null); //hide the guideline
  266. tooltip.hidden(true);
  267. return;
  268. }
  269. }
  270. else {
  271. pointXValue = xScale.invert(mouseX);
  272. }
  273. dispatch.elementMousemove({
  274. mouseX: mouseX,
  275. mouseY: mouseY,
  276. pointXValue: pointXValue
  277. });
  278. //If user double clicks the layer, fire a elementDblclick
  279. if (d3.event.type === "dblclick") {
  280. dispatch.elementDblclick({
  281. mouseX: mouseX,
  282. mouseY: mouseY,
  283. pointXValue: pointXValue
  284. });
  285. }
  286. // if user single clicks the layer, fire elementClick
  287. if (d3.event.type === 'click') {
  288. dispatch.elementClick({
  289. mouseX: mouseX,
  290. mouseY: mouseY,
  291. pointXValue: pointXValue
  292. });
  293. }
  294. // if user presses mouse down the layer, fire elementMouseDown
  295. if (d3.event.type === 'mousedown') {
  296. dispatch.elementMouseDown({
  297. mouseX: mouseX,
  298. mouseY: mouseY,
  299. pointXValue: pointXValue
  300. });
  301. }
  302. // if user presses mouse down the layer, fire elementMouseUp
  303. if (d3.event.type === 'mouseup') {
  304. dispatch.elementMouseUp({
  305. mouseX: mouseX,
  306. mouseY: mouseY,
  307. pointXValue: pointXValue
  308. });
  309. }
  310. }
  311. svgContainer
  312. .on("touchmove",mouseHandler)
  313. .on("mousemove",mouseHandler, true)
  314. .on("mouseout" ,mouseHandler,true)
  315. .on("mousedown" ,mouseHandler,true)
  316. .on("mouseup" ,mouseHandler,true)
  317. .on("dblclick" ,mouseHandler)
  318. .on("click", mouseHandler)
  319. ;
  320. layer.guideLine = null;
  321. //Draws a vertical guideline at the given X postion.
  322. layer.renderGuideLine = function(x) {
  323. if (!showGuideLine) return;
  324. if (layer.guideLine && layer.guideLine.attr("x1") === x) return;
  325. nv.dom.write(function() {
  326. var line = wrap.select(".nv-interactiveGuideLine")
  327. .selectAll("line")
  328. .data((x != null) ? [nv.utils.NaNtoZero(x)] : [], String);
  329. line.enter()
  330. .append("line")
  331. .attr("class", "nv-guideline")
  332. .attr("x1", function(d) { return d;})
  333. .attr("x2", function(d) { return d;})
  334. .attr("y1", availableHeight)
  335. .attr("y2",0);
  336. line.exit().remove();
  337. });
  338. }
  339. });
  340. }
  341. layer.dispatch = dispatch;
  342. layer.tooltip = tooltip;
  343. layer.margin = function(_) {
  344. if (!arguments.length) return margin;
  345. margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
  346. margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
  347. return layer;
  348. };
  349. layer.width = function(_) {
  350. if (!arguments.length) return width;
  351. width = _;
  352. return layer;
  353. };
  354. layer.height = function(_) {
  355. if (!arguments.length) return height;
  356. height = _;
  357. return layer;
  358. };
  359. layer.xScale = function(_) {
  360. if (!arguments.length) return xScale;
  361. xScale = _;
  362. return layer;
  363. };
  364. layer.showGuideLine = function(_) {
  365. if (!arguments.length) return showGuideLine;
  366. showGuideLine = _;
  367. return layer;
  368. };
  369. layer.svgContainer = function(_) {
  370. if (!arguments.length) return svgContainer;
  371. svgContainer = _;
  372. return layer;
  373. };
  374. return layer;
  375. };
  376. /* Utility class that uses d3.bisect to find the index in a given array, where a search value can be inserted.
  377. This is different from normal bisectLeft; this function finds the nearest index to insert the search value.
  378. For instance, lets say your array is [1,2,3,5,10,30], and you search for 28.
  379. Normal d3.bisectLeft will return 4, because 28 is inserted after the number 10. But interactiveBisect will return 5
  380. because 28 is closer to 30 than 10.
  381. Unit tests can be found in: interactiveBisectTest.html
  382. Has the following known issues:
  383. * Will not work if the data points move backwards (ie, 10,9,8,7, etc) or if the data points are in random order.
  384. * Won't work if there are duplicate x coordinate values.
  385. */
  386. nv.interactiveBisect = function (values, searchVal, xAccessor) {
  387. "use strict";
  388. if (! (values instanceof Array)) {
  389. return null;
  390. }
  391. var _xAccessor;
  392. if (typeof xAccessor !== 'function') {
  393. _xAccessor = function(d) {
  394. return d.x;
  395. }
  396. } else {
  397. _xAccessor = xAccessor;
  398. }
  399. var _cmp = function(d, v) {
  400. // Accessors are no longer passed the index of the element along with
  401. // the element itself when invoked by d3.bisector.
  402. //
  403. // Starting at D3 v3.4.4, d3.bisector() started inspecting the
  404. // function passed to determine if it should consider it an accessor
  405. // or a comparator. This meant that accessors that take two arguments
  406. // (expecting an index as the second parameter) are treated as
  407. // comparators where the second argument is the search value against
  408. // which the first argument is compared.
  409. return _xAccessor(d) - v;
  410. };
  411. var bisect = d3.bisector(_cmp).left;
  412. var index = d3.max([0, bisect(values,searchVal) - 1]);
  413. var currentValue = _xAccessor(values[index]);
  414. if (typeof currentValue === 'undefined') {
  415. currentValue = index;
  416. }
  417. if (currentValue === searchVal) {
  418. return index; //found exact match
  419. }
  420. var nextIndex = d3.min([index+1, values.length - 1]);
  421. var nextValue = _xAccessor(values[nextIndex]);
  422. if (typeof nextValue === 'undefined') {
  423. nextValue = nextIndex;
  424. }
  425. if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal)) {
  426. return index;
  427. } else {
  428. return nextIndex
  429. }
  430. };
  431. /*
  432. Returns the index in the array "values" that is closest to searchVal.
  433. Only returns an index if searchVal is within some "threshold".
  434. Otherwise, returns null.
  435. */
  436. nv.nearestValueIndex = function (values, searchVal, threshold) {
  437. "use strict";
  438. var yDistMax = Infinity, indexToHighlight = null;
  439. values.forEach(function(d,i) {
  440. var delta = Math.abs(searchVal - d);
  441. if ( d != null && delta <= yDistMax && delta < threshold) {
  442. yDistMax = delta;
  443. indexToHighlight = i;
  444. }
  445. });
  446. return indexToHighlight;
  447. };
  448. /* Model which can be instantiated to handle tooltip rendering.
  449. Example usage:
  450. var tip = nv.models.tooltip().gravity('w').distance(23)
  451. .data(myDataObject);
  452. tip(); //just invoke the returned function to render tooltip.
  453. */
  454. nv.models.tooltip = function() {
  455. "use strict";
  456. /*
  457. Tooltip data. If data is given in the proper format, a consistent tooltip is generated.
  458. Example Format of data:
  459. {
  460. key: "Date",
  461. value: "August 2009",
  462. series: [
  463. {key: "Series 1", value: "Value 1", color: "#000"},
  464. {key: "Series 2", value: "Value 2", color: "#00f"}
  465. ]
  466. }
  467. */
  468. var id = "nvtooltip-" + Math.floor(Math.random() * 100000) // Generates a unique id when you create a new tooltip() object.
  469. , data = null
  470. , gravity = 'w' // Can be 'n','s','e','w'. Determines how tooltip is positioned.
  471. , distance = 25 // Distance to offset tooltip from the mouse location.
  472. , snapDistance = 0 // Tolerance allowed before tooltip is moved from its current position (creates 'snapping' effect)
  473. , classes = null // Attaches additional CSS classes to the tooltip DIV that is created.
  474. , hidden = true // Start off hidden, toggle with hide/show functions below.
  475. , hideDelay = 200 // Delay (in ms) before the tooltip hides after calling hide().
  476. , tooltip = null // d3 select of the tooltip div.
  477. , lastPosition = { left: null, top: null } // Last position the tooltip was in.
  478. , enabled = true // True -> tooltips are rendered. False -> don't render tooltips.
  479. , duration = 100 // Tooltip movement duration, in ms.
  480. , headerEnabled = true // If is to show the tooltip header.
  481. , nvPointerEventsClass = "nv-pointer-events-none" // CSS class to specify whether element should not have mouse events.
  482. ;
  483. // Format function for the tooltip values column.
  484. var valueFormatter = function(d, i) {
  485. return d;
  486. };
  487. // Format function for the tooltip header value.
  488. var headerFormatter = function(d) {
  489. return d;
  490. };
  491. var keyFormatter = function(d, i) {
  492. return d;
  493. };
  494. // By default, the tooltip model renders a beautiful table inside a DIV.
  495. // You can override this function if a custom tooltip is desired.
  496. var contentGenerator = function(d) {
  497. if (d === null) {
  498. return '';
  499. }
  500. var table = d3.select(document.createElement("table"));
  501. if (headerEnabled) {
  502. var theadEnter = table.selectAll("thead")
  503. .data([d])
  504. .enter().append("thead");
  505. theadEnter.append("tr")
  506. .append("td")
  507. .attr("colspan", 3)
  508. .append("strong")
  509. .classed("x-value", true)
  510. .html(headerFormatter(d.value));
  511. }
  512. var tbodyEnter = table.selectAll("tbody")
  513. .data([d])
  514. .enter().append("tbody");
  515. var trowEnter = tbodyEnter.selectAll("tr")
  516. .data(function(p) { return p.series})
  517. .enter()
  518. .append("tr")
  519. .classed("highlight", function(p) { return p.highlight});
  520. trowEnter.append("td")
  521. .classed("legend-color-guide",true)
  522. .append("div")
  523. .style("background-color", function(p) { return p.color});
  524. trowEnter.append("td")
  525. .classed("key",true)
  526. .classed("total",function(p) { return !!p.total})
  527. .html(function(p, i) { return keyFormatter(p.key, i)});
  528. trowEnter.append("td")
  529. .classed("value",true)
  530. .html(function(p, i) { return valueFormatter(p.value, i) });
  531. trowEnter.filter(function (p,i) { return p.percent !== undefined }).append("td")
  532. .classed("percent", true)
  533. .html(function(p, i) { return "(" + d3.format('%')(p.percent) + ")" });
  534. trowEnter.selectAll("td").each(function(p) {
  535. if (p.highlight) {
  536. var opacityScale = d3.scale.linear().domain([0,1]).range(["#fff",p.color]);
  537. var opacity = 0.6;
  538. d3.select(this)
  539. .style("border-bottom-color", opacityScale(opacity))
  540. .style("border-top-color", opacityScale(opacity))
  541. ;
  542. }
  543. });
  544. var html = table.node().outerHTML;
  545. if (d.footer !== undefined)
  546. html += "<div class='footer'>" + d.footer + "</div>";
  547. return html;
  548. };
  549. /*
  550. Function that returns the position (relative to the viewport/document.body)
  551. the tooltip should be placed in.
  552. Should return: {
  553. left: <leftPos>,
  554. top: <topPos>
  555. }
  556. */
  557. var position = function() {
  558. var pos = {
  559. left: d3.event !== null ? d3.event.clientX : 0,
  560. top: d3.event !== null ? d3.event.clientY : 0
  561. };
  562. if(getComputedStyle(document.body).transform != 'none') {
  563. // Take the offset into account, as now the tooltip is relative
  564. // to document.body.
  565. var client = document.body.getBoundingClientRect();
  566. pos.left -= client.left;
  567. pos.top -= client.top;
  568. }
  569. return pos;
  570. };
  571. var dataSeriesExists = function(d) {
  572. if (d && d.series) {
  573. if (nv.utils.isArray(d.series)) {
  574. return true;
  575. }
  576. // if object, it's okay just convert to array of the object
  577. if (nv.utils.isObject(d.series)) {
  578. d.series = [d.series];
  579. return true;
  580. }
  581. }
  582. return false;
  583. };
  584. // Calculates the gravity offset of the tooltip. Parameter is position of tooltip
  585. // relative to the viewport.
  586. var calcGravityOffset = function(pos) {
  587. var height = tooltip.node().offsetHeight,
  588. width = tooltip.node().offsetWidth,
  589. clientWidth = document.documentElement.clientWidth, // Don't want scrollbars.
  590. clientHeight = document.documentElement.clientHeight, // Don't want scrollbars.
  591. left, top, tmp;
  592. // calculate position based on gravity
  593. switch (gravity) {
  594. case 'e':
  595. left = - width - distance;
  596. top = - (height / 2);
  597. if(pos.left + left < 0) left = distance;
  598. if((tmp = pos.top + top) < 0) top -= tmp;
  599. if((tmp = pos.top + top + height) > clientHeight) top -= tmp - clientHeight;
  600. break;
  601. case 'w':
  602. left = distance;
  603. top = - (height / 2);
  604. if (pos.left + left + width > clientWidth) left = - width - distance;
  605. if ((tmp = pos.top + top) < 0) top -= tmp;
  606. if ((tmp = pos.top + top + height) > clientHeight) top -= tmp - clientHeight;
  607. break;
  608. case 'n':
  609. left = - (width / 2) - 5; // - 5 is an approximation of the mouse's height.
  610. top = distance;
  611. if (pos.top + top + height > clientHeight) top = - height - distance;
  612. if ((tmp = pos.left + left) < 0) left -= tmp;
  613. if ((tmp = pos.left + left + width) > clientWidth) left -= tmp - clientWidth;
  614. break;
  615. case 's':
  616. left = - (width / 2);
  617. top = - height - distance;
  618. if (pos.top + top < 0) top = distance;
  619. if ((tmp = pos.left + left) < 0) left -= tmp;
  620. if ((tmp = pos.left + left + width) > clientWidth) left -= tmp - clientWidth;
  621. break;
  622. case 'center':
  623. left = - (width / 2);
  624. top = - (height / 2);
  625. break;
  626. default:
  627. left = 0;
  628. top = 0;
  629. break;
  630. }
  631. return { 'left': left, 'top': top };
  632. };
  633. /*
  634. Positions the tooltip in the correct place, as given by the position() function.
  635. */
  636. var positionTooltip = function() {
  637. nv.dom.read(function() {
  638. var pos = position(),
  639. gravityOffset = calcGravityOffset(pos),
  640. left = pos.left + gravityOffset.left,
  641. top = pos.top + gravityOffset.top;
  642. // delay hiding a bit to avoid flickering
  643. if (hidden) {
  644. tooltip
  645. .interrupt()
  646. .transition()
  647. .delay(hideDelay)
  648. .duration(0)
  649. .style('opacity', 0);
  650. } else {
  651. // using tooltip.style('transform') returns values un-usable for tween
  652. var old_translate = 'translate(' + lastPosition.left + 'px, ' + lastPosition.top + 'px)';
  653. var new_translate = 'translate(' + Math.round(left) + 'px, ' + Math.round(top) + 'px)';
  654. var translateInterpolator = d3.interpolateString(old_translate, new_translate);
  655. var is_hidden = tooltip.style('opacity') < 0.1;
  656. tooltip
  657. .interrupt() // cancel running transitions
  658. .transition()
  659. .duration(is_hidden ? 0 : duration)
  660. // using tween since some versions of d3 can't auto-tween a translate on a div
  661. .styleTween('transform', function (d) {
  662. return translateInterpolator;
  663. }, 'important')
  664. // Safari has its own `-webkit-transform` and does not support `transform`
  665. .styleTween('-webkit-transform', function (d) {
  666. return translateInterpolator;
  667. })
  668. .style('-ms-transform', new_translate)
  669. .style('opacity', 1);
  670. }
  671. lastPosition.left = left;
  672. lastPosition.top = top;
  673. });
  674. };
  675. // Creates new tooltip container, or uses existing one on DOM.
  676. function initTooltip() {
  677. if (!tooltip || !tooltip.node()) {
  678. // Create new tooltip div if it doesn't exist on DOM.
  679. var data = [1];
  680. tooltip = d3.select(document.body).select('#'+id).data(data);
  681. tooltip.enter().append('div')
  682. .attr("class", "nvtooltip " + (classes ? classes : "xy-tooltip"))
  683. .attr("id", id)
  684. .style("top", 0).style("left", 0)
  685. .style('opacity', 0)
  686. .style('position', 'fixed')
  687. .selectAll("div, table, td, tr").classed(nvPointerEventsClass, true)
  688. .classed(nvPointerEventsClass, true);
  689. tooltip.exit().remove()
  690. }
  691. }
  692. // Draw the tooltip onto the DOM.
  693. function nvtooltip() {
  694. if (!enabled) return;
  695. if (!dataSeriesExists(data)) return;
  696. nv.dom.write(function () {
  697. initTooltip();
  698. // Generate data and set it into tooltip.
  699. // Bonus - If you override contentGenerator and return falsey you can use something like
  700. // React or Knockout to bind the data for your tooltip.
  701. var newContent = contentGenerator(data);
  702. if (newContent) {
  703. tooltip.node().innerHTML = newContent;
  704. }
  705. positionTooltip();
  706. });
  707. return nvtooltip;
  708. }
  709. nvtooltip.nvPointerEventsClass = nvPointerEventsClass;
  710. nvtooltip.options = nv.utils.optionsFunc.bind(nvtooltip);
  711. nvtooltip._options = Object.create({}, {
  712. // simple read/write options
  713. duration: {get: function(){return duration;}, set: function(_){duration=_;}},
  714. gravity: {get: function(){return gravity;}, set: function(_){gravity=_;}},
  715. distance: {get: function(){return distance;}, set: function(_){distance=_;}},
  716. snapDistance: {get: function(){return snapDistance;}, set: function(_){snapDistance=_;}},
  717. classes: {get: function(){return classes;}, set: function(_){classes=_;}},
  718. enabled: {get: function(){return enabled;}, set: function(_){enabled=_;}},
  719. hideDelay: {get: function(){return hideDelay;}, set: function(_){hideDelay=_;}},
  720. contentGenerator: {get: function(){return contentGenerator;}, set: function(_){contentGenerator=_;}},
  721. valueFormatter: {get: function(){return valueFormatter;}, set: function(_){valueFormatter=_;}},
  722. headerFormatter: {get: function(){return headerFormatter;}, set: function(_){headerFormatter=_;}},
  723. keyFormatter: {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}},
  724. headerEnabled: {get: function(){return headerEnabled;}, set: function(_){headerEnabled=_;}},
  725. position: {get: function(){return position;}, set: function(_){position=_;}},
  726. // Deprecated options
  727. chartContainer: {get: function(){return document.body;}, set: function(_){
  728. // deprecated after 1.8.3
  729. nv.deprecated('chartContainer', 'feature removed after 1.8.3');
  730. }},
  731. fixedTop: {get: function(){return null;}, set: function(_){
  732. // deprecated after 1.8.1
  733. nv.deprecated('fixedTop', 'feature removed after 1.8.1');
  734. }},
  735. offset: {get: function(){return {left: 0, top: 0};}, set: function(_){
  736. // deprecated after 1.8.1
  737. nv.deprecated('offset', 'use chart.tooltip.distance() instead');
  738. }},
  739. // options with extra logic
  740. hidden: {get: function(){return hidden;}, set: function(_){
  741. if (hidden != _) {
  742. hidden = !!_;
  743. nvtooltip();
  744. }
  745. }},
  746. data: {get: function(){return data;}, set: function(_){
  747. // if showing a single data point, adjust data format with that
  748. if (_.point) {
  749. _.value = _.point.x;
  750. _.series = _.series || {};
  751. _.series.value = _.point.y;
  752. _.series.color = _.point.color || _.series.color;
  753. }
  754. data = _;
  755. }},
  756. // read only properties
  757. node: {get: function(){return tooltip.node();}, set: function(_){}},
  758. id: {get: function(){return id;}, set: function(_){}}
  759. });
  760. nv.utils.initOptions(nvtooltip);
  761. return nvtooltip;
  762. };
  763. /*
  764. Gets the browser window size
  765. Returns object with height and width properties
  766. */
  767. nv.utils.windowSize = function() {
  768. // Sane defaults
  769. var size = {width: 640, height: 480};
  770. // Most recent browsers use
  771. if (window.innerWidth && window.innerHeight) {
  772. size.width = window.innerWidth;
  773. size.height = window.innerHeight;
  774. return (size);
  775. }
  776. // IE can use depending on mode it is in
  777. if (document.compatMode=='CSS1Compat' &&
  778. document.documentElement &&
  779. document.documentElement.offsetWidth ) {
  780. size.width = document.documentElement.offsetWidth;
  781. size.height = document.documentElement.offsetHeight;
  782. return (size);
  783. }
  784. // Earlier IE uses Doc.body
  785. if (document.body && document.body.offsetWidth) {
  786. size.width = document.body.offsetWidth;
  787. size.height = document.body.offsetHeight;
  788. return (size);
  789. }
  790. return (size);
  791. };
  792. /* handle dumb browser quirks... isinstance breaks if you use frames
  793. typeof returns 'object' for null, NaN is a number, etc.
  794. */
  795. nv.utils.isArray = Array.isArray;
  796. nv.utils.isObject = function(a) {
  797. return a !== null && typeof a === 'object';
  798. };
  799. nv.utils.isFunction = function(a) {
  800. return typeof a === 'function';
  801. };
  802. nv.utils.isDate = function(a) {
  803. return toString.call(a) === '[object Date]';
  804. };
  805. nv.utils.isNumber = function(a) {
  806. return !isNaN(a) && typeof a === 'number';
  807. };
  808. /*
  809. Binds callback function to run when window is resized
  810. */
  811. nv.utils.windowResize = function(handler) {
  812. if (window.addEventListener) {
  813. window.addEventListener('resize', handler);
  814. } else {
  815. nv.log("ERROR: Failed to bind to window.resize with: ", handler);
  816. }
  817. // return object with clear function to remove the single added callback.
  818. return {
  819. callback: handler,
  820. clear: function() {
  821. window.removeEventListener('resize', handler);
  822. }
  823. }
  824. };
  825. /*
  826. Backwards compatible way to implement more d3-like coloring of graphs.
  827. Can take in nothing, an array, or a function/scale
  828. To use a normal scale, get the range and pass that because we must be able
  829. to take two arguments and use the index to keep backward compatibility
  830. */
  831. nv.utils.getColor = function(color) {
  832. //if you pass in nothing, get default colors back
  833. if (color === undefined) {
  834. return nv.utils.defaultColor();
  835. //if passed an array, turn it into a color scale
  836. } else if(nv.utils.isArray(color)) {
  837. var color_scale = d3.scale.ordinal().range(color);
  838. return function(d, i) {
  839. var key = i === undefined ? d : i;
  840. return d.color || color_scale(key);
  841. };
  842. //if passed a function or scale, return it, or whatever it may be
  843. //external libs, such as angularjs-nvd3-directives use this
  844. } else {
  845. //can't really help it if someone passes rubbish as color
  846. return color;
  847. }
  848. };
  849. /*
  850. Default color chooser uses a color scale of 20 colors from D3
  851. https://github.com/mbostock/d3/wiki/Ordinal-Scales#categorical-colors
  852. */
  853. nv.utils.defaultColor = function() {
  854. // get range of the scale so we'll turn it into our own function.
  855. return nv.utils.getColor(d3.scale.category20().range());
  856. };
  857. /*
  858. Returns a color function that takes the result of 'getKey' for each series and
  859. looks for a corresponding color from the dictionary
  860. */
  861. nv.utils.customTheme = function(dictionary, getKey, defaultColors) {
  862. // use default series.key if getKey is undefined
  863. getKey = getKey || function(series) { return series.key };
  864. defaultColors = defaultColors || d3.scale.category20().range();
  865. // start at end of default color list and walk back to index 0
  866. var defIndex = defaultColors.length;
  867. return function(series, index) {
  868. var key = getKey(series);
  869. if (nv.utils.isFunction(dictionary[key])) {
  870. return dictionary[key]();
  871. } else if (dictionary[key] !== undefined) {
  872. return dictionary[key];
  873. } else {
  874. // no match in dictionary, use a default color
  875. if (!defIndex) {
  876. // used all the default colors, start over
  877. defIndex = defaultColors.length;
  878. }
  879. defIndex = defIndex - 1;
  880. return defaultColors[defIndex];
  881. }
  882. };
  883. };
  884. /*
  885. From the PJAX example on d3js.org, while this is not really directly needed
  886. it's a very cool method for doing pjax, I may expand upon it a little bit,
  887. open to suggestions on anything that may be useful
  888. */
  889. nv.utils.pjax = function(links, content) {
  890. var load = function(href) {
  891. d3.html(href, function(fragment) {
  892. var target = d3.select(content).node();
  893. target.parentNode.replaceChild(
  894. d3.select(fragment).select(content).node(),
  895. target);
  896. nv.utils.pjax(links, content);
  897. });
  898. };
  899. d3.selectAll(links).on("click", function() {
  900. history.pushState(this.href, this.textContent, this.href);
  901. load(this.href);
  902. d3.event.preventDefault();
  903. });
  904. d3.select(window).on("popstate", function() {
  905. if (d3.event.state) {
  906. load(d3.event.state);
  907. }
  908. });
  909. };
  910. /*
  911. For when we want to approximate the width in pixels for an SVG:text element.
  912. Most common instance is when the element is in a display:none; container.
  913. Forumla is : text.length * font-size * constant_factor
  914. */
  915. nv.utils.calcApproxTextWidth = function (svgTextElem) {
  916. if (nv.utils.isFunction(svgTextElem.style) && nv.utils.isFunction(svgTextElem.text)) {
  917. var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""), 10);
  918. var textLength = svgTextElem.text().length;
  919. return nv.utils.NaNtoZero(textLength * fontSize * 0.5);
  920. }
  921. return 0;
  922. };
  923. /*
  924. Numbers that are undefined, null or NaN, convert them to zeros.
  925. */
  926. nv.utils.NaNtoZero = function(n) {
  927. if (!nv.utils.isNumber(n)
  928. || isNaN(n)
  929. || n === null
  930. || n === Infinity
  931. || n === -Infinity) {
  932. return 0;
  933. }
  934. return n;
  935. };
  936. /*
  937. Add a way to watch for d3 transition ends to d3
  938. */
  939. d3.selection.prototype.watchTransition = function(renderWatch){
  940. var args = [this].concat([].slice.call(arguments, 1));
  941. return renderWatch.transition.apply(renderWatch, args);
  942. };
  943. /*
  944. Helper object to watch when d3 has rendered something
  945. */
  946. nv.utils.renderWatch = function(dispatch, duration) {
  947. if (!(this instanceof nv.utils.renderWatch)) {
  948. return new nv.utils.renderWatch(dispatch, duration);
  949. }
  950. var _duration = duration !== undefined ? duration : 250;
  951. var renderStack = [];
  952. var self = this;
  953. this.models = function(models) {
  954. models = [].slice.call(arguments, 0);
  955. models.forEach(function(model){
  956. model.__rendered = false;
  957. (function(m){
  958. m.dispatch.on('renderEnd', function(arg){
  959. m.__rendered = true;
  960. self.renderEnd('model');
  961. });
  962. })(model);
  963. if (renderStack.indexOf(model) < 0) {
  964. renderStack.push(model);
  965. }
  966. });
  967. return this;
  968. };
  969. this.reset = function(duration) {
  970. if (duration !== undefined) {
  971. _duration = duration;
  972. }
  973. renderStack = [];
  974. };
  975. this.transition = function(selection, args, duration) {
  976. args = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
  977. if (args.length > 1) {
  978. duration = args.pop();
  979. } else {
  980. duration = _duration !== undefined ? _duration : 250;
  981. }
  982. selection.__rendered = false;
  983. if (renderStack.indexOf(selection) < 0) {
  984. renderStack.push(selection);
  985. }
  986. if (duration === 0) {
  987. selection.__rendered = true;
  988. selection.delay = function() { return this; };
  989. selection.duration = function() { return this; };
  990. return selection;
  991. } else {
  992. if (selection.length === 0) {
  993. selection.__rendered = true;
  994. } else if (selection.every( function(d){ return !d.length; } )) {
  995. selection.__rendered = true;
  996. } else {
  997. selection.__rendered = false;
  998. }
  999. var n = 0;
  1000. return selection
  1001. .transition()
  1002. .duration(duration)
  1003. .each(function(){ ++n; })
  1004. .each('end', function(d, i) {
  1005. if (--n === 0) {
  1006. selection.__rendered = true;
  1007. self.renderEnd.apply(this, args);
  1008. }
  1009. });
  1010. }
  1011. };
  1012. this.renderEnd = function() {
  1013. if (renderStack.every( function(d){ return d.__rendered; } )) {
  1014. renderStack.forEach( function(d){ d.__rendered = false; });
  1015. dispatch.renderEnd.apply(this, arguments);
  1016. }
  1017. }
  1018. };
  1019. /*
  1020. Takes multiple objects and combines them into the first one (dst)
  1021. example: nv.utils.deepExtend({a: 1}, {a: 2, b: 3}, {c: 4});
  1022. gives: {a: 2, b: 3, c: 4}
  1023. */
  1024. nv.utils.deepExtend = function(dst){
  1025. var sources = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
  1026. sources.forEach(function(source) {
  1027. for (var key in source) {
  1028. var isArray = nv.utils.isArray(dst[key]);
  1029. var isObject = nv.utils.isObject(dst[key]);
  1030. var srcObj = nv.utils.isObject(source[key]);
  1031. if (isObject && !isArray && srcObj) {
  1032. nv.utils.deepExtend(dst[key], source[key]);
  1033. } else {
  1034. dst[key] = source[key];
  1035. }
  1036. }
  1037. });
  1038. };
  1039. /*
  1040. state utility object, used to track d3 states in the models
  1041. */
  1042. nv.utils.state = function(){
  1043. if (!(this instanceof nv.utils.state)) {
  1044. return new nv.utils.state();
  1045. }
  1046. var state = {};
  1047. var _self = this;
  1048. var _setState = function(){};
  1049. var _getState = function(){ return {}; };
  1050. var init = null;
  1051. var changed = null;
  1052. this.dispatch = d3.dispatch('change', 'set');
  1053. this.dispatch.on('set', function(state){
  1054. _setState(state, true);
  1055. });
  1056. this.getter = function(fn){
  1057. _getState = fn;
  1058. return this;
  1059. };
  1060. this.setter = function(fn, callback) {
  1061. if (!callback) {
  1062. callback = function(){};
  1063. }
  1064. _setState = function(state, update){
  1065. fn(state);
  1066. if (update) {
  1067. callback();
  1068. }
  1069. };
  1070. return this;
  1071. };
  1072. this.init = function(state){
  1073. init = init || {};
  1074. nv.utils.deepExtend(init, state);
  1075. };
  1076. var _set = function(){
  1077. var settings = _getState();
  1078. if (JSON.stringify(settings) === JSON.stringify(state)) {
  1079. return false;
  1080. }
  1081. for (var key in settings) {
  1082. if (state[key] === undefined) {
  1083. state[key] = {};
  1084. }
  1085. state[key] = settings[key];
  1086. changed = true;
  1087. }
  1088. return true;
  1089. };
  1090. this.update = function(){
  1091. if (init) {
  1092. _setState(init, false);
  1093. init = null;
  1094. }
  1095. if (_set.call(this)) {
  1096. this.dispatch.change(state);
  1097. }
  1098. };
  1099. };
  1100. /*
  1101. Snippet of code you can insert into each nv.models.* to give you the ability to
  1102. do things like:
  1103. chart.options({
  1104. showXAxis: true,
  1105. tooltips: true
  1106. });
  1107. To enable in the chart:
  1108. chart.options = nv.utils.optionsFunc.bind(chart);
  1109. */
  1110. nv.utils.optionsFunc = function(args) {
  1111. if (args) {
  1112. d3.map(args).forEach((function(key,value) {
  1113. if (nv.utils.isFunction(this[key])) {
  1114. this[key](value);
  1115. }
  1116. }).bind(this));
  1117. }
  1118. return this;
  1119. };
  1120. /*
  1121. numTicks: requested number of ticks
  1122. data: the chart data
  1123. returns the number of ticks to actually use on X axis, based on chart data
  1124. to avoid duplicate ticks with the same value
  1125. */
  1126. nv.utils.calcTicksX = function(numTicks, data) {
  1127. // find max number of values from all data streams
  1128. var numValues = 1;
  1129. var i = 0;
  1130. for (i; i < data.length; i += 1) {
  1131. var stream_len = data[i] && data[i].values ? data[i].values.length : 0;
  1132. numValues = stream_len > numValues ? stream_len : numValues;
  1133. }
  1134. nv.log("Requested number of ticks: ", numTicks);
  1135. nv.log("Calculated max values to be: ", numValues);
  1136. // make sure we don't have more ticks than values to avoid duplicates
  1137. numTicks = numTicks > numValues ? numTicks = numValues - 1 : numTicks;
  1138. // make sure we have at least one tick
  1139. numTicks = numTicks < 1 ? 1 : numTicks;
  1140. // make sure it's an integer
  1141. numTicks = Math.floor(numTicks);
  1142. nv.log("Calculating tick count as: ", numTicks);
  1143. return numTicks;
  1144. };
  1145. /*
  1146. returns number of ticks to actually use on Y axis, based on chart data
  1147. */
  1148. nv.utils.calcTicksY = function(numTicks, data) {
  1149. // currently uses the same logic but we can adjust here if needed later
  1150. return nv.utils.calcTicksX(numTicks, data);
  1151. };
  1152. /*
  1153. Add a particular option from an options object onto chart
  1154. Options exposed on a chart are a getter/setter function that returns chart
  1155. on set to mimic typical d3 option chaining, e.g. svg.option1('a').option2('b');
  1156. option objects should be generated via Object.create() to provide
  1157. the option of manipulating data via get/set functions.
  1158. */
  1159. nv.utils.initOption = function(chart, name) {
  1160. // if it's a call option, just call it directly, otherwise do get/set
  1161. if (chart._calls && chart._calls[name]) {
  1162. chart[name] = chart._calls[name];
  1163. } else {
  1164. chart[name] = function (_) {
  1165. if (!arguments.length) return chart._options[name];
  1166. chart._overrides[name] = true;
  1167. chart._options[name] = _;
  1168. return chart;
  1169. };
  1170. // calling the option as _option will ignore if set by option already
  1171. // so nvd3 can set options internally but the stop if set manually
  1172. chart['_' + name] = function(_) {
  1173. if (!arguments.length) return chart._options[name];
  1174. if (!chart._overrides[name]) {
  1175. chart._options[name] = _;
  1176. }
  1177. return chart;
  1178. }
  1179. }
  1180. };
  1181. /*
  1182. Add all options in an options object to the chart
  1183. */
  1184. nv.utils.initOptions = function(chart) {
  1185. chart._overrides = chart._overrides || {};
  1186. var ops = Object.getOwnPropertyNames(chart._options || {});
  1187. var calls = Object.getOwnPropertyNames(chart._calls || {});
  1188. ops = ops.concat(calls);
  1189. for (var i in ops) {
  1190. nv.utils.initOption(chart, ops[i]);
  1191. }
  1192. };
  1193. /*
  1194. Inherit options from a D3 object
  1195. d3.rebind makes calling the function on target actually call it on source
  1196. Also use _d3options so we can track what we inherit for documentation and chained inheritance
  1197. */
  1198. nv.utils.inheritOptionsD3 = function(target, d3_source, oplist) {
  1199. target._d3options = oplist.concat(target._d3options || []);
  1200. oplist.unshift(d3_source);
  1201. oplist.unshift(target);
  1202. d3.rebind.apply(this, oplist);
  1203. };
  1204. /*
  1205. Remove duplicates from an array
  1206. */
  1207. nv.utils.arrayUnique = function(a) {
  1208. return a.sort().filter(function(item, pos) {
  1209. return !pos || item != a[pos - 1];
  1210. });
  1211. };
  1212. /*
  1213. Keeps a list of custom symbols to draw from in addition to d3.svg.symbol
  1214. Necessary since d3 doesn't let you extend its list -_-
  1215. Add new symbols by doing nv.utils.symbols.set('name', function(size){...});
  1216. */
  1217. nv.utils.symbolMap = d3.map();
  1218. /*
  1219. Replaces d3.svg.symbol so that we can look both there and our own map
  1220. */
  1221. nv.utils.symbol = function() {
  1222. var type,
  1223. size = 64;
  1224. function symbol(d,i) {
  1225. var t = type.call(this,d,i);
  1226. var s = size.call(this,d,i);
  1227. if (d3.svg.symbolTypes.indexOf(t) !== -1) {
  1228. return d3.svg.symbol().type(t).size(s)();
  1229. } else {
  1230. return nv.utils.symbolMap.get(t)(s);
  1231. }
  1232. }
  1233. symbol.type = function(_) {
  1234. if (!arguments.length) return type;
  1235. type = d3.functor(_);
  1236. return symbol;
  1237. };
  1238. symbol.size = function(_) {
  1239. if (!arguments.length) return size;
  1240. size = d3.functor(_);
  1241. return symbol;
  1242. };
  1243. return symbol;
  1244. };
  1245. /*
  1246. Inherit option getter/setter functions from source to target
  1247. d3.rebind makes calling the function on target actually call it on source
  1248. Also track via _inherited and _d3options so we can track what we inherit
  1249. for documentation generation purposes and chained inheritance
  1250. */
  1251. nv.utils.inheritOptions = function(target, source) {
  1252. // inherit all the things
  1253. var ops = Object.getOwnPropertyNames(source._options || {});
  1254. var calls = Object.getOwnPropertyNames(source._calls || {});
  1255. var inherited = source._inherited || [];
  1256. var d3ops = source._d3options || [];
  1257. var args = ops.concat(calls).concat(inherited).concat(d3ops);
  1258. args.unshift(source);
  1259. args.unshift(target);
  1260. d3.rebind.apply(this, args);
  1261. // pass along the lists to keep track of them, don't allow duplicates
  1262. target._inherited = nv.utils.arrayUnique(ops.concat(calls).concat(inherited).concat(ops).concat(target._inherited || []));
  1263. target._d3options = nv.utils.arrayUnique(d3ops.concat(target._d3options || []));
  1264. };
  1265. /*
  1266. Runs common initialize code on the svg before the chart builds
  1267. */
  1268. nv.utils.initSVG = function(svg) {
  1269. svg.classed({'nvd3-svg':true});
  1270. };
  1271. /*
  1272. Sanitize and provide default for the container height.
  1273. */
  1274. nv.utils.sanitizeHeight = function(height, container) {
  1275. return (height || parseInt(container.style('height'), 10) || 400);
  1276. };
  1277. /*
  1278. Sanitize and provide default for the container width.
  1279. */
  1280. nv.utils.sanitizeWidth = function(width, container) {
  1281. return (width || parseInt(container.style('width'), 10) || 960);
  1282. };
  1283. /*
  1284. Calculate the available height for a chart.
  1285. */
  1286. nv.utils.availableHeight = function(height, container, margin) {
  1287. return Math.max(0,nv.utils.sanitizeHeight(height, container) - margin.top - margin.bottom);
  1288. };
  1289. /*
  1290. Calculate the available width for a chart.
  1291. */
  1292. nv.utils.availableWidth = function(width, container, margin) {
  1293. return Math.max(0,nv.utils.sanitizeWidth(width, container) - margin.left - margin.right);
  1294. };
  1295. /*
  1296. Clear any rendered chart components and display a chart's 'noData' message
  1297. */
  1298. nv.utils.noData = function(chart, container) {
  1299. var opt = chart.options(),
  1300. margin = opt.margin(),
  1301. noData = opt.noData(),
  1302. data = (noData == null) ? ["No Data Available."] : [noData],
  1303. height = nv.utils.availableHeight(null, container, margin),
  1304. width = nv.utils.availableWidth(null, container, margin),
  1305. x = margin.left + width/2,
  1306. y = margin.top + height/2;
  1307. //Remove any previously created chart components
  1308. container.selectAll('g').remove();
  1309. var noDataText = container.selectAll('.nv-noData').data(data);
  1310. noDataText.enter().append('text')
  1311. .attr('class', 'nvd3 nv-noData')
  1312. .attr('dy', '-.7em')
  1313. .style('text-anchor', 'middle');
  1314. noDataText
  1315. .attr('x', x)
  1316. .attr('y', y)
  1317. .text(function(t){ return t; });
  1318. };
  1319. /*
  1320. Wrap long labels.
  1321. */
  1322. nv.utils.wrapTicks = function (text, width) {
  1323. text.each(function() {
  1324. var text = d3.select(this),
  1325. words = text.text().split(/\s+/).reverse(),
  1326. word,
  1327. line = [],
  1328. lineNumber = 0,
  1329. lineHeight = 1.1,
  1330. y = text.attr("y"),
  1331. dy = parseFloat(text.attr("dy")),
  1332. tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
  1333. while (word = words.pop()) {
  1334. line.push(word);
  1335. tspan.text(line.join(" "));
  1336. if (tspan.node().getComputedTextLength() > width) {
  1337. line.pop();
  1338. tspan.text(line.join(" "));
  1339. line = [word];
  1340. tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
  1341. }
  1342. }
  1343. });
  1344. };
  1345. /*
  1346. Check equality of 2 array
  1347. */
  1348. nv.utils.arrayEquals = function (array1, array2) {
  1349. if (array1 === array2)
  1350. return true;
  1351. if (!array1 || !array2)
  1352. return false;
  1353. // compare lengths - can save a lot of time
  1354. if (array1.length != array2.length)
  1355. return false;
  1356. for (var i = 0,
  1357. l = array1.length; i < l; i++) {
  1358. // Check if we have nested arrays
  1359. if (array1[i] instanceof Array && array2[i] instanceof Array) {
  1360. // recurse into the nested arrays
  1361. if (!nv.arrayEquals(array1[i], array2[i]))
  1362. return false;
  1363. } else if (array1[i] != array2[i]) {
  1364. // Warning - two different object instances will never be equal: {x:20} != {x:20}
  1365. return false;
  1366. }
  1367. }
  1368. return true;
  1369. };
  1370. nv.models.axis = function() {
  1371. "use strict";
  1372. //============================================================
  1373. // Public Variables with Default Settings
  1374. //------------------------------------------------------------
  1375. var axis = d3.svg.axis();
  1376. var scale = d3.scale.linear();
  1377. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  1378. , width = 75 //only used for tickLabel currently
  1379. , height = 60 //only used for tickLabel currently
  1380. , axisLabelText = null
  1381. , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes
  1382. , rotateLabels = 0
  1383. , rotateYLabel = true
  1384. , staggerLabels = false
  1385. , isOrdinal = false
  1386. , ticks = null
  1387. , axisLabelDistance = 0
  1388. , fontSize = undefined
  1389. , duration = 250
  1390. , dispatch = d3.dispatch('renderEnd')
  1391. ;
  1392. axis
  1393. .scale(scale)
  1394. .orient('bottom')
  1395. .tickFormat(function(d) { return d })
  1396. ;
  1397. //============================================================
  1398. // Private Variables
  1399. //------------------------------------------------------------
  1400. var scale0;
  1401. var renderWatch = nv.utils.renderWatch(dispatch, duration);
  1402. function chart(selection) {
  1403. renderWatch.reset();
  1404. selection.each(function(data) {
  1405. var container = d3.select(this);
  1406. nv.utils.initSVG(container);
  1407. // Setup containers and skeleton of chart
  1408. var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]);
  1409. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis');
  1410. var gEnter = wrapEnter.append('g');
  1411. var g = wrap.select('g');
  1412. if (ticks !== null)
  1413. axis.ticks(ticks);
  1414. else if (axis.orient() == 'top' || axis.orient() == 'bottom')
  1415. axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100);
  1416. //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component
  1417. g.watchTransition(renderWatch, 'axis').call(axis);
  1418. scale0 = scale0 || axis.scale();
  1419. var fmt = axis.tickFormat();
  1420. if (fmt == null) {
  1421. fmt = scale0.tickFormat();
  1422. }
  1423. var axisLabel = g.selectAll('text.nv-axislabel')
  1424. .data([axisLabelText || null]);
  1425. axisLabel.exit().remove();
  1426. //only skip when fontSize is undefined so it can be cleared with a null or blank string
  1427. if (fontSize !== undefined) {
  1428. g.selectAll('g').select("text").style('font-size', fontSize);
  1429. }
  1430. var xLabelMargin;
  1431. var axisMaxMin;
  1432. var w;
  1433. switch (axis.orient()) {
  1434. case 'top':
  1435. axisLabel.enter().append('text').attr('class', 'nv-axislabel');
  1436. w = 0;
  1437. if (scale.range().length === 1) {
  1438. w = isOrdinal ? scale.range()[0] * 2 + scale.rangeBand() : 0;
  1439. } else if (scale.range().length === 2) {
  1440. w = isOrdinal ? scale.range()[0] + scale.range()[1] + scale.rangeBand() : scale.range()[1];
  1441. } else if ( scale.range().length > 2){
  1442. w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
  1443. };
  1444. axisLabel
  1445. .attr('text-anchor', 'middle')
  1446. .attr('y', 0)
  1447. .attr('x', w/2);
  1448. if (showMaxMin) {
  1449. axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
  1450. .data(scale.domain());
  1451. axisMaxMin.enter().append('g').attr('class',function(d,i){
  1452. return ['nv-axisMaxMin','nv-axisMaxMin-x',(i == 0 ? 'nv-axisMin-x':'nv-axisMax-x')].join(' ')
  1453. }).append('text');
  1454. axisMaxMin.exit().remove();
  1455. axisMaxMin
  1456. .attr('transform', function(d,i) {
  1457. return 'translate(' + nv.utils.NaNtoZero(scale(d)) + ',0)'
  1458. })
  1459. .select('text')
  1460. .attr('dy', '-0.5em')
  1461. .attr('y', -axis.tickPadding())
  1462. .attr('text-anchor', 'middle')
  1463. .text(function(d,i) {
  1464. var v = fmt(d);
  1465. return ('' + v).match('NaN') ? '' : v;
  1466. });
  1467. axisMaxMin.watchTransition(renderWatch, 'min-max top')
  1468. .attr('transform', function(d,i) {
  1469. return 'translate(' + nv.utils.NaNtoZero(scale.range()[i]) + ',0)'
  1470. });
  1471. }
  1472. break;
  1473. case 'bottom':
  1474. xLabelMargin = axisLabelDistance + 36;
  1475. var maxTextWidth = 30;
  1476. var textHeight = 0;
  1477. var xTicks = g.selectAll('g').select("text");
  1478. var rotateLabelsRule = '';
  1479. if (rotateLabels%360) {
  1480. //Reset transform on ticks so textHeight can be calculated correctly
  1481. xTicks.attr('transform', '');
  1482. //Calculate the longest xTick width
  1483. xTicks.each(function(d,i){
  1484. var box = this.getBoundingClientRect();
  1485. var width = box.width;
  1486. textHeight = box.height;
  1487. if(width > maxTextWidth) maxTextWidth = width;
  1488. });
  1489. rotateLabelsRule = 'rotate(' + rotateLabels + ' 0,' + (textHeight/2 + axis.tickPadding()) + ')';
  1490. //Convert to radians before calculating sin. Add 30 to margin for healthy padding.
  1491. var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180));
  1492. xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30;
  1493. //Rotate all xTicks
  1494. xTicks
  1495. .attr('transform', rotateLabelsRule)
  1496. .style('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end');
  1497. } else {
  1498. if (staggerLabels) {
  1499. xTicks
  1500. .attr('transform', function(d,i) {
  1501. return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')'
  1502. });
  1503. } else {
  1504. xTicks.attr('transform', "translate(0,0)");
  1505. }
  1506. }
  1507. axisLabel.enter().append('text').attr('class', 'nv-axislabel');
  1508. w = 0;
  1509. if (scale.range().length === 1) {
  1510. w = isOrdinal ? scale.range()[0] * 2 + scale.rangeBand() : 0;
  1511. } else if (scale.range().length === 2) {
  1512. w = isOrdinal ? scale.range()[0] + scale.range()[1] + scale.rangeBand() : scale.range()[1];
  1513. } else if ( scale.range().length > 2){
  1514. w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
  1515. };
  1516. axisLabel
  1517. .attr('text-anchor', 'middle')
  1518. .attr('y', xLabelMargin)
  1519. .attr('x', w/2);
  1520. if (showMaxMin) {
  1521. //if (showMaxMin && !isOrdinal) {
  1522. axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
  1523. //.data(scale.domain())
  1524. .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]);
  1525. axisMaxMin.enter().append('g').attr('class',function(d,i){
  1526. return ['nv-axisMaxMin','nv-axisMaxMin-x',(i == 0 ? 'nv-axisMin-x':'nv-axisMax-x')].join(' ')
  1527. }).append('text');
  1528. axisMaxMin.exit().remove();
  1529. axisMaxMin
  1530. .attr('transform', function(d,i) {
  1531. return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)'
  1532. })
  1533. .select('text')
  1534. .attr('dy', '.71em')
  1535. .attr('y', axis.tickPadding())
  1536. .attr('transform', rotateLabelsRule)
  1537. .style('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle')
  1538. .text(function(d,i) {
  1539. var v = fmt(d);
  1540. return ('' + v).match('NaN') ? '' : v;
  1541. });
  1542. axisMaxMin.watchTransition(renderWatch, 'min-max bottom')
  1543. .attr('transform', function(d,i) {
  1544. return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)'
  1545. });
  1546. }
  1547. break;
  1548. case 'right':
  1549. axisLabel.enter().append('text').attr('class', 'nv-axislabel');
  1550. axisLabel
  1551. .style('text-anchor', rotateYLabel ? 'middle' : 'begin')
  1552. .attr('transform', rotateYLabel ? 'rotate(90)' : '')
  1553. .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
  1554. .attr('x', rotateYLabel ? (d3.max(scale.range()) / 2) : axis.tickPadding());
  1555. if (showMaxMin) {
  1556. axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
  1557. .data(scale.domain());
  1558. axisMaxMin.enter().append('g').attr('class',function(d,i){
  1559. return ['nv-axisMaxMin','nv-axisMaxMin-y',(i == 0 ? 'nv-axisMin-y':'nv-axisMax-y')].join(' ')
  1560. }).append('text')
  1561. .style('opacity', 0);
  1562. axisMaxMin.exit().remove();
  1563. axisMaxMin
  1564. .attr('transform', function(d,i) {
  1565. return 'translate(0,' + nv.utils.NaNtoZero(scale(d)) + ')'
  1566. })
  1567. .select('text')
  1568. .attr('dy', '.32em')
  1569. .attr('y', 0)
  1570. .attr('x', axis.tickPadding())
  1571. .style('text-anchor', 'start')
  1572. .text(function(d, i) {
  1573. var v = fmt(d);
  1574. return ('' + v).match('NaN') ? '' : v;
  1575. });
  1576. axisMaxMin.watchTransition(renderWatch, 'min-max right')
  1577. .attr('transform', function(d,i) {
  1578. return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')'
  1579. })
  1580. .select('text')
  1581. .style('opacity', 1);
  1582. }
  1583. break;
  1584. case 'left':
  1585. /*
  1586. //For dynamically placing the label. Can be used with dynamically-sized chart axis margins
  1587. var yTicks = g.selectAll('g').select("text");
  1588. yTicks.each(function(d,i){
  1589. var labelPadding = this.getBoundingClientRect().width + axis.tickPadding() + 16;
  1590. if(labelPadding > width) width = labelPadding;
  1591. });
  1592. */
  1593. axisLabel.enter().append('text').attr('class', 'nv-axislabel');
  1594. axisLabel
  1595. .style('text-anchor', rotateYLabel ? 'middle' : 'end')
  1596. .attr('transform', rotateYLabel ? 'rotate(-90)' : '')
  1597. .attr('y', rotateYLabel ? (-Math.max(margin.left, width) + 25 - (axisLabelDistance || 0)) : -10)
  1598. .attr('x', rotateYLabel ? (-d3.max(scale.range()) / 2) : -axis.tickPadding());
  1599. if (showMaxMin) {
  1600. axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
  1601. .data(scale.domain());
  1602. axisMaxMin.enter().append('g').attr('class',function(d,i){
  1603. return ['nv-axisMaxMin','nv-axisMaxMin-y',(i == 0 ? 'nv-axisMin-y':'nv-axisMax-y')].join(' ')
  1604. }).append('text')
  1605. .style('opacity', 0);
  1606. axisMaxMin.exit().remove();
  1607. axisMaxMin
  1608. .attr('transform', function(d,i) {
  1609. return 'translate(0,' + nv.utils.NaNtoZero(scale0(d)) + ')'
  1610. })
  1611. .select('text')
  1612. .attr('dy', '.32em')
  1613. .attr('y', 0)
  1614. .attr('x', -axis.tickPadding())
  1615. .attr('text-anchor', 'end')
  1616. .text(function(d,i) {
  1617. var v = fmt(d);
  1618. return ('' + v).match('NaN') ? '' : v;
  1619. });
  1620. axisMaxMin.watchTransition(renderWatch, 'min-max right')
  1621. .attr('transform', function(d,i) {
  1622. return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')'
  1623. })
  1624. .select('text')
  1625. .style('opacity', 1);
  1626. }
  1627. break;
  1628. }
  1629. axisLabel.text(function(d) { return d });
  1630. if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) {
  1631. //check if max and min overlap other values, if so, hide the values that overlap
  1632. g.selectAll('g') // the g's wrapping each tick
  1633. .each(function(d,i) {
  1634. d3.select(this).select('text').attr('opacity', 1);
  1635. 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!
  1636. if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
  1637. d3.select(this).attr('opacity', 0);
  1638. d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!!
  1639. }
  1640. });
  1641. //if Max and Min = 0 only show min, Issue #281
  1642. if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0) {
  1643. wrap.selectAll('g.nv-axisMaxMin').style('opacity', function (d, i) {
  1644. return !i ? 1 : 0
  1645. });
  1646. }
  1647. }
  1648. if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) {
  1649. var maxMinRange = [];
  1650. wrap.selectAll('g.nv-axisMaxMin')
  1651. .each(function(d,i) {
  1652. try {
  1653. if (i) // i== 1, max position
  1654. 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)
  1655. else // i==0, min position
  1656. maxMinRange.push(scale(d) + this.getBoundingClientRect().width + 4)
  1657. }catch (err) {
  1658. if (i) // i== 1, max position
  1659. 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)
  1660. else // i==0, min position
  1661. maxMinRange.push(scale(d) + 4);
  1662. }
  1663. });
  1664. // the g's wrapping each tick
  1665. g.selectAll('g').each(function(d, i) {
  1666. if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) {
  1667. if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
  1668. d3.select(this).remove();
  1669. else
  1670. d3.select(this).select('text').remove(); // Don't remove the ZERO line!!
  1671. }
  1672. });
  1673. }
  1674. //Highlight zero tick line
  1675. g.selectAll('.tick')
  1676. .filter(function (d) {
  1677. /*
  1678. The filter needs to return only ticks at or near zero.
  1679. Numbers like 0.00001 need to count as zero as well,
  1680. and the arithmetic trick below solves that.
  1681. */
  1682. return !parseFloat(Math.round(d * 100000) / 1000000) && (d !== undefined)
  1683. })
  1684. .classed('zero', true);
  1685. //store old scales for use in transitions on update
  1686. scale0 = scale.copy();
  1687. });
  1688. renderWatch.renderEnd('axis immediate');
  1689. return chart;
  1690. }
  1691. //============================================================
  1692. // Expose Public Variables
  1693. //------------------------------------------------------------
  1694. // expose chart's sub-components
  1695. chart.axis = axis;
  1696. chart.dispatch = dispatch;
  1697. chart.options = nv.utils.optionsFunc.bind(chart);
  1698. chart._options = Object.create({}, {
  1699. // simple options, just get/set the necessary values
  1700. axisLabelDistance: {get: function(){return axisLabelDistance;}, set: function(_){axisLabelDistance=_;}},
  1701. staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
  1702. rotateLabels: {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
  1703. rotateYLabel: {get: function(){return rotateYLabel;}, set: function(_){rotateYLabel=_;}},
  1704. showMaxMin: {get: function(){return showMaxMin;}, set: function(_){showMaxMin=_;}},
  1705. axisLabel: {get: function(){return axisLabelText;}, set: function(_){axisLabelText=_;}},
  1706. height: {get: function(){return height;}, set: function(_){height=_;}},
  1707. ticks: {get: function(){return ticks;}, set: function(_){ticks=_;}},
  1708. width: {get: function(){return width;}, set: function(_){width=_;}},
  1709. fontSize: {get: function(){return fontSize;}, set: function(_){fontSize=_;}},
  1710. // options that require extra logic in the setter
  1711. margin: {get: function(){return margin;}, set: function(_){
  1712. margin.top = _.top !== undefined ? _.top : margin.top;
  1713. margin.right = _.right !== undefined ? _.right : margin.right;
  1714. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  1715. margin.left = _.left !== undefined ? _.left : margin.left;
  1716. }},
  1717. duration: {get: function(){return duration;}, set: function(_){
  1718. duration=_;
  1719. renderWatch.reset(duration);
  1720. }},
  1721. scale: {get: function(){return scale;}, set: function(_){
  1722. scale = _;
  1723. axis.scale(scale);
  1724. isOrdinal = typeof scale.rangeBands === 'function';
  1725. nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']);
  1726. }}
  1727. });
  1728. nv.utils.initOptions(chart);
  1729. nv.utils.inheritOptionsD3(chart, axis, ['orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat']);
  1730. nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']);
  1731. return chart;
  1732. };
  1733. nv.models.boxPlot = function() {
  1734. "use strict";
  1735. //============================================================
  1736. // Public Variables with Default Settings
  1737. //------------------------------------------------------------
  1738. var margin = {top: 0, right: 0, bottom: 0, left: 0},
  1739. width = 960,
  1740. height = 500,
  1741. id = Math.floor(Math.random() * 10000), // Create semi-unique ID in case user doesn't select one
  1742. xScale = d3.scale.ordinal(),
  1743. yScale = d3.scale.linear(),
  1744. getX = function(d) { return d.label }, // Default data model selectors.
  1745. getQ1 = function(d) { return d.values.Q1 },
  1746. getQ2 = function(d) { return d.values.Q2 },
  1747. getQ3 = function(d) { return d.values.Q3 },
  1748. getWl = function(d) { return d.values.whisker_low },
  1749. getWh = function(d) { return d.values.whisker_high },
  1750. getColor = function(d) { return d.color },
  1751. getOlItems = function(d) { return d.values.outliers },
  1752. getOlValue = function(d, i, j) { return d },
  1753. getOlLabel = function(d, i, j) { return d },
  1754. getOlColor = function(d, i, j) { return undefined },
  1755. color = nv.utils.defaultColor(),
  1756. container = null,
  1757. xDomain, xRange,
  1758. yDomain, yRange,
  1759. dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd'),
  1760. duration = 250,
  1761. maxBoxWidth = null;
  1762. //============================================================
  1763. // Private Variables
  1764. //------------------------------------------------------------
  1765. var xScale0, yScale0;
  1766. var renderWatch = nv.utils.renderWatch(dispatch, duration);
  1767. function chart(selection) {
  1768. renderWatch.reset();
  1769. selection.each(function(data) {
  1770. var availableWidth = width - margin.left - margin.right,
  1771. availableHeight = height - margin.top - margin.bottom;
  1772. container = d3.select(this);
  1773. nv.utils.initSVG(container);
  1774. // Setup Scales
  1775. xScale.domain(xDomain || data.map(function(d,i) { return getX(d,i); }))
  1776. .rangeBands(xRange || [0, availableWidth], 0.1);
  1777. // if we know yDomain, no need to calculate
  1778. var yData = []
  1779. if (!yDomain) {
  1780. // (y-range is based on quartiles, whiskers and outliers)
  1781. var values = [], yMin, yMax;
  1782. data.forEach(function (d, i) {
  1783. var q1 = getQ1(d), q3 = getQ3(d), wl = getWl(d), wh = getWh(d);
  1784. var olItems = getOlItems(d);
  1785. if (olItems) {
  1786. olItems.forEach(function (e, i) {
  1787. values.push(getOlValue(e, i, undefined));
  1788. });
  1789. }
  1790. if (wl) { values.push(wl) }
  1791. if (q1) { values.push(q1) }
  1792. if (q3) { values.push(q3) }
  1793. if (wh) { values.push(wh) }
  1794. });
  1795. yMin = d3.min(values);
  1796. yMax = d3.max(values);
  1797. yData = [ yMin, yMax ] ;
  1798. }
  1799. yScale.domain(yDomain || yData);
  1800. yScale.range(yRange || [availableHeight, 0]);
  1801. //store old scales if they exist
  1802. xScale0 = xScale0 || xScale;
  1803. yScale0 = yScale0 || yScale.copy().range([yScale(0),yScale(0)]);
  1804. // Setup containers and skeleton of chart
  1805. var wrap = container.selectAll('g.nv-wrap').data([data]);
  1806. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap');
  1807. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  1808. var boxplots = wrap.selectAll('.nv-boxplot').data(function(d) { return d });
  1809. var boxEnter = boxplots.enter().append('g').style('stroke-opacity', 1e-6).style('fill-opacity', 1e-6);
  1810. boxplots
  1811. .attr('class', 'nv-boxplot')
  1812. .attr('transform', function(d,i,j) { return 'translate(' + (xScale(getX(d,i)) + xScale.rangeBand() * 0.05) + ', 0)'; })
  1813. .classed('hover', function(d) { return d.hover });
  1814. boxplots
  1815. .watchTransition(renderWatch, 'nv-boxplot: boxplots')
  1816. .style('stroke-opacity', 1)
  1817. .style('fill-opacity', 0.75)
  1818. .delay(function(d,i) { return i * duration / data.length })
  1819. .attr('transform', function(d,i) {
  1820. return 'translate(' + (xScale(getX(d,i)) + xScale.rangeBand() * 0.05) + ', 0)';
  1821. });
  1822. boxplots.exit().remove();
  1823. // ----- add the SVG elements for each boxPlot -----
  1824. // conditionally append whisker lines
  1825. boxEnter.each(function(d,i) {
  1826. var box = d3.select(this);
  1827. [getWl, getWh].forEach(function (f) {
  1828. if (f(d) !== undefined && f(d) !== null) {
  1829. var key = (f === getWl) ? 'low' : 'high';
  1830. box.append('line')
  1831. .style('stroke', getColor(d) || color(d,i))
  1832. .attr('class', 'nv-boxplot-whisker nv-boxplot-' + key);
  1833. box.append('line')
  1834. .style('stroke', getColor(d) || color(d,i))
  1835. .attr('class', 'nv-boxplot-tick nv-boxplot-' + key);
  1836. }
  1837. });
  1838. });
  1839. var box_width = function() { return (maxBoxWidth === null ? xScale.rangeBand() * 0.9 : Math.min(75, xScale.rangeBand() * 0.9)); };
  1840. var box_left = function() { return xScale.rangeBand() * 0.45 - box_width()/2; };
  1841. var box_right = function() { return xScale.rangeBand() * 0.45 + box_width()/2; };
  1842. // update whisker lines and ticks
  1843. [getWl, getWh].forEach(function (f) {
  1844. var key = (f === getWl) ? 'low' : 'high';
  1845. var endpoint = (f === getWl) ? getQ1 : getQ3;
  1846. boxplots.select('line.nv-boxplot-whisker.nv-boxplot-' + key)
  1847. .watchTransition(renderWatch, 'nv-boxplot: boxplots')
  1848. .attr('x1', xScale.rangeBand() * 0.45 )
  1849. .attr('y1', function(d,i) { return yScale(f(d)); })
  1850. .attr('x2', xScale.rangeBand() * 0.45 )
  1851. .attr('y2', function(d,i) { return yScale(endpoint(d)); });
  1852. boxplots.select('line.nv-boxplot-tick.nv-boxplot-' + key)
  1853. .watchTransition(renderWatch, 'nv-boxplot: boxplots')
  1854. .attr('x1', box_left )
  1855. .attr('y1', function(d,i) { return yScale(f(d)); })
  1856. .attr('x2', box_right )
  1857. .attr('y2', function(d,i) { return yScale(f(d)); });
  1858. });
  1859. [getWl, getWh].forEach(function (f) {
  1860. var key = (f === getWl) ? 'low' : 'high';
  1861. boxEnter.selectAll('.nv-boxplot-' + key)
  1862. .on('mouseover', function(d,i,j) {
  1863. d3.select(this).classed('hover', true);
  1864. dispatch.elementMouseover({
  1865. series: { key: f(d), color: getColor(d) || color(d,j) },
  1866. e: d3.event
  1867. });
  1868. })
  1869. .on('mouseout', function(d,i,j) {
  1870. d3.select(this).classed('hover', false);
  1871. dispatch.elementMouseout({
  1872. series: { key: f(d), color: getColor(d) || color(d,j) },
  1873. e: d3.event
  1874. });
  1875. })
  1876. .on('mousemove', function(d,i) {
  1877. dispatch.elementMousemove({e: d3.event});
  1878. });
  1879. });
  1880. // boxes
  1881. boxEnter.append('rect')
  1882. .attr('class', 'nv-boxplot-box')
  1883. // tooltip events
  1884. .on('mouseover', function(d,i) {
  1885. d3.select(this).classed('hover', true);
  1886. dispatch.elementMouseover({
  1887. key: getX(d),
  1888. value: getX(d),
  1889. series: [
  1890. { key: 'Q3', value: getQ3(d), color: getColor(d) || color(d,i) },
  1891. { key: 'Q2', value: getQ2(d), color: getColor(d) || color(d,i) },
  1892. { key: 'Q1', value: getQ1(d), color: getColor(d) || color(d,i) }
  1893. ],
  1894. data: d,
  1895. index: i,
  1896. e: d3.event
  1897. });
  1898. })
  1899. .on('mouseout', function(d,i) {
  1900. d3.select(this).classed('hover', false);
  1901. dispatch.elementMouseout({
  1902. key: getX(d),
  1903. value: getX(d),
  1904. series: [
  1905. { key: 'Q3', value: getQ3(d), color: getColor(d) || color(d,i) },
  1906. { key: 'Q2', value: getQ2(d), color: getColor(d) || color(d,i) },
  1907. { key: 'Q1', value: getQ1(d), color: getColor(d) || color(d,i) }
  1908. ],
  1909. data: d,
  1910. index: i,
  1911. e: d3.event
  1912. });
  1913. })
  1914. .on('mousemove', function(d,i) {
  1915. dispatch.elementMousemove({e: d3.event});
  1916. });
  1917. // box transitions
  1918. boxplots.select('rect.nv-boxplot-box')
  1919. .watchTransition(renderWatch, 'nv-boxplot: boxes')
  1920. .attr('y', function(d,i) { return yScale(getQ3(d)); })
  1921. .attr('width', box_width)
  1922. .attr('x', box_left )
  1923. .attr('height', function(d,i) { return Math.abs(yScale(getQ3(d)) - yScale(getQ1(d))) || 1 })
  1924. .style('fill', function(d,i) { return getColor(d) || color(d,i) })
  1925. .style('stroke', function(d,i) { return getColor(d) || color(d,i) });
  1926. // median line
  1927. boxEnter.append('line').attr('class', 'nv-boxplot-median');
  1928. boxplots.select('line.nv-boxplot-median')
  1929. .watchTransition(renderWatch, 'nv-boxplot: boxplots line')
  1930. .attr('x1', box_left)
  1931. .attr('y1', function(d,i) { return yScale(getQ2(d)); })
  1932. .attr('x2', box_right)
  1933. .attr('y2', function(d,i) { return yScale(getQ2(d)); });
  1934. // outliers
  1935. var outliers = boxplots.selectAll('.nv-boxplot-outlier').data(function(d) {
  1936. return getOlItems(d) || [];
  1937. });
  1938. outliers.enter().append('circle')
  1939. .style('fill', function(d,i,j) { return getOlColor(d,i,j) || color(d,j) })
  1940. .style('stroke', function(d,i,j) { return getOlColor(d,i,j) || color(d,j) })
  1941. .style('z-index', 9000)
  1942. .on('mouseover', function(d,i,j) {
  1943. d3.select(this).classed('hover', true);
  1944. dispatch.elementMouseover({
  1945. series: { key: getOlLabel(d,i,j), color: getOlColor(d,i,j) || color(d,j) },
  1946. e: d3.event
  1947. });
  1948. })
  1949. .on('mouseout', function(d,i,j) {
  1950. d3.select(this).classed('hover', false);
  1951. dispatch.elementMouseout({
  1952. series: { key: getOlLabel(d,i,j), color: getOlColor(d,i,j) || color(d,j) },
  1953. e: d3.event
  1954. });
  1955. })
  1956. .on('mousemove', function(d,i) {
  1957. dispatch.elementMousemove({e: d3.event});
  1958. });
  1959. outliers.attr('class', 'nv-boxplot-outlier');
  1960. outliers
  1961. .watchTransition(renderWatch, 'nv-boxplot: nv-boxplot-outlier')
  1962. .attr('cx', xScale.rangeBand() * 0.45)
  1963. .attr('cy', function(d,i,j) { return yScale(getOlValue(d,i,j)); })
  1964. .attr('r', '3');
  1965. outliers.exit().remove();
  1966. //store old scales for use in transitions on update
  1967. xScale0 = xScale.copy();
  1968. yScale0 = yScale.copy();
  1969. });
  1970. renderWatch.renderEnd('nv-boxplot immediate');
  1971. return chart;
  1972. }
  1973. //============================================================
  1974. // Expose Public Variables
  1975. //------------------------------------------------------------
  1976. chart.dispatch = dispatch;
  1977. chart.options = nv.utils.optionsFunc.bind(chart);
  1978. chart._options = Object.create({}, {
  1979. // simple options, just get/set the necessary values
  1980. width: {get: function(){return width;}, set: function(_){width=_;}},
  1981. height: {get: function(){return height;}, set: function(_){height=_;}},
  1982. maxBoxWidth: {get: function(){return maxBoxWidth;}, set: function(_){maxBoxWidth=_;}},
  1983. x: {get: function(){return getX;}, set: function(_){getX=_;}},
  1984. q1: {get: function(){return getQ1;}, set: function(_){getQ1=_;}},
  1985. q2: {get: function(){return getQ2;}, set: function(_){getQ2=_;}},
  1986. q3: {get: function(){return getQ3;}, set: function(_){getQ3=_;}},
  1987. wl: {get: function(){return getWl;}, set: function(_){getWl=_;}},
  1988. wh: {get: function(){return getWh;}, set: function(_){getWh=_;}},
  1989. itemColor: {get: function(){return getColor;}, set: function(_){getColor=_;}},
  1990. outliers: {get: function(){return getOlItems;}, set: function(_){getOlItems=_;}},
  1991. outlierValue: {get: function(){return getOlValue;}, set: function(_){getOlValue=_;}},
  1992. outlierLabel: {get: function(){return getOlLabel;}, set: function(_){getOlLabel=_;}},
  1993. outlierColor: {get: function(){return getOlColor;}, set: function(_){getOlColor=_;}},
  1994. xScale: {get: function(){return xScale;}, set: function(_){xScale=_;}},
  1995. yScale: {get: function(){return yScale;}, set: function(_){yScale=_;}},
  1996. xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
  1997. yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
  1998. xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
  1999. yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
  2000. id: {get: function(){return id;}, set: function(_){id=_;}},
  2001. // rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}},
  2002. y: {
  2003. get: function() {
  2004. console.warn('BoxPlot \'y\' chart option is deprecated. Please use model overrides instead.');
  2005. return {};
  2006. },
  2007. set: function(_) {
  2008. console.warn('BoxPlot \'y\' chart option is deprecated. Please use model overrides instead.');
  2009. }
  2010. },
  2011. // options that require extra logic in the setter
  2012. margin: {get: function(){return margin;}, set: function(_){
  2013. margin.top = _.top !== undefined ? _.top : margin.top;
  2014. margin.right = _.right !== undefined ? _.right : margin.right;
  2015. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  2016. margin.left = _.left !== undefined ? _.left : margin.left;
  2017. }},
  2018. color: {get: function(){return color;}, set: function(_){
  2019. color = nv.utils.getColor(_);
  2020. }},
  2021. duration: {get: function(){return duration;}, set: function(_){
  2022. duration = _;
  2023. renderWatch.reset(duration);
  2024. }}
  2025. });
  2026. nv.utils.initOptions(chart);
  2027. return chart;
  2028. };
  2029. nv.models.boxPlotChart = function() {
  2030. "use strict";
  2031. //============================================================
  2032. // Public Variables with Default Settings
  2033. //------------------------------------------------------------
  2034. var boxplot = nv.models.boxPlot(),
  2035. xAxis = nv.models.axis(),
  2036. yAxis = nv.models.axis();
  2037. var margin = {top: 15, right: 10, bottom: 50, left: 60},
  2038. width = null,
  2039. height = null,
  2040. color = nv.utils.getColor(),
  2041. showXAxis = true,
  2042. showYAxis = true,
  2043. rightAlignYAxis = false,
  2044. staggerLabels = false,
  2045. tooltip = nv.models.tooltip(),
  2046. x, y,
  2047. noData = 'No Data Available.',
  2048. dispatch = d3.dispatch('beforeUpdate', 'renderEnd'),
  2049. duration = 250;
  2050. xAxis
  2051. .orient('bottom')
  2052. .showMaxMin(false)
  2053. .tickFormat(function(d) { return d })
  2054. ;
  2055. yAxis
  2056. .orient((rightAlignYAxis) ? 'right' : 'left')
  2057. .tickFormat(d3.format(',.1f'))
  2058. ;
  2059. tooltip.duration(0);
  2060. //============================================================
  2061. // Private Variables
  2062. //------------------------------------------------------------
  2063. var renderWatch = nv.utils.renderWatch(dispatch, duration);
  2064. function chart(selection) {
  2065. renderWatch.reset();
  2066. renderWatch.models(boxplot);
  2067. if (showXAxis) renderWatch.models(xAxis);
  2068. if (showYAxis) renderWatch.models(yAxis);
  2069. selection.each(function(data) {
  2070. var container = d3.select(this), that = this;
  2071. nv.utils.initSVG(container);
  2072. var availableWidth = (width || parseInt(container.style('width')) || 960) - margin.left - margin.right;
  2073. var availableHeight = (height || parseInt(container.style('height')) || 400) - margin.top - margin.bottom;
  2074. chart.update = function() {
  2075. dispatch.beforeUpdate();
  2076. container.transition().duration(duration).call(chart);
  2077. };
  2078. chart.container = this;
  2079. // TODO still need to find a way to validate quartile data presence using boxPlot callbacks.
  2080. // Display No Data message if there's nothing to show. (quartiles required at minimum).
  2081. if (!data || !data.length) {
  2082. var noDataText = container.selectAll('.nv-noData').data([noData]);
  2083. noDataText.enter().append('text')
  2084. .attr('class', 'nvd3 nv-noData')
  2085. .attr('dy', '-.7em')
  2086. .style('text-anchor', 'middle');
  2087. noDataText
  2088. .attr('x', margin.left + availableWidth / 2)
  2089. .attr('y', margin.top + availableHeight / 2)
  2090. .text(function(d) { return d });
  2091. return chart;
  2092. } else {
  2093. container.selectAll('.nv-noData').remove();
  2094. }
  2095. // Setup Scales
  2096. x = boxplot.xScale();
  2097. y = boxplot.yScale().clamp(true);
  2098. // Setup containers and skeleton of chart
  2099. var wrap = container.selectAll('g.nv-wrap.nv-boxPlotWithAxes').data([data]);
  2100. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-boxPlotWithAxes').append('g');
  2101. var defsEnter = gEnter.append('defs');
  2102. var g = wrap.select('g');
  2103. gEnter.append('g').attr('class', 'nv-x nv-axis');
  2104. gEnter.append('g').attr('class', 'nv-y nv-axis')
  2105. .append('g').attr('class', 'nv-zeroLine')
  2106. .append('line');
  2107. gEnter.append('g').attr('class', 'nv-barsWrap');
  2108. g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  2109. if (rightAlignYAxis) {
  2110. g.select('.nv-y.nv-axis')
  2111. .attr('transform', 'translate(' + availableWidth + ',0)');
  2112. }
  2113. // Main Chart Component(s)
  2114. boxplot.width(availableWidth).height(availableHeight);
  2115. var barsWrap = g.select('.nv-barsWrap')
  2116. .datum(data.filter(function(d) { return !d.disabled }))
  2117. barsWrap.transition().call(boxplot);
  2118. defsEnter.append('clipPath')
  2119. .attr('id', 'nv-x-label-clip-' + boxplot.id())
  2120. .append('rect');
  2121. g.select('#nv-x-label-clip-' + boxplot.id() + ' rect')
  2122. .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
  2123. .attr('height', 16)
  2124. .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
  2125. // Setup Axes
  2126. if (showXAxis) {
  2127. xAxis
  2128. .scale(x)
  2129. .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
  2130. .tickSize(-availableHeight, 0);
  2131. g.select('.nv-x.nv-axis').attr('transform', 'translate(0,' + y.range()[0] + ')');
  2132. g.select('.nv-x.nv-axis').call(xAxis);
  2133. var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
  2134. if (staggerLabels) {
  2135. xTicks
  2136. .selectAll('text')
  2137. .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 === 0 ? '5' : '17') + ')' })
  2138. }
  2139. }
  2140. if (showYAxis) {
  2141. yAxis
  2142. .scale(y)
  2143. .ticks( Math.floor(availableHeight/36) ) // can't use nv.utils.calcTicksY with Object data
  2144. .tickSize( -availableWidth, 0);
  2145. g.select('.nv-y.nv-axis').call(yAxis);
  2146. }
  2147. // Zero line
  2148. g.select('.nv-zeroLine line')
  2149. .attr('x1',0)
  2150. .attr('x2',availableWidth)
  2151. .attr('y1', y(0))
  2152. .attr('y2', y(0))
  2153. ;
  2154. //============================================================
  2155. // Event Handling/Dispatching (in chart's scope)
  2156. //------------------------------------------------------------
  2157. });
  2158. renderWatch.renderEnd('nv-boxplot chart immediate');
  2159. return chart;
  2160. }
  2161. //============================================================
  2162. // Event Handling/Dispatching (out of chart's scope)
  2163. //------------------------------------------------------------
  2164. boxplot.dispatch.on('elementMouseover.tooltip', function(evt) {
  2165. tooltip.data(evt).hidden(false);
  2166. });
  2167. boxplot.dispatch.on('elementMouseout.tooltip', function(evt) {
  2168. tooltip.data(evt).hidden(true);
  2169. });
  2170. boxplot.dispatch.on('elementMousemove.tooltip', function(evt) {
  2171. tooltip();
  2172. });
  2173. //============================================================
  2174. // Expose Public Variables
  2175. //------------------------------------------------------------
  2176. chart.dispatch = dispatch;
  2177. chart.boxplot = boxplot;
  2178. chart.xAxis = xAxis;
  2179. chart.yAxis = yAxis;
  2180. chart.tooltip = tooltip;
  2181. chart.options = nv.utils.optionsFunc.bind(chart);
  2182. chart._options = Object.create({}, {
  2183. // simple options, just get/set the necessary values
  2184. width: {get: function(){return width;}, set: function(_){width=_;}},
  2185. height: {get: function(){return height;}, set: function(_){height=_;}},
  2186. staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
  2187. showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
  2188. showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
  2189. tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
  2190. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  2191. // options that require extra logic in the setter
  2192. margin: {get: function(){return margin;}, set: function(_){
  2193. margin.top = _.top !== undefined ? _.top : margin.top;
  2194. margin.right = _.right !== undefined ? _.right : margin.right;
  2195. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  2196. margin.left = _.left !== undefined ? _.left : margin.left;
  2197. }},
  2198. duration: {get: function(){return duration;}, set: function(_){
  2199. duration = _;
  2200. renderWatch.reset(duration);
  2201. boxplot.duration(duration);
  2202. xAxis.duration(duration);
  2203. yAxis.duration(duration);
  2204. }},
  2205. color: {get: function(){return color;}, set: function(_){
  2206. color = nv.utils.getColor(_);
  2207. boxplot.color(color);
  2208. }},
  2209. rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
  2210. rightAlignYAxis = _;
  2211. yAxis.orient( (_) ? 'right' : 'left');
  2212. }}
  2213. });
  2214. nv.utils.inheritOptions(chart, boxplot);
  2215. nv.utils.initOptions(chart);
  2216. return chart;
  2217. }
  2218. // Chart design based on the recommendations of Stephen Few. Implementation
  2219. // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
  2220. // http://projects.instantcognition.com/protovis/bulletchart/
  2221. nv.models.bullet = function() {
  2222. "use strict";
  2223. //============================================================
  2224. // Public Variables with Default Settings
  2225. //------------------------------------------------------------
  2226. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  2227. , orient = 'left' // TODO top & bottom
  2228. , reverse = false
  2229. , ranges = function(d) { return d.ranges }
  2230. , markers = function(d) { return d.markers ? d.markers : [] }
  2231. , markerLines = function(d) { return d.markerLines ? d.markerLines : [0] }
  2232. , measures = function(d) { return d.measures }
  2233. , rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] }
  2234. , markerLabels = function(d) { return d.markerLabels ? d.markerLabels : [] }
  2235. , markerLineLabels = function(d) { return d.markerLineLabels ? d.markerLineLabels : [] }
  2236. , measureLabels = function(d) { return d.measureLabels ? d.measureLabels : [] }
  2237. , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
  2238. , width = 380
  2239. , height = 30
  2240. , container = null
  2241. , tickFormat = null
  2242. , color = nv.utils.getColor(['#1f77b4'])
  2243. , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove')
  2244. , defaultRangeLabels = ["Maximum", "Mean", "Minimum"]
  2245. , legacyRangeClassNames = ["Max", "Avg", "Min"]
  2246. , duration = 1000
  2247. ;
  2248. function sortLabels(labels, values){
  2249. var lz = labels.slice();
  2250. labels.sort(function(a, b){
  2251. var iA = lz.indexOf(a);
  2252. var iB = lz.indexOf(b);
  2253. return d3.descending(values[iA], values[iB]);
  2254. });
  2255. };
  2256. function chart(selection) {
  2257. selection.each(function(d, i) {
  2258. var availableWidth = width - margin.left - margin.right,
  2259. availableHeight = height - margin.top - margin.bottom;
  2260. container = d3.select(this);
  2261. nv.utils.initSVG(container);
  2262. var rangez = ranges.call(this, d, i).slice(),
  2263. markerz = markers.call(this, d, i).slice(),
  2264. markerLinez = markerLines.call(this, d, i).slice(),
  2265. measurez = measures.call(this, d, i).slice(),
  2266. rangeLabelz = rangeLabels.call(this, d, i).slice(),
  2267. markerLabelz = markerLabels.call(this, d, i).slice(),
  2268. markerLineLabelz = markerLineLabels.call(this, d, i).slice(),
  2269. measureLabelz = measureLabels.call(this, d, i).slice();
  2270. // Sort labels according to their sorted values
  2271. sortLabels(rangeLabelz, rangez);
  2272. sortLabels(markerLabelz, markerz);
  2273. sortLabels(markerLineLabelz, markerLinez);
  2274. sortLabels(measureLabelz, measurez);
  2275. // sort values descending
  2276. rangez.sort(d3.descending);
  2277. markerz.sort(d3.descending);
  2278. markerLinez.sort(d3.descending);
  2279. measurez.sort(d3.descending);
  2280. // Setup Scales
  2281. // Compute the new x-scale.
  2282. var x1 = d3.scale.linear()
  2283. .domain( d3.extent(d3.merge([forceX, rangez])) )
  2284. .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
  2285. // Retrieve the old x-scale, if this is an update.
  2286. var x0 = this.__chart__ || d3.scale.linear()
  2287. .domain([0, Infinity])
  2288. .range(x1.range());
  2289. // Stash the new scale.
  2290. this.__chart__ = x1;
  2291. var rangeMin = d3.min(rangez), //rangez[2]
  2292. rangeMax = d3.max(rangez), //rangez[0]
  2293. rangeAvg = rangez[1];
  2294. // Setup containers and skeleton of chart
  2295. var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]);
  2296. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet');
  2297. var gEnter = wrapEnter.append('g');
  2298. var g = wrap.select('g');
  2299. for(var i=0,il=rangez.length; i<il; i++){
  2300. var rangeClassNames = 'nv-range nv-range'+i;
  2301. if(i <= 2){
  2302. rangeClassNames = rangeClassNames + ' nv-range'+legacyRangeClassNames[i];
  2303. }
  2304. gEnter.append('rect').attr('class', rangeClassNames);
  2305. }
  2306. gEnter.append('rect').attr('class', 'nv-measure');
  2307. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  2308. var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
  2309. w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
  2310. var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) },
  2311. xp1 = function(d) { return d < 0 ? x1(d) : x1(0) };
  2312. for(var i=0,il=rangez.length; i<il; i++){
  2313. var range = rangez[i];
  2314. g.select('rect.nv-range'+i)
  2315. .datum(range)
  2316. .attr('height', availableHeight)
  2317. .transition()
  2318. .duration(duration)
  2319. .attr('width', w1(range))
  2320. .attr('x', xp1(range))
  2321. }
  2322. g.select('rect.nv-measure')
  2323. .style('fill', color)
  2324. .attr('height', availableHeight / 3)
  2325. .attr('y', availableHeight / 3)
  2326. .on('mouseover', function() {
  2327. dispatch.elementMouseover({
  2328. value: measurez[0],
  2329. label: measureLabelz[0] || 'Current',
  2330. color: d3.select(this).style("fill")
  2331. })
  2332. })
  2333. .on('mousemove', function() {
  2334. dispatch.elementMousemove({
  2335. value: measurez[0],
  2336. label: measureLabelz[0] || 'Current',
  2337. color: d3.select(this).style("fill")
  2338. })
  2339. })
  2340. .on('mouseout', function() {
  2341. dispatch.elementMouseout({
  2342. value: measurez[0],
  2343. label: measureLabelz[0] || 'Current',
  2344. color: d3.select(this).style("fill")
  2345. })
  2346. })
  2347. .transition()
  2348. .duration(duration)
  2349. .attr('width', measurez < 0 ?
  2350. x1(0) - x1(measurez[0])
  2351. : x1(measurez[0]) - x1(0))
  2352. .attr('x', xp1(measurez));
  2353. var h3 = availableHeight / 6;
  2354. var markerData = markerz.map( function(marker, index) {
  2355. return {value: marker, label: markerLabelz[index]}
  2356. });
  2357. gEnter
  2358. .selectAll("path.nv-markerTriangle")
  2359. .data(markerData)
  2360. .enter()
  2361. .append('path')
  2362. .attr('class', 'nv-markerTriangle')
  2363. .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
  2364. .on('mouseover', function(d) {
  2365. dispatch.elementMouseover({
  2366. value: d.value,
  2367. label: d.label || 'Previous',
  2368. color: d3.select(this).style("fill"),
  2369. pos: [x1(d.value), availableHeight/2]
  2370. })
  2371. })
  2372. .on('mousemove', function(d) {
  2373. dispatch.elementMousemove({
  2374. value: d.value,
  2375. label: d.label || 'Previous',
  2376. color: d3.select(this).style("fill")
  2377. })
  2378. })
  2379. .on('mouseout', function(d, i) {
  2380. dispatch.elementMouseout({
  2381. value: d.value,
  2382. label: d.label || 'Previous',
  2383. color: d3.select(this).style("fill")
  2384. })
  2385. });
  2386. g.selectAll("path.nv-markerTriangle")
  2387. .data(markerData)
  2388. .transition()
  2389. .duration(duration)
  2390. .attr('transform', function(d) { return 'translate(' + x1(d.value) + ',' + (availableHeight / 2) + ')' });
  2391. var markerLinesData = markerLinez.map( function(marker, index) {
  2392. return {value: marker, label: markerLineLabelz[index]}
  2393. });
  2394. gEnter
  2395. .selectAll("line.nv-markerLine")
  2396. .data(markerLinesData)
  2397. .enter()
  2398. .append('line')
  2399. .attr('cursor', '')
  2400. .attr('class', 'nv-markerLine')
  2401. .attr('x1', function(d) { return x1(d.value) })
  2402. .attr('y1', '2')
  2403. .attr('x2', function(d) { return x1(d.value) })
  2404. .attr('y2', availableHeight - 2)
  2405. .on('mouseover', function(d) {
  2406. dispatch.elementMouseover({
  2407. value: d.value,
  2408. label: d.label || 'Previous',
  2409. color: d3.select(this).style("fill"),
  2410. pos: [x1(d.value), availableHeight/2]
  2411. })
  2412. })
  2413. .on('mousemove', function(d) {
  2414. dispatch.elementMousemove({
  2415. value: d.value,
  2416. label: d.label || 'Previous',
  2417. color: d3.select(this).style("fill")
  2418. })
  2419. })
  2420. .on('mouseout', function(d, i) {
  2421. dispatch.elementMouseout({
  2422. value: d.value,
  2423. label: d.label || 'Previous',
  2424. color: d3.select(this).style("fill")
  2425. })
  2426. });
  2427. g.selectAll("line.nv-markerLine")
  2428. .data(markerLinesData)
  2429. .transition()
  2430. .duration(duration)
  2431. .attr('x1', function(d) { return x1(d.value) })
  2432. .attr('x2', function(d) { return x1(d.value) });
  2433. wrap.selectAll('.nv-range')
  2434. .on('mouseover', function(d,i) {
  2435. var label = rangeLabelz[i] || defaultRangeLabels[i];
  2436. dispatch.elementMouseover({
  2437. value: d,
  2438. label: label,
  2439. color: d3.select(this).style("fill")
  2440. })
  2441. })
  2442. .on('mousemove', function() {
  2443. dispatch.elementMousemove({
  2444. value: measurez[0],
  2445. label: measureLabelz[0] || 'Previous',
  2446. color: d3.select(this).style("fill")
  2447. })
  2448. })
  2449. .on('mouseout', function(d,i) {
  2450. var label = rangeLabelz[i] || defaultRangeLabels[i];
  2451. dispatch.elementMouseout({
  2452. value: d,
  2453. label: label,
  2454. color: d3.select(this).style("fill")
  2455. })
  2456. });
  2457. });
  2458. return chart;
  2459. }
  2460. //============================================================
  2461. // Expose Public Variables
  2462. //------------------------------------------------------------
  2463. chart.dispatch = dispatch;
  2464. chart.options = nv.utils.optionsFunc.bind(chart);
  2465. chart._options = Object.create({}, {
  2466. // simple options, just get/set the necessary values
  2467. ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good)
  2468. markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal)
  2469. measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast)
  2470. forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
  2471. width: {get: function(){return width;}, set: function(_){width=_;}},
  2472. height: {get: function(){return height;}, set: function(_){height=_;}},
  2473. tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}},
  2474. duration: {get: function(){return duration;}, set: function(_){duration=_;}},
  2475. // options that require extra logic in the setter
  2476. margin: {get: function(){return margin;}, set: function(_){
  2477. margin.top = _.top !== undefined ? _.top : margin.top;
  2478. margin.right = _.right !== undefined ? _.right : margin.right;
  2479. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  2480. margin.left = _.left !== undefined ? _.left : margin.left;
  2481. }},
  2482. orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
  2483. orient = _;
  2484. reverse = orient == 'right' || orient == 'bottom';
  2485. }},
  2486. color: {get: function(){return color;}, set: function(_){
  2487. color = nv.utils.getColor(_);
  2488. }}
  2489. });
  2490. nv.utils.initOptions(chart);
  2491. return chart;
  2492. };
  2493. // Chart design based on the recommendations of Stephen Few. Implementation
  2494. // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
  2495. // http://projects.instantcognition.com/protovis/bulletchart/
  2496. nv.models.bulletChart = function() {
  2497. "use strict";
  2498. //============================================================
  2499. // Public Variables with Default Settings
  2500. //------------------------------------------------------------
  2501. var bullet = nv.models.bullet();
  2502. var tooltip = nv.models.tooltip();
  2503. var orient = 'left' // TODO top & bottom
  2504. , reverse = false
  2505. , margin = {top: 5, right: 40, bottom: 20, left: 120}
  2506. , ranges = function(d) { return d.ranges }
  2507. , markers = function(d) { return d.markers ? d.markers : [] }
  2508. , measures = function(d) { return d.measures }
  2509. , width = null
  2510. , height = 55
  2511. , tickFormat = null
  2512. , ticks = null
  2513. , noData = null
  2514. , dispatch = d3.dispatch()
  2515. ;
  2516. tooltip
  2517. .duration(0)
  2518. .headerEnabled(false);
  2519. function chart(selection) {
  2520. selection.each(function(d, i) {
  2521. var container = d3.select(this);
  2522. nv.utils.initSVG(container);
  2523. var availableWidth = nv.utils.availableWidth(width, container, margin),
  2524. availableHeight = height - margin.top - margin.bottom,
  2525. that = this;
  2526. chart.update = function() { chart(selection) };
  2527. chart.container = this;
  2528. // Display No Data message if there's nothing to show.
  2529. if (!d || !ranges.call(this, d, i)) {
  2530. nv.utils.noData(chart, container)
  2531. return chart;
  2532. } else {
  2533. container.selectAll('.nv-noData').remove();
  2534. }
  2535. var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
  2536. markerz = markers.call(this, d, i).slice().sort(d3.descending),
  2537. measurez = measures.call(this, d, i).slice().sort(d3.descending);
  2538. // Setup containers and skeleton of chart
  2539. var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]);
  2540. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart');
  2541. var gEnter = wrapEnter.append('g');
  2542. var g = wrap.select('g');
  2543. gEnter.append('g').attr('class', 'nv-bulletWrap');
  2544. gEnter.append('g').attr('class', 'nv-titles');
  2545. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  2546. // Compute the new x-scale.
  2547. var x1 = d3.scale.linear()
  2548. .domain([0, Math.max(rangez[0], (markerz[0] || 0), measurez[0])]) // TODO: need to allow forceX and forceY, and xDomain, yDomain
  2549. .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
  2550. // Retrieve the old x-scale, if this is an update.
  2551. var x0 = this.__chart__ || d3.scale.linear()
  2552. .domain([0, Infinity])
  2553. .range(x1.range());
  2554. // Stash the new scale.
  2555. this.__chart__ = x1;
  2556. var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
  2557. w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
  2558. var title = gEnter.select('.nv-titles').append('g')
  2559. .attr('text-anchor', 'end')
  2560. .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')');
  2561. title.append('text')
  2562. .attr('class', 'nv-title')
  2563. .text(function(d) { return d.title; });
  2564. title.append('text')
  2565. .attr('class', 'nv-subtitle')
  2566. .attr('dy', '1em')
  2567. .text(function(d) { return d.subtitle; });
  2568. bullet
  2569. .width(availableWidth)
  2570. .height(availableHeight);
  2571. var bulletWrap = g.select('.nv-bulletWrap');
  2572. d3.transition(bulletWrap).call(bullet);
  2573. // Compute the tick format.
  2574. var format = tickFormat || x1.tickFormat( availableWidth / 100 );
  2575. // Update the tick groups.
  2576. var tick = g.selectAll('g.nv-tick')
  2577. .data(x1.ticks( ticks ? ticks : (availableWidth / 50) ), function(d) {
  2578. return this.textContent || format(d);
  2579. });
  2580. // Initialize the ticks with the old scale, x0.
  2581. var tickEnter = tick.enter().append('g')
  2582. .attr('class', 'nv-tick')
  2583. .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' })
  2584. .style('opacity', 1e-6);
  2585. tickEnter.append('line')
  2586. .attr('y1', availableHeight)
  2587. .attr('y2', availableHeight * 7 / 6);
  2588. tickEnter.append('text')
  2589. .attr('text-anchor', 'middle')
  2590. .attr('dy', '1em')
  2591. .attr('y', availableHeight * 7 / 6)
  2592. .text(format);
  2593. // Transition the updating ticks to the new scale, x1.
  2594. var tickUpdate = d3.transition(tick)
  2595. .transition()
  2596. .duration(bullet.duration())
  2597. .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
  2598. .style('opacity', 1);
  2599. tickUpdate.select('line')
  2600. .attr('y1', availableHeight)
  2601. .attr('y2', availableHeight * 7 / 6);
  2602. tickUpdate.select('text')
  2603. .attr('y', availableHeight * 7 / 6);
  2604. // Transition the exiting ticks to the new scale, x1.
  2605. d3.transition(tick.exit())
  2606. .transition()
  2607. .duration(bullet.duration())
  2608. .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
  2609. .style('opacity', 1e-6)
  2610. .remove();
  2611. });
  2612. d3.timer.flush();
  2613. return chart;
  2614. }
  2615. //============================================================
  2616. // Event Handling/Dispatching (out of chart's scope)
  2617. //------------------------------------------------------------
  2618. bullet.dispatch.on('elementMouseover.tooltip', function(evt) {
  2619. evt['series'] = {
  2620. key: evt.label,
  2621. value: evt.value,
  2622. color: evt.color
  2623. };
  2624. tooltip.data(evt).hidden(false);
  2625. });
  2626. bullet.dispatch.on('elementMouseout.tooltip', function(evt) {
  2627. tooltip.hidden(true);
  2628. });
  2629. bullet.dispatch.on('elementMousemove.tooltip', function(evt) {
  2630. tooltip();
  2631. });
  2632. //============================================================
  2633. // Expose Public Variables
  2634. //------------------------------------------------------------
  2635. chart.bullet = bullet;
  2636. chart.dispatch = dispatch;
  2637. chart.tooltip = tooltip;
  2638. chart.options = nv.utils.optionsFunc.bind(chart);
  2639. chart._options = Object.create({}, {
  2640. // simple options, just get/set the necessary values
  2641. ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good)
  2642. markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal)
  2643. measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast)
  2644. width: {get: function(){return width;}, set: function(_){width=_;}},
  2645. height: {get: function(){return height;}, set: function(_){height=_;}},
  2646. tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}},
  2647. ticks: {get: function(){return ticks;}, set: function(_){ticks=_;}},
  2648. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  2649. // options that require extra logic in the setter
  2650. margin: {get: function(){return margin;}, set: function(_){
  2651. margin.top = _.top !== undefined ? _.top : margin.top;
  2652. margin.right = _.right !== undefined ? _.right : margin.right;
  2653. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  2654. margin.left = _.left !== undefined ? _.left : margin.left;
  2655. }},
  2656. orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
  2657. orient = _;
  2658. reverse = orient == 'right' || orient == 'bottom';
  2659. }}
  2660. });
  2661. nv.utils.inheritOptions(chart, bullet);
  2662. nv.utils.initOptions(chart);
  2663. return chart;
  2664. };
  2665. nv.models.candlestickBar = function() {
  2666. "use strict";
  2667. //============================================================
  2668. // Public Variables with Default Settings
  2669. //------------------------------------------------------------
  2670. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  2671. , width = null
  2672. , height = null
  2673. , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
  2674. , container
  2675. , x = d3.scale.linear()
  2676. , y = d3.scale.linear()
  2677. , getX = function(d) { return d.x }
  2678. , getY = function(d) { return d.y }
  2679. , getOpen = function(d) { return d.open }
  2680. , getClose = function(d) { return d.close }
  2681. , getHigh = function(d) { return d.high }
  2682. , getLow = function(d) { return d.low }
  2683. , forceX = []
  2684. , forceY = []
  2685. , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
  2686. , clipEdge = true
  2687. , color = nv.utils.defaultColor()
  2688. , interactive = false
  2689. , xDomain
  2690. , yDomain
  2691. , xRange
  2692. , yRange
  2693. , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove')
  2694. ;
  2695. //============================================================
  2696. // Private Variables
  2697. //------------------------------------------------------------
  2698. function chart(selection) {
  2699. selection.each(function(data) {
  2700. container = d3.select(this);
  2701. var availableWidth = nv.utils.availableWidth(width, container, margin),
  2702. availableHeight = nv.utils.availableHeight(height, container, margin);
  2703. nv.utils.initSVG(container);
  2704. // Width of the candlestick bars.
  2705. var barWidth = (availableWidth / data[0].values.length) * .45;
  2706. // Setup Scales
  2707. x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
  2708. if (padData)
  2709. x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
  2710. else
  2711. x.range(xRange || [5 + barWidth / 2, availableWidth - barWidth / 2 - 5]);
  2712. y.domain(yDomain || [
  2713. d3.min(data[0].values.map(getLow).concat(forceY)),
  2714. d3.max(data[0].values.map(getHigh).concat(forceY))
  2715. ]
  2716. ).range(yRange || [availableHeight, 0]);
  2717. // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
  2718. if (x.domain()[0] === x.domain()[1])
  2719. x.domain()[0] ?
  2720. x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
  2721. : x.domain([-1,1]);
  2722. if (y.domain()[0] === y.domain()[1])
  2723. y.domain()[0] ?
  2724. y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
  2725. : y.domain([-1,1]);
  2726. // Setup containers and skeleton of chart
  2727. var wrap = d3.select(this).selectAll('g.nv-wrap.nv-candlestickBar').data([data[0].values]);
  2728. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-candlestickBar');
  2729. var defsEnter = wrapEnter.append('defs');
  2730. var gEnter = wrapEnter.append('g');
  2731. var g = wrap.select('g');
  2732. gEnter.append('g').attr('class', 'nv-ticks');
  2733. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  2734. container
  2735. .on('click', function(d,i) {
  2736. dispatch.chartClick({
  2737. data: d,
  2738. index: i,
  2739. pos: d3.event,
  2740. id: id
  2741. });
  2742. });
  2743. defsEnter.append('clipPath')
  2744. .attr('id', 'nv-chart-clip-path-' + id)
  2745. .append('rect');
  2746. wrap.select('#nv-chart-clip-path-' + id + ' rect')
  2747. .attr('width', availableWidth)
  2748. .attr('height', availableHeight);
  2749. g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
  2750. var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
  2751. .data(function(d) { return d });
  2752. ticks.exit().remove();
  2753. var tickGroups = ticks.enter().append('g');
  2754. // The colors are currently controlled by CSS.
  2755. ticks
  2756. .attr('class', function(d, i, j) { return (getOpen(d, i) > getClose(d, i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i});
  2757. var lines = tickGroups.append('line')
  2758. .attr('class', 'nv-candlestick-lines')
  2759. .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; })
  2760. .attr('x1', 0)
  2761. .attr('y1', function(d, i) { return y(getHigh(d, i)); })
  2762. .attr('x2', 0)
  2763. .attr('y2', function(d, i) { return y(getLow(d, i)); });
  2764. var rects = tickGroups.append('rect')
  2765. .attr('class', 'nv-candlestick-rects nv-bars')
  2766. .attr('transform', function(d, i) {
  2767. return 'translate(' + (x(getX(d, i)) - barWidth/2) + ','
  2768. + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0))
  2769. + ')';
  2770. })
  2771. .attr('x', 0)
  2772. .attr('y', 0)
  2773. .attr('width', barWidth)
  2774. .attr('height', function(d, i) {
  2775. var open = getOpen(d, i);
  2776. var close = getClose(d, i);
  2777. return open > close ? y(close) - y(open) : y(open) - y(close);
  2778. });
  2779. ticks.select('.nv-candlestick-lines').transition()
  2780. .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; })
  2781. .attr('x1', 0)
  2782. .attr('y1', function(d, i) { return y(getHigh(d, i)); })
  2783. .attr('x2', 0)
  2784. .attr('y2', function(d, i) { return y(getLow(d, i)); });
  2785. ticks.select('.nv-candlestick-rects').transition()
  2786. .attr('transform', function(d, i) {
  2787. return 'translate(' + (x(getX(d, i)) - barWidth/2) + ','
  2788. + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0))
  2789. + ')';
  2790. })
  2791. .attr('x', 0)
  2792. .attr('y', 0)
  2793. .attr('width', barWidth)
  2794. .attr('height', function(d, i) {
  2795. var open = getOpen(d, i);
  2796. var close = getClose(d, i);
  2797. return open > close ? y(close) - y(open) : y(open) - y(close);
  2798. });
  2799. });
  2800. return chart;
  2801. }
  2802. //Create methods to allow outside functions to highlight a specific bar.
  2803. chart.highlightPoint = function(pointIndex, isHoverOver) {
  2804. chart.clearHighlights();
  2805. container.select(".nv-candlestickBar .nv-tick-0-" + pointIndex)
  2806. .classed("hover", isHoverOver)
  2807. ;
  2808. };
  2809. chart.clearHighlights = function() {
  2810. container.select(".nv-candlestickBar .nv-tick.hover")
  2811. .classed("hover", false)
  2812. ;
  2813. };
  2814. //============================================================
  2815. // Expose Public Variables
  2816. //------------------------------------------------------------
  2817. chart.dispatch = dispatch;
  2818. chart.options = nv.utils.optionsFunc.bind(chart);
  2819. chart._options = Object.create({}, {
  2820. // simple options, just get/set the necessary values
  2821. width: {get: function(){return width;}, set: function(_){width=_;}},
  2822. height: {get: function(){return height;}, set: function(_){height=_;}},
  2823. xScale: {get: function(){return x;}, set: function(_){x=_;}},
  2824. yScale: {get: function(){return y;}, set: function(_){y=_;}},
  2825. xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
  2826. yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
  2827. xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
  2828. yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
  2829. forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
  2830. forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
  2831. padData: {get: function(){return padData;}, set: function(_){padData=_;}},
  2832. clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
  2833. id: {get: function(){return id;}, set: function(_){id=_;}},
  2834. interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
  2835. x: {get: function(){return getX;}, set: function(_){getX=_;}},
  2836. y: {get: function(){return getY;}, set: function(_){getY=_;}},
  2837. open: {get: function(){return getOpen();}, set: function(_){getOpen=_;}},
  2838. close: {get: function(){return getClose();}, set: function(_){getClose=_;}},
  2839. high: {get: function(){return getHigh;}, set: function(_){getHigh=_;}},
  2840. low: {get: function(){return getLow;}, set: function(_){getLow=_;}},
  2841. // options that require extra logic in the setter
  2842. margin: {get: function(){return margin;}, set: function(_){
  2843. margin.top = _.top != undefined ? _.top : margin.top;
  2844. margin.right = _.right != undefined ? _.right : margin.right;
  2845. margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
  2846. margin.left = _.left != undefined ? _.left : margin.left;
  2847. }},
  2848. color: {get: function(){return color;}, set: function(_){
  2849. color = nv.utils.getColor(_);
  2850. }}
  2851. });
  2852. nv.utils.initOptions(chart);
  2853. return chart;
  2854. };
  2855. nv.models.cumulativeLineChart = function() {
  2856. "use strict";
  2857. //============================================================
  2858. // Public Variables with Default Settings
  2859. //------------------------------------------------------------
  2860. var lines = nv.models.line()
  2861. , xAxis = nv.models.axis()
  2862. , yAxis = nv.models.axis()
  2863. , legend = nv.models.legend()
  2864. , controls = nv.models.legend()
  2865. , interactiveLayer = nv.interactiveGuideline()
  2866. , tooltip = nv.models.tooltip()
  2867. ;
  2868. var margin = {top: 30, right: 30, bottom: 50, left: 60}
  2869. , marginTop = null
  2870. , color = nv.utils.defaultColor()
  2871. , width = null
  2872. , height = null
  2873. , showLegend = true
  2874. , showXAxis = true
  2875. , showYAxis = true
  2876. , rightAlignYAxis = false
  2877. , showControls = true
  2878. , useInteractiveGuideline = false
  2879. , rescaleY = true
  2880. , x //can be accessed via chart.xScale()
  2881. , y //can be accessed via chart.yScale()
  2882. , id = lines.id()
  2883. , state = nv.utils.state()
  2884. , defaultState = null
  2885. , noData = null
  2886. , average = function(d) { return d.average }
  2887. , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
  2888. , transitionDuration = 250
  2889. , duration = 250
  2890. , noErrorCheck = false //if set to TRUE, will bypass an error check in the indexify function.
  2891. ;
  2892. state.index = 0;
  2893. state.rescaleY = rescaleY;
  2894. xAxis.orient('bottom').tickPadding(7);
  2895. yAxis.orient((rightAlignYAxis) ? 'right' : 'left');
  2896. tooltip.valueFormatter(function(d, i) {
  2897. return yAxis.tickFormat()(d, i);
  2898. }).headerFormatter(function(d, i) {
  2899. return xAxis.tickFormat()(d, i);
  2900. });
  2901. controls.updateState(false);
  2902. //============================================================
  2903. // Private Variables
  2904. //------------------------------------------------------------
  2905. var dx = d3.scale.linear()
  2906. , index = {i: 0, x: 0}
  2907. , renderWatch = nv.utils.renderWatch(dispatch, duration)
  2908. ;
  2909. var stateGetter = function(data) {
  2910. return function(){
  2911. return {
  2912. active: data.map(function(d) { return !d.disabled }),
  2913. index: index.i,
  2914. rescaleY: rescaleY
  2915. };
  2916. }
  2917. };
  2918. var stateSetter = function(data) {
  2919. return function(state) {
  2920. if (state.index !== undefined)
  2921. index.i = state.index;
  2922. if (state.rescaleY !== undefined)
  2923. rescaleY = state.rescaleY;
  2924. if (state.active !== undefined)
  2925. data.forEach(function(series,i) {
  2926. series.disabled = !state.active[i];
  2927. });
  2928. }
  2929. };
  2930. function chart(selection) {
  2931. renderWatch.reset();
  2932. renderWatch.models(lines);
  2933. if (showXAxis) renderWatch.models(xAxis);
  2934. if (showYAxis) renderWatch.models(yAxis);
  2935. selection.each(function(data) {
  2936. var container = d3.select(this);
  2937. nv.utils.initSVG(container);
  2938. container.classed('nv-chart-' + id, true);
  2939. var that = this;
  2940. var availableWidth = nv.utils.availableWidth(width, container, margin),
  2941. availableHeight = nv.utils.availableHeight(height, container, margin);
  2942. chart.update = function() {
  2943. if (duration === 0)
  2944. container.call(chart);
  2945. else
  2946. container.transition().duration(duration).call(chart)
  2947. };
  2948. chart.container = this;
  2949. state
  2950. .setter(stateSetter(data), chart.update)
  2951. .getter(stateGetter(data))
  2952. .update();
  2953. // DEPRECATED set state.disableddisabled
  2954. state.disabled = data.map(function(d) { return !!d.disabled });
  2955. if (!defaultState) {
  2956. var key;
  2957. defaultState = {};
  2958. for (key in state) {
  2959. if (state[key] instanceof Array)
  2960. defaultState[key] = state[key].slice(0);
  2961. else
  2962. defaultState[key] = state[key];
  2963. }
  2964. }
  2965. var indexDrag = d3.behavior.drag()
  2966. .on('dragstart', dragStart)
  2967. .on('drag', dragMove)
  2968. .on('dragend', dragEnd);
  2969. function dragStart(d,i) {
  2970. d3.select(chart.container)
  2971. .style('cursor', 'ew-resize');
  2972. }
  2973. function dragMove(d,i) {
  2974. index.x = d3.event.x;
  2975. index.i = Math.round(dx.invert(index.x));
  2976. updateZero();
  2977. }
  2978. function dragEnd(d,i) {
  2979. d3.select(chart.container)
  2980. .style('cursor', 'auto');
  2981. // update state and send stateChange with new index
  2982. state.index = index.i;
  2983. dispatch.stateChange(state);
  2984. }
  2985. // Display No Data message if there's nothing to show.
  2986. if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
  2987. nv.utils.noData(chart, container)
  2988. return chart;
  2989. } else {
  2990. container.selectAll('.nv-noData').remove();
  2991. }
  2992. // Setup Scales
  2993. x = lines.xScale();
  2994. y = lines.yScale();
  2995. if (!rescaleY) {
  2996. var seriesDomains = data
  2997. .filter(function(series) { return !series.disabled })
  2998. .map(function(series,i) {
  2999. var initialDomain = d3.extent(series.values, lines.y());
  3000. //account for series being disabled when losing 95% or more
  3001. if (initialDomain[0] < -.95) initialDomain[0] = -.95;
  3002. return [
  3003. (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]),
  3004. (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0])
  3005. ];
  3006. });
  3007. var completeDomain = [
  3008. d3.min(seriesDomains, function(d) { return d[0] }),
  3009. d3.max(seriesDomains, function(d) { return d[1] })
  3010. ];
  3011. lines.yDomain(completeDomain);
  3012. } else {
  3013. lines.yDomain(null);
  3014. }
  3015. dx.domain([0, data[0].values.length - 1]) //Assumes all series have same length
  3016. .range([0, availableWidth])
  3017. .clamp(true);
  3018. var data = indexify(index.i, data);
  3019. // Setup containers and skeleton of chart
  3020. var interactivePointerEvents = (useInteractiveGuideline) ? "none" : "all";
  3021. var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]);
  3022. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g');
  3023. var g = wrap.select('g');
  3024. gEnter.append('g').attr('class', 'nv-interactive');
  3025. gEnter.append('g').attr('class', 'nv-x nv-axis').style("pointer-events","none");
  3026. gEnter.append('g').attr('class', 'nv-y nv-axis');
  3027. gEnter.append('g').attr('class', 'nv-background');
  3028. gEnter.append('g').attr('class', 'nv-linesWrap').style("pointer-events",interactivePointerEvents);
  3029. gEnter.append('g').attr('class', 'nv-avgLinesWrap').style("pointer-events","none");
  3030. gEnter.append('g').attr('class', 'nv-legendWrap');
  3031. gEnter.append('g').attr('class', 'nv-controlsWrap');
  3032. // Legend
  3033. if (!showLegend) {
  3034. g.select('.nv-legendWrap').selectAll('*').remove();
  3035. } else {
  3036. legend.width(availableWidth);
  3037. g.select('.nv-legendWrap')
  3038. .datum(data)
  3039. .call(legend);
  3040. if (!marginTop && legend.height() !== margin.top) {
  3041. margin.top = legend.height();
  3042. availableHeight = nv.utils.availableHeight(height, container, margin);
  3043. }
  3044. g.select('.nv-legendWrap')
  3045. .attr('transform', 'translate(0,' + (-margin.top) +')')
  3046. }
  3047. // Controls
  3048. if (!showControls) {
  3049. g.select('.nv-controlsWrap').selectAll('*').remove();
  3050. } else {
  3051. var controlsData = [
  3052. { key: 'Re-scale y-axis', disabled: !rescaleY }
  3053. ];
  3054. controls
  3055. .width(140)
  3056. .color(['#444', '#444', '#444'])
  3057. .rightAlign(false)
  3058. .margin({top: 5, right: 0, bottom: 5, left: 20})
  3059. ;
  3060. g.select('.nv-controlsWrap')
  3061. .datum(controlsData)
  3062. .attr('transform', 'translate(0,' + (-margin.top) +')')
  3063. .call(controls);
  3064. }
  3065. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  3066. if (rightAlignYAxis) {
  3067. g.select(".nv-y.nv-axis")
  3068. .attr("transform", "translate(" + availableWidth + ",0)");
  3069. }
  3070. // Show error if series goes below 100%
  3071. var tempDisabled = data.filter(function(d) { return d.tempDisabled });
  3072. wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates
  3073. if (tempDisabled.length) {
  3074. wrap.append('text').attr('class', 'tempDisabled')
  3075. .attr('x', availableWidth / 2)
  3076. .attr('y', '-.71em')
  3077. .style('text-anchor', 'end')
  3078. .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.');
  3079. }
  3080. //Set up interactive layer
  3081. if (useInteractiveGuideline) {
  3082. interactiveLayer
  3083. .width(availableWidth)
  3084. .height(availableHeight)
  3085. .margin({left:margin.left,top:margin.top})
  3086. .svgContainer(container)
  3087. .xScale(x);
  3088. wrap.select(".nv-interactive").call(interactiveLayer);
  3089. }
  3090. gEnter.select('.nv-background')
  3091. .append('rect');
  3092. g.select('.nv-background rect')
  3093. .attr('width', availableWidth)
  3094. .attr('height', availableHeight);
  3095. lines
  3096. //.x(function(d) { return d.x })
  3097. .y(function(d) { return d.display.y })
  3098. .width(availableWidth)
  3099. .height(availableHeight)
  3100. .color(data.map(function(d,i) {
  3101. return d.color || color(d, i);
  3102. }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; }));
  3103. var linesWrap = g.select('.nv-linesWrap')
  3104. .datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled }));
  3105. linesWrap.call(lines);
  3106. //Store a series index number in the data array.
  3107. data.forEach(function(d,i) {
  3108. d.seriesIndex = i;
  3109. });
  3110. var avgLineData = data.filter(function(d) {
  3111. return !d.disabled && !!average(d);
  3112. });
  3113. var avgLines = g.select(".nv-avgLinesWrap").selectAll("line")
  3114. .data(avgLineData, function(d) { return d.key; });
  3115. var getAvgLineY = function(d) {
  3116. //If average lines go off the svg element, clamp them to the svg bounds.
  3117. var yVal = y(average(d));
  3118. if (yVal < 0) return 0;
  3119. if (yVal > availableHeight) return availableHeight;
  3120. return yVal;
  3121. };
  3122. avgLines.enter()
  3123. .append('line')
  3124. .style('stroke-width',2)
  3125. .style('stroke-dasharray','10,10')
  3126. .style('stroke',function (d,i) {
  3127. return lines.color()(d,d.seriesIndex);
  3128. })
  3129. .attr('x1',0)
  3130. .attr('x2',availableWidth)
  3131. .attr('y1', getAvgLineY)
  3132. .attr('y2', getAvgLineY);
  3133. avgLines
  3134. .style('stroke-opacity',function(d){
  3135. //If average lines go offscreen, make them transparent
  3136. var yVal = y(average(d));
  3137. if (yVal < 0 || yVal > availableHeight) return 0;
  3138. return 1;
  3139. })
  3140. .attr('x1',0)
  3141. .attr('x2',availableWidth)
  3142. .attr('y1', getAvgLineY)
  3143. .attr('y2', getAvgLineY);
  3144. avgLines.exit().remove();
  3145. //Create index line
  3146. var indexLine = linesWrap.selectAll('.nv-indexLine')
  3147. .data([index]);
  3148. indexLine.enter().append('rect').attr('class', 'nv-indexLine')
  3149. .attr('width', 3)
  3150. .attr('x', -2)
  3151. .attr('fill', 'red')
  3152. .attr('fill-opacity', .5)
  3153. .style("pointer-events","all")
  3154. .call(indexDrag);
  3155. indexLine
  3156. .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' })
  3157. .attr('height', availableHeight);
  3158. // Setup Axes
  3159. if (showXAxis) {
  3160. xAxis
  3161. .scale(x)
  3162. ._ticks( nv.utils.calcTicksX(availableWidth/70, data) )
  3163. .tickSize(-availableHeight, 0);
  3164. g.select('.nv-x.nv-axis')
  3165. .attr('transform', 'translate(0,' + y.range()[0] + ')');
  3166. g.select('.nv-x.nv-axis')
  3167. .call(xAxis);
  3168. }
  3169. if (showYAxis) {
  3170. yAxis
  3171. .scale(y)
  3172. ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
  3173. .tickSize( -availableWidth, 0);
  3174. g.select('.nv-y.nv-axis')
  3175. .call(yAxis);
  3176. }
  3177. //============================================================
  3178. // Event Handling/Dispatching (in chart's scope)
  3179. //------------------------------------------------------------
  3180. function updateZero() {
  3181. indexLine
  3182. .data([index]);
  3183. //When dragging the index line, turn off line transitions.
  3184. // Then turn them back on when done dragging.
  3185. var oldDuration = chart.duration();
  3186. chart.duration(0);
  3187. chart.update();
  3188. chart.duration(oldDuration);
  3189. }
  3190. g.select('.nv-background rect')
  3191. .on('click', function() {
  3192. index.x = d3.mouse(this)[0];
  3193. index.i = Math.round(dx.invert(index.x));
  3194. // update state and send stateChange with new index
  3195. state.index = index.i;
  3196. dispatch.stateChange(state);
  3197. updateZero();
  3198. });
  3199. lines.dispatch.on('elementClick', function(e) {
  3200. index.i = e.pointIndex;
  3201. index.x = dx(index.i);
  3202. // update state and send stateChange with new index
  3203. state.index = index.i;
  3204. dispatch.stateChange(state);
  3205. updateZero();
  3206. });
  3207. controls.dispatch.on('legendClick', function(d,i) {
  3208. d.disabled = !d.disabled;
  3209. rescaleY = !d.disabled;
  3210. state.rescaleY = rescaleY;
  3211. dispatch.stateChange(state);
  3212. chart.update();
  3213. });
  3214. legend.dispatch.on('stateChange', function(newState) {
  3215. for (var key in newState)
  3216. state[key] = newState[key];
  3217. dispatch.stateChange(state);
  3218. chart.update();
  3219. });
  3220. interactiveLayer.dispatch.on('elementMousemove', function(e) {
  3221. lines.clearHighlights();
  3222. var singlePoint, pointIndex, pointXLocation, allData = [];
  3223. data
  3224. .filter(function(series, i) {
  3225. series.seriesIndex = i;
  3226. return !series.disabled;
  3227. })
  3228. .forEach(function(series,i) {
  3229. pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
  3230. lines.highlightPoint(i, pointIndex, true);
  3231. var point = series.values[pointIndex];
  3232. if (typeof point === 'undefined') return;
  3233. if (typeof singlePoint === 'undefined') singlePoint = point;
  3234. if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
  3235. allData.push({
  3236. key: series.key,
  3237. value: chart.y()(point, pointIndex),
  3238. color: color(series,series.seriesIndex)
  3239. });
  3240. });
  3241. //Highlight the tooltip entry based on which point the mouse is closest to.
  3242. if (allData.length > 2) {
  3243. var yValue = chart.yScale().invert(e.mouseY);
  3244. var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
  3245. var threshold = 0.03 * domainExtent;
  3246. var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
  3247. if (indexToHighlight !== null)
  3248. allData[indexToHighlight].highlight = true;
  3249. }
  3250. var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex), pointIndex);
  3251. interactiveLayer.tooltip
  3252. .valueFormatter(function(d,i) {
  3253. return yAxis.tickFormat()(d);
  3254. })
  3255. .data(
  3256. {
  3257. value: xValue,
  3258. series: allData
  3259. }
  3260. )();
  3261. interactiveLayer.renderGuideLine(pointXLocation);
  3262. });
  3263. interactiveLayer.dispatch.on("elementMouseout",function(e) {
  3264. lines.clearHighlights();
  3265. });
  3266. // Update chart from a state object passed to event handler
  3267. dispatch.on('changeState', function(e) {
  3268. if (typeof e.disabled !== 'undefined') {
  3269. data.forEach(function(series,i) {
  3270. series.disabled = e.disabled[i];
  3271. });
  3272. state.disabled = e.disabled;
  3273. }
  3274. if (typeof e.index !== 'undefined') {
  3275. index.i = e.index;
  3276. index.x = dx(index.i);
  3277. state.index = e.index;
  3278. indexLine
  3279. .data([index]);
  3280. }
  3281. if (typeof e.rescaleY !== 'undefined') {
  3282. rescaleY = e.rescaleY;
  3283. }
  3284. chart.update();
  3285. });
  3286. });
  3287. renderWatch.renderEnd('cumulativeLineChart immediate');
  3288. return chart;
  3289. }
  3290. //============================================================
  3291. // Event Handling/Dispatching (out of chart's scope)
  3292. //------------------------------------------------------------
  3293. lines.dispatch.on('elementMouseover.tooltip', function(evt) {
  3294. var point = {
  3295. x: chart.x()(evt.point),
  3296. y: chart.y()(evt.point),
  3297. color: evt.point.color
  3298. };
  3299. evt.point = point;
  3300. tooltip.data(evt).hidden(false);
  3301. });
  3302. lines.dispatch.on('elementMouseout.tooltip', function(evt) {
  3303. tooltip.hidden(true)
  3304. });
  3305. //============================================================
  3306. // Functions
  3307. //------------------------------------------------------------
  3308. var indexifyYGetter = null;
  3309. /* Normalize the data according to an index point. */
  3310. function indexify(idx, data) {
  3311. if (!indexifyYGetter) indexifyYGetter = lines.y();
  3312. return data.map(function(line, i) {
  3313. if (!line.values) {
  3314. return line;
  3315. }
  3316. var indexValue = line.values[idx];
  3317. if (indexValue == null) {
  3318. return line;
  3319. }
  3320. var v = indexifyYGetter(indexValue, idx);
  3321. //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue
  3322. if (v < -.95 && !noErrorCheck) {
  3323. //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100)
  3324. line.tempDisabled = true;
  3325. return line;
  3326. }
  3327. line.tempDisabled = false;
  3328. line.values = line.values.map(function(point, pointIndex) {
  3329. point.display = {'y': (indexifyYGetter(point, pointIndex) - v) / (1 + v) };
  3330. return point;
  3331. });
  3332. return line;
  3333. })
  3334. }
  3335. //============================================================
  3336. // Expose Public Variables
  3337. //------------------------------------------------------------
  3338. // expose chart's sub-components
  3339. chart.dispatch = dispatch;
  3340. chart.lines = lines;
  3341. chart.legend = legend;
  3342. chart.controls = controls;
  3343. chart.xAxis = xAxis;
  3344. chart.yAxis = yAxis;
  3345. chart.interactiveLayer = interactiveLayer;
  3346. chart.state = state;
  3347. chart.tooltip = tooltip;
  3348. chart.options = nv.utils.optionsFunc.bind(chart);
  3349. chart._options = Object.create({}, {
  3350. // simple options, just get/set the necessary values
  3351. width: {get: function(){return width;}, set: function(_){width=_;}},
  3352. height: {get: function(){return height;}, set: function(_){height=_;}},
  3353. rescaleY: {get: function(){return rescaleY;}, set: function(_){rescaleY=_;}},
  3354. showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
  3355. showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
  3356. average: {get: function(){return average;}, set: function(_){average=_;}},
  3357. defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
  3358. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  3359. showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
  3360. showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
  3361. noErrorCheck: {get: function(){return noErrorCheck;}, set: function(_){noErrorCheck=_;}},
  3362. // options that require extra logic in the setter
  3363. margin: {get: function(){return margin;}, set: function(_){
  3364. if (_.top !== undefined) {
  3365. margin.top = _.top;
  3366. marginTop = _.top;
  3367. }
  3368. margin.right = _.right !== undefined ? _.right : margin.right;
  3369. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  3370. margin.left = _.left !== undefined ? _.left : margin.left;
  3371. }},
  3372. color: {get: function(){return color;}, set: function(_){
  3373. color = nv.utils.getColor(_);
  3374. legend.color(color);
  3375. }},
  3376. useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
  3377. useInteractiveGuideline = _;
  3378. if (_ === true) {
  3379. chart.interactive(false);
  3380. chart.useVoronoi(false);
  3381. }
  3382. }},
  3383. rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
  3384. rightAlignYAxis = _;
  3385. yAxis.orient( (_) ? 'right' : 'left');
  3386. }},
  3387. duration: {get: function(){return duration;}, set: function(_){
  3388. duration = _;
  3389. lines.duration(duration);
  3390. xAxis.duration(duration);
  3391. yAxis.duration(duration);
  3392. renderWatch.reset(duration);
  3393. }}
  3394. });
  3395. nv.utils.inheritOptions(chart, lines);
  3396. nv.utils.initOptions(chart);
  3397. return chart;
  3398. };
  3399. //TODO: consider deprecating by adding necessary features to multiBar model
  3400. nv.models.discreteBar = function() {
  3401. "use strict";
  3402. //============================================================
  3403. // Public Variables with Default Settings
  3404. //------------------------------------------------------------
  3405. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  3406. , width = 960
  3407. , height = 500
  3408. , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
  3409. , container
  3410. , x = d3.scale.ordinal()
  3411. , y = d3.scale.linear()
  3412. , getX = function(d) { return d.x }
  3413. , getY = function(d) { return d.y }
  3414. , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
  3415. , color = nv.utils.defaultColor()
  3416. , showValues = false
  3417. , valueFormat = d3.format(',.2f')
  3418. , xDomain
  3419. , yDomain
  3420. , xRange
  3421. , yRange
  3422. , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
  3423. , rectClass = 'discreteBar'
  3424. , duration = 250
  3425. ;
  3426. //============================================================
  3427. // Private Variables
  3428. //------------------------------------------------------------
  3429. var x0, y0;
  3430. var renderWatch = nv.utils.renderWatch(dispatch, duration);
  3431. function chart(selection) {
  3432. renderWatch.reset();
  3433. selection.each(function(data) {
  3434. var availableWidth = width - margin.left - margin.right,
  3435. availableHeight = height - margin.top - margin.bottom;
  3436. container = d3.select(this);
  3437. nv.utils.initSVG(container);
  3438. //add series index to each data point for reference
  3439. data.forEach(function(series, i) {
  3440. series.values.forEach(function(point) {
  3441. point.series = i;
  3442. });
  3443. });
  3444. // Setup Scales
  3445. // remap and flatten the data for use in calculating the scales' domains
  3446. var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
  3447. data.map(function(d) {
  3448. return d.values.map(function(d,i) {
  3449. return { x: getX(d,i), y: getY(d,i), y0: d.y0 }
  3450. })
  3451. });
  3452. x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
  3453. .rangeBands(xRange || [0, availableWidth], .1);
  3454. y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY)));
  3455. // If showValues, pad the Y axis range to account for label height
  3456. if (showValues) y.range(yRange || [availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]);
  3457. else y.range(yRange || [availableHeight, 0]);
  3458. //store old scales if they exist
  3459. x0 = x0 || x;
  3460. y0 = y0 || y.copy().range([y(0),y(0)]);
  3461. // Setup containers and skeleton of chart
  3462. var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]);
  3463. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar');
  3464. var gEnter = wrapEnter.append('g');
  3465. var g = wrap.select('g');
  3466. gEnter.append('g').attr('class', 'nv-groups');
  3467. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  3468. //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later
  3469. var groups = wrap.select('.nv-groups').selectAll('.nv-group')
  3470. .data(function(d) { return d }, function(d) { return d.key });
  3471. groups.enter().append('g')
  3472. .style('stroke-opacity', 1e-6)
  3473. .style('fill-opacity', 1e-6);
  3474. groups.exit()
  3475. .watchTransition(renderWatch, 'discreteBar: exit groups')
  3476. .style('stroke-opacity', 1e-6)
  3477. .style('fill-opacity', 1e-6)
  3478. .remove();
  3479. groups
  3480. .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
  3481. .classed('hover', function(d) { return d.hover });
  3482. groups
  3483. .watchTransition(renderWatch, 'discreteBar: groups')
  3484. .style('stroke-opacity', 1)
  3485. .style('fill-opacity', .75);
  3486. var bars = groups.selectAll('g.nv-bar')
  3487. .data(function(d) { return d.values });
  3488. bars.exit().remove();
  3489. var barsEnter = bars.enter().append('g')
  3490. .attr('transform', function(d,i,j) {
  3491. return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')'
  3492. })
  3493. .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
  3494. d3.select(this).classed('hover', true);
  3495. dispatch.elementMouseover({
  3496. data: d,
  3497. index: i,
  3498. color: d3.select(this).style("fill")
  3499. });
  3500. })
  3501. .on('mouseout', function(d,i) {
  3502. d3.select(this).classed('hover', false);
  3503. dispatch.elementMouseout({
  3504. data: d,
  3505. index: i,
  3506. color: d3.select(this).style("fill")
  3507. });
  3508. })
  3509. .on('mousemove', function(d,i) {
  3510. dispatch.elementMousemove({
  3511. data: d,
  3512. index: i,
  3513. color: d3.select(this).style("fill")
  3514. });
  3515. })
  3516. .on('click', function(d,i) {
  3517. var element = this;
  3518. dispatch.elementClick({
  3519. data: d,
  3520. index: i,
  3521. color: d3.select(this).style("fill"),
  3522. event: d3.event,
  3523. element: element
  3524. });
  3525. d3.event.stopPropagation();
  3526. })
  3527. .on('dblclick', function(d,i) {
  3528. dispatch.elementDblClick({
  3529. data: d,
  3530. index: i,
  3531. color: d3.select(this).style("fill")
  3532. });
  3533. d3.event.stopPropagation();
  3534. });
  3535. barsEnter.append('rect')
  3536. .attr('height', 0)
  3537. .attr('width', x.rangeBand() * .9 / data.length )
  3538. if (showValues) {
  3539. barsEnter.append('text')
  3540. .attr('text-anchor', 'middle')
  3541. ;
  3542. bars.select('text')
  3543. .text(function(d,i) { return valueFormat(getY(d,i)) })
  3544. .watchTransition(renderWatch, 'discreteBar: bars text')
  3545. .attr('x', x.rangeBand() * .9 / 2)
  3546. .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 })
  3547. ;
  3548. } else {
  3549. bars.selectAll('text').remove();
  3550. }
  3551. bars
  3552. .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' })
  3553. .style('fill', function(d,i) { return d.color || color(d,i) })
  3554. .style('stroke', function(d,i) { return d.color || color(d,i) })
  3555. .select('rect')
  3556. .attr('class', rectClass)
  3557. .watchTransition(renderWatch, 'discreteBar: bars rect')
  3558. .attr('width', x.rangeBand() * .9 / data.length);
  3559. bars.watchTransition(renderWatch, 'discreteBar: bars')
  3560. //.delay(function(d,i) { return i * 1200 / data[0].values.length })
  3561. .attr('transform', function(d,i) {
  3562. var left = x(getX(d,i)) + x.rangeBand() * .05,
  3563. top = getY(d,i) < 0 ?
  3564. y(0) :
  3565. y(0) - y(getY(d,i)) < 1 ?
  3566. y(0) - 1 : //make 1 px positive bars show up above y=0
  3567. y(getY(d,i));
  3568. return 'translate(' + left + ', ' + top + ')'
  3569. })
  3570. .select('rect')
  3571. .attr('height', function(d,i) {
  3572. return Math.max(Math.abs(y(getY(d,i)) - y(0)), 1)
  3573. });
  3574. //store old scales for use in transitions on update
  3575. x0 = x.copy();
  3576. y0 = y.copy();
  3577. });
  3578. renderWatch.renderEnd('discreteBar immediate');
  3579. return chart;
  3580. }
  3581. //============================================================
  3582. // Expose Public Variables
  3583. //------------------------------------------------------------
  3584. chart.dispatch = dispatch;
  3585. chart.options = nv.utils.optionsFunc.bind(chart);
  3586. chart._options = Object.create({}, {
  3587. // simple options, just get/set the necessary values
  3588. width: {get: function(){return width;}, set: function(_){width=_;}},
  3589. height: {get: function(){return height;}, set: function(_){height=_;}},
  3590. forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
  3591. showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}},
  3592. x: {get: function(){return getX;}, set: function(_){getX=_;}},
  3593. y: {get: function(){return getY;}, set: function(_){getY=_;}},
  3594. xScale: {get: function(){return x;}, set: function(_){x=_;}},
  3595. yScale: {get: function(){return y;}, set: function(_){y=_;}},
  3596. xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
  3597. yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
  3598. xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
  3599. yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
  3600. valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
  3601. id: {get: function(){return id;}, set: function(_){id=_;}},
  3602. rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}},
  3603. // options that require extra logic in the setter
  3604. margin: {get: function(){return margin;}, set: function(_){
  3605. margin.top = _.top !== undefined ? _.top : margin.top;
  3606. margin.right = _.right !== undefined ? _.right : margin.right;
  3607. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  3608. margin.left = _.left !== undefined ? _.left : margin.left;
  3609. }},
  3610. color: {get: function(){return color;}, set: function(_){
  3611. color = nv.utils.getColor(_);
  3612. }},
  3613. duration: {get: function(){return duration;}, set: function(_){
  3614. duration = _;
  3615. renderWatch.reset(duration);
  3616. }}
  3617. });
  3618. nv.utils.initOptions(chart);
  3619. return chart;
  3620. };
  3621. nv.models.discreteBarChart = function() {
  3622. "use strict";
  3623. //============================================================
  3624. // Public Variables with Default Settings
  3625. //------------------------------------------------------------
  3626. var discretebar = nv.models.discreteBar()
  3627. , xAxis = nv.models.axis()
  3628. , yAxis = nv.models.axis()
  3629. , legend = nv.models.legend()
  3630. , tooltip = nv.models.tooltip()
  3631. ;
  3632. var margin = {top: 15, right: 10, bottom: 50, left: 60}
  3633. , marginTop = null
  3634. , width = null
  3635. , height = null
  3636. , color = nv.utils.getColor()
  3637. , showLegend = false
  3638. , showXAxis = true
  3639. , showYAxis = true
  3640. , rightAlignYAxis = false
  3641. , staggerLabels = false
  3642. , wrapLabels = false
  3643. , rotateLabels = 0
  3644. , x
  3645. , y
  3646. , noData = null
  3647. , dispatch = d3.dispatch('beforeUpdate','renderEnd')
  3648. , duration = 250
  3649. ;
  3650. xAxis
  3651. .orient('bottom')
  3652. .showMaxMin(false)
  3653. .tickFormat(function(d) { return d })
  3654. ;
  3655. yAxis
  3656. .orient((rightAlignYAxis) ? 'right' : 'left')
  3657. .tickFormat(d3.format(',.1f'))
  3658. ;
  3659. tooltip
  3660. .duration(0)
  3661. .headerEnabled(false)
  3662. .valueFormatter(function(d, i) {
  3663. return yAxis.tickFormat()(d, i);
  3664. })
  3665. .keyFormatter(function(d, i) {
  3666. return xAxis.tickFormat()(d, i);
  3667. });
  3668. //============================================================
  3669. // Private Variables
  3670. //------------------------------------------------------------
  3671. var renderWatch = nv.utils.renderWatch(dispatch, duration);
  3672. function chart(selection) {
  3673. renderWatch.reset();
  3674. renderWatch.models(discretebar);
  3675. if (showXAxis) renderWatch.models(xAxis);
  3676. if (showYAxis) renderWatch.models(yAxis);
  3677. selection.each(function(data) {
  3678. var container = d3.select(this),
  3679. that = this;
  3680. nv.utils.initSVG(container);
  3681. var availableWidth = nv.utils.availableWidth(width, container, margin),
  3682. availableHeight = nv.utils.availableHeight(height, container, margin);
  3683. chart.update = function() {
  3684. dispatch.beforeUpdate();
  3685. container.transition().duration(duration).call(chart);
  3686. };
  3687. chart.container = this;
  3688. // Display No Data message if there's nothing to show.
  3689. if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
  3690. nv.utils.noData(chart, container);
  3691. return chart;
  3692. } else {
  3693. container.selectAll('.nv-noData').remove();
  3694. }
  3695. // Setup Scales
  3696. x = discretebar.xScale();
  3697. y = discretebar.yScale().clamp(true);
  3698. // Setup containers and skeleton of chart
  3699. var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]);
  3700. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g');
  3701. var defsEnter = gEnter.append('defs');
  3702. var g = wrap.select('g');
  3703. gEnter.append('g').attr('class', 'nv-x nv-axis');
  3704. gEnter.append('g').attr('class', 'nv-y nv-axis')
  3705. .append('g').attr('class', 'nv-zeroLine')
  3706. .append('line');
  3707. gEnter.append('g').attr('class', 'nv-barsWrap');
  3708. gEnter.append('g').attr('class', 'nv-legendWrap');
  3709. g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  3710. // Legend
  3711. if (!showLegend) {
  3712. g.select('.nv-legendWrap').selectAll('*').remove();
  3713. } else {
  3714. legend.width(availableWidth);
  3715. g.select('.nv-legendWrap')
  3716. .datum(data)
  3717. .call(legend);
  3718. if (!marginTop && legend.height() !== margin.top) {
  3719. margin.top = legend.height();
  3720. availableHeight = nv.utils.availableHeight(height, container, margin);
  3721. }
  3722. wrap.select('.nv-legendWrap')
  3723. .attr('transform', 'translate(0,' + (-margin.top) +')')
  3724. }
  3725. if (rightAlignYAxis) {
  3726. g.select(".nv-y.nv-axis")
  3727. .attr("transform", "translate(" + availableWidth + ",0)");
  3728. }
  3729. // Main Chart Component(s)
  3730. discretebar
  3731. .width(availableWidth)
  3732. .height(availableHeight);
  3733. var barsWrap = g.select('.nv-barsWrap')
  3734. .datum(data.filter(function(d) { return !d.disabled }));
  3735. barsWrap.transition().call(discretebar);
  3736. defsEnter.append('clipPath')
  3737. .attr('id', 'nv-x-label-clip-' + discretebar.id())
  3738. .append('rect');
  3739. g.select('#nv-x-label-clip-' + discretebar.id() + ' rect')
  3740. .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
  3741. .attr('height', 16)
  3742. .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
  3743. // Setup Axes
  3744. if (showXAxis) {
  3745. xAxis
  3746. .scale(x)
  3747. ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
  3748. .tickSize(-availableHeight, 0);
  3749. g.select('.nv-x.nv-axis')
  3750. .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')');
  3751. g.select('.nv-x.nv-axis').call(xAxis);
  3752. var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
  3753. if (staggerLabels) {
  3754. xTicks
  3755. .selectAll('text')
  3756. .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
  3757. }
  3758. if (rotateLabels) {
  3759. xTicks
  3760. .selectAll('.tick text')
  3761. .attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
  3762. .style('text-anchor', rotateLabels > 0 ? 'start' : 'end');
  3763. }
  3764. if (wrapLabels) {
  3765. g.selectAll('.tick text')
  3766. .call(nv.utils.wrapTicks, chart.xAxis.rangeBand())
  3767. }
  3768. }
  3769. if (showYAxis) {
  3770. yAxis
  3771. .scale(y)
  3772. ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
  3773. .tickSize( -availableWidth, 0);
  3774. g.select('.nv-y.nv-axis').call(yAxis);
  3775. }
  3776. // Zero line
  3777. g.select(".nv-zeroLine line")
  3778. .attr("x1",0)
  3779. .attr("x2",(rightAlignYAxis) ? -availableWidth : availableWidth)
  3780. .attr("y1", y(0))
  3781. .attr("y2", y(0))
  3782. ;
  3783. });
  3784. renderWatch.renderEnd('discreteBar chart immediate');
  3785. return chart;
  3786. }
  3787. //============================================================
  3788. // Event Handling/Dispatching (out of chart's scope)
  3789. //------------------------------------------------------------
  3790. discretebar.dispatch.on('elementMouseover.tooltip', function(evt) {
  3791. evt['series'] = {
  3792. key: chart.x()(evt.data),
  3793. value: chart.y()(evt.data),
  3794. color: evt.color
  3795. };
  3796. tooltip.data(evt).hidden(false);
  3797. });
  3798. discretebar.dispatch.on('elementMouseout.tooltip', function(evt) {
  3799. tooltip.hidden(true);
  3800. });
  3801. discretebar.dispatch.on('elementMousemove.tooltip', function(evt) {
  3802. tooltip();
  3803. });
  3804. //============================================================
  3805. // Expose Public Variables
  3806. //------------------------------------------------------------
  3807. chart.dispatch = dispatch;
  3808. chart.discretebar = discretebar;
  3809. chart.legend = legend;
  3810. chart.xAxis = xAxis;
  3811. chart.yAxis = yAxis;
  3812. chart.tooltip = tooltip;
  3813. chart.options = nv.utils.optionsFunc.bind(chart);
  3814. chart._options = Object.create({}, {
  3815. // simple options, just get/set the necessary values
  3816. width: {get: function(){return width;}, set: function(_){width=_;}},
  3817. height: {get: function(){return height;}, set: function(_){height=_;}},
  3818. showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
  3819. staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
  3820. rotateLabels: {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
  3821. wrapLabels: {get: function(){return wrapLabels;}, set: function(_){wrapLabels=!!_;}},
  3822. showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
  3823. showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
  3824. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  3825. // options that require extra logic in the setter
  3826. margin: {get: function(){return margin;}, set: function(_){
  3827. if (_.top !== undefined) {
  3828. margin.top = _.top;
  3829. marginTop = _.top;
  3830. }
  3831. margin.right = _.right !== undefined ? _.right : margin.right;
  3832. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  3833. margin.left = _.left !== undefined ? _.left : margin.left;
  3834. }},
  3835. duration: {get: function(){return duration;}, set: function(_){
  3836. duration = _;
  3837. renderWatch.reset(duration);
  3838. discretebar.duration(duration);
  3839. xAxis.duration(duration);
  3840. yAxis.duration(duration);
  3841. }},
  3842. color: {get: function(){return color;}, set: function(_){
  3843. color = nv.utils.getColor(_);
  3844. discretebar.color(color);
  3845. legend.color(color);
  3846. }},
  3847. rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
  3848. rightAlignYAxis = _;
  3849. yAxis.orient( (_) ? 'right' : 'left');
  3850. }}
  3851. });
  3852. nv.utils.inheritOptions(chart, discretebar);
  3853. nv.utils.initOptions(chart);
  3854. return chart;
  3855. }
  3856. nv.models.distribution = function() {
  3857. "use strict";
  3858. //============================================================
  3859. // Public Variables with Default Settings
  3860. //------------------------------------------------------------
  3861. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  3862. , width = 400 //technically width or height depending on x or y....
  3863. , size = 8
  3864. , axis = 'x' // 'x' or 'y'... horizontal or vertical
  3865. , getData = function(d) { return d[axis] } // defaults d.x or d.y
  3866. , color = nv.utils.defaultColor()
  3867. , scale = d3.scale.linear()
  3868. , domain
  3869. , duration = 250
  3870. , dispatch = d3.dispatch('renderEnd')
  3871. ;
  3872. //============================================================
  3873. //============================================================
  3874. // Private Variables
  3875. //------------------------------------------------------------
  3876. var scale0;
  3877. var renderWatch = nv.utils.renderWatch(dispatch, duration);
  3878. //============================================================
  3879. function chart(selection) {
  3880. renderWatch.reset();
  3881. selection.each(function(data) {
  3882. var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom),
  3883. naxis = axis == 'x' ? 'y' : 'x',
  3884. container = d3.select(this);
  3885. nv.utils.initSVG(container);
  3886. //------------------------------------------------------------
  3887. // Setup Scales
  3888. scale0 = scale0 || scale;
  3889. //------------------------------------------------------------
  3890. //------------------------------------------------------------
  3891. // Setup containers and skeleton of chart
  3892. var wrap = container.selectAll('g.nv-distribution').data([data]);
  3893. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution');
  3894. var gEnter = wrapEnter.append('g');
  3895. var g = wrap.select('g');
  3896. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
  3897. //------------------------------------------------------------
  3898. var distWrap = g.selectAll('g.nv-dist')
  3899. .data(function(d) { return d }, function(d) { return d.key });
  3900. distWrap.enter().append('g');
  3901. distWrap
  3902. .attr('class', function(d,i) { return 'nv-dist nv-series-' + i })
  3903. .style('stroke', function(d,i) { return color(d, i) });
  3904. var dist = distWrap.selectAll('line.nv-dist' + axis)
  3905. .data(function(d) { return d.values })
  3906. dist.enter().append('line')
  3907. .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) })
  3908. .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) })
  3909. renderWatch.transition(distWrap.exit().selectAll('line.nv-dist' + axis), 'dist exit')
  3910. // .transition()
  3911. .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
  3912. .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
  3913. .style('stroke-opacity', 0)
  3914. .remove();
  3915. dist
  3916. .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i })
  3917. .attr(naxis + '1', 0)
  3918. .attr(naxis + '2', size);
  3919. renderWatch.transition(dist, 'dist')
  3920. // .transition()
  3921. .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
  3922. .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
  3923. scale0 = scale.copy();
  3924. });
  3925. renderWatch.renderEnd('distribution immediate');
  3926. return chart;
  3927. }
  3928. //============================================================
  3929. // Expose Public Variables
  3930. //------------------------------------------------------------
  3931. chart.options = nv.utils.optionsFunc.bind(chart);
  3932. chart.dispatch = dispatch;
  3933. chart.margin = function(_) {
  3934. if (!arguments.length) return margin;
  3935. margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
  3936. margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
  3937. margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
  3938. margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
  3939. return chart;
  3940. };
  3941. chart.width = function(_) {
  3942. if (!arguments.length) return width;
  3943. width = _;
  3944. return chart;
  3945. };
  3946. chart.axis = function(_) {
  3947. if (!arguments.length) return axis;
  3948. axis = _;
  3949. return chart;
  3950. };
  3951. chart.size = function(_) {
  3952. if (!arguments.length) return size;
  3953. size = _;
  3954. return chart;
  3955. };
  3956. chart.getData = function(_) {
  3957. if (!arguments.length) return getData;
  3958. getData = d3.functor(_);
  3959. return chart;
  3960. };
  3961. chart.scale = function(_) {
  3962. if (!arguments.length) return scale;
  3963. scale = _;
  3964. return chart;
  3965. };
  3966. chart.color = function(_) {
  3967. if (!arguments.length) return color;
  3968. color = nv.utils.getColor(_);
  3969. return chart;
  3970. };
  3971. chart.duration = function(_) {
  3972. if (!arguments.length) return duration;
  3973. duration = _;
  3974. renderWatch.reset(duration);
  3975. return chart;
  3976. };
  3977. //============================================================
  3978. return chart;
  3979. }
  3980. nv.models.focus = function(content) {
  3981. "use strict";
  3982. //============================================================
  3983. // Public Variables with Default Settings
  3984. //------------------------------------------------------------
  3985. var content = content || nv.models.line()
  3986. , xAxis = nv.models.axis()
  3987. , yAxis = nv.models.axis()
  3988. , brush = d3.svg.brush()
  3989. ;
  3990. var margin = {top: 10, right: 0, bottom: 30, left: 0}
  3991. , color = nv.utils.defaultColor()
  3992. , width = null
  3993. , height = 70
  3994. , showXAxis = true
  3995. , showYAxis = false
  3996. , rightAlignYAxis = false
  3997. , ticks = null
  3998. , x
  3999. , y
  4000. , brushExtent = null
  4001. , duration = 250
  4002. , dispatch = d3.dispatch('brush', 'onBrush', 'renderEnd')
  4003. , syncBrushing = true
  4004. ;
  4005. content.interactive(false);
  4006. content.pointActive(function(d) { return false; });
  4007. //============================================================
  4008. // Private Variables
  4009. //------------------------------------------------------------
  4010. var renderWatch = nv.utils.renderWatch(dispatch, duration);
  4011. function chart(selection) {
  4012. renderWatch.reset();
  4013. renderWatch.models(content);
  4014. if (showXAxis) renderWatch.models(xAxis);
  4015. if (showYAxis) renderWatch.models(yAxis);
  4016. selection.each(function(data) {
  4017. var container = d3.select(this);
  4018. nv.utils.initSVG(container);
  4019. var availableWidth = nv.utils.availableWidth(width, container, margin),
  4020. availableHeight = height - margin.top - margin.bottom;
  4021. chart.update = function() {
  4022. if( duration === 0 ) {
  4023. container.call( chart );
  4024. } else {
  4025. container.transition().duration(duration).call(chart);
  4026. }
  4027. };
  4028. chart.container = this;
  4029. // Setup Scales
  4030. x = content.xScale();
  4031. y = content.yScale();
  4032. // Setup containers and skeleton of chart
  4033. var wrap = container.selectAll('g.nv-focus').data([data]);
  4034. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-focus').append('g');
  4035. var g = wrap.select('g');
  4036. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  4037. gEnter.append('g').attr('class', 'nv-background').append('rect');
  4038. gEnter.append('g').attr('class', 'nv-x nv-axis');
  4039. gEnter.append('g').attr('class', 'nv-y nv-axis');
  4040. gEnter.append('g').attr('class', 'nv-contentWrap');
  4041. gEnter.append('g').attr('class', 'nv-brushBackground');
  4042. gEnter.append('g').attr('class', 'nv-x nv-brush');
  4043. if (rightAlignYAxis) {
  4044. g.select(".nv-y.nv-axis")
  4045. .attr("transform", "translate(" + availableWidth + ",0)");
  4046. }
  4047. g.select('.nv-background rect')
  4048. .attr('width', availableWidth)
  4049. .attr('height', availableHeight);
  4050. content
  4051. .width(availableWidth)
  4052. .height(availableHeight)
  4053. .color(data.map(function(d,i) {
  4054. return d.color || color(d, i);
  4055. }).filter(function(d,i) { return !data[i].disabled; }));
  4056. var contentWrap = g.select('.nv-contentWrap')
  4057. .datum(data.filter(function(d) { return !d.disabled; }));
  4058. d3.transition(contentWrap).call(content);
  4059. // Setup Brush
  4060. brush
  4061. .x(x)
  4062. .on('brush', function() {
  4063. onBrush(syncBrushing);
  4064. });
  4065. brush.on('brushend', function () {
  4066. if (!syncBrushing) {
  4067. dispatch.onBrush(brush.empty() ? x.domain() : brush.extent());
  4068. }
  4069. });
  4070. if (brushExtent) brush.extent(brushExtent);
  4071. var brushBG = g.select('.nv-brushBackground').selectAll('g')
  4072. .data([brushExtent || brush.extent()]);
  4073. var brushBGenter = brushBG.enter()
  4074. .append('g');
  4075. brushBGenter.append('rect')
  4076. .attr('class', 'left')
  4077. .attr('x', 0)
  4078. .attr('y', 0)
  4079. .attr('height', availableHeight);
  4080. brushBGenter.append('rect')
  4081. .attr('class', 'right')
  4082. .attr('x', 0)
  4083. .attr('y', 0)
  4084. .attr('height', availableHeight);
  4085. var gBrush = g.select('.nv-x.nv-brush')
  4086. .call(brush);
  4087. gBrush.selectAll('rect')
  4088. .attr('height', availableHeight);
  4089. gBrush.selectAll('.resize').append('path').attr('d', resizePath);
  4090. onBrush(true);
  4091. g.select('.nv-background rect')
  4092. .attr('width', availableWidth)
  4093. .attr('height', availableHeight);
  4094. if (showXAxis) {
  4095. xAxis.scale(x)
  4096. ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
  4097. .tickSize(-availableHeight, 0);
  4098. g.select('.nv-x.nv-axis')
  4099. .attr('transform', 'translate(0,' + y.range()[0] + ')');
  4100. d3.transition(g.select('.nv-x.nv-axis'))
  4101. .call(xAxis);
  4102. }
  4103. if (showYAxis) {
  4104. yAxis
  4105. .scale(y)
  4106. ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
  4107. .tickSize( -availableWidth, 0);
  4108. d3.transition(g.select('.nv-y.nv-axis'))
  4109. .call(yAxis);
  4110. }
  4111. g.select('.nv-x.nv-axis')
  4112. .attr('transform', 'translate(0,' + y.range()[0] + ')');
  4113. //============================================================
  4114. // Event Handling/Dispatching (in chart's scope)
  4115. //------------------------------------------------------------
  4116. //============================================================
  4117. // Functions
  4118. //------------------------------------------------------------
  4119. // Taken from crossfilter (http://square.github.com/crossfilter/)
  4120. function resizePath(d) {
  4121. var e = +(d == 'e'),
  4122. x = e ? 1 : -1,
  4123. y = availableHeight / 3;
  4124. return 'M' + (0.5 * x) + ',' + y
  4125. + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
  4126. + 'V' + (2 * y - 6)
  4127. + 'A6,6 0 0 ' + e + ' ' + (0.5 * x) + ',' + (2 * y)
  4128. + 'Z'
  4129. + 'M' + (2.5 * x) + ',' + (y + 8)
  4130. + 'V' + (2 * y - 8)
  4131. + 'M' + (4.5 * x) + ',' + (y + 8)
  4132. + 'V' + (2 * y - 8);
  4133. }
  4134. function updateBrushBG() {
  4135. if (!brush.empty()) brush.extent(brushExtent);
  4136. brushBG
  4137. .data([brush.empty() ? x.domain() : brushExtent])
  4138. .each(function(d,i) {
  4139. var leftWidth = x(d[0]) - x.range()[0],
  4140. rightWidth = availableWidth - x(d[1]);
  4141. d3.select(this).select('.left')
  4142. .attr('width', leftWidth < 0 ? 0 : leftWidth);
  4143. d3.select(this).select('.right')
  4144. .attr('x', x(d[1]))
  4145. .attr('width', rightWidth < 0 ? 0 : rightWidth);
  4146. });
  4147. }
  4148. function onBrush(shouldDispatch) {
  4149. brushExtent = brush.empty() ? null : brush.extent();
  4150. var extent = brush.empty() ? x.domain() : brush.extent();
  4151. dispatch.brush({extent: extent, brush: brush});
  4152. updateBrushBG();
  4153. if (shouldDispatch) {
  4154. dispatch.onBrush(extent);
  4155. }
  4156. }
  4157. });
  4158. renderWatch.renderEnd('focus immediate');
  4159. return chart;
  4160. }
  4161. //============================================================
  4162. // Event Handling/Dispatching (out of chart's scope)
  4163. //------------------------------------------------------------
  4164. //============================================================
  4165. // Expose Public Variables
  4166. //------------------------------------------------------------
  4167. // expose chart's sub-components
  4168. chart.dispatch = dispatch;
  4169. chart.content = content;
  4170. chart.brush = brush;
  4171. chart.xAxis = xAxis;
  4172. chart.yAxis = yAxis;
  4173. chart.options = nv.utils.optionsFunc.bind(chart);
  4174. chart._options = Object.create({}, {
  4175. // simple options, just get/set the necessary values
  4176. width: {get: function(){return width;}, set: function(_){width=_;}},
  4177. height: {get: function(){return height;}, set: function(_){height=_;}},
  4178. showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
  4179. showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
  4180. brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
  4181. syncBrushing: {get: function(){return syncBrushing;}, set: function(_){syncBrushing=_;}},
  4182. // options that require extra logic in the setter
  4183. margin: {get: function(){return margin;}, set: function(_){
  4184. margin.top = _.top !== undefined ? _.top : margin.top;
  4185. margin.right = _.right !== undefined ? _.right : margin.right;
  4186. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  4187. margin.left = _.left !== undefined ? _.left : margin.left;
  4188. }},
  4189. duration: {get: function(){return duration;}, set: function(_){
  4190. duration = _;
  4191. renderWatch.reset(duration);
  4192. content.duration(duration);
  4193. xAxis.duration(duration);
  4194. yAxis.duration(duration);
  4195. }},
  4196. color: {get: function(){return color;}, set: function(_){
  4197. color = nv.utils.getColor(_);
  4198. content.color(color);
  4199. }},
  4200. interpolate: {get: function(){return content.interpolate();}, set: function(_){
  4201. content.interpolate(_);
  4202. }},
  4203. xTickFormat: {get: function(){return xAxis.tickFormat();}, set: function(_){
  4204. xAxis.tickFormat(_);
  4205. }},
  4206. yTickFormat: {get: function(){return yAxis.tickFormat();}, set: function(_){
  4207. yAxis.tickFormat(_);
  4208. }},
  4209. x: {get: function(){return content.x();}, set: function(_){
  4210. content.x(_);
  4211. }},
  4212. y: {get: function(){return content.y();}, set: function(_){
  4213. content.y(_);
  4214. }},
  4215. rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
  4216. rightAlignYAxis = _;
  4217. yAxis.orient( rightAlignYAxis ? 'right' : 'left');
  4218. }}
  4219. });
  4220. nv.utils.inheritOptions(chart, content);
  4221. nv.utils.initOptions(chart);
  4222. return chart;
  4223. };
  4224. nv.models.forceDirectedGraph = function() {
  4225. "use strict";
  4226. //============================================================
  4227. // Public Variables with Default Settings
  4228. //------------------------------------------------------------
  4229. var margin = {top: 2, right: 0, bottom: 2, left: 0}
  4230. , width = 400
  4231. , height = 32
  4232. , container = null
  4233. , dispatch = d3.dispatch('renderEnd')
  4234. , color = nv.utils.getColor(['#000'])
  4235. , tooltip = nv.models.tooltip()
  4236. , noData = null
  4237. // Force directed graph specific parameters [default values]
  4238. , linkStrength = 0.1
  4239. , friction = 0.9
  4240. , linkDist = 30
  4241. , charge = -120
  4242. , gravity = 0.1
  4243. , theta = 0.8
  4244. , alpha = 0.1
  4245. , radius = 5
  4246. // These functions allow to add extra attributes to ndes and links
  4247. ,nodeExtras = function(nodes) { /* Do nothing */ }
  4248. ,linkExtras = function(links) { /* Do nothing */ }
  4249. ;
  4250. //============================================================
  4251. // Private Variables
  4252. //------------------------------------------------------------
  4253. var renderWatch = nv.utils.renderWatch(dispatch);
  4254. function chart(selection) {
  4255. renderWatch.reset();
  4256. selection.each(function(data) {
  4257. container = d3.select(this);
  4258. nv.utils.initSVG(container);
  4259. var availableWidth = nv.utils.availableWidth(width, container, margin),
  4260. availableHeight = nv.utils.availableHeight(height, container, margin);
  4261. container
  4262. .attr("width", availableWidth)
  4263. .attr("height", availableHeight);
  4264. // Display No Data message if there's nothing to show.
  4265. if (!data || !data.links || !data.nodes) {
  4266. nv.utils.noData(chart, container)
  4267. return chart;
  4268. } else {
  4269. container.selectAll('.nv-noData').remove();
  4270. }
  4271. container.selectAll('*').remove();
  4272. // Collect names of all fields in the nodes
  4273. var nodeFieldSet = new Set();
  4274. data.nodes.forEach(function(node) {
  4275. var keys = Object.keys(node);
  4276. keys.forEach(function(key) {
  4277. nodeFieldSet.add(key);
  4278. });
  4279. });
  4280. var force = d3.layout.force()
  4281. .nodes(data.nodes)
  4282. .links(data.links)
  4283. .size([availableWidth, availableHeight])
  4284. .linkStrength(linkStrength)
  4285. .friction(friction)
  4286. .linkDistance(linkDist)
  4287. .charge(charge)
  4288. .gravity(gravity)
  4289. .theta(theta)
  4290. .alpha(alpha)
  4291. .start();
  4292. var link = container.selectAll(".link")
  4293. .data(data.links)
  4294. .enter().append("line")
  4295. .attr("class", "nv-force-link")
  4296. .style("stroke-width", function(d) { return Math.sqrt(d.value); });
  4297. var node = container.selectAll(".node")
  4298. .data(data.nodes)
  4299. .enter()
  4300. .append("g")
  4301. .attr("class", "nv-force-node")
  4302. .call(force.drag);
  4303. node
  4304. .append("circle")
  4305. .attr("r", radius)
  4306. .style("fill", function(d) { return color(d) } )
  4307. .on("mouseover", function(evt) {
  4308. container.select('.nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex)
  4309. .attr('y1', evt.py);
  4310. container.select('.nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
  4311. .attr('x2', evt.px);
  4312. // Add 'series' object to
  4313. var nodeColor = color(evt);
  4314. evt.series = [];
  4315. nodeFieldSet.forEach(function(field) {
  4316. evt.series.push({
  4317. color: nodeColor,
  4318. key: field,
  4319. value: evt[field]
  4320. });
  4321. });
  4322. tooltip.data(evt).hidden(false);
  4323. })
  4324. .on("mouseout", function(d) {
  4325. tooltip.hidden(true);
  4326. });
  4327. tooltip.headerFormatter(function(d) {return "Node";});
  4328. // Apply extra attributes to nodes and links (if any)
  4329. linkExtras(link);
  4330. nodeExtras(node);
  4331. force.on("tick", function() {
  4332. link.attr("x1", function(d) { return d.source.x; })
  4333. .attr("y1", function(d) { return d.source.y; })
  4334. .attr("x2", function(d) { return d.target.x; })
  4335. .attr("y2", function(d) { return d.target.y; });
  4336. node.attr("transform", function(d) {
  4337. return "translate(" + d.x + ", " + d.y + ")";
  4338. });
  4339. });
  4340. });
  4341. return chart;
  4342. }
  4343. //============================================================
  4344. // Expose Public Variables
  4345. //------------------------------------------------------------
  4346. chart.options = nv.utils.optionsFunc.bind(chart);
  4347. chart._options = Object.create({}, {
  4348. // simple options, just get/set the necessary values
  4349. width: {get: function(){return width;}, set: function(_){width=_;}},
  4350. height: {get: function(){return height;}, set: function(_){height=_;}},
  4351. // Force directed graph specific parameters
  4352. linkStrength:{get: function(){return linkStrength;}, set: function(_){linkStrength=_;}},
  4353. friction: {get: function(){return friction;}, set: function(_){friction=_;}},
  4354. linkDist: {get: function(){return linkDist;}, set: function(_){linkDist=_;}},
  4355. charge: {get: function(){return charge;}, set: function(_){charge=_;}},
  4356. gravity: {get: function(){return gravity;}, set: function(_){gravity=_;}},
  4357. theta: {get: function(){return theta;}, set: function(_){theta=_;}},
  4358. alpha: {get: function(){return alpha;}, set: function(_){alpha=_;}},
  4359. radius: {get: function(){return radius;}, set: function(_){radius=_;}},
  4360. //functor options
  4361. x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}},
  4362. y: {get: function(){return getY;}, set: function(_){getY=d3.functor(_);}},
  4363. // options that require extra logic in the setter
  4364. margin: {get: function(){return margin;}, set: function(_){
  4365. margin.top = _.top !== undefined ? _.top : margin.top;
  4366. margin.right = _.right !== undefined ? _.right : margin.right;
  4367. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  4368. margin.left = _.left !== undefined ? _.left : margin.left;
  4369. }},
  4370. color: {get: function(){return color;}, set: function(_){
  4371. color = nv.utils.getColor(_);
  4372. }},
  4373. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  4374. nodeExtras: {get: function(){return nodeExtras;}, set: function(_){
  4375. nodeExtras = _;
  4376. }},
  4377. linkExtras: {get: function(){return linkExtras;}, set: function(_){
  4378. linkExtras = _;
  4379. }}
  4380. });
  4381. chart.dispatch = dispatch;
  4382. chart.tooltip = tooltip;
  4383. nv.utils.initOptions(chart);
  4384. return chart;
  4385. };
  4386. nv.models.furiousLegend = function() {
  4387. "use strict";
  4388. //============================================================
  4389. // Public Variables with Default Settings
  4390. //------------------------------------------------------------
  4391. var margin = {top: 5, right: 0, bottom: 5, left: 0}
  4392. , width = 400
  4393. , height = 20
  4394. , getKey = function(d) { return d.key }
  4395. , keyFormatter = function (d) { return d }
  4396. , color = nv.utils.getColor()
  4397. , maxKeyLength = 20 //default value for key lengths
  4398. , align = true
  4399. , padding = 28 //define how much space between legend items. - recommend 32 for furious version
  4400. , rightAlign = true
  4401. , updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
  4402. , radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
  4403. , expanded = false
  4404. , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
  4405. , vers = 'classic' //Options are "classic" and "furious"
  4406. ;
  4407. function chart(selection) {
  4408. selection.each(function(data) {
  4409. var availableWidth = width - margin.left - margin.right,
  4410. container = d3.select(this);
  4411. nv.utils.initSVG(container);
  4412. // Setup containers and skeleton of chart
  4413. var wrap = container.selectAll('g.nv-legend').data([data]);
  4414. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
  4415. var g = wrap.select('g');
  4416. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  4417. var series = g.selectAll('.nv-series')
  4418. .data(function(d) {
  4419. if(vers != 'furious') return d;
  4420. return d.filter(function(n) {
  4421. return expanded ? true : !n.disengaged;
  4422. });
  4423. });
  4424. var seriesEnter = series.enter().append('g').attr('class', 'nv-series')
  4425. var seriesShape;
  4426. if(vers == 'classic') {
  4427. seriesEnter.append('circle')
  4428. .style('stroke-width', 2)
  4429. .attr('class','nv-legend-symbol')
  4430. .attr('r', 5);
  4431. seriesShape = series.select('circle');
  4432. } else if (vers == 'furious') {
  4433. seriesEnter.append('rect')
  4434. .style('stroke-width', 2)
  4435. .attr('class','nv-legend-symbol')
  4436. .attr('rx', 3)
  4437. .attr('ry', 3);
  4438. seriesShape = series.select('rect');
  4439. seriesEnter.append('g')
  4440. .attr('class', 'nv-check-box')
  4441. .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>')
  4442. .attr('transform', 'translate(-10,-8)scale(0.5)');
  4443. var seriesCheckbox = series.select('.nv-check-box');
  4444. seriesCheckbox.each(function(d,i) {
  4445. d3.select(this).selectAll('path')
  4446. .attr('stroke', setTextColor(d,i));
  4447. });
  4448. }
  4449. seriesEnter.append('text')
  4450. .attr('text-anchor', 'start')
  4451. .attr('class','nv-legend-text')
  4452. .attr('dy', '.32em')
  4453. .attr('dx', '8');
  4454. var seriesText = series.select('text.nv-legend-text');
  4455. series
  4456. .on('mouseover', function(d,i) {
  4457. dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects
  4458. })
  4459. .on('mouseout', function(d,i) {
  4460. dispatch.legendMouseout(d,i);
  4461. })
  4462. .on('click', function(d,i) {
  4463. dispatch.legendClick(d,i);
  4464. // make sure we re-get data in case it was modified
  4465. var data = series.data();
  4466. if (updateState) {
  4467. if(vers =='classic') {
  4468. if (radioButtonMode) {
  4469. //Radio button mode: set every series to disabled,
  4470. // and enable the clicked series.
  4471. data.forEach(function(series) { series.disabled = true});
  4472. d.disabled = false;
  4473. }
  4474. else {
  4475. d.disabled = !d.disabled;
  4476. if (data.every(function(series) { return series.disabled})) {
  4477. //the default behavior of NVD3 legends is, if every single series
  4478. // is disabled, turn all series' back on.
  4479. data.forEach(function(series) { series.disabled = false});
  4480. }
  4481. }
  4482. } else if(vers == 'furious') {
  4483. if(expanded) {
  4484. d.disengaged = !d.disengaged;
  4485. d.userDisabled = d.userDisabled == undefined ? !!d.disabled : d.userDisabled;
  4486. d.disabled = d.disengaged || d.userDisabled;
  4487. } else if (!expanded) {
  4488. d.disabled = !d.disabled;
  4489. d.userDisabled = d.disabled;
  4490. var engaged = data.filter(function(d) { return !d.disengaged; });
  4491. if (engaged.every(function(series) { return series.userDisabled })) {
  4492. //the default behavior of NVD3 legends is, if every single series
  4493. // is disabled, turn all series' back on.
  4494. data.forEach(function(series) {
  4495. series.disabled = series.userDisabled = false;
  4496. });
  4497. }
  4498. }
  4499. }
  4500. dispatch.stateChange({
  4501. disabled: data.map(function(d) { return !!d.disabled }),
  4502. disengaged: data.map(function(d) { return !!d.disengaged })
  4503. });
  4504. }
  4505. })
  4506. .on('dblclick', function(d,i) {
  4507. if(vers == 'furious' && expanded) return;
  4508. dispatch.legendDblclick(d,i);
  4509. if (updateState) {
  4510. // make sure we re-get data in case it was modified
  4511. var data = series.data();
  4512. //the default behavior of NVD3 legends, when double clicking one,
  4513. // is to set all other series' to false, and make the double clicked series enabled.
  4514. data.forEach(function(series) {
  4515. series.disabled = true;
  4516. if(vers == 'furious') series.userDisabled = series.disabled;
  4517. });
  4518. d.disabled = false;
  4519. if(vers == 'furious') d.userDisabled = d.disabled;
  4520. dispatch.stateChange({
  4521. disabled: data.map(function(d) { return !!d.disabled })
  4522. });
  4523. }
  4524. });
  4525. series.classed('nv-disabled', function(d) { return d.userDisabled });
  4526. series.exit().remove();
  4527. seriesText
  4528. .attr('fill', setTextColor)
  4529. .text(function (d) { return keyFormatter(getKey(d)) });
  4530. //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
  4531. // NEW ALIGNING CODE, TODO: clean up
  4532. var versPadding;
  4533. switch(vers) {
  4534. case 'furious' :
  4535. versPadding = 23;
  4536. break;
  4537. case 'classic' :
  4538. versPadding = 20;
  4539. }
  4540. if (align) {
  4541. var seriesWidths = [];
  4542. series.each(function(d,i) {
  4543. var legendText;
  4544. if (keyFormatter(getKey(d)) && keyFormatter(getKey(d)).length > maxKeyLength) {
  4545. var trimmedKey = keyFormatter(getKey(d)).substring(0, maxKeyLength);
  4546. legendText = d3.select(this).select('text').text(trimmedKey + "...");
  4547. d3.select(this).append("svg:title").text(keyFormatter(getKey(d)));
  4548. } else {
  4549. legendText = d3.select(this).select('text');
  4550. }
  4551. var nodeTextLength;
  4552. try {
  4553. nodeTextLength = legendText.node().getComputedTextLength();
  4554. // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead
  4555. if(nodeTextLength <= 0) throw Error();
  4556. }
  4557. catch(e) {
  4558. nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
  4559. }
  4560. seriesWidths.push(nodeTextLength + padding);
  4561. });
  4562. var seriesPerRow = 0;
  4563. var legendWidth = 0;
  4564. var columnWidths = [];
  4565. while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
  4566. columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
  4567. legendWidth += seriesWidths[seriesPerRow++];
  4568. }
  4569. if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
  4570. while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
  4571. columnWidths = [];
  4572. seriesPerRow--;
  4573. for (var k = 0; k < seriesWidths.length; k++) {
  4574. if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
  4575. columnWidths[k % seriesPerRow] = seriesWidths[k];
  4576. }
  4577. legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
  4578. return prev + cur;
  4579. });
  4580. }
  4581. var xPositions = [];
  4582. for (var i = 0, curX = 0; i < seriesPerRow; i++) {
  4583. xPositions[i] = curX;
  4584. curX += columnWidths[i];
  4585. }
  4586. series
  4587. .attr('transform', function(d, i) {
  4588. return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')';
  4589. });
  4590. //position legend as far right as possible within the total width
  4591. if (rightAlign) {
  4592. g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
  4593. }
  4594. else {
  4595. g.attr('transform', 'translate(0' + ',' + margin.top + ')');
  4596. }
  4597. height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding);
  4598. } else {
  4599. var ypos = 5,
  4600. newxpos = 5,
  4601. maxwidth = 0,
  4602. xpos;
  4603. series
  4604. .attr('transform', function(d, i) {
  4605. var length = d3.select(this).select('text').node().getComputedTextLength() + padding;
  4606. xpos = newxpos;
  4607. if (width < margin.left + margin.right + xpos + length) {
  4608. newxpos = xpos = 5;
  4609. ypos += versPadding;
  4610. }
  4611. newxpos += length;
  4612. if (newxpos > maxwidth) maxwidth = newxpos;
  4613. return 'translate(' + xpos + ',' + ypos + ')';
  4614. });
  4615. //position legend as far right as possible within the total width
  4616. g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
  4617. height = margin.top + margin.bottom + ypos + 15;
  4618. }
  4619. if(vers == 'furious') {
  4620. // Size rectangles after text is placed
  4621. seriesShape
  4622. .attr('width', function(d,i) {
  4623. return seriesText[0][i].getComputedTextLength() + 27;
  4624. })
  4625. .attr('height', 18)
  4626. .attr('y', -9)
  4627. .attr('x', -15)
  4628. }
  4629. seriesShape
  4630. .style('fill', setBGColor)
  4631. .style('stroke', function(d,i) { return d.color || color(d, i) });
  4632. });
  4633. function setTextColor(d,i) {
  4634. if(vers != 'furious') return '#000';
  4635. if(expanded) {
  4636. return d.disengaged ? color(d,i) : '#fff';
  4637. } else if (!expanded) {
  4638. return !!d.disabled ? color(d,i) : '#fff';
  4639. }
  4640. }
  4641. function setBGColor(d,i) {
  4642. if(expanded && vers == 'furious') {
  4643. return d.disengaged ? '#fff' : color(d,i);
  4644. } else {
  4645. return !!d.disabled ? '#fff' : color(d,i);
  4646. }
  4647. }
  4648. return chart;
  4649. }
  4650. //============================================================
  4651. // Expose Public Variables
  4652. //------------------------------------------------------------
  4653. chart.dispatch = dispatch;
  4654. chart.options = nv.utils.optionsFunc.bind(chart);
  4655. chart._options = Object.create({}, {
  4656. // simple options, just get/set the necessary values
  4657. width: {get: function(){return width;}, set: function(_){width=_;}},
  4658. height: {get: function(){return height;}, set: function(_){height=_;}},
  4659. key: {get: function(){return getKey;}, set: function(_){getKey=_;}},
  4660. keyFormatter: {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}},
  4661. align: {get: function(){return align;}, set: function(_){align=_;}},
  4662. rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}},
  4663. maxKeyLength: {get: function(){return maxKeyLength;}, set: function(_){maxKeyLength=_;}},
  4664. padding: {get: function(){return padding;}, set: function(_){padding=_;}},
  4665. updateState: {get: function(){return updateState;}, set: function(_){updateState=_;}},
  4666. radioButtonMode:{get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}},
  4667. expanded: {get: function(){return expanded;}, set: function(_){expanded=_;}},
  4668. vers: {get: function(){return vers;}, set: function(_){vers=_;}},
  4669. // options that require extra logic in the setter
  4670. margin: {get: function(){return margin;}, set: function(_){
  4671. margin.top = _.top !== undefined ? _.top : margin.top;
  4672. margin.right = _.right !== undefined ? _.right : margin.right;
  4673. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  4674. margin.left = _.left !== undefined ? _.left : margin.left;
  4675. }},
  4676. color: {get: function(){return color;}, set: function(_){
  4677. color = nv.utils.getColor(_);
  4678. }}
  4679. });
  4680. nv.utils.initOptions(chart);
  4681. return chart;
  4682. };
  4683. //TODO: consider deprecating and using multibar with single series for this
  4684. nv.models.historicalBar = function() {
  4685. "use strict";
  4686. //============================================================
  4687. // Public Variables with Default Settings
  4688. //------------------------------------------------------------
  4689. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  4690. , width = null
  4691. , height = null
  4692. , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
  4693. , container = null
  4694. , x = d3.scale.linear()
  4695. , y = d3.scale.linear()
  4696. , getX = function(d) { return d.x }
  4697. , getY = function(d) { return d.y }
  4698. , forceX = []
  4699. , forceY = [0]
  4700. , padData = false
  4701. , clipEdge = true
  4702. , color = nv.utils.defaultColor()
  4703. , xDomain
  4704. , yDomain
  4705. , xRange
  4706. , yRange
  4707. , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
  4708. , interactive = true
  4709. ;
  4710. var renderWatch = nv.utils.renderWatch(dispatch, 0);
  4711. function chart(selection) {
  4712. selection.each(function(data) {
  4713. renderWatch.reset();
  4714. container = d3.select(this);
  4715. var availableWidth = nv.utils.availableWidth(width, container, margin),
  4716. availableHeight = nv.utils.availableHeight(height, container, margin);
  4717. nv.utils.initSVG(container);
  4718. // Setup Scales
  4719. x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
  4720. if (padData)
  4721. x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
  4722. else
  4723. x.range(xRange || [0, availableWidth]);
  4724. y.domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) ))
  4725. .range(yRange || [availableHeight, 0]);
  4726. // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
  4727. if (x.domain()[0] === x.domain()[1])
  4728. x.domain()[0] ?
  4729. x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
  4730. : x.domain([-1,1]);
  4731. if (y.domain()[0] === y.domain()[1])
  4732. y.domain()[0] ?
  4733. y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
  4734. : y.domain([-1,1]);
  4735. // Setup containers and skeleton of chart
  4736. var wrap = container.selectAll('g.nv-wrap.nv-historicalBar-' + id).data([data[0].values]);
  4737. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBar-' + id);
  4738. var defsEnter = wrapEnter.append('defs');
  4739. var gEnter = wrapEnter.append('g');
  4740. var g = wrap.select('g');
  4741. gEnter.append('g').attr('class', 'nv-bars');
  4742. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  4743. container
  4744. .on('click', function(d,i) {
  4745. dispatch.chartClick({
  4746. data: d,
  4747. index: i,
  4748. pos: d3.event,
  4749. id: id
  4750. });
  4751. });
  4752. defsEnter.append('clipPath')
  4753. .attr('id', 'nv-chart-clip-path-' + id)
  4754. .append('rect');
  4755. wrap.select('#nv-chart-clip-path-' + id + ' rect')
  4756. .attr('width', availableWidth)
  4757. .attr('height', availableHeight);
  4758. g.attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
  4759. var bars = wrap.select('.nv-bars').selectAll('.nv-bar')
  4760. .data(function(d) { return d }, function(d,i) {return getX(d,i)});
  4761. bars.exit().remove();
  4762. bars.enter().append('rect')
  4763. .attr('x', 0 )
  4764. .attr('y', function(d,i) { return nv.utils.NaNtoZero(y(Math.max(0, getY(d,i)))) })
  4765. .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.abs(y(getY(d,i)) - y(0))) })
  4766. .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
  4767. .on('mouseover', function(d,i) {
  4768. if (!interactive) return;
  4769. d3.select(this).classed('hover', true);
  4770. dispatch.elementMouseover({
  4771. data: d,
  4772. index: i,
  4773. color: d3.select(this).style("fill")
  4774. });
  4775. })
  4776. .on('mouseout', function(d,i) {
  4777. if (!interactive) return;
  4778. d3.select(this).classed('hover', false);
  4779. dispatch.elementMouseout({
  4780. data: d,
  4781. index: i,
  4782. color: d3.select(this).style("fill")
  4783. });
  4784. })
  4785. .on('mousemove', function(d,i) {
  4786. if (!interactive) return;
  4787. dispatch.elementMousemove({
  4788. data: d,
  4789. index: i,
  4790. color: d3.select(this).style("fill")
  4791. });
  4792. })
  4793. .on('click', function(d,i) {
  4794. if (!interactive) return;
  4795. var element = this;
  4796. dispatch.elementClick({
  4797. data: d,
  4798. index: i,
  4799. color: d3.select(this).style("fill"),
  4800. event: d3.event,
  4801. element: element
  4802. });
  4803. d3.event.stopPropagation();
  4804. })
  4805. .on('dblclick', function(d,i) {
  4806. if (!interactive) return;
  4807. dispatch.elementDblClick({
  4808. data: d,
  4809. index: i,
  4810. color: d3.select(this).style("fill")
  4811. });
  4812. d3.event.stopPropagation();
  4813. });
  4814. bars
  4815. .attr('fill', function(d,i) { return color(d, i); })
  4816. .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
  4817. .watchTransition(renderWatch, 'bars')
  4818. .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
  4819. //TODO: better width calculations that don't assume always uniform data spacing;w
  4820. .attr('width', (availableWidth / data[0].values.length) * .9 );
  4821. bars.watchTransition(renderWatch, 'bars')
  4822. .attr('y', function(d,i) {
  4823. var rval = getY(d,i) < 0 ?
  4824. y(0) :
  4825. y(0) - y(getY(d,i)) < 1 ?
  4826. y(0) - 1 :
  4827. y(getY(d,i));
  4828. return nv.utils.NaNtoZero(rval);
  4829. })
  4830. .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) });
  4831. });
  4832. renderWatch.renderEnd('historicalBar immediate');
  4833. return chart;
  4834. }
  4835. //Create methods to allow outside functions to highlight a specific bar.
  4836. chart.highlightPoint = function(pointIndex, isHoverOver) {
  4837. container
  4838. .select(".nv-bars .nv-bar-0-" + pointIndex)
  4839. .classed("hover", isHoverOver)
  4840. ;
  4841. };
  4842. chart.clearHighlights = function() {
  4843. container
  4844. .select(".nv-bars .nv-bar.hover")
  4845. .classed("hover", false)
  4846. ;
  4847. };
  4848. //============================================================
  4849. // Expose Public Variables
  4850. //------------------------------------------------------------
  4851. chart.dispatch = dispatch;
  4852. chart.options = nv.utils.optionsFunc.bind(chart);
  4853. chart._options = Object.create({}, {
  4854. // simple options, just get/set the necessary values
  4855. width: {get: function(){return width;}, set: function(_){width=_;}},
  4856. height: {get: function(){return height;}, set: function(_){height=_;}},
  4857. forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
  4858. forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
  4859. padData: {get: function(){return padData;}, set: function(_){padData=_;}},
  4860. x: {get: function(){return getX;}, set: function(_){getX=_;}},
  4861. y: {get: function(){return getY;}, set: function(_){getY=_;}},
  4862. xScale: {get: function(){return x;}, set: function(_){x=_;}},
  4863. yScale: {get: function(){return y;}, set: function(_){y=_;}},
  4864. xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
  4865. yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
  4866. xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
  4867. yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
  4868. clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
  4869. id: {get: function(){return id;}, set: function(_){id=_;}},
  4870. interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
  4871. // options that require extra logic in the setter
  4872. margin: {get: function(){return margin;}, set: function(_){
  4873. margin.top = _.top !== undefined ? _.top : margin.top;
  4874. margin.right = _.right !== undefined ? _.right : margin.right;
  4875. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  4876. margin.left = _.left !== undefined ? _.left : margin.left;
  4877. }},
  4878. color: {get: function(){return color;}, set: function(_){
  4879. color = nv.utils.getColor(_);
  4880. }}
  4881. });
  4882. nv.utils.initOptions(chart);
  4883. return chart;
  4884. };
  4885. nv.models.historicalBarChart = function(bar_model) {
  4886. "use strict";
  4887. //============================================================
  4888. // Public Variables with Default Settings
  4889. //------------------------------------------------------------
  4890. var bars = bar_model || nv.models.historicalBar()
  4891. , xAxis = nv.models.axis()
  4892. , yAxis = nv.models.axis()
  4893. , legend = nv.models.legend()
  4894. , interactiveLayer = nv.interactiveGuideline()
  4895. , tooltip = nv.models.tooltip()
  4896. ;
  4897. var margin = {top: 30, right: 90, bottom: 50, left: 90}
  4898. , marginTop = null
  4899. , color = nv.utils.defaultColor()
  4900. , width = null
  4901. , height = null
  4902. , showLegend = false
  4903. , showXAxis = true
  4904. , showYAxis = true
  4905. , rightAlignYAxis = false
  4906. , useInteractiveGuideline = false
  4907. , x
  4908. , y
  4909. , state = {}
  4910. , defaultState = null
  4911. , noData = null
  4912. , dispatch = d3.dispatch('tooltipHide', 'stateChange', 'changeState', 'renderEnd')
  4913. , transitionDuration = 250
  4914. ;
  4915. xAxis.orient('bottom').tickPadding(7);
  4916. yAxis.orient( (rightAlignYAxis) ? 'right' : 'left');
  4917. tooltip
  4918. .duration(0)
  4919. .headerEnabled(false)
  4920. .valueFormatter(function(d, i) {
  4921. return yAxis.tickFormat()(d, i);
  4922. })
  4923. .headerFormatter(function(d, i) {
  4924. return xAxis.tickFormat()(d, i);
  4925. });
  4926. //============================================================
  4927. // Private Variables
  4928. //------------------------------------------------------------
  4929. var renderWatch = nv.utils.renderWatch(dispatch, 0);
  4930. function chart(selection) {
  4931. selection.each(function(data) {
  4932. renderWatch.reset();
  4933. renderWatch.models(bars);
  4934. if (showXAxis) renderWatch.models(xAxis);
  4935. if (showYAxis) renderWatch.models(yAxis);
  4936. var container = d3.select(this),
  4937. that = this;
  4938. nv.utils.initSVG(container);
  4939. var availableWidth = nv.utils.availableWidth(width, container, margin),
  4940. availableHeight = nv.utils.availableHeight(height, container, margin);
  4941. chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
  4942. chart.container = this;
  4943. //set state.disabled
  4944. state.disabled = data.map(function(d) { return !!d.disabled });
  4945. if (!defaultState) {
  4946. var key;
  4947. defaultState = {};
  4948. for (key in state) {
  4949. if (state[key] instanceof Array)
  4950. defaultState[key] = state[key].slice(0);
  4951. else
  4952. defaultState[key] = state[key];
  4953. }
  4954. }
  4955. // Display noData message if there's nothing to show.
  4956. if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
  4957. nv.utils.noData(chart, container)
  4958. return chart;
  4959. } else {
  4960. container.selectAll('.nv-noData').remove();
  4961. }
  4962. // Setup Scales
  4963. x = bars.xScale();
  4964. y = bars.yScale();
  4965. // Setup containers and skeleton of chart
  4966. var wrap = container.selectAll('g.nv-wrap.nv-historicalBarChart').data([data]);
  4967. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBarChart').append('g');
  4968. var g = wrap.select('g');
  4969. gEnter.append('g').attr('class', 'nv-x nv-axis');
  4970. gEnter.append('g').attr('class', 'nv-y nv-axis');
  4971. gEnter.append('g').attr('class', 'nv-barsWrap');
  4972. gEnter.append('g').attr('class', 'nv-legendWrap');
  4973. gEnter.append('g').attr('class', 'nv-interactive');
  4974. // Legend
  4975. if (!showLegend) {
  4976. g.select('.nv-legendWrap').selectAll('*').remove();
  4977. } else {
  4978. legend.width(availableWidth);
  4979. g.select('.nv-legendWrap')
  4980. .datum(data)
  4981. .call(legend);
  4982. if (!marginTop && legend.height() !== margin.top) {
  4983. margin.top = legend.height();
  4984. availableHeight = nv.utils.availableHeight(height, container, margin);
  4985. }
  4986. wrap.select('.nv-legendWrap')
  4987. .attr('transform', 'translate(0,' + (-margin.top) +')')
  4988. }
  4989. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  4990. if (rightAlignYAxis) {
  4991. g.select(".nv-y.nv-axis")
  4992. .attr("transform", "translate(" + availableWidth + ",0)");
  4993. }
  4994. //Set up interactive layer
  4995. if (useInteractiveGuideline) {
  4996. interactiveLayer
  4997. .width(availableWidth)
  4998. .height(availableHeight)
  4999. .margin({left:margin.left, top:margin.top})
  5000. .svgContainer(container)
  5001. .xScale(x);
  5002. wrap.select(".nv-interactive").call(interactiveLayer);
  5003. }
  5004. bars
  5005. .width(availableWidth)
  5006. .height(availableHeight)
  5007. .color(data.map(function(d,i) {
  5008. return d.color || color(d, i);
  5009. }).filter(function(d,i) { return !data[i].disabled }));
  5010. var barsWrap = g.select('.nv-barsWrap')
  5011. .datum(data.filter(function(d) { return !d.disabled }));
  5012. barsWrap.transition().call(bars);
  5013. // Setup Axes
  5014. if (showXAxis) {
  5015. xAxis
  5016. .scale(x)
  5017. ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
  5018. .tickSize(-availableHeight, 0);
  5019. g.select('.nv-x.nv-axis')
  5020. .attr('transform', 'translate(0,' + y.range()[0] + ')');
  5021. g.select('.nv-x.nv-axis')
  5022. .transition()
  5023. .call(xAxis);
  5024. }
  5025. if (showYAxis) {
  5026. yAxis
  5027. .scale(y)
  5028. ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
  5029. .tickSize( -availableWidth, 0);
  5030. g.select('.nv-y.nv-axis')
  5031. .transition()
  5032. .call(yAxis);
  5033. }
  5034. //============================================================
  5035. // Event Handling/Dispatching (in chart's scope)
  5036. //------------------------------------------------------------
  5037. interactiveLayer.dispatch.on('elementMousemove', function(e) {
  5038. bars.clearHighlights();
  5039. var singlePoint, pointIndex, pointXLocation, allData = [];
  5040. data
  5041. .filter(function(series, i) {
  5042. series.seriesIndex = i;
  5043. return !series.disabled;
  5044. })
  5045. .forEach(function(series,i) {
  5046. pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
  5047. bars.highlightPoint(pointIndex,true);
  5048. var point = series.values[pointIndex];
  5049. if (point === undefined) return;
  5050. if (singlePoint === undefined) singlePoint = point;
  5051. if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
  5052. allData.push({
  5053. key: series.key,
  5054. value: chart.y()(point, pointIndex),
  5055. color: color(series,series.seriesIndex),
  5056. data: series.values[pointIndex]
  5057. });
  5058. });
  5059. var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
  5060. interactiveLayer.tooltip
  5061. .valueFormatter(function(d,i) {
  5062. return yAxis.tickFormat()(d);
  5063. })
  5064. .data({
  5065. value: xValue,
  5066. index: pointIndex,
  5067. series: allData
  5068. })();
  5069. interactiveLayer.renderGuideLine(pointXLocation);
  5070. });
  5071. interactiveLayer.dispatch.on("elementMouseout",function(e) {
  5072. dispatch.tooltipHide();
  5073. bars.clearHighlights();
  5074. });
  5075. legend.dispatch.on('legendClick', function(d,i) {
  5076. d.disabled = !d.disabled;
  5077. if (!data.filter(function(d) { return !d.disabled }).length) {
  5078. data.map(function(d) {
  5079. d.disabled = false;
  5080. wrap.selectAll('.nv-series').classed('disabled', false);
  5081. return d;
  5082. });
  5083. }
  5084. state.disabled = data.map(function(d) { return !!d.disabled });
  5085. dispatch.stateChange(state);
  5086. selection.transition().call(chart);
  5087. });
  5088. legend.dispatch.on('legendDblclick', function(d) {
  5089. //Double clicking should always enable current series, and disabled all others.
  5090. data.forEach(function(d) {
  5091. d.disabled = true;
  5092. });
  5093. d.disabled = false;
  5094. state.disabled = data.map(function(d) { return !!d.disabled });
  5095. dispatch.stateChange(state);
  5096. chart.update();
  5097. });
  5098. dispatch.on('changeState', function(e) {
  5099. if (typeof e.disabled !== 'undefined') {
  5100. data.forEach(function(series,i) {
  5101. series.disabled = e.disabled[i];
  5102. });
  5103. state.disabled = e.disabled;
  5104. }
  5105. chart.update();
  5106. });
  5107. });
  5108. renderWatch.renderEnd('historicalBarChart immediate');
  5109. return chart;
  5110. }
  5111. //============================================================
  5112. // Event Handling/Dispatching (out of chart's scope)
  5113. //------------------------------------------------------------
  5114. bars.dispatch.on('elementMouseover.tooltip', function(evt) {
  5115. evt['series'] = {
  5116. key: chart.x()(evt.data),
  5117. value: chart.y()(evt.data),
  5118. color: evt.color
  5119. };
  5120. tooltip.data(evt).hidden(false);
  5121. });
  5122. bars.dispatch.on('elementMouseout.tooltip', function(evt) {
  5123. tooltip.hidden(true);
  5124. });
  5125. bars.dispatch.on('elementMousemove.tooltip', function(evt) {
  5126. tooltip();
  5127. });
  5128. //============================================================
  5129. // Expose Public Variables
  5130. //------------------------------------------------------------
  5131. // expose chart's sub-components
  5132. chart.dispatch = dispatch;
  5133. chart.bars = bars;
  5134. chart.legend = legend;
  5135. chart.xAxis = xAxis;
  5136. chart.yAxis = yAxis;
  5137. chart.interactiveLayer = interactiveLayer;
  5138. chart.tooltip = tooltip;
  5139. chart.options = nv.utils.optionsFunc.bind(chart);
  5140. chart._options = Object.create({}, {
  5141. // simple options, just get/set the necessary values
  5142. width: {get: function(){return width;}, set: function(_){width=_;}},
  5143. height: {get: function(){return height;}, set: function(_){height=_;}},
  5144. showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
  5145. showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
  5146. showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
  5147. defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
  5148. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  5149. // options that require extra logic in the setter
  5150. margin: {get: function(){return margin;}, set: function(_){
  5151. if (_.top !== undefined) {
  5152. margin.top = _.top;
  5153. marginTop = _.top;
  5154. }
  5155. margin.right = _.right !== undefined ? _.right : margin.right;
  5156. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  5157. margin.left = _.left !== undefined ? _.left : margin.left;
  5158. }},
  5159. color: {get: function(){return color;}, set: function(_){
  5160. color = nv.utils.getColor(_);
  5161. legend.color(color);
  5162. bars.color(color);
  5163. }},
  5164. duration: {get: function(){return transitionDuration;}, set: function(_){
  5165. transitionDuration=_;
  5166. renderWatch.reset(transitionDuration);
  5167. yAxis.duration(transitionDuration);
  5168. xAxis.duration(transitionDuration);
  5169. }},
  5170. rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
  5171. rightAlignYAxis = _;
  5172. yAxis.orient( (_) ? 'right' : 'left');
  5173. }},
  5174. useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
  5175. useInteractiveGuideline = _;
  5176. if (_ === true) {
  5177. chart.interactive(false);
  5178. }
  5179. }}
  5180. });
  5181. nv.utils.inheritOptions(chart, bars);
  5182. nv.utils.initOptions(chart);
  5183. return chart;
  5184. };
  5185. // ohlcChart is just a historical chart with ohlc bars and some tweaks
  5186. nv.models.ohlcBarChart = function() {
  5187. var chart = nv.models.historicalBarChart(nv.models.ohlcBar());
  5188. // special default tooltip since we show multiple values per x
  5189. chart.useInteractiveGuideline(true);
  5190. chart.interactiveLayer.tooltip.contentGenerator(function(data) {
  5191. // we assume only one series exists for this chart
  5192. var d = data.series[0].data;
  5193. // match line colors as defined in nv.d3.css
  5194. var color = d.open < d.close ? "2ca02c" : "d62728";
  5195. return '' +
  5196. '<h3 style="color: #' + color + '">' + data.value + '</h3>' +
  5197. '<table>' +
  5198. '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' +
  5199. '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' +
  5200. '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' +
  5201. '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' +
  5202. '</table>';
  5203. });
  5204. return chart;
  5205. };
  5206. // candlestickChart is just a historical chart with candlestick bars and some tweaks
  5207. nv.models.candlestickBarChart = function() {
  5208. var chart = nv.models.historicalBarChart(nv.models.candlestickBar());
  5209. // special default tooltip since we show multiple values per x
  5210. chart.useInteractiveGuideline(true);
  5211. chart.interactiveLayer.tooltip.contentGenerator(function(data) {
  5212. // we assume only one series exists for this chart
  5213. var d = data.series[0].data;
  5214. // match line colors as defined in nv.d3.css
  5215. var color = d.open < d.close ? "2ca02c" : "d62728";
  5216. return '' +
  5217. '<h3 style="color: #' + color + '">' + data.value + '</h3>' +
  5218. '<table>' +
  5219. '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' +
  5220. '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' +
  5221. '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' +
  5222. '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' +
  5223. '</table>';
  5224. });
  5225. return chart;
  5226. };
  5227. nv.models.legend = function() {
  5228. "use strict";
  5229. //============================================================
  5230. // Public Variables with Default Settings
  5231. //------------------------------------------------------------
  5232. var margin = {top: 5, right: 0, bottom: 5, left: 0}
  5233. , width = 400
  5234. , height = 20
  5235. , getKey = function(d) { return d.key }
  5236. , keyFormatter = function (d) { return d }
  5237. , color = nv.utils.getColor()
  5238. , maxKeyLength = 20 //default value for key lengths
  5239. , align = true
  5240. , padding = 32 //define how much space between legend items. - recommend 32 for furious version
  5241. , rightAlign = true
  5242. , updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
  5243. , radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
  5244. , expanded = false
  5245. , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
  5246. , vers = 'classic' //Options are "classic" and "furious"
  5247. ;
  5248. function chart(selection) {
  5249. selection.each(function(data) {
  5250. var availableWidth = width - margin.left - margin.right,
  5251. container = d3.select(this);
  5252. nv.utils.initSVG(container);
  5253. // Setup containers and skeleton of chart
  5254. var wrap = container.selectAll('g.nv-legend').data([data]);
  5255. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
  5256. var g = wrap.select('g');
  5257. if (rightAlign)
  5258. wrap.attr('transform', 'translate(' + (- margin.right) + ',' + margin.top + ')');
  5259. else
  5260. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  5261. var series = g.selectAll('.nv-series')
  5262. .data(function(d) {
  5263. if(vers != 'furious') return d;
  5264. return d.filter(function(n) {
  5265. return expanded ? true : !n.disengaged;
  5266. });
  5267. });
  5268. var seriesEnter = series.enter().append('g').attr('class', 'nv-series');
  5269. var seriesShape;
  5270. var versPadding;
  5271. switch(vers) {
  5272. case 'furious' :
  5273. versPadding = 23;
  5274. break;
  5275. case 'classic' :
  5276. versPadding = 20;
  5277. }
  5278. if(vers == 'classic') {
  5279. seriesEnter.append('circle')
  5280. .style('stroke-width', 2)
  5281. .attr('class','nv-legend-symbol')
  5282. .attr('r', 5);
  5283. seriesShape = series.select('.nv-legend-symbol');
  5284. } else if (vers == 'furious') {
  5285. seriesEnter.append('rect')
  5286. .style('stroke-width', 2)
  5287. .attr('class','nv-legend-symbol')
  5288. .attr('rx', 3)
  5289. .attr('ry', 3);
  5290. seriesShape = series.select('.nv-legend-symbol');
  5291. seriesEnter.append('g')
  5292. .attr('class', 'nv-check-box')
  5293. .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>')
  5294. .attr('transform', 'translate(-10,-8)scale(0.5)');
  5295. var seriesCheckbox = series.select('.nv-check-box');
  5296. seriesCheckbox.each(function(d,i) {
  5297. d3.select(this).selectAll('path')
  5298. .attr('stroke', setTextColor(d,i));
  5299. });
  5300. }
  5301. seriesEnter.append('text')
  5302. .attr('text-anchor', 'start')
  5303. .attr('class','nv-legend-text')
  5304. .attr('dy', '.32em')
  5305. .attr('dx', '8');
  5306. var seriesText = series.select('text.nv-legend-text');
  5307. series
  5308. .on('mouseover', function(d,i) {
  5309. dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects
  5310. })
  5311. .on('mouseout', function(d,i) {
  5312. dispatch.legendMouseout(d,i);
  5313. })
  5314. .on('click', function(d,i) {
  5315. dispatch.legendClick(d,i);
  5316. // make sure we re-get data in case it was modified
  5317. var data = series.data();
  5318. if (updateState) {
  5319. if(vers =='classic') {
  5320. if (radioButtonMode) {
  5321. //Radio button mode: set every series to disabled,
  5322. // and enable the clicked series.
  5323. data.forEach(function(series) { series.disabled = true});
  5324. d.disabled = false;
  5325. }
  5326. else {
  5327. d.disabled = !d.disabled;
  5328. if (data.every(function(series) { return series.disabled})) {
  5329. //the default behavior of NVD3 legends is, if every single series
  5330. // is disabled, turn all series' back on.
  5331. data.forEach(function(series) { series.disabled = false});
  5332. }
  5333. }
  5334. } else if(vers == 'furious') {
  5335. if(expanded) {
  5336. d.disengaged = !d.disengaged;
  5337. d.userDisabled = d.userDisabled == undefined ? !!d.disabled : d.userDisabled;
  5338. d.disabled = d.disengaged || d.userDisabled;
  5339. } else if (!expanded) {
  5340. d.disabled = !d.disabled;
  5341. d.userDisabled = d.disabled;
  5342. var engaged = data.filter(function(d) { return !d.disengaged; });
  5343. if (engaged.every(function(series) { return series.userDisabled })) {
  5344. //the default behavior of NVD3 legends is, if every single series
  5345. // is disabled, turn all series' back on.
  5346. data.forEach(function(series) {
  5347. series.disabled = series.userDisabled = false;
  5348. });
  5349. }
  5350. }
  5351. }
  5352. dispatch.stateChange({
  5353. disabled: data.map(function(d) { return !!d.disabled }),
  5354. disengaged: data.map(function(d) { return !!d.disengaged })
  5355. });
  5356. }
  5357. })
  5358. .on('dblclick', function(d,i) {
  5359. if(vers == 'furious' && expanded) return;
  5360. dispatch.legendDblclick(d,i);
  5361. if (updateState) {
  5362. // make sure we re-get data in case it was modified
  5363. var data = series.data();
  5364. //the default behavior of NVD3 legends, when double clicking one,
  5365. // is to set all other series' to false, and make the double clicked series enabled.
  5366. data.forEach(function(series) {
  5367. series.disabled = true;
  5368. if(vers == 'furious') series.userDisabled = series.disabled;
  5369. });
  5370. d.disabled = false;
  5371. if(vers == 'furious') d.userDisabled = d.disabled;
  5372. dispatch.stateChange({
  5373. disabled: data.map(function(d) { return !!d.disabled })
  5374. });
  5375. }
  5376. });
  5377. series.classed('nv-disabled', function(d) { return d.userDisabled });
  5378. series.exit().remove();
  5379. seriesText
  5380. .attr('fill', setTextColor)
  5381. .text(function (d) { return keyFormatter(getKey(d)) });
  5382. //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
  5383. // NEW ALIGNING CODE, TODO: clean up
  5384. var legendWidth = 0;
  5385. if (align) {
  5386. var seriesWidths = [];
  5387. series.each(function(d,i) {
  5388. var legendText;
  5389. if (keyFormatter(getKey(d)) && keyFormatter(getKey(d)).length > maxKeyLength) {
  5390. var trimmedKey = keyFormatter(getKey(d)).substring(0, maxKeyLength);
  5391. legendText = d3.select(this).select('text').text(trimmedKey + "...");
  5392. d3.select(this).append("svg:title").text(keyFormatter(getKey(d)));
  5393. } else {
  5394. legendText = d3.select(this).select('text');
  5395. }
  5396. var nodeTextLength;
  5397. try {
  5398. nodeTextLength = legendText.node().getComputedTextLength();
  5399. // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead
  5400. if(nodeTextLength <= 0) throw Error();
  5401. }
  5402. catch(e) {
  5403. nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
  5404. }
  5405. seriesWidths.push(nodeTextLength + padding);
  5406. });
  5407. var seriesPerRow = 0;
  5408. var columnWidths = [];
  5409. legendWidth = 0;
  5410. while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
  5411. columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
  5412. legendWidth += seriesWidths[seriesPerRow++];
  5413. }
  5414. if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
  5415. while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
  5416. columnWidths = [];
  5417. seriesPerRow--;
  5418. for (var k = 0; k < seriesWidths.length; k++) {
  5419. if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
  5420. columnWidths[k % seriesPerRow] = seriesWidths[k];
  5421. }
  5422. legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
  5423. return prev + cur;
  5424. });
  5425. }
  5426. var xPositions = [];
  5427. for (var i = 0, curX = 0; i < seriesPerRow; i++) {
  5428. xPositions[i] = curX;
  5429. curX += columnWidths[i];
  5430. }
  5431. series
  5432. .attr('transform', function(d, i) {
  5433. return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')';
  5434. });
  5435. //position legend as far right as possible within the total width
  5436. if (rightAlign) {
  5437. g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
  5438. }
  5439. else {
  5440. g.attr('transform', 'translate(0' + ',' + margin.top + ')');
  5441. }
  5442. height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding);
  5443. } else {
  5444. var ypos = 5,
  5445. newxpos = 5,
  5446. maxwidth = 0,
  5447. xpos;
  5448. series
  5449. .attr('transform', function(d, i) {
  5450. var length = d3.select(this).select('text').node().getComputedTextLength() + padding;
  5451. xpos = newxpos;
  5452. if (width < margin.left + margin.right + xpos + length) {
  5453. newxpos = xpos = 5;
  5454. ypos += versPadding;
  5455. }
  5456. newxpos += length;
  5457. if (newxpos > maxwidth) maxwidth = newxpos;
  5458. if(legendWidth < xpos + maxwidth) {
  5459. legendWidth = xpos + maxwidth;
  5460. }
  5461. return 'translate(' + xpos + ',' + ypos + ')';
  5462. });
  5463. //position legend as far right as possible within the total width
  5464. g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
  5465. height = margin.top + margin.bottom + ypos + 15;
  5466. }
  5467. if(vers == 'furious') {
  5468. // Size rectangles after text is placed
  5469. seriesShape
  5470. .attr('width', function(d,i) {
  5471. return seriesText[0][i].getComputedTextLength() + 27;
  5472. })
  5473. .attr('height', 18)
  5474. .attr('y', -9)
  5475. .attr('x', -15);
  5476. // The background for the expanded legend (UI)
  5477. gEnter.insert('rect',':first-child')
  5478. .attr('class', 'nv-legend-bg')
  5479. .attr('fill', '#eee')
  5480. // .attr('stroke', '#444')
  5481. .attr('opacity',0);
  5482. var seriesBG = g.select('.nv-legend-bg');
  5483. seriesBG
  5484. .transition().duration(300)
  5485. .attr('x', -versPadding )
  5486. .attr('width', legendWidth + versPadding - 12)
  5487. .attr('height', height + 10)
  5488. .attr('y', -margin.top - 10)
  5489. .attr('opacity', expanded ? 1 : 0);
  5490. }
  5491. seriesShape
  5492. .style('fill', setBGColor)
  5493. .style('fill-opacity', setBGOpacity)
  5494. .style('stroke', setBGColor);
  5495. });
  5496. function setTextColor(d,i) {
  5497. if(vers != 'furious') return '#000';
  5498. if(expanded) {
  5499. return d.disengaged ? '#000' : '#fff';
  5500. } else if (!expanded) {
  5501. if(!d.color) d.color = color(d,i);
  5502. return !!d.disabled ? d.color : '#fff';
  5503. }
  5504. }
  5505. function setBGColor(d,i) {
  5506. if(expanded && vers == 'furious') {
  5507. return d.disengaged ? '#eee' : d.color || color(d,i);
  5508. } else {
  5509. return d.color || color(d,i);
  5510. }
  5511. }
  5512. function setBGOpacity(d,i) {
  5513. if(expanded && vers == 'furious') {
  5514. return 1;
  5515. } else {
  5516. return !!d.disabled ? 0 : 1;
  5517. }
  5518. }
  5519. return chart;
  5520. }
  5521. //============================================================
  5522. // Expose Public Variables
  5523. //------------------------------------------------------------
  5524. chart.dispatch = dispatch;
  5525. chart.options = nv.utils.optionsFunc.bind(chart);
  5526. chart._options = Object.create({}, {
  5527. // simple options, just get/set the necessary values
  5528. width: {get: function(){return width;}, set: function(_){width=_;}},
  5529. height: {get: function(){return height;}, set: function(_){height=_;}},
  5530. key: {get: function(){return getKey;}, set: function(_){getKey=_;}},
  5531. keyFormatter: {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}},
  5532. align: {get: function(){return align;}, set: function(_){align=_;}},
  5533. maxKeyLength: {get: function(){return maxKeyLength;}, set: function(_){maxKeyLength=_;}},
  5534. rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}},
  5535. padding: {get: function(){return padding;}, set: function(_){padding=_;}},
  5536. updateState: {get: function(){return updateState;}, set: function(_){updateState=_;}},
  5537. radioButtonMode:{get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}},
  5538. expanded: {get: function(){return expanded;}, set: function(_){expanded=_;}},
  5539. vers: {get: function(){return vers;}, set: function(_){vers=_;}},
  5540. // options that require extra logic in the setter
  5541. margin: {get: function(){return margin;}, set: function(_){
  5542. margin.top = _.top !== undefined ? _.top : margin.top;
  5543. margin.right = _.right !== undefined ? _.right : margin.right;
  5544. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  5545. margin.left = _.left !== undefined ? _.left : margin.left;
  5546. }},
  5547. color: {get: function(){return color;}, set: function(_){
  5548. color = nv.utils.getColor(_);
  5549. }}
  5550. });
  5551. nv.utils.initOptions(chart);
  5552. return chart;
  5553. };
  5554. nv.models.line = function() {
  5555. "use strict";
  5556. //============================================================
  5557. // Public Variables with Default Settings
  5558. //------------------------------------------------------------
  5559. var scatter = nv.models.scatter()
  5560. ;
  5561. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  5562. , width = 960
  5563. , height = 500
  5564. , container = null
  5565. , strokeWidth = 1.5
  5566. , color = nv.utils.defaultColor() // a function that returns a color
  5567. , getX = function(d) { return d.x } // accessor to get the x value from a data point
  5568. , getY = function(d) { return d.y } // accessor to get the y value from a data point
  5569. , 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
  5570. , isArea = function(d) { return d.area } // decides if a line is an area or just a line
  5571. , clipEdge = false // if true, masks lines within x and y scale
  5572. , x //can be accessed via chart.xScale()
  5573. , y //can be accessed via chart.yScale()
  5574. , interpolate = "linear" // controls the line interpolation
  5575. , duration = 250
  5576. , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
  5577. ;
  5578. scatter
  5579. .pointSize(16) // default size
  5580. .pointDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor
  5581. ;
  5582. //============================================================
  5583. //============================================================
  5584. // Private Variables
  5585. //------------------------------------------------------------
  5586. var x0, y0 //used to store previous scales
  5587. , renderWatch = nv.utils.renderWatch(dispatch, duration)
  5588. ;
  5589. //============================================================
  5590. function chart(selection) {
  5591. renderWatch.reset();
  5592. renderWatch.models(scatter);
  5593. selection.each(function(data) {
  5594. container = d3.select(this);
  5595. var availableWidth = nv.utils.availableWidth(width, container, margin),
  5596. availableHeight = nv.utils.availableHeight(height, container, margin);
  5597. nv.utils.initSVG(container);
  5598. // Setup Scales
  5599. x = scatter.xScale();
  5600. y = scatter.yScale();
  5601. x0 = x0 || x;
  5602. y0 = y0 || y;
  5603. // Setup containers and skeleton of chart
  5604. var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]);
  5605. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line');
  5606. var defsEnter = wrapEnter.append('defs');
  5607. var gEnter = wrapEnter.append('g');
  5608. var g = wrap.select('g');
  5609. gEnter.append('g').attr('class', 'nv-groups');
  5610. gEnter.append('g').attr('class', 'nv-scatterWrap');
  5611. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  5612. scatter
  5613. .width(availableWidth)
  5614. .height(availableHeight);
  5615. var scatterWrap = wrap.select('.nv-scatterWrap');
  5616. scatterWrap.call(scatter);
  5617. defsEnter.append('clipPath')
  5618. .attr('id', 'nv-edge-clip-' + scatter.id())
  5619. .append('rect');
  5620. wrap.select('#nv-edge-clip-' + scatter.id() + ' rect')
  5621. .attr('width', availableWidth)
  5622. .attr('height', (availableHeight > 0) ? availableHeight : 0);
  5623. g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
  5624. scatterWrap
  5625. .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
  5626. var groups = wrap.select('.nv-groups').selectAll('.nv-group')
  5627. .data(function(d) { return d }, function(d) { return d.key });
  5628. groups.enter().append('g')
  5629. .style('stroke-opacity', 1e-6)
  5630. .style('stroke-width', function(d) { return d.strokeWidth || strokeWidth })
  5631. .style('fill-opacity', 1e-6);
  5632. groups.exit().remove();
  5633. groups
  5634. .attr('class', function(d,i) {
  5635. return (d.classed || '') + ' nv-group nv-series-' + i;
  5636. })
  5637. .classed('hover', function(d) { return d.hover })
  5638. .style('fill', function(d,i){ return color(d, i) })
  5639. .style('stroke', function(d,i){ return color(d, i)});
  5640. groups.watchTransition(renderWatch, 'line: groups')
  5641. .style('stroke-opacity', 1)
  5642. .style('fill-opacity', function(d) { return d.fillOpacity || .5});
  5643. var areaPaths = groups.selectAll('path.nv-area')
  5644. .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area
  5645. areaPaths.enter().append('path')
  5646. .attr('class', 'nv-area')
  5647. .attr('d', function(d) {
  5648. return d3.svg.area()
  5649. .interpolate(interpolate)
  5650. .defined(defined)
  5651. .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
  5652. .y0(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
  5653. .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
  5654. //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
  5655. .apply(this, [d.values])
  5656. });
  5657. groups.exit().selectAll('path.nv-area')
  5658. .remove();
  5659. areaPaths.watchTransition(renderWatch, 'line: areaPaths')
  5660. .attr('d', function(d) {
  5661. return d3.svg.area()
  5662. .interpolate(interpolate)
  5663. .defined(defined)
  5664. .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
  5665. .y0(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
  5666. .y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
  5667. //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
  5668. .apply(this, [d.values])
  5669. });
  5670. var linePaths = groups.selectAll('path.nv-line')
  5671. .data(function(d) { return [d.values] });
  5672. linePaths.enter().append('path')
  5673. .attr('class', 'nv-line')
  5674. .attr('d',
  5675. d3.svg.line()
  5676. .interpolate(interpolate)
  5677. .defined(defined)
  5678. .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
  5679. .y(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
  5680. );
  5681. linePaths.watchTransition(renderWatch, 'line: linePaths')
  5682. .attr('d',
  5683. d3.svg.line()
  5684. .interpolate(interpolate)
  5685. .defined(defined)
  5686. .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
  5687. .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
  5688. );
  5689. //store old scales for use in transitions on update
  5690. x0 = x.copy();
  5691. y0 = y.copy();
  5692. });
  5693. renderWatch.renderEnd('line immediate');
  5694. return chart;
  5695. }
  5696. //============================================================
  5697. // Expose Public Variables
  5698. //------------------------------------------------------------
  5699. chart.dispatch = dispatch;
  5700. chart.scatter = scatter;
  5701. // Pass through events
  5702. scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); });
  5703. scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); });
  5704. scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); });
  5705. chart.options = nv.utils.optionsFunc.bind(chart);
  5706. chart._options = Object.create({}, {
  5707. // simple options, just get/set the necessary values
  5708. width: {get: function(){return width;}, set: function(_){width=_;}},
  5709. height: {get: function(){return height;}, set: function(_){height=_;}},
  5710. defined: {get: function(){return defined;}, set: function(_){defined=_;}},
  5711. interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
  5712. clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
  5713. // options that require extra logic in the setter
  5714. margin: {get: function(){return margin;}, set: function(_){
  5715. margin.top = _.top !== undefined ? _.top : margin.top;
  5716. margin.right = _.right !== undefined ? _.right : margin.right;
  5717. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  5718. margin.left = _.left !== undefined ? _.left : margin.left;
  5719. }},
  5720. duration: {get: function(){return duration;}, set: function(_){
  5721. duration = _;
  5722. renderWatch.reset(duration);
  5723. scatter.duration(duration);
  5724. }},
  5725. isArea: {get: function(){return isArea;}, set: function(_){
  5726. isArea = d3.functor(_);
  5727. }},
  5728. x: {get: function(){return getX;}, set: function(_){
  5729. getX = _;
  5730. scatter.x(_);
  5731. }},
  5732. y: {get: function(){return getY;}, set: function(_){
  5733. getY = _;
  5734. scatter.y(_);
  5735. }},
  5736. color: {get: function(){return color;}, set: function(_){
  5737. color = nv.utils.getColor(_);
  5738. scatter.color(color);
  5739. }}
  5740. });
  5741. nv.utils.inheritOptions(chart, scatter);
  5742. nv.utils.initOptions(chart);
  5743. return chart;
  5744. };
  5745. nv.models.lineChart = function() {
  5746. "use strict";
  5747. //============================================================
  5748. // Public Variables with Default Settings
  5749. //------------------------------------------------------------
  5750. var lines = nv.models.line()
  5751. , xAxis = nv.models.axis()
  5752. , yAxis = nv.models.axis()
  5753. , legend = nv.models.legend()
  5754. , interactiveLayer = nv.interactiveGuideline()
  5755. , tooltip = nv.models.tooltip()
  5756. , focus = nv.models.focus(nv.models.line())
  5757. ;
  5758. var margin = {top: 30, right: 20, bottom: 50, left: 60}
  5759. , marginTop = null
  5760. , color = nv.utils.defaultColor()
  5761. , width = null
  5762. , height = null
  5763. , showLegend = true
  5764. , legendPosition = 'top'
  5765. , showXAxis = true
  5766. , showYAxis = true
  5767. , rightAlignYAxis = false
  5768. , useInteractiveGuideline = false
  5769. , x
  5770. , y
  5771. , focusEnable = false
  5772. , state = nv.utils.state()
  5773. , defaultState = null
  5774. , noData = null
  5775. , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
  5776. , duration = 250
  5777. ;
  5778. // set options on sub-objects for this chart
  5779. xAxis.orient('bottom').tickPadding(7);
  5780. yAxis.orient(rightAlignYAxis ? 'right' : 'left');
  5781. lines.clipEdge(true).duration(0);
  5782. tooltip.valueFormatter(function(d, i) {
  5783. return yAxis.tickFormat()(d, i);
  5784. }).headerFormatter(function(d, i) {
  5785. return xAxis.tickFormat()(d, i);
  5786. });
  5787. interactiveLayer.tooltip.valueFormatter(function(d, i) {
  5788. return yAxis.tickFormat()(d, i);
  5789. }).headerFormatter(function(d, i) {
  5790. return xAxis.tickFormat()(d, i);
  5791. });
  5792. //============================================================
  5793. // Private Variables
  5794. //------------------------------------------------------------
  5795. var renderWatch = nv.utils.renderWatch(dispatch, duration);
  5796. var stateGetter = function(data) {
  5797. return function(){
  5798. return {
  5799. active: data.map(function(d) { return !d.disabled; })
  5800. };
  5801. };
  5802. };
  5803. var stateSetter = function(data) {
  5804. return function(state) {
  5805. if (state.active !== undefined)
  5806. data.forEach(function(series,i) {
  5807. series.disabled = !state.active[i];
  5808. });
  5809. };
  5810. };
  5811. function chart(selection) {
  5812. renderWatch.reset();
  5813. renderWatch.models(lines);
  5814. if (showXAxis) renderWatch.models(xAxis);
  5815. if (showYAxis) renderWatch.models(yAxis);
  5816. selection.each(function(data) {
  5817. var container = d3.select(this);
  5818. nv.utils.initSVG(container);
  5819. var availableWidth = nv.utils.availableWidth(width, container, margin),
  5820. availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0);
  5821. chart.update = function() {
  5822. if( duration === 0 ) {
  5823. container.call( chart );
  5824. } else {
  5825. container.transition().duration(duration).call(chart);
  5826. }
  5827. };
  5828. chart.container = this;
  5829. state
  5830. .setter(stateSetter(data), chart.update)
  5831. .getter(stateGetter(data))
  5832. .update();
  5833. // DEPRECATED set state.disabled
  5834. state.disabled = data.map(function(d) { return !!d.disabled; });
  5835. if (!defaultState) {
  5836. var key;
  5837. defaultState = {};
  5838. for (key in state) {
  5839. if (state[key] instanceof Array)
  5840. defaultState[key] = state[key].slice(0);
  5841. else
  5842. defaultState[key] = state[key];
  5843. }
  5844. }
  5845. // Display noData message if there's nothing to show.
  5846. if (!data || !data.length || !data.filter(function(d) { return d.values.length; }).length) {
  5847. nv.utils.noData(chart, container);
  5848. return chart;
  5849. } else {
  5850. container.selectAll('.nv-noData').remove();
  5851. }
  5852. /* Update `main' graph on brush update. */
  5853. focus.dispatch.on("onBrush", function(extent) {
  5854. onBrush(extent);
  5855. });
  5856. // Setup Scales
  5857. x = lines.xScale();
  5858. y = lines.yScale();
  5859. // Setup containers and skeleton of chart
  5860. var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]);
  5861. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g');
  5862. var g = wrap.select('g');
  5863. gEnter.append('g').attr('class', 'nv-legendWrap');
  5864. var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
  5865. focusEnter.append('g').attr('class', 'nv-background').append('rect');
  5866. focusEnter.append('g').attr('class', 'nv-x nv-axis');
  5867. focusEnter.append('g').attr('class', 'nv-y nv-axis');
  5868. focusEnter.append('g').attr('class', 'nv-linesWrap');
  5869. focusEnter.append('g').attr('class', 'nv-interactive');
  5870. var contextEnter = gEnter.append('g').attr('class', 'nv-focusWrap');
  5871. // Legend
  5872. if (!showLegend) {
  5873. g.select('.nv-legendWrap').selectAll('*').remove();
  5874. } else {
  5875. legend.width(availableWidth);
  5876. g.select('.nv-legendWrap')
  5877. .datum(data)
  5878. .call(legend);
  5879. if (legendPosition === 'bottom') {
  5880. wrap.select('.nv-legendWrap')
  5881. .attr('transform', 'translate(0,' + availableHeight +')');
  5882. } else if (legendPosition === 'top') {
  5883. if (!marginTop && legend.height() !== margin.top) {
  5884. margin.top = legend.height();
  5885. availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0);
  5886. }
  5887. wrap.select('.nv-legendWrap')
  5888. .attr('transform', 'translate(0,' + (-margin.top) +')');
  5889. }
  5890. }
  5891. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  5892. if (rightAlignYAxis) {
  5893. g.select(".nv-y.nv-axis")
  5894. .attr("transform", "translate(" + availableWidth + ",0)");
  5895. }
  5896. //Set up interactive layer
  5897. if (useInteractiveGuideline) {
  5898. interactiveLayer
  5899. .width(availableWidth)
  5900. .height(availableHeight)
  5901. .margin({left:margin.left, top:margin.top})
  5902. .svgContainer(container)
  5903. .xScale(x);
  5904. wrap.select(".nv-interactive").call(interactiveLayer);
  5905. }
  5906. g.select('.nv-focus .nv-background rect')
  5907. .attr('width', availableWidth)
  5908. .attr('height', availableHeight);
  5909. lines
  5910. .width(availableWidth)
  5911. .height(availableHeight)
  5912. .color(data.map(function(d,i) {
  5913. return d.color || color(d, i);
  5914. }).filter(function(d,i) { return !data[i].disabled; }));
  5915. var linesWrap = g.select('.nv-linesWrap')
  5916. .datum(data.filter(function(d) { return !d.disabled; }));
  5917. // Setup Main (Focus) Axes
  5918. if (showXAxis) {
  5919. xAxis
  5920. .scale(x)
  5921. ._ticks(nv.utils.calcTicksX(availableWidth/100, data) )
  5922. .tickSize(-availableHeight, 0);
  5923. }
  5924. if (showYAxis) {
  5925. yAxis
  5926. .scale(y)
  5927. ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
  5928. .tickSize( -availableWidth, 0);
  5929. }
  5930. //============================================================
  5931. // Update Axes
  5932. //============================================================
  5933. function updateXAxis() {
  5934. if(showXAxis) {
  5935. g.select('.nv-focus .nv-x.nv-axis')
  5936. .transition()
  5937. .duration(duration)
  5938. .call(xAxis)
  5939. ;
  5940. }
  5941. }
  5942. function updateYAxis() {
  5943. if(showYAxis) {
  5944. g.select('.nv-focus .nv-y.nv-axis')
  5945. .transition()
  5946. .duration(duration)
  5947. .call(yAxis)
  5948. ;
  5949. }
  5950. }
  5951. g.select('.nv-focus .nv-x.nv-axis')
  5952. .attr('transform', 'translate(0,' + availableHeight + ')');
  5953. //============================================================
  5954. // Update Focus
  5955. //============================================================
  5956. if(!focusEnable) {
  5957. linesWrap.call(lines);
  5958. updateXAxis();
  5959. updateYAxis();
  5960. } else {
  5961. focus.width(availableWidth);
  5962. g.select('.nv-focusWrap')
  5963. .attr('transform', 'translate(0,' + ( availableHeight + margin.bottom + focus.margin().top) + ')')
  5964. .datum(data.filter(function(d) { return !d.disabled; }))
  5965. .call(focus);
  5966. var extent = focus.brush.empty() ? focus.xDomain() : focus.brush.extent();
  5967. if(extent !== null){
  5968. onBrush(extent);
  5969. }
  5970. }
  5971. //============================================================
  5972. // Event Handling/Dispatching (in chart's scope)
  5973. //------------------------------------------------------------
  5974. legend.dispatch.on('stateChange', function(newState) {
  5975. for (var key in newState)
  5976. state[key] = newState[key];
  5977. dispatch.stateChange(state);
  5978. chart.update();
  5979. });
  5980. interactiveLayer.dispatch.on('elementMousemove', function(e) {
  5981. lines.clearHighlights();
  5982. var singlePoint, pointIndex, pointXLocation, allData = [];
  5983. data
  5984. .filter(function(series, i) {
  5985. series.seriesIndex = i;
  5986. return !series.disabled && !series.disableTooltip;
  5987. })
  5988. .forEach(function(series,i) {
  5989. var extent = focusEnable ? (focus.brush.empty() ? focus.xScale().domain() : focus.brush.extent()) : x.domain();
  5990. var currentValues = series.values.filter(function(d,i) {
  5991. // Checks if the x point is between the extents, handling case where extent[0] is greater than extent[1]
  5992. // (e.g. x domain is manually set to reverse the x-axis)
  5993. if(extent[0] <= extent[1]) {
  5994. return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
  5995. } else {
  5996. return lines.x()(d,i) >= extent[1] && lines.x()(d,i) <= extent[0];
  5997. }
  5998. });
  5999. pointIndex = nv.interactiveBisect(currentValues, e.pointXValue, lines.x());
  6000. var point = currentValues[pointIndex];
  6001. var pointYValue = chart.y()(point, pointIndex);
  6002. if (pointYValue !== null) {
  6003. lines.highlightPoint(i, pointIndex, true);
  6004. }
  6005. if (point === undefined) return;
  6006. if (singlePoint === undefined) singlePoint = point;
  6007. if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
  6008. allData.push({
  6009. key: series.key,
  6010. value: pointYValue,
  6011. color: color(series,series.seriesIndex),
  6012. data: point
  6013. });
  6014. });
  6015. //Highlight the tooltip entry based on which point the mouse is closest to.
  6016. if (allData.length > 2) {
  6017. var yValue = chart.yScale().invert(e.mouseY);
  6018. var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
  6019. var threshold = 0.03 * domainExtent;
  6020. var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value;}),yValue,threshold);
  6021. if (indexToHighlight !== null)
  6022. allData[indexToHighlight].highlight = true;
  6023. }
  6024. var defaultValueFormatter = function(d,i) {
  6025. return d == null ? "N/A" : yAxis.tickFormat()(d);
  6026. };
  6027. interactiveLayer.tooltip
  6028. .valueFormatter(interactiveLayer.tooltip.valueFormatter() || defaultValueFormatter)
  6029. .data({
  6030. value: chart.x()( singlePoint,pointIndex ),
  6031. index: pointIndex,
  6032. series: allData
  6033. })();
  6034. interactiveLayer.renderGuideLine(pointXLocation);
  6035. });
  6036. interactiveLayer.dispatch.on('elementClick', function(e) {
  6037. var pointXLocation, allData = [];
  6038. data.filter(function(series, i) {
  6039. series.seriesIndex = i;
  6040. return !series.disabled;
  6041. }).forEach(function(series) {
  6042. var pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
  6043. var point = series.values[pointIndex];
  6044. if (typeof point === 'undefined') return;
  6045. if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
  6046. var yPos = chart.yScale()(chart.y()(point,pointIndex));
  6047. allData.push({
  6048. point: point,
  6049. pointIndex: pointIndex,
  6050. pos: [pointXLocation, yPos],
  6051. seriesIndex: series.seriesIndex,
  6052. series: series
  6053. });
  6054. });
  6055. lines.dispatch.elementClick(allData);
  6056. });
  6057. interactiveLayer.dispatch.on("elementMouseout",function(e) {
  6058. lines.clearHighlights();
  6059. });
  6060. dispatch.on('changeState', function(e) {
  6061. if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
  6062. data.forEach(function(series,i) {
  6063. series.disabled = e.disabled[i];
  6064. });
  6065. state.disabled = e.disabled;
  6066. }
  6067. chart.update();
  6068. });
  6069. //============================================================
  6070. // Functions
  6071. //------------------------------------------------------------
  6072. // Taken from crossfilter (http://square.github.com/crossfilter/)
  6073. function resizePath(d) {
  6074. var e = +(d == 'e'),
  6075. x = e ? 1 : -1,
  6076. y = availableHeight / 3;
  6077. return 'M' + (0.5 * x) + ',' + y
  6078. + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
  6079. + 'V' + (2 * y - 6)
  6080. + 'A6,6 0 0 ' + e + ' ' + (0.5 * x) + ',' + (2 * y)
  6081. + 'Z'
  6082. + 'M' + (2.5 * x) + ',' + (y + 8)
  6083. + 'V' + (2 * y - 8)
  6084. + 'M' + (4.5 * x) + ',' + (y + 8)
  6085. + 'V' + (2 * y - 8);
  6086. }
  6087. function onBrush(extent) {
  6088. // Update Main (Focus)
  6089. var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
  6090. .datum(
  6091. data.filter(function(d) { return !d.disabled; })
  6092. .map(function(d,i) {
  6093. return {
  6094. key: d.key,
  6095. area: d.area,
  6096. classed: d.classed,
  6097. values: d.values.filter(function(d,i) {
  6098. return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
  6099. }),
  6100. disableTooltip: d.disableTooltip
  6101. };
  6102. })
  6103. );
  6104. focusLinesWrap.transition().duration(duration).call(lines);
  6105. // Update Main (Focus) Axes
  6106. updateXAxis();
  6107. updateYAxis();
  6108. }
  6109. });
  6110. renderWatch.renderEnd('lineChart immediate');
  6111. return chart;
  6112. }
  6113. //============================================================
  6114. // Event Handling/Dispatching (out of chart's scope)
  6115. //------------------------------------------------------------
  6116. lines.dispatch.on('elementMouseover.tooltip', function(evt) {
  6117. if(!evt.series.disableTooltip){
  6118. tooltip.data(evt).hidden(false);
  6119. }
  6120. });
  6121. lines.dispatch.on('elementMouseout.tooltip', function(evt) {
  6122. tooltip.hidden(true);
  6123. });
  6124. //============================================================
  6125. // Expose Public Variables
  6126. //------------------------------------------------------------
  6127. // expose chart's sub-components
  6128. chart.dispatch = dispatch;
  6129. chart.lines = lines;
  6130. chart.legend = legend;
  6131. chart.focus = focus;
  6132. chart.xAxis = xAxis;
  6133. chart.x2Axis = focus.xAxis
  6134. chart.yAxis = yAxis;
  6135. chart.y2Axis = focus.yAxis
  6136. chart.interactiveLayer = interactiveLayer;
  6137. chart.tooltip = tooltip;
  6138. chart.state = state;
  6139. chart.dispatch = dispatch;
  6140. chart.options = nv.utils.optionsFunc.bind(chart);
  6141. chart._options = Object.create({}, {
  6142. // simple options, just get/set the necessary values
  6143. width: {get: function(){return width;}, set: function(_){width=_;}},
  6144. height: {get: function(){return height;}, set: function(_){height=_;}},
  6145. showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
  6146. legendPosition: {get: function(){return legendPosition;}, set: function(_){legendPosition=_;}},
  6147. showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
  6148. showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
  6149. defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
  6150. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  6151. // Focus options, mostly passed onto focus model.
  6152. focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}},
  6153. focusHeight: {get: function(){return focus.height();}, set: function(_){focus.height(_);}},
  6154. focusShowAxisX: {get: function(){return focus.showXAxis();}, set: function(_){focus.showXAxis(_);}},
  6155. focusShowAxisY: {get: function(){return focus.showYAxis();}, set: function(_){focus.showYAxis(_);}},
  6156. brushExtent: {get: function(){return focus.brushExtent();}, set: function(_){focus.brushExtent(_);}},
  6157. // options that require extra logic in the setter
  6158. focusMargin: {get: function(){return focus.margin}, set: function(_){
  6159. if (_.top !== undefined) {
  6160. margin.top = _.top;
  6161. marginTop = _.top;
  6162. }
  6163. focus.margin.right = _.right !== undefined ? _.right : focus.margin.right;
  6164. focus.margin.bottom = _.bottom !== undefined ? _.bottom : focus.margin.bottom;
  6165. focus.margin.left = _.left !== undefined ? _.left : focus.margin.left;
  6166. }},
  6167. margin: {get: function(){return margin;}, set: function(_){
  6168. margin.top = _.top !== undefined ? _.top : margin.top;
  6169. margin.right = _.right !== undefined ? _.right : margin.right;
  6170. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  6171. margin.left = _.left !== undefined ? _.left : margin.left;
  6172. }},
  6173. duration: {get: function(){return duration;}, set: function(_){
  6174. duration = _;
  6175. renderWatch.reset(duration);
  6176. lines.duration(duration);
  6177. focus.duration(duration);
  6178. xAxis.duration(duration);
  6179. yAxis.duration(duration);
  6180. }},
  6181. color: {get: function(){return color;}, set: function(_){
  6182. color = nv.utils.getColor(_);
  6183. legend.color(color);
  6184. lines.color(color);
  6185. focus.color(color);
  6186. }},
  6187. interpolate: {get: function(){return lines.interpolate();}, set: function(_){
  6188. lines.interpolate(_);
  6189. focus.interpolate(_);
  6190. }},
  6191. xTickFormat: {get: function(){return xAxis.tickFormat();}, set: function(_){
  6192. xAxis.tickFormat(_);
  6193. focus.xTickFormat(_);
  6194. }},
  6195. yTickFormat: {get: function(){return yAxis.tickFormat();}, set: function(_){
  6196. yAxis.tickFormat(_);
  6197. focus.yTickFormat(_);
  6198. }},
  6199. x: {get: function(){return lines.x();}, set: function(_){
  6200. lines.x(_);
  6201. focus.x(_);
  6202. }},
  6203. y: {get: function(){return lines.y();}, set: function(_){
  6204. lines.y(_);
  6205. focus.y(_);
  6206. }},
  6207. rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
  6208. rightAlignYAxis = _;
  6209. yAxis.orient( rightAlignYAxis ? 'right' : 'left');
  6210. }},
  6211. useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
  6212. useInteractiveGuideline = _;
  6213. if (useInteractiveGuideline) {
  6214. lines.interactive(false);
  6215. lines.useVoronoi(false);
  6216. }
  6217. }}
  6218. });
  6219. nv.utils.inheritOptions(chart, lines);
  6220. nv.utils.initOptions(chart);
  6221. return chart;
  6222. };
  6223. nv.models.lineWithFocusChart = function() {
  6224. return nv.models.lineChart()
  6225. .margin({ bottom: 30 })
  6226. .focusEnable( true );
  6227. };
  6228. nv.models.linePlusBarChart = function() {
  6229. "use strict";
  6230. //============================================================
  6231. // Public Variables with Default Settings
  6232. //------------------------------------------------------------
  6233. var lines = nv.models.line()
  6234. , lines2 = nv.models.line()
  6235. , bars = nv.models.historicalBar()
  6236. , bars2 = nv.models.historicalBar()
  6237. , xAxis = nv.models.axis()
  6238. , x2Axis = nv.models.axis()
  6239. , y1Axis = nv.models.axis()
  6240. , y2Axis = nv.models.axis()
  6241. , y3Axis = nv.models.axis()
  6242. , y4Axis = nv.models.axis()
  6243. , legend = nv.models.legend()
  6244. , brush = d3.svg.brush()
  6245. , tooltip = nv.models.tooltip()
  6246. ;
  6247. var margin = {top: 30, right: 30, bottom: 30, left: 60}
  6248. , marginTop = null
  6249. , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
  6250. , width = null
  6251. , height = null
  6252. , getX = function(d) { return d.x }
  6253. , getY = function(d) { return d.y }
  6254. , color = nv.utils.defaultColor()
  6255. , showLegend = true
  6256. , focusEnable = true
  6257. , focusShowAxisY = false
  6258. , focusShowAxisX = true
  6259. , focusHeight = 50
  6260. , extent
  6261. , brushExtent = null
  6262. , x
  6263. , x2
  6264. , y1
  6265. , y2
  6266. , y3
  6267. , y4
  6268. , noData = null
  6269. , dispatch = d3.dispatch('brush', 'stateChange', 'changeState')
  6270. , transitionDuration = 0
  6271. , state = nv.utils.state()
  6272. , defaultState = null
  6273. , legendLeftAxisHint = ' (left axis)'
  6274. , legendRightAxisHint = ' (right axis)'
  6275. , switchYAxisOrder = false
  6276. ;
  6277. lines.clipEdge(true);
  6278. lines2.interactive(false);
  6279. // We don't want any points emitted for the focus chart's scatter graph.
  6280. lines2.pointActive(function(d) { return false });
  6281. xAxis.orient('bottom').tickPadding(5);
  6282. y1Axis.orient('left');
  6283. y2Axis.orient('right');
  6284. x2Axis.orient('bottom').tickPadding(5);
  6285. y3Axis.orient('left');
  6286. y4Axis.orient('right');
  6287. tooltip.headerEnabled(true).headerFormatter(function(d, i) {
  6288. return xAxis.tickFormat()(d, i);
  6289. });
  6290. //============================================================
  6291. // Private Variables
  6292. //------------------------------------------------------------
  6293. var getBarsAxis = function() {
  6294. return switchYAxisOrder
  6295. ? { main: y2Axis, focus: y4Axis }
  6296. : { main: y1Axis, focus: y3Axis }
  6297. }
  6298. var getLinesAxis = function() {
  6299. return switchYAxisOrder
  6300. ? { main: y1Axis, focus: y3Axis }
  6301. : { main: y2Axis, focus: y4Axis }
  6302. }
  6303. var stateGetter = function(data) {
  6304. return function(){
  6305. return {
  6306. active: data.map(function(d) { return !d.disabled })
  6307. };
  6308. }
  6309. };
  6310. var stateSetter = function(data) {
  6311. return function(state) {
  6312. if (state.active !== undefined)
  6313. data.forEach(function(series,i) {
  6314. series.disabled = !state.active[i];
  6315. });
  6316. }
  6317. };
  6318. var allDisabled = function(data) {
  6319. return data.every(function(series) {
  6320. return series.disabled;
  6321. });
  6322. }
  6323. function chart(selection) {
  6324. selection.each(function(data) {
  6325. var container = d3.select(this),
  6326. that = this;
  6327. nv.utils.initSVG(container);
  6328. var availableWidth = nv.utils.availableWidth(width, container, margin),
  6329. availableHeight1 = nv.utils.availableHeight(height, container, margin)
  6330. - (focusEnable ? focusHeight : 0),
  6331. availableHeight2 = focusHeight - margin2.top - margin2.bottom;
  6332. chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
  6333. chart.container = this;
  6334. state
  6335. .setter(stateSetter(data), chart.update)
  6336. .getter(stateGetter(data))
  6337. .update();
  6338. // DEPRECATED set state.disableddisabled
  6339. state.disabled = data.map(function(d) { return !!d.disabled });
  6340. if (!defaultState) {
  6341. var key;
  6342. defaultState = {};
  6343. for (key in state) {
  6344. if (state[key] instanceof Array)
  6345. defaultState[key] = state[key].slice(0);
  6346. else
  6347. defaultState[key] = state[key];
  6348. }
  6349. }
  6350. // Display No Data message if there's nothing to show.
  6351. if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
  6352. nv.utils.noData(chart, container)
  6353. return chart;
  6354. } else {
  6355. container.selectAll('.nv-noData').remove();
  6356. }
  6357. // Setup Scales
  6358. var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
  6359. var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
  6360. if (dataBars.length && !switchYAxisOrder) {
  6361. x = bars.xScale();
  6362. } else {
  6363. x = lines.xScale();
  6364. }
  6365. x2 = x2Axis.scale();
  6366. // select the scales and series based on the position of the yAxis
  6367. y1 = switchYAxisOrder ? lines.yScale() : bars.yScale();
  6368. y2 = switchYAxisOrder ? bars.yScale() : lines.yScale();
  6369. y3 = switchYAxisOrder ? lines2.yScale() : bars2.yScale();
  6370. y4 = switchYAxisOrder ? bars2.yScale() : lines2.yScale();
  6371. var series1 = data
  6372. .filter(function(d) { return !d.disabled && (switchYAxisOrder ? !d.bar : d.bar) })
  6373. .map(function(d) {
  6374. return d.values.map(function(d,i) {
  6375. return { x: getX(d,i), y: getY(d,i) }
  6376. })
  6377. });
  6378. var series2 = data
  6379. .filter(function(d) { return !d.disabled && (switchYAxisOrder ? d.bar : !d.bar) })
  6380. .map(function(d) {
  6381. return d.values.map(function(d,i) {
  6382. return { x: getX(d,i), y: getY(d,i) }
  6383. })
  6384. });
  6385. x.range([0, availableWidth]);
  6386. x2 .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
  6387. .range([0, availableWidth]);
  6388. // Setup containers and skeleton of chart
  6389. var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
  6390. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
  6391. var g = wrap.select('g');
  6392. gEnter.append('g').attr('class', 'nv-legendWrap');
  6393. // this is the main chart
  6394. var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
  6395. focusEnter.append('g').attr('class', 'nv-x nv-axis');
  6396. focusEnter.append('g').attr('class', 'nv-y1 nv-axis');
  6397. focusEnter.append('g').attr('class', 'nv-y2 nv-axis');
  6398. focusEnter.append('g').attr('class', 'nv-barsWrap');
  6399. focusEnter.append('g').attr('class', 'nv-linesWrap');
  6400. // context chart is where you can focus in
  6401. var contextEnter = gEnter.append('g').attr('class', 'nv-context');
  6402. contextEnter.append('g').attr('class', 'nv-x nv-axis');
  6403. contextEnter.append('g').attr('class', 'nv-y1 nv-axis');
  6404. contextEnter.append('g').attr('class', 'nv-y2 nv-axis');
  6405. contextEnter.append('g').attr('class', 'nv-barsWrap');
  6406. contextEnter.append('g').attr('class', 'nv-linesWrap');
  6407. contextEnter.append('g').attr('class', 'nv-brushBackground');
  6408. contextEnter.append('g').attr('class', 'nv-x nv-brush');
  6409. //============================================================
  6410. // Legend
  6411. //------------------------------------------------------------
  6412. if (!showLegend) {
  6413. g.select('.nv-legendWrap').selectAll('*').remove();
  6414. } else {
  6415. var legendWidth = legend.align() ? availableWidth / 2 : availableWidth;
  6416. var legendXPosition = legend.align() ? legendWidth : 0;
  6417. legend.width(legendWidth);
  6418. g.select('.nv-legendWrap')
  6419. .datum(data.map(function(series) {
  6420. series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
  6421. if(switchYAxisOrder) {
  6422. series.key = series.originalKey + (series.bar ? legendRightAxisHint : legendLeftAxisHint);
  6423. } else {
  6424. series.key = series.originalKey + (series.bar ? legendLeftAxisHint : legendRightAxisHint);
  6425. }
  6426. return series;
  6427. }))
  6428. .call(legend);
  6429. if (!marginTop && legend.height() !== margin.top) {
  6430. margin.top = legend.height();
  6431. // FIXME: shouldn't this be "- (focusEnabled ? focusHeight : 0)"?
  6432. availableHeight1 = nv.utils.availableHeight(height, container, margin) - focusHeight;
  6433. }
  6434. g.select('.nv-legendWrap')
  6435. .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')');
  6436. }
  6437. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  6438. //============================================================
  6439. // Context chart (focus chart) components
  6440. //------------------------------------------------------------
  6441. // hide or show the focus context chart
  6442. g.select('.nv-context').style('display', focusEnable ? 'initial' : 'none');
  6443. bars2
  6444. .width(availableWidth)
  6445. .height(availableHeight2)
  6446. .color(data.map(function (d, i) {
  6447. return d.color || color(d, i);
  6448. }).filter(function (d, i) {
  6449. return !data[i].disabled && data[i].bar
  6450. }));
  6451. lines2
  6452. .width(availableWidth)
  6453. .height(availableHeight2)
  6454. .color(data.map(function (d, i) {
  6455. return d.color || color(d, i);
  6456. }).filter(function (d, i) {
  6457. return !data[i].disabled && !data[i].bar
  6458. }));
  6459. var bars2Wrap = g.select('.nv-context .nv-barsWrap')
  6460. .datum(dataBars.length ? dataBars : [
  6461. {values: []}
  6462. ]);
  6463. var lines2Wrap = g.select('.nv-context .nv-linesWrap')
  6464. .datum(allDisabled(dataLines) ?
  6465. [{values: []}] :
  6466. dataLines.filter(function(dataLine) {
  6467. return !dataLine.disabled;
  6468. }));
  6469. g.select('.nv-context')
  6470. .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')');
  6471. bars2Wrap.transition().call(bars2);
  6472. lines2Wrap.transition().call(lines2);
  6473. // context (focus chart) axis controls
  6474. if (focusShowAxisX) {
  6475. x2Axis
  6476. ._ticks( nv.utils.calcTicksX(availableWidth / 100, data))
  6477. .tickSize(-availableHeight2, 0);
  6478. g.select('.nv-context .nv-x.nv-axis')
  6479. .attr('transform', 'translate(0,' + y3.range()[0] + ')');
  6480. g.select('.nv-context .nv-x.nv-axis').transition()
  6481. .call(x2Axis);
  6482. }
  6483. if (focusShowAxisY) {
  6484. y3Axis
  6485. .scale(y3)
  6486. ._ticks( availableHeight2 / 36 )
  6487. .tickSize( -availableWidth, 0);
  6488. y4Axis
  6489. .scale(y4)
  6490. ._ticks( availableHeight2 / 36 )
  6491. .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
  6492. g.select('.nv-context .nv-y3.nv-axis')
  6493. .style('opacity', dataBars.length ? 1 : 0)
  6494. .attr('transform', 'translate(0,' + x2.range()[0] + ')');
  6495. g.select('.nv-context .nv-y2.nv-axis')
  6496. .style('opacity', dataLines.length ? 1 : 0)
  6497. .attr('transform', 'translate(' + x2.range()[1] + ',0)');
  6498. g.select('.nv-context .nv-y1.nv-axis').transition()
  6499. .call(y3Axis);
  6500. g.select('.nv-context .nv-y2.nv-axis').transition()
  6501. .call(y4Axis);
  6502. }
  6503. // Setup Brush
  6504. brush.x(x2).on('brush', onBrush);
  6505. if (brushExtent) brush.extent(brushExtent);
  6506. var brushBG = g.select('.nv-brushBackground').selectAll('g')
  6507. .data([brushExtent || brush.extent()]);
  6508. var brushBGenter = brushBG.enter()
  6509. .append('g');
  6510. brushBGenter.append('rect')
  6511. .attr('class', 'left')
  6512. .attr('x', 0)
  6513. .attr('y', 0)
  6514. .attr('height', availableHeight2);
  6515. brushBGenter.append('rect')
  6516. .attr('class', 'right')
  6517. .attr('x', 0)
  6518. .attr('y', 0)
  6519. .attr('height', availableHeight2);
  6520. var gBrush = g.select('.nv-x.nv-brush')
  6521. .call(brush);
  6522. gBrush.selectAll('rect')
  6523. //.attr('y', -5)
  6524. .attr('height', availableHeight2);
  6525. gBrush.selectAll('.resize').append('path').attr('d', resizePath);
  6526. //============================================================
  6527. // Event Handling/Dispatching (in chart's scope)
  6528. //------------------------------------------------------------
  6529. legend.dispatch.on('stateChange', function(newState) {
  6530. for (var key in newState)
  6531. state[key] = newState[key];
  6532. dispatch.stateChange(state);
  6533. chart.update();
  6534. });
  6535. // Update chart from a state object passed to event handler
  6536. dispatch.on('changeState', function(e) {
  6537. if (typeof e.disabled !== 'undefined') {
  6538. data.forEach(function(series,i) {
  6539. series.disabled = e.disabled[i];
  6540. });
  6541. state.disabled = e.disabled;
  6542. }
  6543. chart.update();
  6544. });
  6545. //============================================================
  6546. // Functions
  6547. //------------------------------------------------------------
  6548. // Taken from crossfilter (http://square.github.com/crossfilter/)
  6549. function resizePath(d) {
  6550. var e = +(d == 'e'),
  6551. x = e ? 1 : -1,
  6552. y = availableHeight2 / 3;
  6553. return 'M' + (.5 * x) + ',' + y
  6554. + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
  6555. + 'V' + (2 * y - 6)
  6556. + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
  6557. + 'Z'
  6558. + 'M' + (2.5 * x) + ',' + (y + 8)
  6559. + 'V' + (2 * y - 8)
  6560. + 'M' + (4.5 * x) + ',' + (y + 8)
  6561. + 'V' + (2 * y - 8);
  6562. }
  6563. function updateBrushBG() {
  6564. if (!brush.empty()) brush.extent(brushExtent);
  6565. brushBG
  6566. .data([brush.empty() ? x2.domain() : brushExtent])
  6567. .each(function(d,i) {
  6568. var leftWidth = x2(d[0]) - x2.range()[0],
  6569. rightWidth = x2.range()[1] - x2(d[1]);
  6570. d3.select(this).select('.left')
  6571. .attr('width', leftWidth < 0 ? 0 : leftWidth);
  6572. d3.select(this).select('.right')
  6573. .attr('x', x2(d[1]))
  6574. .attr('width', rightWidth < 0 ? 0 : rightWidth);
  6575. });
  6576. }
  6577. function onBrush() {
  6578. brushExtent = brush.empty() ? null : brush.extent();
  6579. extent = brush.empty() ? x2.domain() : brush.extent();
  6580. dispatch.brush({extent: extent, brush: brush});
  6581. updateBrushBG();
  6582. // Prepare Main (Focus) Bars and Lines
  6583. bars
  6584. .width(availableWidth)
  6585. .height(availableHeight1)
  6586. .color(data.map(function(d,i) {
  6587. return d.color || color(d, i);
  6588. }).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
  6589. lines
  6590. .width(availableWidth)
  6591. .height(availableHeight1)
  6592. .color(data.map(function(d,i) {
  6593. return d.color || color(d, i);
  6594. }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
  6595. var focusBarsWrap = g.select('.nv-focus .nv-barsWrap')
  6596. .datum(!dataBars.length ? [{values:[]}] :
  6597. dataBars
  6598. .map(function(d,i) {
  6599. return {
  6600. key: d.key,
  6601. values: d.values.filter(function(d,i) {
  6602. return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1];
  6603. })
  6604. }
  6605. })
  6606. );
  6607. var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
  6608. .datum(allDisabled(dataLines) ? [{values:[]}] :
  6609. dataLines
  6610. .filter(function(dataLine) { return !dataLine.disabled; })
  6611. .map(function(d,i) {
  6612. return {
  6613. area: d.area,
  6614. fillOpacity: d.fillOpacity,
  6615. strokeWidth: d.strokeWidth,
  6616. key: d.key,
  6617. values: d.values.filter(function(d,i) {
  6618. return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
  6619. })
  6620. }
  6621. })
  6622. );
  6623. // Update Main (Focus) X Axis
  6624. if (dataBars.length && !switchYAxisOrder) {
  6625. x = bars.xScale();
  6626. } else {
  6627. x = lines.xScale();
  6628. }
  6629. xAxis
  6630. .scale(x)
  6631. ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
  6632. .tickSize(-availableHeight1, 0);
  6633. xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]);
  6634. g.select('.nv-x.nv-axis').transition().duration(transitionDuration)
  6635. .call(xAxis);
  6636. // Update Main (Focus) Bars and Lines
  6637. focusBarsWrap.transition().duration(transitionDuration).call(bars);
  6638. focusLinesWrap.transition().duration(transitionDuration).call(lines);
  6639. // Setup and Update Main (Focus) Y Axes
  6640. g.select('.nv-focus .nv-x.nv-axis')
  6641. .attr('transform', 'translate(0,' + y1.range()[0] + ')');
  6642. y1Axis
  6643. .scale(y1)
  6644. ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
  6645. .tickSize(-availableWidth, 0);
  6646. y2Axis
  6647. .scale(y2)
  6648. ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) );
  6649. // Show the y2 rules only if y1 has none
  6650. if(!switchYAxisOrder) {
  6651. y2Axis.tickSize(dataBars.length ? 0 : -availableWidth, 0);
  6652. } else {
  6653. y2Axis.tickSize(dataLines.length ? 0 : -availableWidth, 0);
  6654. }
  6655. // Calculate opacity of the axis
  6656. var barsOpacity = dataBars.length ? 1 : 0;
  6657. var linesOpacity = dataLines.length && !allDisabled(dataLines) ? 1 : 0;
  6658. var y1Opacity = switchYAxisOrder ? linesOpacity : barsOpacity;
  6659. var y2Opacity = switchYAxisOrder ? barsOpacity : linesOpacity;
  6660. g.select('.nv-focus .nv-y1.nv-axis')
  6661. .style('opacity', y1Opacity);
  6662. g.select('.nv-focus .nv-y2.nv-axis')
  6663. .style('opacity', y2Opacity)
  6664. .attr('transform', 'translate(' + x.range()[1] + ',0)');
  6665. g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration)
  6666. .call(y1Axis);
  6667. g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration)
  6668. .call(y2Axis);
  6669. }
  6670. onBrush();
  6671. });
  6672. return chart;
  6673. }
  6674. //============================================================
  6675. // Event Handling/Dispatching (out of chart's scope)
  6676. //------------------------------------------------------------
  6677. lines.dispatch.on('elementMouseover.tooltip', function(evt) {
  6678. tooltip
  6679. .duration(100)
  6680. .valueFormatter(function(d, i) {
  6681. return getLinesAxis().main.tickFormat()(d, i);
  6682. })
  6683. .data(evt)
  6684. .hidden(false);
  6685. });
  6686. lines.dispatch.on('elementMouseout.tooltip', function(evt) {
  6687. tooltip.hidden(true)
  6688. });
  6689. bars.dispatch.on('elementMouseover.tooltip', function(evt) {
  6690. evt.value = chart.x()(evt.data);
  6691. evt['series'] = {
  6692. value: chart.y()(evt.data),
  6693. color: evt.color
  6694. };
  6695. tooltip
  6696. .duration(0)
  6697. .valueFormatter(function(d, i) {
  6698. return getBarsAxis().main.tickFormat()(d, i);
  6699. })
  6700. .data(evt)
  6701. .hidden(false);
  6702. });
  6703. bars.dispatch.on('elementMouseout.tooltip', function(evt) {
  6704. tooltip.hidden(true);
  6705. });
  6706. bars.dispatch.on('elementMousemove.tooltip', function(evt) {
  6707. tooltip();
  6708. });
  6709. //============================================================
  6710. //============================================================
  6711. // Expose Public Variables
  6712. //------------------------------------------------------------
  6713. // expose chart's sub-components
  6714. chart.dispatch = dispatch;
  6715. chart.legend = legend;
  6716. chart.lines = lines;
  6717. chart.lines2 = lines2;
  6718. chart.bars = bars;
  6719. chart.bars2 = bars2;
  6720. chart.xAxis = xAxis;
  6721. chart.x2Axis = x2Axis;
  6722. chart.y1Axis = y1Axis;
  6723. chart.y2Axis = y2Axis;
  6724. chart.y3Axis = y3Axis;
  6725. chart.y4Axis = y4Axis;
  6726. chart.tooltip = tooltip;
  6727. chart.options = nv.utils.optionsFunc.bind(chart);
  6728. chart._options = Object.create({}, {
  6729. // simple options, just get/set the necessary values
  6730. width: {get: function(){return width;}, set: function(_){width=_;}},
  6731. height: {get: function(){return height;}, set: function(_){height=_;}},
  6732. showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
  6733. brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
  6734. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  6735. focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}},
  6736. focusHeight: {get: function(){return focusHeight;}, set: function(_){focusHeight=_;}},
  6737. focusShowAxisX: {get: function(){return focusShowAxisX;}, set: function(_){focusShowAxisX=_;}},
  6738. focusShowAxisY: {get: function(){return focusShowAxisY;}, set: function(_){focusShowAxisY=_;}},
  6739. legendLeftAxisHint: {get: function(){return legendLeftAxisHint;}, set: function(_){legendLeftAxisHint=_;}},
  6740. legendRightAxisHint: {get: function(){return legendRightAxisHint;}, set: function(_){legendRightAxisHint=_;}},
  6741. // options that require extra logic in the setter
  6742. margin: {get: function(){return margin;}, set: function(_){
  6743. if (_.top !== undefined) {
  6744. margin.top = _.top;
  6745. marginTop = _.top;
  6746. }
  6747. margin.right = _.right !== undefined ? _.right : margin.right;
  6748. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  6749. margin.left = _.left !== undefined ? _.left : margin.left;
  6750. }},
  6751. focusMargin: {get: function(){return margin2;}, set: function(_){
  6752. margin2.top = _.top !== undefined ? _.top : margin2.top;
  6753. margin2.right = _.right !== undefined ? _.right : margin2.right;
  6754. margin2.bottom = _.bottom !== undefined ? _.bottom : margin2.bottom;
  6755. margin2.left = _.left !== undefined ? _.left : margin2.left;
  6756. }},
  6757. duration: {get: function(){return transitionDuration;}, set: function(_){
  6758. transitionDuration = _;
  6759. }},
  6760. color: {get: function(){return color;}, set: function(_){
  6761. color = nv.utils.getColor(_);
  6762. legend.color(color);
  6763. }},
  6764. x: {get: function(){return getX;}, set: function(_){
  6765. getX = _;
  6766. lines.x(_);
  6767. lines2.x(_);
  6768. bars.x(_);
  6769. bars2.x(_);
  6770. }},
  6771. y: {get: function(){return getY;}, set: function(_){
  6772. getY = _;
  6773. lines.y(_);
  6774. lines2.y(_);
  6775. bars.y(_);
  6776. bars2.y(_);
  6777. }},
  6778. switchYAxisOrder: {get: function(){return switchYAxisOrder;}, set: function(_){
  6779. // Switch the tick format for the yAxis
  6780. if(switchYAxisOrder !== _) {
  6781. var y1 = y1Axis;
  6782. y1Axis = y2Axis;
  6783. y2Axis = y1;
  6784. var y3 = y3Axis;
  6785. y3Axis = y4Axis;
  6786. y4Axis = y3;
  6787. }
  6788. switchYAxisOrder=_;
  6789. y1Axis.orient('left');
  6790. y2Axis.orient('right');
  6791. y3Axis.orient('left');
  6792. y4Axis.orient('right');
  6793. }}
  6794. });
  6795. nv.utils.inheritOptions(chart, lines);
  6796. nv.utils.initOptions(chart);
  6797. return chart;
  6798. };
  6799. nv.models.multiBar = function() {
  6800. "use strict";
  6801. //============================================================
  6802. // Public Variables with Default Settings
  6803. //------------------------------------------------------------
  6804. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  6805. , width = 960
  6806. , height = 500
  6807. , x = d3.scale.ordinal()
  6808. , y = d3.scale.linear()
  6809. , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
  6810. , container = null
  6811. , getX = function(d) { return d.x }
  6812. , getY = function(d) { return d.y }
  6813. , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
  6814. , clipEdge = true
  6815. , stacked = false
  6816. , stackOffset = 'zero' // options include 'silhouette', 'wiggle', 'expand', 'zero', or a custom function
  6817. , color = nv.utils.defaultColor()
  6818. , hideable = false
  6819. , barColor = null // adding the ability to set the color for each rather than the whole group
  6820. , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
  6821. , duration = 500
  6822. , xDomain
  6823. , yDomain
  6824. , xRange
  6825. , yRange
  6826. , groupSpacing = 0.1
  6827. , fillOpacity = 0.75
  6828. , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
  6829. ;
  6830. //============================================================
  6831. // Private Variables
  6832. //------------------------------------------------------------
  6833. var x0, y0 //used to store previous scales
  6834. , renderWatch = nv.utils.renderWatch(dispatch, duration)
  6835. ;
  6836. var last_datalength = 0;
  6837. function chart(selection) {
  6838. renderWatch.reset();
  6839. selection.each(function(data) {
  6840. var availableWidth = width - margin.left - margin.right,
  6841. availableHeight = height - margin.top - margin.bottom;
  6842. container = d3.select(this);
  6843. nv.utils.initSVG(container);
  6844. var nonStackableCount = 0;
  6845. // This function defines the requirements for render complete
  6846. var endFn = function(d, i) {
  6847. if (d.series === data.length - 1 && i === data[0].values.length - 1)
  6848. return true;
  6849. return false;
  6850. };
  6851. if(hideable && data.length) hideable = [{
  6852. values: data[0].values.map(function(d) {
  6853. return {
  6854. x: d.x,
  6855. y: 0,
  6856. series: d.series,
  6857. size: 0.01
  6858. };}
  6859. )}];
  6860. if (stacked) {
  6861. var parsed = d3.layout.stack()
  6862. .offset(stackOffset)
  6863. .values(function(d){ return d.values })
  6864. .y(getY)
  6865. (!data.length && hideable ? hideable : data);
  6866. parsed.forEach(function(series, i){
  6867. // if series is non-stackable, use un-parsed data
  6868. if (series.nonStackable) {
  6869. data[i].nonStackableSeries = nonStackableCount++;
  6870. parsed[i] = data[i];
  6871. } else {
  6872. // don't stack this seires on top of the nonStackable seriees
  6873. if (i > 0 && parsed[i - 1].nonStackable){
  6874. parsed[i].values.map(function(d,j){
  6875. d.y0 -= parsed[i - 1].values[j].y;
  6876. d.y1 = d.y0 + d.y;
  6877. });
  6878. }
  6879. }
  6880. });
  6881. data = parsed;
  6882. }
  6883. //add series index and key to each data point for reference
  6884. data.forEach(function(series, i) {
  6885. series.values.forEach(function(point) {
  6886. point.series = i;
  6887. point.key = series.key;
  6888. });
  6889. });
  6890. // HACK for negative value stacking
  6891. if (stacked && data.length > 0) {
  6892. data[0].values.map(function(d,i) {
  6893. var posBase = 0, negBase = 0;
  6894. data.map(function(d, idx) {
  6895. if (!data[idx].nonStackable) {
  6896. var f = d.values[i]
  6897. f.size = Math.abs(f.y);
  6898. if (f.y<0) {
  6899. f.y1 = negBase;
  6900. negBase = negBase - f.size;
  6901. } else
  6902. {
  6903. f.y1 = f.size + posBase;
  6904. posBase = posBase + f.size;
  6905. }
  6906. }
  6907. });
  6908. });
  6909. }
  6910. // Setup Scales
  6911. // remap and flatten the data for use in calculating the scales' domains
  6912. var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
  6913. data.map(function(d, idx) {
  6914. return d.values.map(function(d,i) {
  6915. return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1, idx:idx }
  6916. })
  6917. });
  6918. x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
  6919. .rangeBands(xRange || [0, availableWidth], groupSpacing);
  6920. y.domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) {
  6921. var domain = d.y;
  6922. // increase the domain range if this series is stackable
  6923. if (stacked && !data[d.idx].nonStackable) {
  6924. if (d.y > 0){
  6925. domain = d.y1
  6926. } else {
  6927. domain = d.y1 + d.y
  6928. }
  6929. }
  6930. return domain;
  6931. }).concat(forceY)))
  6932. .range(yRange || [availableHeight, 0]);
  6933. // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
  6934. if (x.domain()[0] === x.domain()[1])
  6935. x.domain()[0] ?
  6936. x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
  6937. : x.domain([-1,1]);
  6938. if (y.domain()[0] === y.domain()[1])
  6939. y.domain()[0] ?
  6940. y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
  6941. : y.domain([-1,1]);
  6942. x0 = x0 || x;
  6943. y0 = y0 || y;
  6944. // Setup containers and skeleton of chart
  6945. var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]);
  6946. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar');
  6947. var defsEnter = wrapEnter.append('defs');
  6948. var gEnter = wrapEnter.append('g');
  6949. var g = wrap.select('g');
  6950. gEnter.append('g').attr('class', 'nv-groups');
  6951. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  6952. defsEnter.append('clipPath')
  6953. .attr('id', 'nv-edge-clip-' + id)
  6954. .append('rect');
  6955. wrap.select('#nv-edge-clip-' + id + ' rect')
  6956. .attr('width', availableWidth)
  6957. .attr('height', availableHeight);
  6958. g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
  6959. var groups = wrap.select('.nv-groups').selectAll('.nv-group')
  6960. .data(function(d) { return d }, function(d,i) { return i });
  6961. groups.enter().append('g')
  6962. .style('stroke-opacity', 1e-6)
  6963. .style('fill-opacity', 1e-6);
  6964. var exitTransition = renderWatch
  6965. .transition(groups.exit().selectAll('rect.nv-bar'), 'multibarExit', Math.min(100, duration))
  6966. .attr('y', function(d, i, j) {
  6967. var yVal = y0(0) || 0;
  6968. if (stacked) {
  6969. if (data[d.series] && !data[d.series].nonStackable) {
  6970. yVal = y0(d.y0);
  6971. }
  6972. }
  6973. return yVal;
  6974. })
  6975. .attr('height', 0)
  6976. .remove();
  6977. if (exitTransition.delay)
  6978. exitTransition.delay(function(d,i) {
  6979. var delay = i * (duration / (last_datalength + 1)) - i;
  6980. return delay;
  6981. });
  6982. groups
  6983. .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
  6984. .classed('hover', function(d) { return d.hover })
  6985. .style('fill', function(d,i){ return color(d, i) })
  6986. .style('stroke', function(d,i){ return color(d, i) });
  6987. groups
  6988. .style('stroke-opacity', 1)
  6989. .style('fill-opacity', fillOpacity);
  6990. var bars = groups.selectAll('rect.nv-bar')
  6991. .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values });
  6992. bars.exit().remove();
  6993. var barsEnter = bars.enter().append('rect')
  6994. .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
  6995. .attr('x', function(d,i,j) {
  6996. return stacked && !data[j].nonStackable ? 0 : (j * x.rangeBand() / data.length )
  6997. })
  6998. .attr('y', function(d,i,j) { return y0(stacked && !data[j].nonStackable ? d.y0 : 0) || 0 })
  6999. .attr('height', 0)
  7000. .attr('width', function(d,i,j) { return x.rangeBand() / (stacked && !data[j].nonStackable ? 1 : data.length) })
  7001. .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
  7002. ;
  7003. bars
  7004. .style('fill', function(d,i,j){ return color(d, j, i); })
  7005. .style('stroke', function(d,i,j){ return color(d, j, i); })
  7006. .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
  7007. d3.select(this).classed('hover', true);
  7008. dispatch.elementMouseover({
  7009. data: d,
  7010. index: i,
  7011. color: d3.select(this).style("fill")
  7012. });
  7013. })
  7014. .on('mouseout', function(d,i) {
  7015. d3.select(this).classed('hover', false);
  7016. dispatch.elementMouseout({
  7017. data: d,
  7018. index: i,
  7019. color: d3.select(this).style("fill")
  7020. });
  7021. })
  7022. .on('mousemove', function(d,i) {
  7023. dispatch.elementMousemove({
  7024. data: d,
  7025. index: i,
  7026. color: d3.select(this).style("fill")
  7027. });
  7028. })
  7029. .on('click', function(d,i) {
  7030. var element = this;
  7031. dispatch.elementClick({
  7032. data: d,
  7033. index: i,
  7034. color: d3.select(this).style("fill"),
  7035. event: d3.event,
  7036. element: element
  7037. });
  7038. d3.event.stopPropagation();
  7039. })
  7040. .on('dblclick', function(d,i) {
  7041. dispatch.elementDblClick({
  7042. data: d,
  7043. index: i,
  7044. color: d3.select(this).style("fill")
  7045. });
  7046. d3.event.stopPropagation();
  7047. });
  7048. bars
  7049. .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
  7050. .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
  7051. if (barColor) {
  7052. if (!disabled) disabled = data.map(function() { return true });
  7053. bars
  7054. .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(); })
  7055. .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(); });
  7056. }
  7057. var barSelection =
  7058. bars.watchTransition(renderWatch, 'multibar', Math.min(250, duration))
  7059. .delay(function(d,i) {
  7060. return i * duration / data[0].values.length;
  7061. });
  7062. if (stacked){
  7063. barSelection
  7064. .attr('y', function(d,i,j) {
  7065. var yVal = 0;
  7066. // if stackable, stack it on top of the previous series
  7067. if (!data[j].nonStackable) {
  7068. yVal = y(d.y1);
  7069. } else {
  7070. if (getY(d,i) < 0){
  7071. yVal = y(0);
  7072. } else {
  7073. if (y(0) - y(getY(d,i)) < -1){
  7074. yVal = y(0) - 1;
  7075. } else {
  7076. yVal = y(getY(d, i)) || 0;
  7077. }
  7078. }
  7079. }
  7080. return yVal;
  7081. })
  7082. .attr('height', function(d,i,j) {
  7083. if (!data[j].nonStackable) {
  7084. return Math.max(Math.abs(y(d.y+d.y0) - y(d.y0)), 0);
  7085. } else {
  7086. return Math.max(Math.abs(y(getY(d,i)) - y(0)), 0) || 0;
  7087. }
  7088. })
  7089. .attr('x', function(d,i,j) {
  7090. var width = 0;
  7091. if (data[j].nonStackable) {
  7092. width = d.series * x.rangeBand() / data.length;
  7093. if (data.length !== nonStackableCount){
  7094. width = data[j].nonStackableSeries * x.rangeBand()/(nonStackableCount*2);
  7095. }
  7096. }
  7097. return width;
  7098. })
  7099. .attr('width', function(d,i,j){
  7100. if (!data[j].nonStackable) {
  7101. return x.rangeBand();
  7102. } else {
  7103. // if all series are nonStacable, take the full width
  7104. var width = (x.rangeBand() / nonStackableCount);
  7105. // otherwise, nonStackable graph will be only taking the half-width
  7106. // of the x rangeBand
  7107. if (data.length !== nonStackableCount) {
  7108. width = x.rangeBand()/(nonStackableCount*2);
  7109. }
  7110. return width;
  7111. }
  7112. });
  7113. }
  7114. else {
  7115. barSelection
  7116. .attr('x', function(d,i) {
  7117. return d.series * x.rangeBand() / data.length;
  7118. })
  7119. .attr('width', x.rangeBand() / data.length)
  7120. .attr('y', function(d,i) {
  7121. return getY(d,i) < 0 ?
  7122. y(0) :
  7123. y(0) - y(getY(d,i)) < 1 ?
  7124. y(0) - 1 :
  7125. y(getY(d,i)) || 0;
  7126. })
  7127. .attr('height', function(d,i) {
  7128. return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
  7129. });
  7130. }
  7131. //store old scales for use in transitions on update
  7132. x0 = x.copy();
  7133. y0 = y.copy();
  7134. // keep track of the last data value length for transition calculations
  7135. if (data[0] && data[0].values) {
  7136. last_datalength = data[0].values.length;
  7137. }
  7138. });
  7139. renderWatch.renderEnd('multibar immediate');
  7140. return chart;
  7141. }
  7142. //============================================================
  7143. // Expose Public Variables
  7144. //------------------------------------------------------------
  7145. chart.dispatch = dispatch;
  7146. chart.options = nv.utils.optionsFunc.bind(chart);
  7147. chart._options = Object.create({}, {
  7148. // simple options, just get/set the necessary values
  7149. width: {get: function(){return width;}, set: function(_){width=_;}},
  7150. height: {get: function(){return height;}, set: function(_){height=_;}},
  7151. x: {get: function(){return getX;}, set: function(_){getX=_;}},
  7152. y: {get: function(){return getY;}, set: function(_){getY=_;}},
  7153. xScale: {get: function(){return x;}, set: function(_){x=_;}},
  7154. yScale: {get: function(){return y;}, set: function(_){y=_;}},
  7155. xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
  7156. yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
  7157. xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
  7158. yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
  7159. forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
  7160. stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}},
  7161. stackOffset: {get: function(){return stackOffset;}, set: function(_){stackOffset=_;}},
  7162. clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
  7163. disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}},
  7164. id: {get: function(){return id;}, set: function(_){id=_;}},
  7165. hideable: {get: function(){return hideable;}, set: function(_){hideable=_;}},
  7166. groupSpacing:{get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}},
  7167. fillOpacity: {get: function(){return fillOpacity;}, set: function(_){fillOpacity=_;}},
  7168. // options that require extra logic in the setter
  7169. margin: {get: function(){return margin;}, set: function(_){
  7170. margin.top = _.top !== undefined ? _.top : margin.top;
  7171. margin.right = _.right !== undefined ? _.right : margin.right;
  7172. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  7173. margin.left = _.left !== undefined ? _.left : margin.left;
  7174. }},
  7175. duration: {get: function(){return duration;}, set: function(_){
  7176. duration = _;
  7177. renderWatch.reset(duration);
  7178. }},
  7179. color: {get: function(){return color;}, set: function(_){
  7180. color = nv.utils.getColor(_);
  7181. }},
  7182. barColor: {get: function(){return barColor;}, set: function(_){
  7183. barColor = _ ? nv.utils.getColor(_) : null;
  7184. }}
  7185. });
  7186. nv.utils.initOptions(chart);
  7187. return chart;
  7188. };
  7189. nv.models.multiBarChart = function() {
  7190. "use strict";
  7191. //============================================================
  7192. // Public Variables with Default Settings
  7193. //------------------------------------------------------------
  7194. var multibar = nv.models.multiBar()
  7195. , xAxis = nv.models.axis()
  7196. , yAxis = nv.models.axis()
  7197. , interactiveLayer = nv.interactiveGuideline()
  7198. , legend = nv.models.legend()
  7199. , controls = nv.models.legend()
  7200. , tooltip = nv.models.tooltip()
  7201. ;
  7202. var margin = {top: 30, right: 20, bottom: 50, left: 60}
  7203. , marginTop = null
  7204. , width = null
  7205. , height = null
  7206. , color = nv.utils.defaultColor()
  7207. , showControls = true
  7208. , controlLabels = {}
  7209. , showLegend = true
  7210. , showXAxis = true
  7211. , showYAxis = true
  7212. , rightAlignYAxis = false
  7213. , reduceXTicks = true // if false a tick will show for every data point
  7214. , staggerLabels = false
  7215. , wrapLabels = false
  7216. , rotateLabels = 0
  7217. , x //can be accessed via chart.xScale()
  7218. , y //can be accessed via chart.yScale()
  7219. , state = nv.utils.state()
  7220. , defaultState = null
  7221. , noData = null
  7222. , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
  7223. , controlWidth = function() { return showControls ? 180 : 0 }
  7224. , duration = 250
  7225. , useInteractiveGuideline = false
  7226. ;
  7227. state.stacked = false // DEPRECATED Maintained for backward compatibility
  7228. multibar.stacked(false);
  7229. xAxis
  7230. .orient('bottom')
  7231. .tickPadding(7)
  7232. .showMaxMin(false)
  7233. .tickFormat(function(d) { return d })
  7234. ;
  7235. yAxis
  7236. .orient((rightAlignYAxis) ? 'right' : 'left')
  7237. .tickFormat(d3.format(',.1f'))
  7238. ;
  7239. tooltip
  7240. .duration(0)
  7241. .valueFormatter(function(d, i) {
  7242. return yAxis.tickFormat()(d, i);
  7243. })
  7244. .headerFormatter(function(d, i) {
  7245. return xAxis.tickFormat()(d, i);
  7246. });
  7247. interactiveLayer.tooltip
  7248. .valueFormatter(function(d, i) {
  7249. return d == null ? "N/A" : yAxis.tickFormat()(d, i);
  7250. })
  7251. .headerFormatter(function(d, i) {
  7252. return xAxis.tickFormat()(d, i);
  7253. });
  7254. interactiveLayer.tooltip
  7255. .valueFormatter(function (d, i) {
  7256. return d == null ? "N/A" : yAxis.tickFormat()(d, i);
  7257. })
  7258. .headerFormatter(function (d, i) {
  7259. return xAxis.tickFormat()(d, i);
  7260. });
  7261. interactiveLayer.tooltip
  7262. .duration(0)
  7263. .valueFormatter(function(d, i) {
  7264. return yAxis.tickFormat()(d, i);
  7265. })
  7266. .headerFormatter(function(d, i) {
  7267. return xAxis.tickFormat()(d, i);
  7268. });
  7269. controls.updateState(false);
  7270. //============================================================
  7271. // Private Variables
  7272. //------------------------------------------------------------
  7273. var renderWatch = nv.utils.renderWatch(dispatch);
  7274. var stacked = false;
  7275. var stateGetter = function(data) {
  7276. return function(){
  7277. return {
  7278. active: data.map(function(d) { return !d.disabled }),
  7279. stacked: stacked
  7280. };
  7281. }
  7282. };
  7283. var stateSetter = function(data) {
  7284. return function(state) {
  7285. if (state.stacked !== undefined)
  7286. stacked = state.stacked;
  7287. if (state.active !== undefined)
  7288. data.forEach(function(series,i) {
  7289. series.disabled = !state.active[i];
  7290. });
  7291. }
  7292. };
  7293. function chart(selection) {
  7294. renderWatch.reset();
  7295. renderWatch.models(multibar);
  7296. if (showXAxis) renderWatch.models(xAxis);
  7297. if (showYAxis) renderWatch.models(yAxis);
  7298. selection.each(function(data) {
  7299. var container = d3.select(this),
  7300. that = this;
  7301. nv.utils.initSVG(container);
  7302. var availableWidth = nv.utils.availableWidth(width, container, margin),
  7303. availableHeight = nv.utils.availableHeight(height, container, margin);
  7304. chart.update = function() {
  7305. if (duration === 0)
  7306. container.call(chart);
  7307. else
  7308. container.transition()
  7309. .duration(duration)
  7310. .call(chart);
  7311. };
  7312. chart.container = this;
  7313. state
  7314. .setter(stateSetter(data), chart.update)
  7315. .getter(stateGetter(data))
  7316. .update();
  7317. // DEPRECATED set state.disableddisabled
  7318. state.disabled = data.map(function(d) { return !!d.disabled });
  7319. if (!defaultState) {
  7320. var key;
  7321. defaultState = {};
  7322. for (key in state) {
  7323. if (state[key] instanceof Array)
  7324. defaultState[key] = state[key].slice(0);
  7325. else
  7326. defaultState[key] = state[key];
  7327. }
  7328. }
  7329. // Display noData message if there's nothing to show.
  7330. if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
  7331. nv.utils.noData(chart, container)
  7332. return chart;
  7333. } else {
  7334. container.selectAll('.nv-noData').remove();
  7335. }
  7336. // Setup Scales
  7337. x = multibar.xScale();
  7338. y = multibar.yScale();
  7339. // Setup containers and skeleton of chart
  7340. var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]);
  7341. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g');
  7342. var g = wrap.select('g');
  7343. gEnter.append('g').attr('class', 'nv-x nv-axis');
  7344. gEnter.append('g').attr('class', 'nv-y nv-axis');
  7345. gEnter.append('g').attr('class', 'nv-barsWrap');
  7346. gEnter.append('g').attr('class', 'nv-legendWrap');
  7347. gEnter.append('g').attr('class', 'nv-controlsWrap');
  7348. gEnter.append('g').attr('class', 'nv-interactive');
  7349. // Legend
  7350. if (!showLegend) {
  7351. g.select('.nv-legendWrap').selectAll('*').remove();
  7352. } else {
  7353. legend.width(availableWidth - controlWidth());
  7354. g.select('.nv-legendWrap')
  7355. .datum(data)
  7356. .call(legend);
  7357. if (!marginTop && legend.height() !== margin.top) {
  7358. margin.top = legend.height();
  7359. availableHeight = nv.utils.availableHeight(height, container, margin);
  7360. }
  7361. g.select('.nv-legendWrap')
  7362. .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
  7363. }
  7364. // Controls
  7365. if (!showControls) {
  7366. g.select('.nv-controlsWrap').selectAll('*').remove();
  7367. } else {
  7368. var controlsData = [
  7369. { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() },
  7370. { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() }
  7371. ];
  7372. controls.width(controlWidth()).color(['#444', '#444', '#444']);
  7373. g.select('.nv-controlsWrap')
  7374. .datum(controlsData)
  7375. .attr('transform', 'translate(0,' + (-margin.top) +')')
  7376. .call(controls);
  7377. }
  7378. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  7379. if (rightAlignYAxis) {
  7380. g.select(".nv-y.nv-axis")
  7381. .attr("transform", "translate(" + availableWidth + ",0)");
  7382. }
  7383. // Main Chart Component(s)
  7384. multibar
  7385. .disabled(data.map(function(series) { return series.disabled }))
  7386. .width(availableWidth)
  7387. .height(availableHeight)
  7388. .color(data.map(function(d,i) {
  7389. return d.color || color(d, i);
  7390. }).filter(function(d,i) { return !data[i].disabled }));
  7391. var barsWrap = g.select('.nv-barsWrap')
  7392. .datum(data.filter(function(d) { return !d.disabled }));
  7393. barsWrap.call(multibar);
  7394. // Setup Axes
  7395. if (showXAxis) {
  7396. xAxis
  7397. .scale(x)
  7398. ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
  7399. .tickSize(-availableHeight, 0);
  7400. g.select('.nv-x.nv-axis')
  7401. .attr('transform', 'translate(0,' + y.range()[0] + ')');
  7402. g.select('.nv-x.nv-axis')
  7403. .call(xAxis);
  7404. var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g');
  7405. xTicks
  7406. .selectAll('line, text')
  7407. .style('opacity', 1)
  7408. if (staggerLabels) {
  7409. var getTranslate = function(x,y) {
  7410. return "translate(" + x + "," + y + ")";
  7411. };
  7412. var staggerUp = 5, staggerDown = 17; //pixels to stagger by
  7413. // Issue #140
  7414. xTicks
  7415. .selectAll("text")
  7416. .attr('transform', function(d,i,j) {
  7417. return getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown));
  7418. });
  7419. var totalInBetweenTicks = d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;
  7420. g.selectAll(".nv-x.nv-axis .nv-axisMaxMin text")
  7421. .attr("transform", function(d,i) {
  7422. return getTranslate(0, (i === 0 || totalInBetweenTicks % 2 !== 0) ? staggerDown : staggerUp);
  7423. });
  7424. }
  7425. if (wrapLabels) {
  7426. g.selectAll('.tick text')
  7427. .call(nv.utils.wrapTicks, chart.xAxis.rangeBand())
  7428. }
  7429. if (reduceXTicks)
  7430. xTicks
  7431. .filter(function(d,i) {
  7432. return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0;
  7433. })
  7434. .selectAll('text, line')
  7435. .style('opacity', 0);
  7436. if(rotateLabels)
  7437. xTicks
  7438. .selectAll('.tick text')
  7439. .attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
  7440. .style('text-anchor', rotateLabels > 0 ? 'start' : 'end');
  7441. g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text')
  7442. .style('opacity', 1);
  7443. }
  7444. if (showYAxis) {
  7445. yAxis
  7446. .scale(y)
  7447. ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
  7448. .tickSize( -availableWidth, 0);
  7449. g.select('.nv-y.nv-axis')
  7450. .call(yAxis);
  7451. }
  7452. //Set up interactive layer
  7453. if (useInteractiveGuideline) {
  7454. interactiveLayer
  7455. .width(availableWidth)
  7456. .height(availableHeight)
  7457. .margin({left:margin.left, top:margin.top})
  7458. .svgContainer(container)
  7459. .xScale(x);
  7460. wrap.select(".nv-interactive").call(interactiveLayer);
  7461. }
  7462. //============================================================
  7463. // Event Handling/Dispatching (in chart's scope)
  7464. //------------------------------------------------------------
  7465. legend.dispatch.on('stateChange', function(newState) {
  7466. for (var key in newState)
  7467. state[key] = newState[key];
  7468. dispatch.stateChange(state);
  7469. chart.update();
  7470. });
  7471. controls.dispatch.on('legendClick', function(d,i) {
  7472. if (!d.disabled) return;
  7473. controlsData = controlsData.map(function(s) {
  7474. s.disabled = true;
  7475. return s;
  7476. });
  7477. d.disabled = false;
  7478. switch (d.key) {
  7479. case 'Grouped':
  7480. case controlLabels.grouped:
  7481. multibar.stacked(false);
  7482. break;
  7483. case 'Stacked':
  7484. case controlLabels.stacked:
  7485. multibar.stacked(true);
  7486. break;
  7487. }
  7488. state.stacked = multibar.stacked();
  7489. dispatch.stateChange(state);
  7490. chart.update();
  7491. });
  7492. // Update chart from a state object passed to event handler
  7493. dispatch.on('changeState', function(e) {
  7494. if (typeof e.disabled !== 'undefined') {
  7495. data.forEach(function(series,i) {
  7496. series.disabled = e.disabled[i];
  7497. });
  7498. state.disabled = e.disabled;
  7499. }
  7500. if (typeof e.stacked !== 'undefined') {
  7501. multibar.stacked(e.stacked);
  7502. state.stacked = e.stacked;
  7503. stacked = e.stacked;
  7504. }
  7505. chart.update();
  7506. });
  7507. if (useInteractiveGuideline) {
  7508. interactiveLayer.dispatch.on('elementMousemove', function(e) {
  7509. if (e.pointXValue == undefined) return;
  7510. var singlePoint, pointIndex, pointXLocation, xValue, allData = [];
  7511. data
  7512. .filter(function(series, i) {
  7513. series.seriesIndex = i;
  7514. return !series.disabled;
  7515. })
  7516. .forEach(function(series,i) {
  7517. pointIndex = x.domain().indexOf(e.pointXValue)
  7518. var point = series.values[pointIndex];
  7519. if (point === undefined) return;
  7520. xValue = point.x;
  7521. if (singlePoint === undefined) singlePoint = point;
  7522. if (pointXLocation === undefined) pointXLocation = e.mouseX
  7523. allData.push({
  7524. key: series.key,
  7525. value: chart.y()(point, pointIndex),
  7526. color: color(series,series.seriesIndex),
  7527. data: series.values[pointIndex]
  7528. });
  7529. });
  7530. interactiveLayer.tooltip
  7531. .data({
  7532. value: xValue,
  7533. index: pointIndex,
  7534. series: allData
  7535. })();
  7536. interactiveLayer.renderGuideLine(pointXLocation);
  7537. });
  7538. interactiveLayer.dispatch.on("elementMouseout",function(e) {
  7539. interactiveLayer.tooltip.hidden(true);
  7540. });
  7541. }
  7542. else {
  7543. multibar.dispatch.on('elementMouseover.tooltip', function(evt) {
  7544. evt.value = chart.x()(evt.data);
  7545. evt['series'] = {
  7546. key: evt.data.key,
  7547. value: chart.y()(evt.data),
  7548. color: evt.color
  7549. };
  7550. tooltip.data(evt).hidden(false);
  7551. });
  7552. multibar.dispatch.on('elementMouseout.tooltip', function(evt) {
  7553. tooltip.hidden(true);
  7554. });
  7555. multibar.dispatch.on('elementMousemove.tooltip', function(evt) {
  7556. tooltip();
  7557. });
  7558. }
  7559. });
  7560. renderWatch.renderEnd('multibarchart immediate');
  7561. return chart;
  7562. }
  7563. //============================================================
  7564. // Expose Public Variables
  7565. //------------------------------------------------------------
  7566. // expose chart's sub-components
  7567. chart.dispatch = dispatch;
  7568. chart.multibar = multibar;
  7569. chart.legend = legend;
  7570. chart.controls = controls;
  7571. chart.xAxis = xAxis;
  7572. chart.yAxis = yAxis;
  7573. chart.state = state;
  7574. chart.tooltip = tooltip;
  7575. chart.interactiveLayer = interactiveLayer;
  7576. chart.options = nv.utils.optionsFunc.bind(chart);
  7577. chart._options = Object.create({}, {
  7578. // simple options, just get/set the necessary values
  7579. width: {get: function(){return width;}, set: function(_){width=_;}},
  7580. height: {get: function(){return height;}, set: function(_){height=_;}},
  7581. showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
  7582. showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
  7583. controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
  7584. showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
  7585. showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
  7586. defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
  7587. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  7588. reduceXTicks: {get: function(){return reduceXTicks;}, set: function(_){reduceXTicks=_;}},
  7589. rotateLabels: {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
  7590. staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
  7591. wrapLabels: {get: function(){return wrapLabels;}, set: function(_){wrapLabels=!!_;}},
  7592. // options that require extra logic in the setter
  7593. margin: {get: function(){return margin;}, set: function(_){
  7594. if (_.top !== undefined) {
  7595. margin.top = _.top;
  7596. marginTop = _.top;
  7597. }
  7598. margin.right = _.right !== undefined ? _.right : margin.right;
  7599. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  7600. margin.left = _.left !== undefined ? _.left : margin.left;
  7601. }},
  7602. duration: {get: function(){return duration;}, set: function(_){
  7603. duration = _;
  7604. multibar.duration(duration);
  7605. xAxis.duration(duration);
  7606. yAxis.duration(duration);
  7607. renderWatch.reset(duration);
  7608. }},
  7609. color: {get: function(){return color;}, set: function(_){
  7610. color = nv.utils.getColor(_);
  7611. legend.color(color);
  7612. }},
  7613. rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
  7614. rightAlignYAxis = _;
  7615. yAxis.orient( rightAlignYAxis ? 'right' : 'left');
  7616. }},
  7617. useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
  7618. useInteractiveGuideline = _;
  7619. }},
  7620. barColor: {get: function(){return multibar.barColor;}, set: function(_){
  7621. multibar.barColor(_);
  7622. legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();})
  7623. }}
  7624. });
  7625. nv.utils.inheritOptions(chart, multibar);
  7626. nv.utils.initOptions(chart);
  7627. return chart;
  7628. };
  7629. nv.models.multiBarHorizontal = function() {
  7630. "use strict";
  7631. //============================================================
  7632. // Public Variables with Default Settings
  7633. //------------------------------------------------------------
  7634. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  7635. , width = 960
  7636. , height = 500
  7637. , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
  7638. , container = null
  7639. , x = d3.scale.ordinal()
  7640. , y = d3.scale.linear()
  7641. , getX = function(d) { return d.x }
  7642. , getY = function(d) { return d.y }
  7643. , getYerr = function(d) { return d.yErr }
  7644. , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
  7645. , color = nv.utils.defaultColor()
  7646. , barColor = null // adding the ability to set the color for each rather than the whole group
  7647. , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
  7648. , stacked = false
  7649. , showValues = false
  7650. , showBarLabels = false
  7651. , valuePadding = 60
  7652. , groupSpacing = 0.1
  7653. , fillOpacity = 0.75
  7654. , valueFormat = d3.format(',.2f')
  7655. , delay = 1200
  7656. , xDomain
  7657. , yDomain
  7658. , xRange
  7659. , yRange
  7660. , duration = 250
  7661. , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
  7662. ;
  7663. //============================================================
  7664. // Private Variables
  7665. //------------------------------------------------------------
  7666. var x0, y0; //used to store previous scales
  7667. var renderWatch = nv.utils.renderWatch(dispatch, duration);
  7668. function chart(selection) {
  7669. renderWatch.reset();
  7670. selection.each(function(data) {
  7671. var availableWidth = width - margin.left - margin.right,
  7672. availableHeight = height - margin.top - margin.bottom;
  7673. container = d3.select(this);
  7674. nv.utils.initSVG(container);
  7675. if (stacked)
  7676. data = d3.layout.stack()
  7677. .offset('zero')
  7678. .values(function(d){ return d.values })
  7679. .y(getY)
  7680. (data);
  7681. //add series index and key to each data point for reference
  7682. data.forEach(function(series, i) {
  7683. series.values.forEach(function(point) {
  7684. point.series = i;
  7685. point.key = series.key;
  7686. });
  7687. });
  7688. // HACK for negative value stacking
  7689. if (stacked)
  7690. data[0].values.map(function(d,i) {
  7691. var posBase = 0, negBase = 0;
  7692. data.map(function(d) {
  7693. var f = d.values[i]
  7694. f.size = Math.abs(f.y);
  7695. if (f.y<0) {
  7696. f.y1 = negBase - f.size;
  7697. negBase = negBase - f.size;
  7698. } else
  7699. {
  7700. f.y1 = posBase;
  7701. posBase = posBase + f.size;
  7702. }
  7703. });
  7704. });
  7705. // Setup Scales
  7706. // remap and flatten the data for use in calculating the scales' domains
  7707. var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
  7708. data.map(function(d) {
  7709. return d.values.map(function(d,i) {
  7710. return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
  7711. })
  7712. });
  7713. x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
  7714. .rangeBands(xRange || [0, availableHeight], groupSpacing);
  7715. 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)))
  7716. if (showValues && !stacked)
  7717. y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]);
  7718. else
  7719. y.range(yRange || [0, availableWidth]);
  7720. x0 = x0 || x;
  7721. y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]);
  7722. // Setup containers and skeleton of chart
  7723. var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]);
  7724. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal');
  7725. var defsEnter = wrapEnter.append('defs');
  7726. var gEnter = wrapEnter.append('g');
  7727. var g = wrap.select('g');
  7728. gEnter.append('g').attr('class', 'nv-groups');
  7729. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  7730. var groups = wrap.select('.nv-groups').selectAll('.nv-group')
  7731. .data(function(d) { return d }, function(d,i) { return i });
  7732. groups.enter().append('g')
  7733. .style('stroke-opacity', 1e-6)
  7734. .style('fill-opacity', 1e-6);
  7735. groups.exit().watchTransition(renderWatch, 'multibarhorizontal: exit groups')
  7736. .style('stroke-opacity', 1e-6)
  7737. .style('fill-opacity', 1e-6)
  7738. .remove();
  7739. groups
  7740. .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
  7741. .classed('hover', function(d) { return d.hover })
  7742. .style('fill', function(d,i){ return color(d, i) })
  7743. .style('stroke', function(d,i){ return color(d, i) });
  7744. groups.watchTransition(renderWatch, 'multibarhorizontal: groups')
  7745. .style('stroke-opacity', 1)
  7746. .style('fill-opacity', fillOpacity);
  7747. var bars = groups.selectAll('g.nv-bar')
  7748. .data(function(d) { return d.values });
  7749. bars.exit().remove();
  7750. var barsEnter = bars.enter().append('g')
  7751. .attr('transform', function(d,i,j) {
  7752. return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')'
  7753. });
  7754. barsEnter.append('rect')
  7755. .attr('width', 0)
  7756. .attr('height', x.rangeBand() / (stacked ? 1 : data.length) )
  7757. bars
  7758. .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
  7759. d3.select(this).classed('hover', true);
  7760. dispatch.elementMouseover({
  7761. data: d,
  7762. index: i,
  7763. color: d3.select(this).style("fill")
  7764. });
  7765. })
  7766. .on('mouseout', function(d,i) {
  7767. d3.select(this).classed('hover', false);
  7768. dispatch.elementMouseout({
  7769. data: d,
  7770. index: i,
  7771. color: d3.select(this).style("fill")
  7772. });
  7773. })
  7774. .on('mouseout', function(d,i) {
  7775. dispatch.elementMouseout({
  7776. data: d,
  7777. index: i,
  7778. color: d3.select(this).style("fill")
  7779. });
  7780. })
  7781. .on('mousemove', function(d,i) {
  7782. dispatch.elementMousemove({
  7783. data: d,
  7784. index: i,
  7785. color: d3.select(this).style("fill")
  7786. });
  7787. })
  7788. .on('click', function(d,i) {
  7789. var element = this;
  7790. dispatch.elementClick({
  7791. data: d,
  7792. index: i,
  7793. color: d3.select(this).style("fill"),
  7794. event: d3.event,
  7795. element: element
  7796. });
  7797. d3.event.stopPropagation();
  7798. })
  7799. .on('dblclick', function(d,i) {
  7800. dispatch.elementDblClick({
  7801. data: d,
  7802. index: i,
  7803. color: d3.select(this).style("fill")
  7804. });
  7805. d3.event.stopPropagation();
  7806. });
  7807. if (getYerr(data[0],0)) {
  7808. barsEnter.append('polyline');
  7809. bars.select('polyline')
  7810. .attr('fill', 'none')
  7811. .attr('points', function(d,i) {
  7812. var xerr = getYerr(d,i)
  7813. , mid = 0.8 * x.rangeBand() / ((stacked ? 1 : data.length) * 2);
  7814. xerr = xerr.length ? xerr : [-Math.abs(xerr), Math.abs(xerr)];
  7815. xerr = xerr.map(function(e) { return y(e) - y(0); });
  7816. var a = [[xerr[0],-mid], [xerr[0],mid], [xerr[0],0], [xerr[1],0], [xerr[1],-mid], [xerr[1],mid]];
  7817. return a.map(function (path) { return path.join(',') }).join(' ');
  7818. })
  7819. .attr('transform', function(d,i) {
  7820. var mid = x.rangeBand() / ((stacked ? 1 : data.length) * 2);
  7821. return 'translate(' + (getY(d,i) < 0 ? 0 : y(getY(d,i)) - y(0)) + ', ' + mid + ')'
  7822. });
  7823. }
  7824. barsEnter.append('text');
  7825. if (showValues && !stacked) {
  7826. bars.select('text')
  7827. .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' })
  7828. .attr('y', x.rangeBand() / (data.length * 2))
  7829. .attr('dy', '.32em')
  7830. .text(function(d,i) {
  7831. var t = valueFormat(getY(d,i))
  7832. , yerr = getYerr(d,i);
  7833. if (yerr === undefined)
  7834. return t;
  7835. if (!yerr.length)
  7836. return t + '±' + valueFormat(Math.abs(yerr));
  7837. return t + '+' + valueFormat(Math.abs(yerr[1])) + '-' + valueFormat(Math.abs(yerr[0]));
  7838. });
  7839. bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
  7840. .select('text')
  7841. .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 })
  7842. } else {
  7843. bars.selectAll('text').text('');
  7844. }
  7845. if (showBarLabels && !stacked) {
  7846. barsEnter.append('text').classed('nv-bar-label',true);
  7847. bars.select('text.nv-bar-label')
  7848. .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'start' : 'end' })
  7849. .attr('y', x.rangeBand() / (data.length * 2))
  7850. .attr('dy', '.32em')
  7851. .text(function(d,i) { return getX(d,i) });
  7852. bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
  7853. .select('text.nv-bar-label')
  7854. .attr('x', function(d,i) { return getY(d,i) < 0 ? y(0) - y(getY(d,i)) + 4 : -4 });
  7855. }
  7856. else {
  7857. bars.selectAll('text.nv-bar-label').text('');
  7858. }
  7859. bars
  7860. .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
  7861. if (barColor) {
  7862. if (!disabled) disabled = data.map(function() { return true });
  7863. bars
  7864. .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(); })
  7865. .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(); });
  7866. }
  7867. if (stacked)
  7868. bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
  7869. .attr('transform', function(d,i) {
  7870. return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')'
  7871. })
  7872. .select('rect')
  7873. .attr('width', function(d,i) {
  7874. return Math.abs(y(getY(d,i) + d.y0) - y(d.y0)) || 0
  7875. })
  7876. .attr('height', x.rangeBand() );
  7877. else
  7878. bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
  7879. .attr('transform', function(d,i) {
  7880. //TODO: stacked must be all positive or all negative, not both?
  7881. return 'translate(' +
  7882. (getY(d,i) < 0 ? y(getY(d,i)) : y(0))
  7883. + ',' +
  7884. (d.series * x.rangeBand() / data.length
  7885. +
  7886. x(getX(d,i)) )
  7887. + ')'
  7888. })
  7889. .select('rect')
  7890. .attr('height', x.rangeBand() / data.length )
  7891. .attr('width', function(d,i) {
  7892. return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0
  7893. });
  7894. //store old scales for use in transitions on update
  7895. x0 = x.copy();
  7896. y0 = y.copy();
  7897. });
  7898. renderWatch.renderEnd('multibarHorizontal immediate');
  7899. return chart;
  7900. }
  7901. //============================================================
  7902. // Expose Public Variables
  7903. //------------------------------------------------------------
  7904. chart.dispatch = dispatch;
  7905. chart.options = nv.utils.optionsFunc.bind(chart);
  7906. chart._options = Object.create({}, {
  7907. // simple options, just get/set the necessary values
  7908. width: {get: function(){return width;}, set: function(_){width=_;}},
  7909. height: {get: function(){return height;}, set: function(_){height=_;}},
  7910. x: {get: function(){return getX;}, set: function(_){getX=_;}},
  7911. y: {get: function(){return getY;}, set: function(_){getY=_;}},
  7912. yErr: {get: function(){return getYerr;}, set: function(_){getYerr=_;}},
  7913. xScale: {get: function(){return x;}, set: function(_){x=_;}},
  7914. yScale: {get: function(){return y;}, set: function(_){y=_;}},
  7915. xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
  7916. yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
  7917. xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
  7918. yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
  7919. forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
  7920. stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}},
  7921. showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}},
  7922. // this shows the group name, seems pointless?
  7923. //showBarLabels: {get: function(){return showBarLabels;}, set: function(_){showBarLabels=_;}},
  7924. disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}},
  7925. id: {get: function(){return id;}, set: function(_){id=_;}},
  7926. valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
  7927. valuePadding: {get: function(){return valuePadding;}, set: function(_){valuePadding=_;}},
  7928. groupSpacing: {get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}},
  7929. fillOpacity: {get: function(){return fillOpacity;}, set: function(_){fillOpacity=_;}},
  7930. // options that require extra logic in the setter
  7931. margin: {get: function(){return margin;}, set: function(_){
  7932. margin.top = _.top !== undefined ? _.top : margin.top;
  7933. margin.right = _.right !== undefined ? _.right : margin.right;
  7934. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  7935. margin.left = _.left !== undefined ? _.left : margin.left;
  7936. }},
  7937. duration: {get: function(){return duration;}, set: function(_){
  7938. duration = _;
  7939. renderWatch.reset(duration);
  7940. }},
  7941. color: {get: function(){return color;}, set: function(_){
  7942. color = nv.utils.getColor(_);
  7943. }},
  7944. barColor: {get: function(){return barColor;}, set: function(_){
  7945. barColor = _ ? nv.utils.getColor(_) : null;
  7946. }}
  7947. });
  7948. nv.utils.initOptions(chart);
  7949. return chart;
  7950. };
  7951. nv.models.multiBarHorizontalChart = function() {
  7952. "use strict";
  7953. //============================================================
  7954. // Public Variables with Default Settings
  7955. //------------------------------------------------------------
  7956. var multibar = nv.models.multiBarHorizontal()
  7957. , xAxis = nv.models.axis()
  7958. , yAxis = nv.models.axis()
  7959. , legend = nv.models.legend().height(30)
  7960. , controls = nv.models.legend().height(30)
  7961. , tooltip = nv.models.tooltip()
  7962. ;
  7963. var margin = {top: 30, right: 20, bottom: 50, left: 60}
  7964. , marginTop = null
  7965. , width = null
  7966. , height = null
  7967. , color = nv.utils.defaultColor()
  7968. , showControls = true
  7969. , controlLabels = {}
  7970. , showLegend = true
  7971. , showXAxis = true
  7972. , showYAxis = true
  7973. , stacked = false
  7974. , x //can be accessed via chart.xScale()
  7975. , y //can be accessed via chart.yScale()
  7976. , state = nv.utils.state()
  7977. , defaultState = null
  7978. , noData = null
  7979. , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd')
  7980. , controlWidth = function() { return showControls ? 180 : 0 }
  7981. , duration = 250
  7982. ;
  7983. state.stacked = false; // DEPRECATED Maintained for backward compatibility
  7984. multibar.stacked(stacked);
  7985. xAxis
  7986. .orient('left')
  7987. .tickPadding(5)
  7988. .showMaxMin(false)
  7989. .tickFormat(function(d) { return d })
  7990. ;
  7991. yAxis
  7992. .orient('bottom')
  7993. .tickFormat(d3.format(',.1f'))
  7994. ;
  7995. tooltip
  7996. .duration(0)
  7997. .valueFormatter(function(d, i) {
  7998. return yAxis.tickFormat()(d, i);
  7999. })
  8000. .headerFormatter(function(d, i) {
  8001. return xAxis.tickFormat()(d, i);
  8002. });
  8003. controls.updateState(false);
  8004. //============================================================
  8005. // Private Variables
  8006. //------------------------------------------------------------
  8007. var stateGetter = function(data) {
  8008. return function(){
  8009. return {
  8010. active: data.map(function(d) { return !d.disabled }),
  8011. stacked: stacked
  8012. };
  8013. }
  8014. };
  8015. var stateSetter = function(data) {
  8016. return function(state) {
  8017. if (state.stacked !== undefined)
  8018. stacked = state.stacked;
  8019. if (state.active !== undefined)
  8020. data.forEach(function(series,i) {
  8021. series.disabled = !state.active[i];
  8022. });
  8023. }
  8024. };
  8025. var renderWatch = nv.utils.renderWatch(dispatch, duration);
  8026. function chart(selection) {
  8027. renderWatch.reset();
  8028. renderWatch.models(multibar);
  8029. if (showXAxis) renderWatch.models(xAxis);
  8030. if (showYAxis) renderWatch.models(yAxis);
  8031. selection.each(function(data) {
  8032. var container = d3.select(this),
  8033. that = this;
  8034. nv.utils.initSVG(container);
  8035. var availableWidth = nv.utils.availableWidth(width, container, margin),
  8036. availableHeight = nv.utils.availableHeight(height, container, margin);
  8037. chart.update = function() { container.transition().duration(duration).call(chart) };
  8038. chart.container = this;
  8039. stacked = multibar.stacked();
  8040. state
  8041. .setter(stateSetter(data), chart.update)
  8042. .getter(stateGetter(data))
  8043. .update();
  8044. // DEPRECATED set state.disableddisabled
  8045. state.disabled = data.map(function(d) { return !!d.disabled });
  8046. if (!defaultState) {
  8047. var key;
  8048. defaultState = {};
  8049. for (key in state) {
  8050. if (state[key] instanceof Array)
  8051. defaultState[key] = state[key].slice(0);
  8052. else
  8053. defaultState[key] = state[key];
  8054. }
  8055. }
  8056. // Display No Data message if there's nothing to show.
  8057. if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
  8058. nv.utils.noData(chart, container)
  8059. return chart;
  8060. } else {
  8061. container.selectAll('.nv-noData').remove();
  8062. }
  8063. // Setup Scales
  8064. x = multibar.xScale();
  8065. y = multibar.yScale().clamp(true);
  8066. // Setup containers and skeleton of chart
  8067. var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]);
  8068. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g');
  8069. var g = wrap.select('g');
  8070. gEnter.append('g').attr('class', 'nv-x nv-axis');
  8071. gEnter.append('g').attr('class', 'nv-y nv-axis')
  8072. .append('g').attr('class', 'nv-zeroLine')
  8073. .append('line');
  8074. gEnter.append('g').attr('class', 'nv-barsWrap');
  8075. gEnter.append('g').attr('class', 'nv-legendWrap');
  8076. gEnter.append('g').attr('class', 'nv-controlsWrap');
  8077. // Legend
  8078. if (!showLegend) {
  8079. g.select('.nv-legendWrap').selectAll('*').remove();
  8080. } else {
  8081. legend.width(availableWidth - controlWidth());
  8082. g.select('.nv-legendWrap')
  8083. .datum(data)
  8084. .call(legend);
  8085. if (!marginTop && legend.height() !== margin.top) {
  8086. margin.top = legend.height();
  8087. availableHeight = nv.utils.availableHeight(height, container, margin);
  8088. }
  8089. g.select('.nv-legendWrap')
  8090. .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
  8091. }
  8092. // Controls
  8093. if (!showControls) {
  8094. g.select('.nv-controlsWrap').selectAll('*').remove();
  8095. } else {
  8096. var controlsData = [
  8097. { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() },
  8098. { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() }
  8099. ];
  8100. controls.width(controlWidth()).color(['#444', '#444', '#444']);
  8101. g.select('.nv-controlsWrap')
  8102. .datum(controlsData)
  8103. .attr('transform', 'translate(0,' + (-margin.top) +')')
  8104. .call(controls);
  8105. }
  8106. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  8107. // Main Chart Component(s)
  8108. multibar
  8109. .disabled(data.map(function(series) { return series.disabled }))
  8110. .width(availableWidth)
  8111. .height(availableHeight)
  8112. .color(data.map(function(d,i) {
  8113. return d.color || color(d, i);
  8114. }).filter(function(d,i) { return !data[i].disabled }));
  8115. var barsWrap = g.select('.nv-barsWrap')
  8116. .datum(data.filter(function(d) { return !d.disabled }));
  8117. barsWrap.transition().call(multibar);
  8118. // Setup Axes
  8119. if (showXAxis) {
  8120. xAxis
  8121. .scale(x)
  8122. ._ticks( nv.utils.calcTicksY(availableHeight/24, data) )
  8123. .tickSize(-availableWidth, 0);
  8124. g.select('.nv-x.nv-axis').call(xAxis);
  8125. var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
  8126. xTicks
  8127. .selectAll('line, text');
  8128. }
  8129. if (showYAxis) {
  8130. yAxis
  8131. .scale(y)
  8132. ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
  8133. .tickSize( -availableHeight, 0);
  8134. g.select('.nv-y.nv-axis')
  8135. .attr('transform', 'translate(0,' + availableHeight + ')');
  8136. g.select('.nv-y.nv-axis').call(yAxis);
  8137. }
  8138. // Zero line
  8139. g.select(".nv-zeroLine line")
  8140. .attr("x1", y(0))
  8141. .attr("x2", y(0))
  8142. .attr("y1", 0)
  8143. .attr("y2", -availableHeight)
  8144. ;
  8145. //============================================================
  8146. // Event Handling/Dispatching (in chart's scope)
  8147. //------------------------------------------------------------
  8148. legend.dispatch.on('stateChange', function(newState) {
  8149. for (var key in newState)
  8150. state[key] = newState[key];
  8151. dispatch.stateChange(state);
  8152. chart.update();
  8153. });
  8154. controls.dispatch.on('legendClick', function(d,i) {
  8155. if (!d.disabled) return;
  8156. controlsData = controlsData.map(function(s) {
  8157. s.disabled = true;
  8158. return s;
  8159. });
  8160. d.disabled = false;
  8161. switch (d.key) {
  8162. case 'Grouped':
  8163. case controlLabels.grouped:
  8164. multibar.stacked(false);
  8165. break;
  8166. case 'Stacked':
  8167. case controlLabels.stacked:
  8168. multibar.stacked(true);
  8169. break;
  8170. }
  8171. state.stacked = multibar.stacked();
  8172. dispatch.stateChange(state);
  8173. stacked = multibar.stacked();
  8174. chart.update();
  8175. });
  8176. // Update chart from a state object passed to event handler
  8177. dispatch.on('changeState', function(e) {
  8178. if (typeof e.disabled !== 'undefined') {
  8179. data.forEach(function(series,i) {
  8180. series.disabled = e.disabled[i];
  8181. });
  8182. state.disabled = e.disabled;
  8183. }
  8184. if (typeof e.stacked !== 'undefined') {
  8185. multibar.stacked(e.stacked);
  8186. state.stacked = e.stacked;
  8187. stacked = e.stacked;
  8188. }
  8189. chart.update();
  8190. });
  8191. });
  8192. renderWatch.renderEnd('multibar horizontal chart immediate');
  8193. return chart;
  8194. }
  8195. //============================================================
  8196. // Event Handling/Dispatching (out of chart's scope)
  8197. //------------------------------------------------------------
  8198. multibar.dispatch.on('elementMouseover.tooltip', function(evt) {
  8199. evt.value = chart.x()(evt.data);
  8200. evt['series'] = {
  8201. key: evt.data.key,
  8202. value: chart.y()(evt.data),
  8203. color: evt.color
  8204. };
  8205. tooltip.data(evt).hidden(false);
  8206. });
  8207. multibar.dispatch.on('elementMouseout.tooltip', function(evt) {
  8208. tooltip.hidden(true);
  8209. });
  8210. multibar.dispatch.on('elementMousemove.tooltip', function(evt) {
  8211. tooltip();
  8212. });
  8213. //============================================================
  8214. // Expose Public Variables
  8215. //------------------------------------------------------------
  8216. // expose chart's sub-components
  8217. chart.dispatch = dispatch;
  8218. chart.multibar = multibar;
  8219. chart.legend = legend;
  8220. chart.controls = controls;
  8221. chart.xAxis = xAxis;
  8222. chart.yAxis = yAxis;
  8223. chart.state = state;
  8224. chart.tooltip = tooltip;
  8225. chart.options = nv.utils.optionsFunc.bind(chart);
  8226. chart._options = Object.create({}, {
  8227. // simple options, just get/set the necessary values
  8228. width: {get: function(){return width;}, set: function(_){width=_;}},
  8229. height: {get: function(){return height;}, set: function(_){height=_;}},
  8230. showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
  8231. showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
  8232. controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
  8233. showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
  8234. showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
  8235. defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
  8236. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  8237. // options that require extra logic in the setter
  8238. margin: {get: function(){return margin;}, set: function(_){
  8239. if (_.top !== undefined) {
  8240. margin.top = _.top;
  8241. marginTop = _.top;
  8242. }
  8243. margin.right = _.right !== undefined ? _.right : margin.right;
  8244. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  8245. margin.left = _.left !== undefined ? _.left : margin.left;
  8246. }},
  8247. duration: {get: function(){return duration;}, set: function(_){
  8248. duration = _;
  8249. renderWatch.reset(duration);
  8250. multibar.duration(duration);
  8251. xAxis.duration(duration);
  8252. yAxis.duration(duration);
  8253. }},
  8254. color: {get: function(){return color;}, set: function(_){
  8255. color = nv.utils.getColor(_);
  8256. legend.color(color);
  8257. }},
  8258. barColor: {get: function(){return multibar.barColor;}, set: function(_){
  8259. multibar.barColor(_);
  8260. legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();})
  8261. }}
  8262. });
  8263. nv.utils.inheritOptions(chart, multibar);
  8264. nv.utils.initOptions(chart);
  8265. return chart;
  8266. };
  8267. nv.models.multiChart = function() {
  8268. "use strict";
  8269. //============================================================
  8270. // Public Variables with Default Settings
  8271. //------------------------------------------------------------
  8272. var margin = {top: 30, right: 20, bottom: 50, left: 60},
  8273. marginTop = null,
  8274. color = nv.utils.defaultColor(),
  8275. width = null,
  8276. height = null,
  8277. showLegend = true,
  8278. noData = null,
  8279. yDomain1,
  8280. yDomain2,
  8281. getX = function(d) { return d.x },
  8282. getY = function(d) { return d.y},
  8283. interpolate = 'linear',
  8284. useVoronoi = true,
  8285. interactiveLayer = nv.interactiveGuideline(),
  8286. useInteractiveGuideline = false,
  8287. legendRightAxisHint = ' (right axis)',
  8288. duration = 250
  8289. ;
  8290. //============================================================
  8291. // Private Variables
  8292. //------------------------------------------------------------
  8293. var x = d3.scale.linear(),
  8294. yScale1 = d3.scale.linear(),
  8295. yScale2 = d3.scale.linear(),
  8296. lines1 = nv.models.line().yScale(yScale1).duration(duration),
  8297. lines2 = nv.models.line().yScale(yScale2).duration(duration),
  8298. scatters1 = nv.models.scatter().yScale(yScale1).duration(duration),
  8299. scatters2 = nv.models.scatter().yScale(yScale2).duration(duration),
  8300. bars1 = nv.models.multiBar().stacked(false).yScale(yScale1).duration(duration),
  8301. bars2 = nv.models.multiBar().stacked(false).yScale(yScale2).duration(duration),
  8302. stack1 = nv.models.stackedArea().yScale(yScale1).duration(duration),
  8303. stack2 = nv.models.stackedArea().yScale(yScale2).duration(duration),
  8304. xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5).duration(duration),
  8305. yAxis1 = nv.models.axis().scale(yScale1).orient('left').duration(duration),
  8306. yAxis2 = nv.models.axis().scale(yScale2).orient('right').duration(duration),
  8307. legend = nv.models.legend().height(30),
  8308. tooltip = nv.models.tooltip(),
  8309. dispatch = d3.dispatch();
  8310. var charts = [lines1, lines2, scatters1, scatters2, bars1, bars2, stack1, stack2];
  8311. function chart(selection) {
  8312. selection.each(function(data) {
  8313. var container = d3.select(this),
  8314. that = this;
  8315. nv.utils.initSVG(container);
  8316. chart.update = function() { container.transition().call(chart); };
  8317. chart.container = this;
  8318. var availableWidth = nv.utils.availableWidth(width, container, margin),
  8319. availableHeight = nv.utils.availableHeight(height, container, margin);
  8320. var dataLines1 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 1});
  8321. var dataLines2 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 2});
  8322. var dataScatters1 = data.filter(function(d) {return d.type == 'scatter' && d.yAxis == 1});
  8323. var dataScatters2 = data.filter(function(d) {return d.type == 'scatter' && d.yAxis == 2});
  8324. var dataBars1 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 1});
  8325. var dataBars2 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 2});
  8326. var dataStack1 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 1});
  8327. var dataStack2 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 2});
  8328. // Display noData message if there's nothing to show.
  8329. if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
  8330. nv.utils.noData(chart, container);
  8331. return chart;
  8332. } else {
  8333. container.selectAll('.nv-noData').remove();
  8334. }
  8335. var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1})
  8336. .map(function(d) {
  8337. return d.values.map(function(d,i) {
  8338. return { x: getX(d), y: getY(d) }
  8339. })
  8340. });
  8341. var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2})
  8342. .map(function(d) {
  8343. return d.values.map(function(d,i) {
  8344. return { x: getX(d), y: getY(d) }
  8345. })
  8346. });
  8347. x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x }))
  8348. .range([0, availableWidth]);
  8349. var wrap = container.selectAll('g.wrap.multiChart').data([data]);
  8350. var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g');
  8351. gEnter.append('g').attr('class', 'nv-x nv-axis');
  8352. gEnter.append('g').attr('class', 'nv-y1 nv-axis');
  8353. gEnter.append('g').attr('class', 'nv-y2 nv-axis');
  8354. gEnter.append('g').attr('class', 'stack1Wrap');
  8355. gEnter.append('g').attr('class', 'stack2Wrap');
  8356. gEnter.append('g').attr('class', 'bars1Wrap');
  8357. gEnter.append('g').attr('class', 'bars2Wrap');
  8358. gEnter.append('g').attr('class', 'scatters1Wrap');
  8359. gEnter.append('g').attr('class', 'scatters2Wrap');
  8360. gEnter.append('g').attr('class', 'lines1Wrap');
  8361. gEnter.append('g').attr('class', 'lines2Wrap');
  8362. gEnter.append('g').attr('class', 'legendWrap');
  8363. gEnter.append('g').attr('class', 'nv-interactive');
  8364. var g = wrap.select('g');
  8365. var color_array = data.map(function(d,i) {
  8366. return data[i].color || color(d, i);
  8367. });
  8368. // Legend
  8369. if (!showLegend) {
  8370. g.select('.legendWrap').selectAll('*').remove();
  8371. } else {
  8372. var legendWidth = legend.align() ? availableWidth / 2 : availableWidth;
  8373. var legendXPosition = legend.align() ? legendWidth : 0;
  8374. legend.width(legendWidth);
  8375. legend.color(color_array);
  8376. g.select('.legendWrap')
  8377. .datum(data.map(function(series) {
  8378. series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
  8379. series.key = series.originalKey + (series.yAxis == 1 ? '' : legendRightAxisHint);
  8380. return series;
  8381. }))
  8382. .call(legend);
  8383. if (!marginTop && legend.height() !== margin.top) {
  8384. margin.top = legend.height();
  8385. availableHeight = nv.utils.availableHeight(height, container, margin);
  8386. }
  8387. g.select('.legendWrap')
  8388. .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')');
  8389. }
  8390. lines1
  8391. .width(availableWidth)
  8392. .height(availableHeight)
  8393. .interpolate(interpolate)
  8394. .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'}));
  8395. lines2
  8396. .width(availableWidth)
  8397. .height(availableHeight)
  8398. .interpolate(interpolate)
  8399. .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'}));
  8400. scatters1
  8401. .width(availableWidth)
  8402. .height(availableHeight)
  8403. .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'scatter'}));
  8404. scatters2
  8405. .width(availableWidth)
  8406. .height(availableHeight)
  8407. .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'scatter'}));
  8408. bars1
  8409. .width(availableWidth)
  8410. .height(availableHeight)
  8411. .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'}));
  8412. bars2
  8413. .width(availableWidth)
  8414. .height(availableHeight)
  8415. .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'}));
  8416. stack1
  8417. .width(availableWidth)
  8418. .height(availableHeight)
  8419. .interpolate(interpolate)
  8420. .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'}));
  8421. stack2
  8422. .width(availableWidth)
  8423. .height(availableHeight)
  8424. .interpolate(interpolate)
  8425. .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'}));
  8426. g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  8427. var lines1Wrap = g.select('.lines1Wrap')
  8428. .datum(dataLines1.filter(function(d){return !d.disabled}));
  8429. var scatters1Wrap = g.select('.scatters1Wrap')
  8430. .datum(dataScatters1.filter(function(d){return !d.disabled}));
  8431. var bars1Wrap = g.select('.bars1Wrap')
  8432. .datum(dataBars1.filter(function(d){return !d.disabled}));
  8433. var stack1Wrap = g.select('.stack1Wrap')
  8434. .datum(dataStack1.filter(function(d){return !d.disabled}));
  8435. var lines2Wrap = g.select('.lines2Wrap')
  8436. .datum(dataLines2.filter(function(d){return !d.disabled}));
  8437. var scatters2Wrap = g.select('.scatters2Wrap')
  8438. .datum(dataScatters2.filter(function(d){return !d.disabled}));
  8439. var bars2Wrap = g.select('.bars2Wrap')
  8440. .datum(dataBars2.filter(function(d){return !d.disabled}));
  8441. var stack2Wrap = g.select('.stack2Wrap')
  8442. .datum(dataStack2.filter(function(d){return !d.disabled}));
  8443. var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){
  8444. return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
  8445. }).concat([{x:0, y:0}]) : [];
  8446. var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){
  8447. return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
  8448. }).concat([{x:0, y:0}]) : [];
  8449. yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } ))
  8450. .range([0, availableHeight]);
  8451. yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } ))
  8452. .range([0, availableHeight]);
  8453. lines1.yDomain(yScale1.domain());
  8454. scatters1.yDomain(yScale1.domain());
  8455. bars1.yDomain(yScale1.domain());
  8456. stack1.yDomain(yScale1.domain());
  8457. lines2.yDomain(yScale2.domain());
  8458. scatters2.yDomain(yScale2.domain());
  8459. bars2.yDomain(yScale2.domain());
  8460. stack2.yDomain(yScale2.domain());
  8461. if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);}
  8462. if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);}
  8463. if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);}
  8464. if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);}
  8465. if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);}
  8466. if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);}
  8467. if(dataScatters1.length){d3.transition(scatters1Wrap).call(scatters1);}
  8468. if(dataScatters2.length){d3.transition(scatters2Wrap).call(scatters2);}
  8469. xAxis
  8470. ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
  8471. .tickSize(-availableHeight, 0);
  8472. g.select('.nv-x.nv-axis')
  8473. .attr('transform', 'translate(0,' + availableHeight + ')');
  8474. d3.transition(g.select('.nv-x.nv-axis'))
  8475. .call(xAxis);
  8476. yAxis1
  8477. ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
  8478. .tickSize( -availableWidth, 0);
  8479. d3.transition(g.select('.nv-y1.nv-axis'))
  8480. .call(yAxis1);
  8481. yAxis2
  8482. ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
  8483. .tickSize( -availableWidth, 0);
  8484. d3.transition(g.select('.nv-y2.nv-axis'))
  8485. .call(yAxis2);
  8486. g.select('.nv-y1.nv-axis')
  8487. .classed('nv-disabled', series1.length ? false : true)
  8488. .attr('transform', 'translate(' + x.range()[0] + ',0)');
  8489. g.select('.nv-y2.nv-axis')
  8490. .classed('nv-disabled', series2.length ? false : true)
  8491. .attr('transform', 'translate(' + x.range()[1] + ',0)');
  8492. legend.dispatch.on('stateChange', function(newState) {
  8493. chart.update();
  8494. });
  8495. if(useInteractiveGuideline){
  8496. interactiveLayer
  8497. .width(availableWidth)
  8498. .height(availableHeight)
  8499. .margin({left:margin.left, top:margin.top})
  8500. .svgContainer(container)
  8501. .xScale(x);
  8502. wrap.select(".nv-interactive").call(interactiveLayer);
  8503. }
  8504. //============================================================
  8505. // Event Handling/Dispatching
  8506. //------------------------------------------------------------
  8507. function mouseover_line(evt) {
  8508. var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1;
  8509. evt.value = evt.point.x;
  8510. evt.series = {
  8511. value: evt.point.y,
  8512. color: evt.point.color,
  8513. key: evt.series.key
  8514. };
  8515. tooltip
  8516. .duration(0)
  8517. .headerFormatter(function(d, i) {
  8518. return xAxis.tickFormat()(d, i);
  8519. })
  8520. .valueFormatter(function(d, i) {
  8521. return yaxis.tickFormat()(d, i);
  8522. })
  8523. .data(evt)
  8524. .hidden(false);
  8525. }
  8526. function mouseover_scatter(evt) {
  8527. var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1;
  8528. evt.value = evt.point.x;
  8529. evt.series = {
  8530. value: evt.point.y,
  8531. color: evt.point.color,
  8532. key: evt.series.key
  8533. };
  8534. tooltip
  8535. .duration(100)
  8536. .headerFormatter(function(d, i) {
  8537. return xAxis.tickFormat()(d, i);
  8538. })
  8539. .valueFormatter(function(d, i) {
  8540. return yaxis.tickFormat()(d, i);
  8541. })
  8542. .data(evt)
  8543. .hidden(false);
  8544. }
  8545. function mouseover_stack(evt) {
  8546. var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1;
  8547. evt.point['x'] = stack1.x()(evt.point);
  8548. evt.point['y'] = stack1.y()(evt.point);
  8549. tooltip
  8550. .duration(0)
  8551. .headerFormatter(function(d, i) {
  8552. return xAxis.tickFormat()(d, i);
  8553. })
  8554. .valueFormatter(function(d, i) {
  8555. return yaxis.tickFormat()(d, i);
  8556. })
  8557. .data(evt)
  8558. .hidden(false);
  8559. }
  8560. function mouseover_bar(evt) {
  8561. var yaxis = data[evt.data.series].yAxis === 2 ? yAxis2 : yAxis1;
  8562. evt.value = bars1.x()(evt.data);
  8563. evt['series'] = {
  8564. value: bars1.y()(evt.data),
  8565. color: evt.color,
  8566. key: evt.data.key
  8567. };
  8568. tooltip
  8569. .duration(0)
  8570. .headerFormatter(function(d, i) {
  8571. return xAxis.tickFormat()(d, i);
  8572. })
  8573. .valueFormatter(function(d, i) {
  8574. return yaxis.tickFormat()(d, i);
  8575. })
  8576. .data(evt)
  8577. .hidden(false);
  8578. }
  8579. function clearHighlights() {
  8580. for(var i=0, il=charts.length; i < il; i++){
  8581. var chart = charts[i];
  8582. try {
  8583. chart.clearHighlights();
  8584. } catch(e){}
  8585. }
  8586. }
  8587. function highlightPoint(serieIndex, pointIndex, b){
  8588. for(var i=0, il=charts.length; i < il; i++){
  8589. var chart = charts[i];
  8590. try {
  8591. chart.highlightPoint(serieIndex, pointIndex, b);
  8592. } catch(e){}
  8593. }
  8594. }
  8595. if(useInteractiveGuideline){
  8596. interactiveLayer.dispatch.on('elementMousemove', function(e) {
  8597. clearHighlights();
  8598. var singlePoint, pointIndex, pointXLocation, allData = [];
  8599. data
  8600. .filter(function(series, i) {
  8601. series.seriesIndex = i;
  8602. return !series.disabled;
  8603. })
  8604. .forEach(function(series,i) {
  8605. var extent = x.domain();
  8606. var currentValues = series.values.filter(function(d,i) {
  8607. return chart.x()(d,i) >= extent[0] && chart.x()(d,i) <= extent[1];
  8608. });
  8609. pointIndex = nv.interactiveBisect(currentValues, e.pointXValue, chart.x());
  8610. var point = currentValues[pointIndex];
  8611. var pointYValue = chart.y()(point, pointIndex);
  8612. if (pointYValue !== null) {
  8613. highlightPoint(i, pointIndex, true);
  8614. }
  8615. if (point === undefined) return;
  8616. if (singlePoint === undefined) singlePoint = point;
  8617. if (pointXLocation === undefined) pointXLocation = x(chart.x()(point,pointIndex));
  8618. allData.push({
  8619. key: series.key,
  8620. value: pointYValue,
  8621. color: color(series,series.seriesIndex),
  8622. data: point,
  8623. yAxis: series.yAxis == 2 ? yAxis2 : yAxis1
  8624. });
  8625. });
  8626. var defaultValueFormatter = function(d,i) {
  8627. var yAxis = allData[i].yAxis;
  8628. return d == null ? "N/A" : yAxis.tickFormat()(d);
  8629. };
  8630. interactiveLayer.tooltip
  8631. .headerFormatter(function(d, i) {
  8632. return xAxis.tickFormat()(d, i);
  8633. })
  8634. .valueFormatter(interactiveLayer.tooltip.valueFormatter() || defaultValueFormatter)
  8635. .data({
  8636. value: chart.x()( singlePoint,pointIndex ),
  8637. index: pointIndex,
  8638. series: allData
  8639. })();
  8640. interactiveLayer.renderGuideLine(pointXLocation);
  8641. });
  8642. interactiveLayer.dispatch.on("elementMouseout",function(e) {
  8643. clearHighlights();
  8644. });
  8645. } else {
  8646. lines1.dispatch.on('elementMouseover.tooltip', mouseover_line);
  8647. lines2.dispatch.on('elementMouseover.tooltip', mouseover_line);
  8648. lines1.dispatch.on('elementMouseout.tooltip', function(evt) {
  8649. tooltip.hidden(true)
  8650. });
  8651. lines2.dispatch.on('elementMouseout.tooltip', function(evt) {
  8652. tooltip.hidden(true)
  8653. });
  8654. scatters1.dispatch.on('elementMouseover.tooltip', mouseover_scatter);
  8655. scatters2.dispatch.on('elementMouseover.tooltip', mouseover_scatter);
  8656. scatters1.dispatch.on('elementMouseout.tooltip', function(evt) {
  8657. tooltip.hidden(true)
  8658. });
  8659. scatters2.dispatch.on('elementMouseout.tooltip', function(evt) {
  8660. tooltip.hidden(true)
  8661. });
  8662. stack1.dispatch.on('elementMouseover.tooltip', mouseover_stack);
  8663. stack2.dispatch.on('elementMouseover.tooltip', mouseover_stack);
  8664. stack1.dispatch.on('elementMouseout.tooltip', function(evt) {
  8665. tooltip.hidden(true)
  8666. });
  8667. stack2.dispatch.on('elementMouseout.tooltip', function(evt) {
  8668. tooltip.hidden(true)
  8669. });
  8670. bars1.dispatch.on('elementMouseover.tooltip', mouseover_bar);
  8671. bars2.dispatch.on('elementMouseover.tooltip', mouseover_bar);
  8672. bars1.dispatch.on('elementMouseout.tooltip', function(evt) {
  8673. tooltip.hidden(true);
  8674. });
  8675. bars2.dispatch.on('elementMouseout.tooltip', function(evt) {
  8676. tooltip.hidden(true);
  8677. });
  8678. bars1.dispatch.on('elementMousemove.tooltip', function(evt) {
  8679. tooltip();
  8680. });
  8681. bars2.dispatch.on('elementMousemove.tooltip', function(evt) {
  8682. tooltip();
  8683. });
  8684. }
  8685. });
  8686. return chart;
  8687. }
  8688. //============================================================
  8689. // Global getters and setters
  8690. //------------------------------------------------------------
  8691. chart.dispatch = dispatch;
  8692. chart.legend = legend;
  8693. chart.lines1 = lines1;
  8694. chart.lines2 = lines2;
  8695. chart.scatters1 = scatters1;
  8696. chart.scatters2 = scatters2;
  8697. chart.bars1 = bars1;
  8698. chart.bars2 = bars2;
  8699. chart.stack1 = stack1;
  8700. chart.stack2 = stack2;
  8701. chart.xAxis = xAxis;
  8702. chart.yAxis1 = yAxis1;
  8703. chart.yAxis2 = yAxis2;
  8704. chart.tooltip = tooltip;
  8705. chart.interactiveLayer = interactiveLayer;
  8706. chart.options = nv.utils.optionsFunc.bind(chart);
  8707. chart._options = Object.create({}, {
  8708. // simple options, just get/set the necessary values
  8709. width: {get: function(){return width;}, set: function(_){width=_;}},
  8710. height: {get: function(){return height;}, set: function(_){height=_;}},
  8711. showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
  8712. yDomain1: {get: function(){return yDomain1;}, set: function(_){yDomain1=_;}},
  8713. yDomain2: {get: function(){return yDomain2;}, set: function(_){yDomain2=_;}},
  8714. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  8715. interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
  8716. legendRightAxisHint: {get: function(){return legendRightAxisHint;}, set: function(_){legendRightAxisHint=_;}},
  8717. // options that require extra logic in the setter
  8718. margin: {get: function(){return margin;}, set: function(_){
  8719. if (_.top !== undefined) {
  8720. margin.top = _.top;
  8721. marginTop = _.top;
  8722. }
  8723. margin.right = _.right !== undefined ? _.right : margin.right;
  8724. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  8725. margin.left = _.left !== undefined ? _.left : margin.left;
  8726. }},
  8727. color: {get: function(){return color;}, set: function(_){
  8728. color = nv.utils.getColor(_);
  8729. }},
  8730. x: {get: function(){return getX;}, set: function(_){
  8731. getX = _;
  8732. lines1.x(_);
  8733. lines2.x(_);
  8734. scatters1.x(_);
  8735. scatters2.x(_);
  8736. bars1.x(_);
  8737. bars2.x(_);
  8738. stack1.x(_);
  8739. stack2.x(_);
  8740. }},
  8741. y: {get: function(){return getY;}, set: function(_){
  8742. getY = _;
  8743. lines1.y(_);
  8744. lines2.y(_);
  8745. scatters1.y(_);
  8746. scatters2.y(_);
  8747. stack1.y(_);
  8748. stack2.y(_);
  8749. bars1.y(_);
  8750. bars2.y(_);
  8751. }},
  8752. useVoronoi: {get: function(){return useVoronoi;}, set: function(_){
  8753. useVoronoi=_;
  8754. lines1.useVoronoi(_);
  8755. lines2.useVoronoi(_);
  8756. stack1.useVoronoi(_);
  8757. stack2.useVoronoi(_);
  8758. }},
  8759. useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
  8760. useInteractiveGuideline = _;
  8761. if (useInteractiveGuideline) {
  8762. lines1.interactive(false);
  8763. lines1.useVoronoi(false);
  8764. lines2.interactive(false);
  8765. lines2.useVoronoi(false);
  8766. stack1.interactive(false);
  8767. stack1.useVoronoi(false);
  8768. stack2.interactive(false);
  8769. stack2.useVoronoi(false);
  8770. scatters1.interactive(false);
  8771. scatters2.interactive(false);
  8772. }
  8773. }},
  8774. duration: {get: function(){return duration;}, set: function(_) {
  8775. duration = _;
  8776. [lines1, lines2, stack1, stack2, scatters1, scatters2, xAxis, yAxis1, yAxis2].forEach(function(model){
  8777. model.duration(duration);
  8778. });
  8779. }}
  8780. });
  8781. nv.utils.initOptions(chart);
  8782. return chart;
  8783. };
  8784. nv.models.ohlcBar = function() {
  8785. "use strict";
  8786. //============================================================
  8787. // Public Variables with Default Settings
  8788. //------------------------------------------------------------
  8789. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  8790. , width = null
  8791. , height = null
  8792. , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
  8793. , container = null
  8794. , x = d3.scale.linear()
  8795. , y = d3.scale.linear()
  8796. , getX = function(d) { return d.x }
  8797. , getY = function(d) { return d.y }
  8798. , getOpen = function(d) { return d.open }
  8799. , getClose = function(d) { return d.close }
  8800. , getHigh = function(d) { return d.high }
  8801. , getLow = function(d) { return d.low }
  8802. , forceX = []
  8803. , forceY = []
  8804. , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
  8805. , clipEdge = true
  8806. , color = nv.utils.defaultColor()
  8807. , interactive = false
  8808. , xDomain
  8809. , yDomain
  8810. , xRange
  8811. , yRange
  8812. , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove')
  8813. ;
  8814. //============================================================
  8815. // Private Variables
  8816. //------------------------------------------------------------
  8817. function chart(selection) {
  8818. selection.each(function(data) {
  8819. container = d3.select(this);
  8820. var availableWidth = nv.utils.availableWidth(width, container, margin),
  8821. availableHeight = nv.utils.availableHeight(height, container, margin);
  8822. nv.utils.initSVG(container);
  8823. // ohlc bar width.
  8824. var w = (availableWidth / data[0].values.length) * .9;
  8825. // Setup Scales
  8826. x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
  8827. if (padData)
  8828. x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
  8829. else
  8830. x.range(xRange || [5 + w/2, availableWidth - w/2 - 5]);
  8831. y.domain(yDomain || [
  8832. d3.min(data[0].values.map(getLow).concat(forceY)),
  8833. d3.max(data[0].values.map(getHigh).concat(forceY))
  8834. ]
  8835. ).range(yRange || [availableHeight, 0]);
  8836. // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
  8837. if (x.domain()[0] === x.domain()[1])
  8838. x.domain()[0] ?
  8839. x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
  8840. : x.domain([-1,1]);
  8841. if (y.domain()[0] === y.domain()[1])
  8842. y.domain()[0] ?
  8843. y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
  8844. : y.domain([-1,1]);
  8845. // Setup containers and skeleton of chart
  8846. var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]);
  8847. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar');
  8848. var defsEnter = wrapEnter.append('defs');
  8849. var gEnter = wrapEnter.append('g');
  8850. var g = wrap.select('g');
  8851. gEnter.append('g').attr('class', 'nv-ticks');
  8852. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  8853. container
  8854. .on('click', function(d,i) {
  8855. dispatch.chartClick({
  8856. data: d,
  8857. index: i,
  8858. pos: d3.event,
  8859. id: id
  8860. });
  8861. });
  8862. defsEnter.append('clipPath')
  8863. .attr('id', 'nv-chart-clip-path-' + id)
  8864. .append('rect');
  8865. wrap.select('#nv-chart-clip-path-' + id + ' rect')
  8866. .attr('width', availableWidth)
  8867. .attr('height', availableHeight);
  8868. g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
  8869. var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
  8870. .data(function(d) { return d });
  8871. ticks.exit().remove();
  8872. ticks.enter().append('path')
  8873. .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
  8874. .attr('d', function(d,i) {
  8875. return 'm0,0l0,'
  8876. + (y(getOpen(d,i))
  8877. - y(getHigh(d,i)))
  8878. + 'l'
  8879. + (-w/2)
  8880. + ',0l'
  8881. + (w/2)
  8882. + ',0l0,'
  8883. + (y(getLow(d,i)) - y(getOpen(d,i)))
  8884. + 'l0,'
  8885. + (y(getClose(d,i))
  8886. - y(getLow(d,i)))
  8887. + 'l'
  8888. + (w/2)
  8889. + ',0l'
  8890. + (-w/2)
  8891. + ',0z';
  8892. })
  8893. .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
  8894. .attr('fill', function(d,i) { return color[0]; })
  8895. .attr('stroke', function(d,i) { return color[0]; })
  8896. .attr('x', 0 )
  8897. .attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) })
  8898. .attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) });
  8899. // the bar colors are controlled by CSS currently
  8900. ticks.attr('class', function(d,i,j) {
  8901. return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i;
  8902. });
  8903. d3.transition(ticks)
  8904. .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
  8905. .attr('d', function(d,i) {
  8906. var w = (availableWidth / data[0].values.length) * .9;
  8907. return 'm0,0l0,'
  8908. + (y(getOpen(d,i))
  8909. - y(getHigh(d,i)))
  8910. + 'l'
  8911. + (-w/2)
  8912. + ',0l'
  8913. + (w/2)
  8914. + ',0l0,'
  8915. + (y(getLow(d,i))
  8916. - y(getOpen(d,i)))
  8917. + 'l0,'
  8918. + (y(getClose(d,i))
  8919. - y(getLow(d,i)))
  8920. + 'l'
  8921. + (w/2)
  8922. + ',0l'
  8923. + (-w/2)
  8924. + ',0z';
  8925. });
  8926. });
  8927. return chart;
  8928. }
  8929. //Create methods to allow outside functions to highlight a specific bar.
  8930. chart.highlightPoint = function(pointIndex, isHoverOver) {
  8931. chart.clearHighlights();
  8932. container.select(".nv-ohlcBar .nv-tick-0-" + pointIndex)
  8933. .classed("hover", isHoverOver)
  8934. ;
  8935. };
  8936. chart.clearHighlights = function() {
  8937. container.select(".nv-ohlcBar .nv-tick.hover")
  8938. .classed("hover", false)
  8939. ;
  8940. };
  8941. //============================================================
  8942. // Expose Public Variables
  8943. //------------------------------------------------------------
  8944. chart.dispatch = dispatch;
  8945. chart.options = nv.utils.optionsFunc.bind(chart);
  8946. chart._options = Object.create({}, {
  8947. // simple options, just get/set the necessary values
  8948. width: {get: function(){return width;}, set: function(_){width=_;}},
  8949. height: {get: function(){return height;}, set: function(_){height=_;}},
  8950. xScale: {get: function(){return x;}, set: function(_){x=_;}},
  8951. yScale: {get: function(){return y;}, set: function(_){y=_;}},
  8952. xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
  8953. yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
  8954. xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
  8955. yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
  8956. forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
  8957. forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
  8958. padData: {get: function(){return padData;}, set: function(_){padData=_;}},
  8959. clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
  8960. id: {get: function(){return id;}, set: function(_){id=_;}},
  8961. interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
  8962. x: {get: function(){return getX;}, set: function(_){getX=_;}},
  8963. y: {get: function(){return getY;}, set: function(_){getY=_;}},
  8964. open: {get: function(){return getOpen();}, set: function(_){getOpen=_;}},
  8965. close: {get: function(){return getClose();}, set: function(_){getClose=_;}},
  8966. high: {get: function(){return getHigh;}, set: function(_){getHigh=_;}},
  8967. low: {get: function(){return getLow;}, set: function(_){getLow=_;}},
  8968. // options that require extra logic in the setter
  8969. margin: {get: function(){return margin;}, set: function(_){
  8970. margin.top = _.top != undefined ? _.top : margin.top;
  8971. margin.right = _.right != undefined ? _.right : margin.right;
  8972. margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
  8973. margin.left = _.left != undefined ? _.left : margin.left;
  8974. }},
  8975. color: {get: function(){return color;}, set: function(_){
  8976. color = nv.utils.getColor(_);
  8977. }}
  8978. });
  8979. nv.utils.initOptions(chart);
  8980. return chart;
  8981. };
  8982. // Code adapted from Jason Davies' "Parallel Coordinates"
  8983. // http://bl.ocks.org/jasondavies/1341281
  8984. nv.models.parallelCoordinates = function() {
  8985. "use strict";
  8986. //============================================================
  8987. // Public Variables with Default Settings
  8988. //------------------------------------------------------------
  8989. var margin = {top: 30, right: 0, bottom: 10, left: 0}
  8990. , width = null
  8991. , height = null
  8992. , availableWidth = null
  8993. , availableHeight = null
  8994. , x = d3.scale.ordinal()
  8995. , y = {}
  8996. , undefinedValuesLabel = "undefined values"
  8997. , dimensionData = []
  8998. , enabledDimensions = []
  8999. , dimensionNames = []
  9000. , displayBrush = true
  9001. , color = nv.utils.defaultColor()
  9002. , filters = []
  9003. , active = []
  9004. , dragging = []
  9005. , axisWithUndefinedValues = []
  9006. , lineTension = 1
  9007. , foreground
  9008. , background
  9009. , dimensions
  9010. , line = d3.svg.line()
  9011. , axis = d3.svg.axis()
  9012. , dispatch = d3.dispatch('brushstart', 'brush', 'brushEnd', 'dimensionsOrder', "stateChange", 'elementClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd', 'activeChanged')
  9013. ;
  9014. //============================================================
  9015. // Private Variables
  9016. //------------------------------------------------------------
  9017. var renderWatch = nv.utils.renderWatch(dispatch);
  9018. function chart(selection) {
  9019. renderWatch.reset();
  9020. selection.each(function(data) {
  9021. var container = d3.select(this);
  9022. availableWidth = nv.utils.availableWidth(width, container, margin);
  9023. availableHeight = nv.utils.availableHeight(height, container, margin);
  9024. nv.utils.initSVG(container);
  9025. //Convert old data to new format (name, values)
  9026. if (data[0].values === undefined) {
  9027. var newData = [];
  9028. data.forEach(function (d) {
  9029. var val = {};
  9030. var key = Object.keys(d);
  9031. key.forEach(function (k) { if (k !== "name") val[k] = d[k] });
  9032. newData.push({ key: d.name, values: val });
  9033. });
  9034. data = newData;
  9035. }
  9036. var dataValues = data.map(function (d) {return d.values});
  9037. if (active.length === 0) {
  9038. active = data;
  9039. }; //set all active before first brush call
  9040. dimensionNames = dimensionData.sort(function (a, b) { return a.currentPosition - b.currentPosition; }).map(function (d) { return d.key });
  9041. enabledDimensions = dimensionData.filter(function (d) { return !d.disabled; });
  9042. // Setup Scales
  9043. x.rangePoints([0, availableWidth], 1).domain(enabledDimensions.map(function (d) { return d.key; }));
  9044. //Set as true if all values on an axis are missing.
  9045. // Extract the list of dimensions and create a scale for each.
  9046. var oldDomainMaxValue = {};
  9047. var displayMissingValuesline = false;
  9048. var currentTicks = [];
  9049. dimensionNames.forEach(function(d) {
  9050. var extent = d3.extent(dataValues, function (p) { return +p[d]; });
  9051. var min = extent[0];
  9052. var max = extent[1];
  9053. var onlyUndefinedValues = false;
  9054. //If there is no values to display on an axis, set the extent to 0
  9055. if (isNaN(min) || isNaN(max)) {
  9056. onlyUndefinedValues = true;
  9057. min = 0;
  9058. max = 0;
  9059. }
  9060. //Scale axis if there is only one value
  9061. if (min === max) {
  9062. min = min - 1;
  9063. max = max + 1;
  9064. }
  9065. var f = filters.filter(function (k) { return k.dimension == d; });
  9066. if (f.length !== 0) {
  9067. //If there is only NaN values, keep the existing domain.
  9068. if (onlyUndefinedValues) {
  9069. min = y[d].domain()[0];
  9070. max = y[d].domain()[1];
  9071. }
  9072. //If the brush extent is > max (< min), keep the extent value.
  9073. else if (!f[0].hasOnlyNaN && displayBrush) {
  9074. min = min > f[0].extent[0] ? f[0].extent[0] : min;
  9075. max = max < f[0].extent[1] ? f[0].extent[1] : max;
  9076. }
  9077. //If there is NaN values brushed be sure the brush extent is on the domain.
  9078. else if (f[0].hasNaN) {
  9079. max = max < f[0].extent[1] ? f[0].extent[1] : max;
  9080. oldDomainMaxValue[d] = y[d].domain()[1];
  9081. displayMissingValuesline = true;
  9082. }
  9083. }
  9084. //Use 90% of (availableHeight - 12) for the axis range, 12 reprensenting the space necessary to display "undefined values" text.
  9085. //The remaining 10% are used to display the missingValue line.
  9086. y[d] = d3.scale.linear()
  9087. .domain([min, max])
  9088. .range([(availableHeight - 12) * 0.9, 0]);
  9089. axisWithUndefinedValues = [];
  9090. y[d].brush = d3.svg.brush().y(y[d]).on('brushstart', brushstart).on('brush', brush).on('brushend', brushend);
  9091. });
  9092. // Setup containers and skeleton of chart
  9093. var wrap = container.selectAll('g.nv-wrap.nv-parallelCoordinates').data([data]);
  9094. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-parallelCoordinates');
  9095. var gEnter = wrapEnter.append('g');
  9096. var g = wrap.select('g');
  9097. gEnter.append('g').attr('class', 'nv-parallelCoordinates background');
  9098. gEnter.append('g').attr('class', 'nv-parallelCoordinates foreground');
  9099. gEnter.append('g').attr('class', 'nv-parallelCoordinates missingValuesline');
  9100. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  9101. line.interpolate('cardinal').tension(lineTension);
  9102. axis.orient('left');
  9103. var axisDrag = d3.behavior.drag()
  9104. .on('dragstart', dragStart)
  9105. .on('drag', dragMove)
  9106. .on('dragend', dragEnd);
  9107. //Add missing value line at the bottom of the chart
  9108. var missingValuesline, missingValueslineText;
  9109. var step = x.range()[1] - x.range()[0];
  9110. step = isNaN(step) ? x.range()[0] : step;
  9111. if (!isNaN(step)) {
  9112. var lineData = [0 + step / 2, availableHeight - 12, availableWidth - step / 2, availableHeight - 12];
  9113. missingValuesline = wrap.select('.missingValuesline').selectAll('line').data([lineData]);
  9114. missingValuesline.enter().append('line');
  9115. missingValuesline.exit().remove();
  9116. missingValuesline.attr("x1", function(d) { return d[0]; })
  9117. .attr("y1", function(d) { return d[1]; })
  9118. .attr("x2", function(d) { return d[2]; })
  9119. .attr("y2", function(d) { return d[3]; });
  9120. //Add the text "undefined values" under the missing value line
  9121. missingValueslineText = wrap.select('.missingValuesline').selectAll('text').data([undefinedValuesLabel]);
  9122. missingValueslineText.append('text').data([undefinedValuesLabel]);
  9123. missingValueslineText.enter().append('text');
  9124. missingValueslineText.exit().remove();
  9125. missingValueslineText.attr("y", availableHeight)
  9126. //To have the text right align with the missingValues line, substract 92 representing the text size.
  9127. .attr("x", availableWidth - 92 - step / 2)
  9128. .text(function(d) { return d; });
  9129. }
  9130. // Add grey background lines for context.
  9131. background = wrap.select('.background').selectAll('path').data(data);
  9132. background.enter().append('path');
  9133. background.exit().remove();
  9134. background.attr('d', path);
  9135. // Add blue foreground lines for focus.
  9136. foreground = wrap.select('.foreground').selectAll('path').data(data);
  9137. foreground.enter().append('path')
  9138. foreground.exit().remove();
  9139. foreground.attr('d', path)
  9140. .style("stroke-width", function (d, i) {
  9141. if (isNaN(d.strokeWidth)) { d.strokeWidth = 1;} return d.strokeWidth;})
  9142. .attr('stroke', function (d, i) { return d.color || color(d, i); });
  9143. foreground.on("mouseover", function (d, i) {
  9144. d3.select(this).classed('hover', true).style("stroke-width", d.strokeWidth + 2 + "px").style("stroke-opacity", 1);
  9145. dispatch.elementMouseover({
  9146. label: d.name,
  9147. color: d.color || color(d, i),
  9148. values: d.values,
  9149. dimensions: enabledDimensions
  9150. });
  9151. });
  9152. foreground.on("mouseout", function (d, i) {
  9153. d3.select(this).classed('hover', false).style("stroke-width", d.strokeWidth + "px").style("stroke-opacity", 0.7);
  9154. dispatch.elementMouseout({
  9155. label: d.name,
  9156. index: i
  9157. });
  9158. });
  9159. foreground.on('mousemove', function (d, i) {
  9160. dispatch.elementMousemove();
  9161. });
  9162. foreground.on('click', function (d) {
  9163. dispatch.elementClick({
  9164. id: d.id
  9165. });
  9166. });
  9167. // Add a group element for each dimension.
  9168. dimensions = g.selectAll('.dimension').data(enabledDimensions);
  9169. var dimensionsEnter = dimensions.enter().append('g').attr('class', 'nv-parallelCoordinates dimension');
  9170. dimensions.attr('transform', function(d) { return 'translate(' + x(d.key) + ',0)'; });
  9171. dimensionsEnter.append('g').attr('class', 'nv-axis');
  9172. // Add an axis and title.
  9173. dimensionsEnter.append('text')
  9174. .attr('class', 'nv-label')
  9175. .style("cursor", "move")
  9176. .attr('dy', '-1em')
  9177. .attr('text-anchor', 'middle')
  9178. .on("mouseover", function(d, i) {
  9179. dispatch.elementMouseover({
  9180. label: d.tooltip || d.key,
  9181. color: d.color
  9182. });
  9183. })
  9184. .on("mouseout", function(d, i) {
  9185. dispatch.elementMouseout({
  9186. label: d.tooltip
  9187. });
  9188. })
  9189. .on('mousemove', function (d, i) {
  9190. dispatch.elementMousemove();
  9191. })
  9192. .call(axisDrag);
  9193. dimensionsEnter.append('g').attr('class', 'nv-brushBackground');
  9194. dimensions.exit().remove();
  9195. dimensions.select('.nv-label').text(function (d) { return d.key });
  9196. // Add and store a brush for each axis.
  9197. restoreBrush(displayBrush);
  9198. var actives = dimensionNames.filter(function (p) { return !y[p].brush.empty(); }),
  9199. extents = actives.map(function (p) { return y[p].brush.extent(); });
  9200. var formerActive = active.slice(0);
  9201. //Restore active values
  9202. active = [];
  9203. foreground.style("display", function (d) {
  9204. var isActive = actives.every(function (p, i) {
  9205. if ((isNaN(d.values[p]) || isNaN(parseFloat(d.values[p]))) && extents[i][0] == y[p].brush.y().domain()[0]) {
  9206. return true;
  9207. }
  9208. return (extents[i][0] <= d.values[p] && d.values[p] <= extents[i][1]) && !isNaN(parseFloat(d.values[p]));
  9209. });
  9210. if (isActive)
  9211. active.push(d);
  9212. return !isActive ? "none" : null;
  9213. });
  9214. if (filters.length > 0 || !nv.utils.arrayEquals(active, formerActive)) {
  9215. dispatch.activeChanged(active);
  9216. }
  9217. // Returns the path for a given data point.
  9218. function path(d) {
  9219. return line(enabledDimensions.map(function (p) {
  9220. //If value if missing, put the value on the missing value line
  9221. if (isNaN(d.values[p.key]) || isNaN(parseFloat(d.values[p.key])) || displayMissingValuesline) {
  9222. var domain = y[p.key].domain();
  9223. var range = y[p.key].range();
  9224. var min = domain[0] - (domain[1] - domain[0]) / 9;
  9225. //If it's not already the case, allow brush to select undefined values
  9226. if (axisWithUndefinedValues.indexOf(p.key) < 0) {
  9227. var newscale = d3.scale.linear().domain([min, domain[1]]).range([availableHeight - 12, range[1]]);
  9228. y[p.key].brush.y(newscale);
  9229. axisWithUndefinedValues.push(p.key);
  9230. }
  9231. if (isNaN(d.values[p.key]) || isNaN(parseFloat(d.values[p.key]))) {
  9232. return [x(p.key), y[p.key](min)];
  9233. }
  9234. }
  9235. //If parallelCoordinate contain missing values show the missing values line otherwise, hide it.
  9236. if (missingValuesline !== undefined) {
  9237. if (axisWithUndefinedValues.length > 0 || displayMissingValuesline) {
  9238. missingValuesline.style("display", "inline");
  9239. missingValueslineText.style("display", "inline");
  9240. } else {
  9241. missingValuesline.style("display", "none");
  9242. missingValueslineText.style("display", "none");
  9243. }
  9244. }
  9245. return [x(p.key), y[p.key](d.values[p.key])];
  9246. }));
  9247. }
  9248. function restoreBrush(visible) {
  9249. filters.forEach(function (f) {
  9250. //If filter brushed NaN values, keep the brush on the bottom of the axis.
  9251. var brushDomain = y[f.dimension].brush.y().domain();
  9252. if (f.hasOnlyNaN) {
  9253. f.extent[1] = (y[f.dimension].domain()[1] - brushDomain[0]) * (f.extent[1] - f.extent[0]) / (oldDomainMaxValue[f.dimension] - f.extent[0]) + brushDomain[0];
  9254. }
  9255. if (f.hasNaN) {
  9256. f.extent[0] = brushDomain[0];
  9257. }
  9258. if (visible)
  9259. y[f.dimension].brush.extent(f.extent);
  9260. });
  9261. dimensions.select('.nv-brushBackground')
  9262. .each(function (d) {
  9263. d3.select(this).call(y[d.key].brush);
  9264. })
  9265. .selectAll('rect')
  9266. .attr('x', -8)
  9267. .attr('width', 16);
  9268. updateTicks();
  9269. }
  9270. // Handles a brush event, toggling the display of foreground lines.
  9271. function brushstart() {
  9272. //If brush aren't visible, show it before brushing again.
  9273. if (displayBrush === false) {
  9274. displayBrush = true;
  9275. restoreBrush(true);
  9276. }
  9277. }
  9278. // Handles a brush event, toggling the display of foreground lines.
  9279. function brush() {
  9280. actives = dimensionNames.filter(function (p) { return !y[p].brush.empty(); });
  9281. extents = actives.map(function(p) { return y[p].brush.extent(); });
  9282. filters = []; //erase current filters
  9283. actives.forEach(function(d,i) {
  9284. filters[i] = {
  9285. dimension: d,
  9286. extent: extents[i],
  9287. hasNaN: false,
  9288. hasOnlyNaN: false
  9289. }
  9290. });
  9291. active = []; //erase current active list
  9292. foreground.style('display', function(d) {
  9293. var isActive = actives.every(function(p, i) {
  9294. if ((isNaN(d.values[p]) || isNaN(parseFloat(d.values[p]))) && extents[i][0] == y[p].brush.y().domain()[0]) return true;
  9295. return (extents[i][0] <= d.values[p] && d.values[p] <= extents[i][1]) && !isNaN(parseFloat(d.values[p]));
  9296. });
  9297. if (isActive) active.push(d);
  9298. return isActive ? null : 'none';
  9299. });
  9300. updateTicks();
  9301. dispatch.brush({
  9302. filters: filters,
  9303. active: active
  9304. });
  9305. }
  9306. function brushend() {
  9307. var hasActiveBrush = actives.length > 0 ? true : false;
  9308. filters.forEach(function (f) {
  9309. if (f.extent[0] === y[f.dimension].brush.y().domain()[0] && axisWithUndefinedValues.indexOf(f.dimension) >= 0)
  9310. f.hasNaN = true;
  9311. if (f.extent[1] < y[f.dimension].domain()[0])
  9312. f.hasOnlyNaN = true;
  9313. });
  9314. dispatch.brushEnd(active, hasActiveBrush);
  9315. }
  9316. function updateTicks() {
  9317. dimensions.select('.nv-axis')
  9318. .each(function (d, i) {
  9319. var f = filters.filter(function (k) { return k.dimension == d.key; });
  9320. currentTicks[d.key] = y[d.key].domain();
  9321. //If brush are available, display brush extent
  9322. if (f.length != 0 && displayBrush)
  9323. {
  9324. currentTicks[d.key] = [];
  9325. if (f[0].extent[1] > y[d.key].domain()[0])
  9326. currentTicks[d.key] = [f[0].extent[1]];
  9327. if (f[0].extent[0] >= y[d.key].domain()[0])
  9328. currentTicks[d.key].push(f[0].extent[0]);
  9329. }
  9330. d3.select(this).call(axis.scale(y[d.key]).tickFormat(d.format).tickValues(currentTicks[d.key]));
  9331. });
  9332. }
  9333. function dragStart(d) {
  9334. dragging[d.key] = this.parentNode.__origin__ = x(d.key);
  9335. background.attr("visibility", "hidden");
  9336. }
  9337. function dragMove(d) {
  9338. dragging[d.key] = Math.min(availableWidth, Math.max(0, this.parentNode.__origin__ += d3.event.x));
  9339. foreground.attr("d", path);
  9340. enabledDimensions.sort(function (a, b) { return dimensionPosition(a.key) - dimensionPosition(b.key); });
  9341. enabledDimensions.forEach(function (d, i) { return d.currentPosition = i; });
  9342. x.domain(enabledDimensions.map(function (d) { return d.key; }));
  9343. dimensions.attr("transform", function(d) { return "translate(" + dimensionPosition(d.key) + ")"; });
  9344. }
  9345. function dragEnd(d, i) {
  9346. delete this.parentNode.__origin__;
  9347. delete dragging[d.key];
  9348. d3.select(this.parentNode).attr("transform", "translate(" + x(d.key) + ")");
  9349. foreground
  9350. .attr("d", path);
  9351. background
  9352. .attr("d", path)
  9353. .attr("visibility", null);
  9354. dispatch.dimensionsOrder(enabledDimensions);
  9355. }
  9356. function dimensionPosition(d) {
  9357. var v = dragging[d];
  9358. return v == null ? x(d) : v;
  9359. }
  9360. });
  9361. return chart;
  9362. }
  9363. //============================================================
  9364. // Expose Public Variables
  9365. //------------------------------------------------------------
  9366. chart.dispatch = dispatch;
  9367. chart.options = nv.utils.optionsFunc.bind(chart);
  9368. chart._options = Object.create({}, {
  9369. // simple options, just get/set the necessary values
  9370. width: {get: function(){return width;}, set: function(_){width= _;}},
  9371. height: {get: function(){return height;}, set: function(_){height= _;}},
  9372. dimensionData: { get: function () { return dimensionData; }, set: function (_) { dimensionData = _; } },
  9373. displayBrush: { get: function () { return displayBrush; }, set: function (_) { displayBrush = _; } },
  9374. filters: { get: function () { return filters; }, set: function (_) { filters = _; } },
  9375. active: { get: function () { return active; }, set: function (_) { active = _; } },
  9376. lineTension: {get: function(){return lineTension;}, set: function(_){lineTension = _;}},
  9377. undefinedValuesLabel : {get: function(){return undefinedValuesLabel;}, set: function(_){undefinedValuesLabel=_;}},
  9378. // deprecated options
  9379. dimensions: {get: function () { return dimensionData.map(function (d){return d.key}); }, set: function (_) {
  9380. // deprecated after 1.8.1
  9381. nv.deprecated('dimensions', 'use dimensionData instead');
  9382. if (dimensionData.length === 0) {
  9383. _.forEach(function (k) { dimensionData.push({ key: k }) })
  9384. } else {
  9385. _.forEach(function (k, i) { dimensionData[i].key= k })
  9386. }
  9387. }},
  9388. dimensionNames: {get: function () { return dimensionData.map(function (d){return d.key}); }, set: function (_) {
  9389. // deprecated after 1.8.1
  9390. nv.deprecated('dimensionNames', 'use dimensionData instead');
  9391. dimensionNames = [];
  9392. if (dimensionData.length === 0) {
  9393. _.forEach(function (k) { dimensionData.push({ key: k }) })
  9394. } else {
  9395. _.forEach(function (k, i) { dimensionData[i].key = k })
  9396. }
  9397. }},
  9398. dimensionFormats: {get: function () { return dimensionData.map(function (d) { return d.format }); }, set: function (_) {
  9399. // deprecated after 1.8.1
  9400. nv.deprecated('dimensionFormats', 'use dimensionData instead');
  9401. if (dimensionData.length === 0) {
  9402. _.forEach(function (f) { dimensionData.push({ format: f }) })
  9403. } else {
  9404. _.forEach(function (f, i) { dimensionData[i].format = f })
  9405. }
  9406. }},
  9407. // options that require extra logic in the setter
  9408. margin: {get: function(){return margin;}, set: function(_){
  9409. margin.top = _.top !== undefined ? _.top : margin.top;
  9410. margin.right = _.right !== undefined ? _.right : margin.right;
  9411. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  9412. margin.left = _.left !== undefined ? _.left : margin.left;
  9413. }},
  9414. color: {get: function(){return color;}, set: function(_){
  9415. color = nv.utils.getColor(_);
  9416. }}
  9417. });
  9418. nv.utils.initOptions(chart);
  9419. return chart;
  9420. };
  9421. nv.models.parallelCoordinatesChart = function () {
  9422. "use strict";
  9423. //============================================================
  9424. // Public Variables with Default Settings
  9425. //------------------------------------------------------------
  9426. var parallelCoordinates = nv.models.parallelCoordinates()
  9427. var legend = nv.models.legend()
  9428. var tooltip = nv.models.tooltip();
  9429. var dimensionTooltip = nv.models.tooltip();
  9430. var margin = { top: 0, right: 0, bottom: 0, left: 0 }
  9431. , marginTop = null
  9432. , width = null
  9433. , height = null
  9434. , showLegend = true
  9435. , color = nv.utils.defaultColor()
  9436. , state = nv.utils.state()
  9437. , dimensionData = []
  9438. , displayBrush = true
  9439. , defaultState = null
  9440. , noData = null
  9441. , nanValue = "undefined"
  9442. , dispatch = d3.dispatch('dimensionsOrder', 'brushEnd', 'stateChange', 'changeState', 'renderEnd')
  9443. , controlWidth = function () { return showControls ? 180 : 0 }
  9444. ;
  9445. //============================================================
  9446. //============================================================
  9447. // Private Variables
  9448. //------------------------------------------------------------
  9449. var renderWatch = nv.utils.renderWatch(dispatch);
  9450. var stateGetter = function(data) {
  9451. return function() {
  9452. return {
  9453. active: data.map(function(d) { return !d.disabled })
  9454. };
  9455. }
  9456. };
  9457. var stateSetter = function(data) {
  9458. return function(state) {
  9459. if(state.active !== undefined) {
  9460. data.forEach(function(series, i) {
  9461. series.disabled = !state.active[i];
  9462. });
  9463. }
  9464. }
  9465. };
  9466. tooltip.contentGenerator(function(data) {
  9467. 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>';
  9468. if(data.series.length !== 0)
  9469. {
  9470. str = str + '<tbody><tr><td height ="10px"></td></tr>';
  9471. data.series.forEach(function(d){
  9472. 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>';
  9473. });
  9474. str = str + '</tbody>';
  9475. }
  9476. str = str + '</table>';
  9477. return str;
  9478. });
  9479. //============================================================
  9480. // Chart function
  9481. //------------------------------------------------------------
  9482. function chart(selection) {
  9483. renderWatch.reset();
  9484. renderWatch.models(parallelCoordinates);
  9485. selection.each(function(data) {
  9486. var container = d3.select(this);
  9487. nv.utils.initSVG(container);
  9488. var that = this;
  9489. var availableWidth = nv.utils.availableWidth(width, container, margin),
  9490. availableHeight = nv.utils.availableHeight(height, container, margin);
  9491. chart.update = function() { container.call(chart); };
  9492. chart.container = this;
  9493. state.setter(stateSetter(dimensionData), chart.update)
  9494. .getter(stateGetter(dimensionData))
  9495. .update();
  9496. //set state.disabled
  9497. state.disabled = dimensionData.map(function (d) { return !!d.disabled });
  9498. //Keep dimensions position in memory
  9499. dimensionData = dimensionData.map(function (d) {d.disabled = !!d.disabled; return d});
  9500. dimensionData.forEach(function (d, i) {
  9501. d.originalPosition = isNaN(d.originalPosition) ? i : d.originalPosition;
  9502. d.currentPosition = isNaN(d.currentPosition) ? i : d.currentPosition;
  9503. });
  9504. if (!defaultState) {
  9505. var key;
  9506. defaultState = {};
  9507. for(key in state) {
  9508. if(state[key] instanceof Array)
  9509. defaultState[key] = state[key].slice(0);
  9510. else
  9511. defaultState[key] = state[key];
  9512. }
  9513. }
  9514. // Display No Data message if there's nothing to show.
  9515. if(!data || !data.length) {
  9516. nv.utils.noData(chart, container);
  9517. return chart;
  9518. } else {
  9519. container.selectAll('.nv-noData').remove();
  9520. }
  9521. //------------------------------------------------------------
  9522. // Setup containers and skeleton of chart
  9523. var wrap = container.selectAll('g.nv-wrap.nv-parallelCoordinatesChart').data([data]);
  9524. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-parallelCoordinatesChart').append('g');
  9525. var g = wrap.select('g');
  9526. gEnter.append('g').attr('class', 'nv-parallelCoordinatesWrap');
  9527. gEnter.append('g').attr('class', 'nv-legendWrap');
  9528. g.select("rect")
  9529. .attr("width", availableWidth)
  9530. .attr("height", (availableHeight > 0) ? availableHeight : 0);
  9531. // Legend
  9532. if (!showLegend) {
  9533. g.select('.nv-legendWrap').selectAll('*').remove();
  9534. } else {
  9535. legend.width(availableWidth)
  9536. .color(function (d) { return "rgb(188,190,192)"; });
  9537. g.select('.nv-legendWrap')
  9538. .datum(dimensionData.sort(function (a, b) { return a.originalPosition - b.originalPosition; }))
  9539. .call(legend);
  9540. if (!marginTop && legend.height() !== margin.top) {
  9541. margin.top = legend.height();
  9542. availableHeight = nv.utils.availableHeight(height, container, margin);
  9543. }
  9544. wrap.select('.nv-legendWrap')
  9545. .attr('transform', 'translate( 0 ,' + (-margin.top) + ')');
  9546. }
  9547. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  9548. // Main Chart Component(s)
  9549. parallelCoordinates
  9550. .width(availableWidth)
  9551. .height(availableHeight)
  9552. .dimensionData(dimensionData)
  9553. .displayBrush(displayBrush);
  9554. var parallelCoordinatesWrap = g.select('.nv-parallelCoordinatesWrap ')
  9555. .datum(data);
  9556. parallelCoordinatesWrap.transition().call(parallelCoordinates);
  9557. //============================================================
  9558. // Event Handling/Dispatching (in chart's scope)
  9559. //------------------------------------------------------------
  9560. //Display reset brush button
  9561. parallelCoordinates.dispatch.on('brushEnd', function (active, hasActiveBrush) {
  9562. if (hasActiveBrush) {
  9563. displayBrush = true;
  9564. dispatch.brushEnd(active);
  9565. } else {
  9566. displayBrush = false;
  9567. }
  9568. });
  9569. legend.dispatch.on('stateChange', function(newState) {
  9570. for(var key in newState) {
  9571. state[key] = newState[key];
  9572. }
  9573. dispatch.stateChange(state);
  9574. chart.update();
  9575. });
  9576. //Update dimensions order and display reset sorting button
  9577. parallelCoordinates.dispatch.on('dimensionsOrder', function (e) {
  9578. dimensionData.sort(function (a, b) { return a.currentPosition - b.currentPosition; });
  9579. var isSorted = false;
  9580. dimensionData.forEach(function (d, i) {
  9581. d.currentPosition = i;
  9582. if (d.currentPosition !== d.originalPosition)
  9583. isSorted = true;
  9584. });
  9585. dispatch.dimensionsOrder(dimensionData, isSorted);
  9586. });
  9587. // Update chart from a state object passed to event handler
  9588. dispatch.on('changeState', function (e) {
  9589. if (typeof e.disabled !== 'undefined') {
  9590. dimensionData.forEach(function (series, i) {
  9591. series.disabled = e.disabled[i];
  9592. });
  9593. state.disabled = e.disabled;
  9594. }
  9595. chart.update();
  9596. });
  9597. });
  9598. renderWatch.renderEnd('parraleleCoordinateChart immediate');
  9599. return chart;
  9600. }
  9601. //============================================================
  9602. // Event Handling/Dispatching (out of chart's scope)
  9603. //------------------------------------------------------------
  9604. parallelCoordinates.dispatch.on('elementMouseover.tooltip', function (evt) {
  9605. var tp = {
  9606. key: evt.label,
  9607. color: evt.color,
  9608. series: []
  9609. }
  9610. if(evt.values){
  9611. Object.keys(evt.values).forEach(function (d) {
  9612. var dim = evt.dimensions.filter(function (dd) {return dd.key === d;})[0];
  9613. if(dim){
  9614. var v;
  9615. if (isNaN(evt.values[d]) || isNaN(parseFloat(evt.values[d]))) {
  9616. v = nanValue;
  9617. } else {
  9618. v = dim.format(evt.values[d]);
  9619. }
  9620. tp.series.push({ idx: dim.currentPosition, key: d, value: v, color: dim.color });
  9621. }
  9622. });
  9623. tp.series.sort(function(a,b) {return a.idx - b.idx});
  9624. }
  9625. tooltip.data(tp).hidden(false);
  9626. });
  9627. parallelCoordinates.dispatch.on('elementMouseout.tooltip', function(evt) {
  9628. tooltip.hidden(true)
  9629. });
  9630. parallelCoordinates.dispatch.on('elementMousemove.tooltip', function () {
  9631. tooltip();
  9632. });
  9633. //============================================================
  9634. // Expose Public Variables
  9635. //------------------------------------------------------------
  9636. // expose chart's sub-components
  9637. chart.dispatch = dispatch;
  9638. chart.parallelCoordinates = parallelCoordinates;
  9639. chart.legend = legend;
  9640. chart.tooltip = tooltip;
  9641. chart.options = nv.utils.optionsFunc.bind(chart);
  9642. chart._options = Object.create({}, {
  9643. // simple options, just get/set the necessary values
  9644. width: { get: function () { return width; }, set: function (_) { width = _; } },
  9645. height: { get: function () { return height; }, set: function (_) { height = _; } },
  9646. showLegend: { get: function () { return showLegend; }, set: function (_) { showLegend = _; } },
  9647. defaultState: { get: function () { return defaultState; }, set: function (_) { defaultState = _; } },
  9648. dimensionData: { get: function () { return dimensionData; }, set: function (_) { dimensionData = _; } },
  9649. displayBrush: { get: function () { return displayBrush; }, set: function (_) { displayBrush = _; } },
  9650. noData: { get: function () { return noData; }, set: function (_) { noData = _; } },
  9651. nanValue: { get: function () { return nanValue; }, set: function (_) { nanValue = _; } },
  9652. // options that require extra logic in the setter
  9653. margin: {
  9654. get: function () { return margin; },
  9655. set: function (_) {
  9656. if (_.top !== undefined) {
  9657. margin.top = _.top;
  9658. marginTop = _.top;
  9659. }
  9660. margin.right = _.right !== undefined ? _.right : margin.right;
  9661. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  9662. margin.left = _.left !== undefined ? _.left : margin.left;
  9663. }
  9664. },
  9665. color: {get: function(){return color;}, set: function(_){
  9666. color = nv.utils.getColor(_);
  9667. legend.color(color);
  9668. parallelCoordinates.color(color);
  9669. }}
  9670. });
  9671. nv.utils.inheritOptions(chart, parallelCoordinates);
  9672. nv.utils.initOptions(chart);
  9673. return chart;
  9674. };
  9675. nv.models.pie = function() {
  9676. "use strict";
  9677. //============================================================
  9678. // Public Variables with Default Settings
  9679. //------------------------------------------------------------
  9680. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  9681. , width = 500
  9682. , height = 500
  9683. , getX = function(d) { return d.x }
  9684. , getY = function(d) { return d.y }
  9685. , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
  9686. , container = null
  9687. , color = nv.utils.defaultColor()
  9688. , valueFormat = d3.format(',.2f')
  9689. , showLabels = true
  9690. , labelsOutside = false
  9691. , labelType = "key"
  9692. , labelThreshold = .02 //if slice percentage is under this, don't show label
  9693. , donut = false
  9694. , title = false
  9695. , growOnHover = true
  9696. , titleOffset = 0
  9697. , labelSunbeamLayout = false
  9698. , startAngle = false
  9699. , padAngle = false
  9700. , endAngle = false
  9701. , cornerRadius = 0
  9702. , donutRatio = 0.5
  9703. , duration = 250
  9704. , arcsRadius = []
  9705. , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
  9706. ;
  9707. var arcs = [];
  9708. var arcsOver = [];
  9709. //============================================================
  9710. // chart function
  9711. //------------------------------------------------------------
  9712. var renderWatch = nv.utils.renderWatch(dispatch);
  9713. function chart(selection) {
  9714. renderWatch.reset();
  9715. selection.each(function(data) {
  9716. var availableWidth = width - margin.left - margin.right
  9717. , availableHeight = height - margin.top - margin.bottom
  9718. , radius = Math.min(availableWidth, availableHeight) / 2
  9719. , arcsRadiusOuter = []
  9720. , arcsRadiusInner = []
  9721. ;
  9722. container = d3.select(this)
  9723. if (arcsRadius.length === 0) {
  9724. var outer = radius - radius / 5;
  9725. var inner = donutRatio * radius;
  9726. for (var i = 0; i < data[0].length; i++) {
  9727. arcsRadiusOuter.push(outer);
  9728. arcsRadiusInner.push(inner);
  9729. }
  9730. } else {
  9731. if(growOnHover){
  9732. arcsRadiusOuter = arcsRadius.map(function (d) { return (d.outer - d.outer / 5) * radius; });
  9733. arcsRadiusInner = arcsRadius.map(function (d) { return (d.inner - d.inner / 5) * radius; });
  9734. donutRatio = d3.min(arcsRadius.map(function (d) { return (d.inner - d.inner / 5); }));
  9735. } else {
  9736. arcsRadiusOuter = arcsRadius.map(function (d) { return d.outer * radius; });
  9737. arcsRadiusInner = arcsRadius.map(function (d) { return d.inner * radius; });
  9738. donutRatio = d3.min(arcsRadius.map(function (d) { return d.inner; }));
  9739. }
  9740. }
  9741. nv.utils.initSVG(container);
  9742. // Setup containers and skeleton of chart
  9743. var wrap = container.selectAll('.nv-wrap.nv-pie').data(data);
  9744. var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id);
  9745. var gEnter = wrapEnter.append('g');
  9746. var g = wrap.select('g');
  9747. var g_pie = gEnter.append('g').attr('class', 'nv-pie');
  9748. gEnter.append('g').attr('class', 'nv-pieLabels');
  9749. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  9750. g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
  9751. g.select('.nv-pieLabels').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
  9752. //
  9753. container.on('click', function(d,i) {
  9754. dispatch.chartClick({
  9755. data: d,
  9756. index: i,
  9757. pos: d3.event,
  9758. id: id
  9759. });
  9760. });
  9761. arcs = [];
  9762. arcsOver = [];
  9763. for (var i = 0; i < data[0].length; i++) {
  9764. var arc = d3.svg.arc().outerRadius(arcsRadiusOuter[i]);
  9765. var arcOver = d3.svg.arc().outerRadius(arcsRadiusOuter[i] + 5);
  9766. if (startAngle !== false) {
  9767. arc.startAngle(startAngle);
  9768. arcOver.startAngle(startAngle);
  9769. }
  9770. if (endAngle !== false) {
  9771. arc.endAngle(endAngle);
  9772. arcOver.endAngle(endAngle);
  9773. }
  9774. if (donut) {
  9775. arc.innerRadius(arcsRadiusInner[i]);
  9776. arcOver.innerRadius(arcsRadiusInner[i]);
  9777. }
  9778. if (arc.cornerRadius && cornerRadius) {
  9779. arc.cornerRadius(cornerRadius);
  9780. arcOver.cornerRadius(cornerRadius);
  9781. }
  9782. arcs.push(arc);
  9783. arcsOver.push(arcOver);
  9784. }
  9785. // Setup the Pie chart and choose the data element
  9786. var pie = d3.layout.pie()
  9787. .sort(null)
  9788. .value(function(d) { return d.disabled ? 0 : getY(d) });
  9789. // padAngle added in d3 3.5
  9790. if (pie.padAngle && padAngle) {
  9791. pie.padAngle(padAngle);
  9792. }
  9793. // if title is specified and donut, put it in the middle
  9794. if (donut && title) {
  9795. g_pie.append("text").attr('class', 'nv-pie-title');
  9796. wrap.select('.nv-pie-title')
  9797. .style("text-anchor", "middle")
  9798. .text(function (d) {
  9799. return title;
  9800. })
  9801. .style("font-size", (Math.min(availableWidth, availableHeight)) * donutRatio * 2 / (title.length + 2) + "px")
  9802. .attr("dy", "0.35em") // trick to vertically center text
  9803. .attr('transform', function(d, i) {
  9804. return 'translate(0, '+ titleOffset + ')';
  9805. });
  9806. }
  9807. var slices = wrap.select('.nv-pie').selectAll('.nv-slice').data(pie);
  9808. var pieLabels = wrap.select('.nv-pieLabels').selectAll('.nv-label').data(pie);
  9809. slices.exit().remove();
  9810. pieLabels.exit().remove();
  9811. var ae = slices.enter().append('g');
  9812. ae.attr('class', 'nv-slice');
  9813. ae.on('mouseover', function(d, i) {
  9814. d3.select(this).classed('hover', true);
  9815. if (growOnHover) {
  9816. d3.select(this).select("path").transition()
  9817. .duration(70)
  9818. .attr("d", arcsOver[i]);
  9819. }
  9820. dispatch.elementMouseover({
  9821. data: d.data,
  9822. index: i,
  9823. color: d3.select(this).style("fill"),
  9824. percent: (d.endAngle - d.startAngle) / (2 * Math.PI)
  9825. });
  9826. });
  9827. ae.on('mouseout', function(d, i) {
  9828. d3.select(this).classed('hover', false);
  9829. if (growOnHover) {
  9830. d3.select(this).select("path").transition()
  9831. .duration(50)
  9832. .attr("d", arcs[i]);
  9833. }
  9834. dispatch.elementMouseout({data: d.data, index: i});
  9835. });
  9836. ae.on('mousemove', function(d, i) {
  9837. dispatch.elementMousemove({data: d.data, index: i});
  9838. });
  9839. ae.on('click', function(d, i) {
  9840. var element = this;
  9841. dispatch.elementClick({
  9842. data: d.data,
  9843. index: i,
  9844. color: d3.select(this).style("fill"),
  9845. event: d3.event,
  9846. element: element
  9847. });
  9848. });
  9849. ae.on('dblclick', function(d, i) {
  9850. dispatch.elementDblClick({
  9851. data: d.data,
  9852. index: i,
  9853. color: d3.select(this).style("fill")
  9854. });
  9855. });
  9856. slices.attr('fill', function(d,i) { return color(d.data, i); });
  9857. slices.attr('stroke', function(d,i) { return color(d.data, i); });
  9858. var paths = ae.append('path').each(function(d) {
  9859. this._current = d;
  9860. });
  9861. slices.select('path')
  9862. .transition()
  9863. .duration(duration)
  9864. .attr('d', function (d, i) { return arcs[i](d); })
  9865. .attrTween('d', arcTween);
  9866. if (showLabels) {
  9867. // This does the normal label
  9868. var labelsArc = [];
  9869. for (var i = 0; i < data[0].length; i++) {
  9870. labelsArc.push(arcs[i]);
  9871. if (labelsOutside) {
  9872. if (donut) {
  9873. labelsArc[i] = d3.svg.arc().outerRadius(arcs[i].outerRadius());
  9874. if (startAngle !== false) labelsArc[i].startAngle(startAngle);
  9875. if (endAngle !== false) labelsArc[i].endAngle(endAngle);
  9876. }
  9877. } else if (!donut) {
  9878. labelsArc[i].innerRadius(0);
  9879. }
  9880. }
  9881. pieLabels.enter().append("g").classed("nv-label",true).each(function(d,i) {
  9882. var group = d3.select(this);
  9883. group.attr('transform', function (d, i) {
  9884. if (labelSunbeamLayout) {
  9885. d.outerRadius = arcsRadiusOuter[i] + 10; // Set Outer Coordinate
  9886. d.innerRadius = arcsRadiusOuter[i] + 15; // Set Inner Coordinate
  9887. var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
  9888. if ((d.startAngle + d.endAngle) / 2 < Math.PI) {
  9889. rotateAngle -= 90;
  9890. } else {
  9891. rotateAngle += 90;
  9892. }
  9893. return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')';
  9894. } else {
  9895. d.outerRadius = radius + 10; // Set Outer Coordinate
  9896. d.innerRadius = radius + 15; // Set Inner Coordinate
  9897. return 'translate(' + labelsArc[i].centroid(d) + ')'
  9898. }
  9899. });
  9900. group.append('rect')
  9901. .style('stroke', '#fff')
  9902. .style('fill', '#fff')
  9903. .attr("rx", 3)
  9904. .attr("ry", 3);
  9905. group.append('text')
  9906. .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
  9907. .style('fill', '#000')
  9908. });
  9909. var labelLocationHash = {};
  9910. var avgHeight = 14;
  9911. var avgWidth = 140;
  9912. var createHashKey = function(coordinates) {
  9913. return Math.floor(coordinates[0]/avgWidth) * avgWidth + ',' + Math.floor(coordinates[1]/avgHeight) * avgHeight;
  9914. };
  9915. var getSlicePercentage = function(d) {
  9916. return (d.endAngle - d.startAngle) / (2 * Math.PI);
  9917. };
  9918. pieLabels.watchTransition(renderWatch, 'pie labels').attr('transform', function (d, i) {
  9919. if (labelSunbeamLayout) {
  9920. d.outerRadius = arcsRadiusOuter[i] + 10; // Set Outer Coordinate
  9921. d.innerRadius = arcsRadiusOuter[i] + 15; // Set Inner Coordinate
  9922. var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
  9923. if ((d.startAngle + d.endAngle) / 2 < Math.PI) {
  9924. rotateAngle -= 90;
  9925. } else {
  9926. rotateAngle += 90;
  9927. }
  9928. return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')';
  9929. } else {
  9930. d.outerRadius = radius + 10; // Set Outer Coordinate
  9931. d.innerRadius = radius + 15; // Set Inner Coordinate
  9932. /*
  9933. Overlapping pie labels are not good. What this attempts to do is, prevent overlapping.
  9934. Each label location is hashed, and if a hash collision occurs, we assume an overlap.
  9935. Adjust the label's y-position to remove the overlap.
  9936. */
  9937. var center = labelsArc[i].centroid(d);
  9938. var percent = getSlicePercentage(d);
  9939. if (d.value && percent >= labelThreshold) {
  9940. var hashKey = createHashKey(center);
  9941. if (labelLocationHash[hashKey]) {
  9942. center[1] -= avgHeight;
  9943. }
  9944. labelLocationHash[createHashKey(center)] = true;
  9945. }
  9946. return 'translate(' + center + ')'
  9947. }
  9948. });
  9949. pieLabels.select(".nv-label text")
  9950. .style('text-anchor', function(d,i) {
  9951. //center the text on it's origin or begin/end if orthogonal aligned
  9952. return labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle';
  9953. })
  9954. .text(function(d, i) {
  9955. var percent = getSlicePercentage(d);
  9956. var label = '';
  9957. if (!d.value || percent < labelThreshold) return '';
  9958. if(typeof labelType === 'function') {
  9959. label = labelType(d, i, {
  9960. 'key': getX(d.data),
  9961. 'value': getY(d.data),
  9962. 'percent': valueFormat(percent)
  9963. });
  9964. } else {
  9965. switch (labelType) {
  9966. case 'key':
  9967. label = getX(d.data);
  9968. break;
  9969. case 'value':
  9970. label = valueFormat(getY(d.data));
  9971. break;
  9972. case 'percent':
  9973. label = d3.format('%')(percent);
  9974. break;
  9975. }
  9976. }
  9977. return label;
  9978. })
  9979. ;
  9980. }
  9981. // Computes the angle of an arc, converting from radians to degrees.
  9982. function angle(d) {
  9983. var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
  9984. return a > 90 ? a - 180 : a;
  9985. }
  9986. function arcTween(a, idx) {
  9987. a.endAngle = isNaN(a.endAngle) ? 0 : a.endAngle;
  9988. a.startAngle = isNaN(a.startAngle) ? 0 : a.startAngle;
  9989. if (!donut) a.innerRadius = 0;
  9990. var i = d3.interpolate(this._current, a);
  9991. this._current = i(0);
  9992. return function (t) {
  9993. return arcs[idx](i(t));
  9994. };
  9995. }
  9996. });
  9997. renderWatch.renderEnd('pie immediate');
  9998. return chart;
  9999. }
  10000. //============================================================
  10001. // Expose Public Variables
  10002. //------------------------------------------------------------
  10003. chart.dispatch = dispatch;
  10004. chart.options = nv.utils.optionsFunc.bind(chart);
  10005. chart._options = Object.create({}, {
  10006. // simple options, just get/set the necessary values
  10007. arcsRadius: { get: function () { return arcsRadius; }, set: function (_) { arcsRadius = _; } },
  10008. width: {get: function(){return width;}, set: function(_){width=_;}},
  10009. height: {get: function(){return height;}, set: function(_){height=_;}},
  10010. showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}},
  10011. title: {get: function(){return title;}, set: function(_){title=_;}},
  10012. titleOffset: {get: function(){return titleOffset;}, set: function(_){titleOffset=_;}},
  10013. labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_;}},
  10014. valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
  10015. x: {get: function(){return getX;}, set: function(_){getX=_;}},
  10016. id: {get: function(){return id;}, set: function(_){id=_;}},
  10017. endAngle: {get: function(){return endAngle;}, set: function(_){endAngle=_;}},
  10018. startAngle: {get: function(){return startAngle;}, set: function(_){startAngle=_;}},
  10019. padAngle: {get: function(){return padAngle;}, set: function(_){padAngle=_;}},
  10020. cornerRadius: {get: function(){return cornerRadius;}, set: function(_){cornerRadius=_;}},
  10021. donutRatio: {get: function(){return donutRatio;}, set: function(_){donutRatio=_;}},
  10022. labelsOutside: {get: function(){return labelsOutside;}, set: function(_){labelsOutside=_;}},
  10023. labelSunbeamLayout: {get: function(){return labelSunbeamLayout;}, set: function(_){labelSunbeamLayout=_;}},
  10024. donut: {get: function(){return donut;}, set: function(_){donut=_;}},
  10025. growOnHover: {get: function(){return growOnHover;}, set: function(_){growOnHover=_;}},
  10026. // depreciated after 1.7.1
  10027. pieLabelsOutside: {get: function(){return labelsOutside;}, set: function(_){
  10028. labelsOutside=_;
  10029. nv.deprecated('pieLabelsOutside', 'use labelsOutside instead');
  10030. }},
  10031. // depreciated after 1.7.1
  10032. donutLabelsOutside: {get: function(){return labelsOutside;}, set: function(_){
  10033. labelsOutside=_;
  10034. nv.deprecated('donutLabelsOutside', 'use labelsOutside instead');
  10035. }},
  10036. // deprecated after 1.7.1
  10037. labelFormat: {get: function(){ return valueFormat;}, set: function(_) {
  10038. valueFormat=_;
  10039. nv.deprecated('labelFormat','use valueFormat instead');
  10040. }},
  10041. // options that require extra logic in the setter
  10042. margin: {get: function(){return margin;}, set: function(_){
  10043. margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
  10044. margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
  10045. margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
  10046. margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
  10047. }},
  10048. duration: {get: function(){return duration;}, set: function(_){
  10049. duration = _;
  10050. renderWatch.reset(duration);
  10051. }},
  10052. y: {get: function(){return getY;}, set: function(_){
  10053. getY=d3.functor(_);
  10054. }},
  10055. color: {get: function(){return color;}, set: function(_){
  10056. color=nv.utils.getColor(_);
  10057. }},
  10058. labelType: {get: function(){return labelType;}, set: function(_){
  10059. labelType= _ || 'key';
  10060. }}
  10061. });
  10062. nv.utils.initOptions(chart);
  10063. return chart;
  10064. };
  10065. nv.models.pieChart = function() {
  10066. "use strict";
  10067. //============================================================
  10068. // Public Variables with Default Settings
  10069. //------------------------------------------------------------
  10070. var pie = nv.models.pie();
  10071. var legend = nv.models.legend();
  10072. var tooltip = nv.models.tooltip();
  10073. var margin = {top: 30, right: 20, bottom: 20, left: 20}
  10074. , marginTop = null
  10075. , width = null
  10076. , height = null
  10077. , showTooltipPercent = false
  10078. , showLegend = true
  10079. , legendPosition = "top"
  10080. , color = nv.utils.defaultColor()
  10081. , state = nv.utils.state()
  10082. , defaultState = null
  10083. , noData = null
  10084. , duration = 250
  10085. , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd')
  10086. ;
  10087. tooltip
  10088. .duration(0)
  10089. .headerEnabled(false)
  10090. .valueFormatter(function(d, i) {
  10091. return pie.valueFormat()(d, i);
  10092. });
  10093. //============================================================
  10094. // Private Variables
  10095. //------------------------------------------------------------
  10096. var renderWatch = nv.utils.renderWatch(dispatch);
  10097. var stateGetter = function(data) {
  10098. return function(){
  10099. return {
  10100. active: data.map(function(d) { return !d.disabled })
  10101. };
  10102. }
  10103. };
  10104. var stateSetter = function(data) {
  10105. return function(state) {
  10106. if (state.active !== undefined) {
  10107. data.forEach(function (series, i) {
  10108. series.disabled = !state.active[i];
  10109. });
  10110. }
  10111. }
  10112. };
  10113. //============================================================
  10114. // Chart function
  10115. //------------------------------------------------------------
  10116. function chart(selection) {
  10117. renderWatch.reset();
  10118. renderWatch.models(pie);
  10119. selection.each(function(data) {
  10120. var container = d3.select(this);
  10121. nv.utils.initSVG(container);
  10122. var that = this;
  10123. var availableWidth = nv.utils.availableWidth(width, container, margin),
  10124. availableHeight = nv.utils.availableHeight(height, container, margin);
  10125. chart.update = function() { container.transition().call(chart); };
  10126. chart.container = this;
  10127. state.setter(stateSetter(data), chart.update)
  10128. .getter(stateGetter(data))
  10129. .update();
  10130. //set state.disabled
  10131. state.disabled = data.map(function(d) { return !!d.disabled });
  10132. if (!defaultState) {
  10133. var key;
  10134. defaultState = {};
  10135. for (key in state) {
  10136. if (state[key] instanceof Array)
  10137. defaultState[key] = state[key].slice(0);
  10138. else
  10139. defaultState[key] = state[key];
  10140. }
  10141. }
  10142. // Display No Data message if there's nothing to show.
  10143. if (!data || !data.length) {
  10144. nv.utils.noData(chart, container);
  10145. return chart;
  10146. } else {
  10147. container.selectAll('.nv-noData').remove();
  10148. }
  10149. // Setup containers and skeleton of chart
  10150. var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]);
  10151. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g');
  10152. var g = wrap.select('g');
  10153. gEnter.append('g').attr('class', 'nv-pieWrap');
  10154. gEnter.append('g').attr('class', 'nv-legendWrap');
  10155. // Legend
  10156. if (!showLegend) {
  10157. g.select('.nv-legendWrap').selectAll('*').remove();
  10158. } else {
  10159. if (legendPosition === "top") {
  10160. legend.width( availableWidth ).key(pie.x());
  10161. wrap.select('.nv-legendWrap')
  10162. .datum(data)
  10163. .call(legend);
  10164. if (!marginTop && legend.height() !== margin.top) {
  10165. margin.top = legend.height();
  10166. availableHeight = nv.utils.availableHeight(height, container, margin);
  10167. }
  10168. wrap.select('.nv-legendWrap')
  10169. .attr('transform', 'translate(0,' + (-margin.top) +')');
  10170. } else if (legendPosition === "right") {
  10171. var legendWidth = nv.models.legend().width();
  10172. if (availableWidth / 2 < legendWidth) {
  10173. legendWidth = (availableWidth / 2)
  10174. }
  10175. legend.height(availableHeight).key(pie.x());
  10176. legend.width(legendWidth);
  10177. availableWidth -= legend.width();
  10178. wrap.select('.nv-legendWrap')
  10179. .datum(data)
  10180. .call(legend)
  10181. .attr('transform', 'translate(' + (availableWidth) +',0)');
  10182. }
  10183. }
  10184. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  10185. // Main Chart Component(s)
  10186. pie.width(availableWidth).height(availableHeight);
  10187. var pieWrap = g.select('.nv-pieWrap').datum([data]);
  10188. d3.transition(pieWrap).call(pie);
  10189. //============================================================
  10190. // Event Handling/Dispatching (in chart's scope)
  10191. //------------------------------------------------------------
  10192. legend.dispatch.on('stateChange', function(newState) {
  10193. for (var key in newState) {
  10194. state[key] = newState[key];
  10195. }
  10196. dispatch.stateChange(state);
  10197. chart.update();
  10198. });
  10199. // Update chart from a state object passed to event handler
  10200. dispatch.on('changeState', function(e) {
  10201. if (typeof e.disabled !== 'undefined') {
  10202. data.forEach(function(series,i) {
  10203. series.disabled = e.disabled[i];
  10204. });
  10205. state.disabled = e.disabled;
  10206. }
  10207. chart.update();
  10208. });
  10209. });
  10210. renderWatch.renderEnd('pieChart immediate');
  10211. return chart;
  10212. }
  10213. //============================================================
  10214. // Event Handling/Dispatching (out of chart's scope)
  10215. //------------------------------------------------------------
  10216. pie.dispatch.on('elementMouseover.tooltip', function(evt) {
  10217. evt['series'] = {
  10218. key: chart.x()(evt.data),
  10219. value: chart.y()(evt.data),
  10220. color: evt.color,
  10221. percent: evt.percent
  10222. };
  10223. if (!showTooltipPercent) {
  10224. delete evt.percent;
  10225. delete evt.series.percent;
  10226. }
  10227. tooltip.data(evt).hidden(false);
  10228. });
  10229. pie.dispatch.on('elementMouseout.tooltip', function(evt) {
  10230. tooltip.hidden(true);
  10231. });
  10232. pie.dispatch.on('elementMousemove.tooltip', function(evt) {
  10233. tooltip();
  10234. });
  10235. //============================================================
  10236. // Expose Public Variables
  10237. //------------------------------------------------------------
  10238. // expose chart's sub-components
  10239. chart.legend = legend;
  10240. chart.dispatch = dispatch;
  10241. chart.pie = pie;
  10242. chart.tooltip = tooltip;
  10243. chart.options = nv.utils.optionsFunc.bind(chart);
  10244. // use Object get/set functionality to map between vars and chart functions
  10245. chart._options = Object.create({}, {
  10246. // simple options, just get/set the necessary values
  10247. width: {get: function(){return width;}, set: function(_){width=_;}},
  10248. height: {get: function(){return height;}, set: function(_){height=_;}},
  10249. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  10250. showTooltipPercent: {get: function(){return showTooltipPercent;}, set: function(_){showTooltipPercent=_;}},
  10251. showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
  10252. legendPosition: {get: function(){return legendPosition;}, set: function(_){legendPosition=_;}},
  10253. defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
  10254. // options that require extra logic in the setter
  10255. color: {get: function(){return color;}, set: function(_){
  10256. color = _;
  10257. legend.color(color);
  10258. pie.color(color);
  10259. }},
  10260. duration: {get: function(){return duration;}, set: function(_){
  10261. duration = _;
  10262. renderWatch.reset(duration);
  10263. pie.duration(duration);
  10264. }},
  10265. margin: {get: function(){return margin;}, set: function(_){
  10266. if (_.top !== undefined) {
  10267. margin.top = _.top;
  10268. marginTop = _.top;
  10269. }
  10270. margin.right = _.right !== undefined ? _.right : margin.right;
  10271. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  10272. margin.left = _.left !== undefined ? _.left : margin.left;
  10273. }}
  10274. });
  10275. nv.utils.inheritOptions(chart, pie);
  10276. nv.utils.initOptions(chart);
  10277. return chart;
  10278. };
  10279. nv.models.sankey = function() {
  10280. 'use strict';
  10281. // Sources:
  10282. // - https://bost.ocks.org/mike/sankey/
  10283. // - https://github.com/soxofaan/d3-plugin-captain-sankey
  10284. //============================================================
  10285. // Public Variables with Default Settings
  10286. //------------------------------------------------------------
  10287. var sankey = {},
  10288. nodeWidth = 24,
  10289. nodePadding = 8,
  10290. size = [1, 1],
  10291. nodes = [],
  10292. links = [],
  10293. sinksRight = true;
  10294. var layout = function(iterations) {
  10295. computeNodeLinks();
  10296. computeNodeValues();
  10297. computeNodeBreadths();
  10298. computeNodeDepths(iterations);
  10299. };
  10300. var relayout = function() {
  10301. computeLinkDepths();
  10302. };
  10303. // SVG path data generator, to be used as 'd' attribute on 'path' element selection.
  10304. var link = function() {
  10305. var curvature = .5;
  10306. function link(d) {
  10307. var x0 = d.source.x + d.source.dx,
  10308. x1 = d.target.x,
  10309. xi = d3.interpolateNumber(x0, x1),
  10310. x2 = xi(curvature),
  10311. x3 = xi(1 - curvature),
  10312. y0 = d.source.y + d.sy + d.dy / 2,
  10313. y1 = d.target.y + d.ty + d.dy / 2;
  10314. var linkPath = 'M' + x0 + ',' + y0
  10315. + 'C' + x2 + ',' + y0
  10316. + ' ' + x3 + ',' + y1
  10317. + ' ' + x1 + ',' + y1;
  10318. return linkPath;
  10319. }
  10320. link.curvature = function(_) {
  10321. if (!arguments.length) return curvature;
  10322. curvature = +_;
  10323. return link;
  10324. };
  10325. return link;
  10326. };
  10327. // Y-position of the middle of a node.
  10328. var center = function(node) {
  10329. return node.y + node.dy / 2;
  10330. };
  10331. //============================================================
  10332. // Private Variables
  10333. //------------------------------------------------------------
  10334. // Populate the sourceLinks and targetLinks for each node.
  10335. // Also, if the source and target are not objects, assume they are indices.
  10336. function computeNodeLinks() {
  10337. nodes.forEach(function(node) {
  10338. // Links that have this node as source.
  10339. node.sourceLinks = [];
  10340. // Links that have this node as target.
  10341. node.targetLinks = [];
  10342. });
  10343. links.forEach(function(link) {
  10344. var source = link.source,
  10345. target = link.target;
  10346. if (typeof source === 'number') source = link.source = nodes[link.source];
  10347. if (typeof target === 'number') target = link.target = nodes[link.target];
  10348. source.sourceLinks.push(link);
  10349. target.targetLinks.push(link);
  10350. });
  10351. }
  10352. // Compute the value (size) of each node by summing the associated links.
  10353. function computeNodeValues() {
  10354. nodes.forEach(function(node) {
  10355. node.value = Math.max(
  10356. d3.sum(node.sourceLinks, value),
  10357. d3.sum(node.targetLinks, value)
  10358. );
  10359. });
  10360. }
  10361. // Iteratively assign the breadth (x-position) for each node.
  10362. // Nodes are assigned the maximum breadth of incoming neighbors plus one;
  10363. // nodes with no incoming links are assigned breadth zero, while
  10364. // nodes with no outgoing links are assigned the maximum breadth.
  10365. function computeNodeBreadths() {
  10366. //
  10367. var remainingNodes = nodes,
  10368. nextNodes,
  10369. x = 0;
  10370. // Work from left to right.
  10371. // Keep updating the breath (x-position) of nodes that are target of recently updated nodes.
  10372. //
  10373. while (remainingNodes.length && x < nodes.length) {
  10374. nextNodes = [];
  10375. remainingNodes.forEach(function(node) {
  10376. node.x = x;
  10377. node.dx = nodeWidth;
  10378. node.sourceLinks.forEach(function(link) {
  10379. if (nextNodes.indexOf(link.target) < 0) {
  10380. nextNodes.push(link.target);
  10381. }
  10382. });
  10383. });
  10384. remainingNodes = nextNodes;
  10385. ++x;
  10386. //
  10387. }
  10388. // Optionally move pure sinks always to the right.
  10389. if (sinksRight) {
  10390. moveSinksRight(x);
  10391. }
  10392. scaleNodeBreadths((size[0] - nodeWidth) / (x - 1));
  10393. }
  10394. function moveSourcesRight() {
  10395. nodes.forEach(function(node) {
  10396. if (!node.targetLinks.length) {
  10397. node.x = d3.min(node.sourceLinks, function(d) { return d.target.x; }) - 1;
  10398. }
  10399. });
  10400. }
  10401. function moveSinksRight(x) {
  10402. nodes.forEach(function(node) {
  10403. if (!node.sourceLinks.length) {
  10404. node.x = x - 1;
  10405. }
  10406. });
  10407. }
  10408. function scaleNodeBreadths(kx) {
  10409. nodes.forEach(function(node) {
  10410. node.x *= kx;
  10411. });
  10412. }
  10413. // Compute the depth (y-position) for each node.
  10414. function computeNodeDepths(iterations) {
  10415. // Group nodes by breath.
  10416. var nodesByBreadth = d3.nest()
  10417. .key(function(d) { return d.x; })
  10418. .sortKeys(d3.ascending)
  10419. .entries(nodes)
  10420. .map(function(d) { return d.values; });
  10421. //
  10422. initializeNodeDepth();
  10423. resolveCollisions();
  10424. computeLinkDepths();
  10425. for (var alpha = 1; iterations > 0; --iterations) {
  10426. relaxRightToLeft(alpha *= .99);
  10427. resolveCollisions();
  10428. computeLinkDepths();
  10429. relaxLeftToRight(alpha);
  10430. resolveCollisions();
  10431. computeLinkDepths();
  10432. }
  10433. function initializeNodeDepth() {
  10434. // Calculate vertical scaling factor.
  10435. var ky = d3.min(nodesByBreadth, function(nodes) {
  10436. return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value);
  10437. });
  10438. nodesByBreadth.forEach(function(nodes) {
  10439. nodes.forEach(function(node, i) {
  10440. node.y = i;
  10441. node.dy = node.value * ky;
  10442. });
  10443. });
  10444. links.forEach(function(link) {
  10445. link.dy = link.value * ky;
  10446. });
  10447. }
  10448. function relaxLeftToRight(alpha) {
  10449. nodesByBreadth.forEach(function(nodes, breadth) {
  10450. nodes.forEach(function(node) {
  10451. if (node.targetLinks.length) {
  10452. // Value-weighted average of the y-position of source node centers linked to this node.
  10453. var y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value);
  10454. node.y += (y - center(node)) * alpha;
  10455. }
  10456. });
  10457. });
  10458. function weightedSource(link) {
  10459. return (link.source.y + link.sy + link.dy / 2) * link.value;
  10460. }
  10461. }
  10462. function relaxRightToLeft(alpha) {
  10463. nodesByBreadth.slice().reverse().forEach(function(nodes) {
  10464. nodes.forEach(function(node) {
  10465. if (node.sourceLinks.length) {
  10466. // Value-weighted average of the y-positions of target nodes linked to this node.
  10467. var y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value);
  10468. node.y += (y - center(node)) * alpha;
  10469. }
  10470. });
  10471. });
  10472. function weightedTarget(link) {
  10473. return (link.target.y + link.ty + link.dy / 2) * link.value;
  10474. }
  10475. }
  10476. function resolveCollisions() {
  10477. nodesByBreadth.forEach(function(nodes) {
  10478. var node,
  10479. dy,
  10480. y0 = 0,
  10481. n = nodes.length,
  10482. i;
  10483. // Push any overlapping nodes down.
  10484. nodes.sort(ascendingDepth);
  10485. for (i = 0; i < n; ++i) {
  10486. node = nodes[i];
  10487. dy = y0 - node.y;
  10488. if (dy > 0) node.y += dy;
  10489. y0 = node.y + node.dy + nodePadding;
  10490. }
  10491. // If the bottommost node goes outside the bounds, push it back up.
  10492. dy = y0 - nodePadding - size[1];
  10493. if (dy > 0) {
  10494. y0 = node.y -= dy;
  10495. // Push any overlapping nodes back up.
  10496. for (i = n - 2; i >= 0; --i) {
  10497. node = nodes[i];
  10498. dy = node.y + node.dy + nodePadding - y0;
  10499. if (dy > 0) node.y -= dy;
  10500. y0 = node.y;
  10501. }
  10502. }
  10503. });
  10504. }
  10505. function ascendingDepth(a, b) {
  10506. return a.y - b.y;
  10507. }
  10508. }
  10509. // Compute y-offset of the source endpoint (sy) and target endpoints (ty) of links,
  10510. // relative to the source/target node's y-position.
  10511. function computeLinkDepths() {
  10512. nodes.forEach(function(node) {
  10513. node.sourceLinks.sort(ascendingTargetDepth);
  10514. node.targetLinks.sort(ascendingSourceDepth);
  10515. });
  10516. nodes.forEach(function(node) {
  10517. var sy = 0, ty = 0;
  10518. node.sourceLinks.forEach(function(link) {
  10519. link.sy = sy;
  10520. sy += link.dy;
  10521. });
  10522. node.targetLinks.forEach(function(link) {
  10523. link.ty = ty;
  10524. ty += link.dy;
  10525. });
  10526. });
  10527. function ascendingSourceDepth(a, b) {
  10528. return a.source.y - b.source.y;
  10529. }
  10530. function ascendingTargetDepth(a, b) {
  10531. return a.target.y - b.target.y;
  10532. }
  10533. }
  10534. // Value property accessor.
  10535. function value(x) {
  10536. return x.value;
  10537. }
  10538. sankey.options = nv.utils.optionsFunc.bind(sankey);
  10539. sankey._options = Object.create({}, {
  10540. nodeWidth: {get: function(){return nodeWidth;}, set: function(_){nodeWidth=+_;}},
  10541. nodePadding: {get: function(){return nodePadding;}, set: function(_){nodePadding=_;}},
  10542. nodes: {get: function(){return nodes;}, set: function(_){nodes=_;}},
  10543. links: {get: function(){return links ;}, set: function(_){links=_;}},
  10544. size: {get: function(){return size;}, set: function(_){size=_;}},
  10545. sinksRight: {get: function(){return sinksRight;}, set: function(_){sinksRight=_;}},
  10546. layout: {get: function(){layout(32);}, set: function(_){layout(_);}},
  10547. relayout: {get: function(){relayout();}, set: function(_){}},
  10548. center: {get: function(){return center();}, set: function(_){
  10549. if(typeof _ === 'function'){
  10550. center=_;
  10551. }
  10552. }},
  10553. link: {get: function(){return link();}, set: function(_){
  10554. if(typeof _ === 'function'){
  10555. link=_;
  10556. }
  10557. return link();
  10558. }}
  10559. });
  10560. nv.utils.initOptions(sankey);
  10561. return sankey;
  10562. };
  10563. nv.models.sankeyChart = function() {
  10564. "use strict";
  10565. // Sources:
  10566. // - https://bost.ocks.org/mike/sankey/
  10567. // - https://github.com/soxofaan/d3-plugin-captain-sankey
  10568. //============================================================
  10569. // Public Variables with Default Settings
  10570. //------------------------------------------------------------
  10571. var margin = {top: 5, right: 0, bottom: 5, left: 0}
  10572. , sankey = nv.models.sankey()
  10573. , width = 600
  10574. , height = 400
  10575. , nodeWidth = 36
  10576. , nodePadding = 40
  10577. , units = 'units'
  10578. , center = undefined
  10579. ;
  10580. //============================================================
  10581. // Private Variables
  10582. //------------------------------------------------------------
  10583. var formatNumber = d3.format(',.0f'); // zero decimal places
  10584. var format = function(d) {
  10585. return formatNumber(d) + ' ' + units;
  10586. };
  10587. var color = d3.scale.category20();
  10588. var linkTitle = function(d){
  10589. return d.source.name + ' → ' + d.target.name + '\n' + format(d.value);
  10590. };
  10591. var nodeFillColor = function(d){
  10592. return d.color = color(d.name.replace(/ .*/, ''));
  10593. };
  10594. var nodeStrokeColor = function(d){
  10595. return d3.rgb(d.color).darker(2);
  10596. };
  10597. var nodeTitle = function(d){
  10598. return d.name + '\n' + format(d.value);
  10599. };
  10600. var showError = function(element, message) {
  10601. element.append('text')
  10602. .attr('x', 0)
  10603. .attr('y', 0)
  10604. .attr('class', 'nvd3-sankey-chart-error')
  10605. .attr('text-anchor', 'middle')
  10606. .text(message);
  10607. };
  10608. function chart(selection) {
  10609. selection.each(function(data) {
  10610. var testData = {
  10611. nodes:
  10612. [
  10613. {'node': 1, 'name': 'Test 1'},
  10614. {'node': 2, 'name': 'Test 2'},
  10615. {'node': 3, 'name': 'Test 3'},
  10616. {'node': 4, 'name': 'Test 4'},
  10617. {'node': 5, 'name': 'Test 5'},
  10618. {'node': 6, 'name': 'Test 6'}
  10619. ],
  10620. links:
  10621. [
  10622. {'source': 0, 'target': 1, 'value': 2295},
  10623. {'source': 0, 'target': 5, 'value': 1199},
  10624. {'source': 1, 'target': 2, 'value': 1119},
  10625. {'source': 1, 'target': 5, 'value': 1176},
  10626. {'source': 2, 'target': 3, 'value': 487},
  10627. {'source': 2, 'target': 5, 'value': 632},
  10628. {'source': 3, 'target': 4, 'value': 301},
  10629. {'source': 3, 'target': 5, 'value': 186}
  10630. ]
  10631. };
  10632. // Error handling
  10633. var isDataValid = false;
  10634. var dataAvailable = false;
  10635. // check if data is valid
  10636. if(
  10637. (typeof data['nodes'] === 'object' && data['nodes'].length) >= 0 &&
  10638. (typeof data['links'] === 'object' && data['links'].length) >= 0
  10639. ){
  10640. isDataValid = true;
  10641. }
  10642. // check if data is available
  10643. if(
  10644. data['nodes'] && data['nodes'].length > 0 &&
  10645. data['links'] && data['links'].length > 0
  10646. ) {
  10647. dataAvailable = true;
  10648. }
  10649. // show error
  10650. if(!isDataValid) {
  10651. console.error('NVD3 Sankey chart error:', 'invalid data format for', data);
  10652. console.info('Valid data format is: ', testData, JSON.stringify(testData));
  10653. showError(selection, 'Error loading chart, data is invalid');
  10654. return false;
  10655. }
  10656. // TODO use nv.utils.noData
  10657. if(!dataAvailable) {
  10658. showError(selection, 'No data available');
  10659. return false;
  10660. }
  10661. // No errors, continue
  10662. // append the svg canvas to the page
  10663. var svg = selection.append('svg')
  10664. .attr('width', width)
  10665. .attr('height', height)
  10666. .append('g')
  10667. .attr('class', 'nvd3 nv-wrap nv-sankeyChart');
  10668. // Set the sankey diagram properties
  10669. sankey
  10670. .nodeWidth(nodeWidth)
  10671. .nodePadding(nodePadding)
  10672. .size([width, height]);
  10673. var path = sankey.link();
  10674. sankey
  10675. .nodes(data.nodes)
  10676. .links(data.links)
  10677. .layout(32)
  10678. .center(center);
  10679. // add in the links
  10680. var link = svg.append('g').selectAll('.link')
  10681. .data(data.links)
  10682. .enter().append('path')
  10683. .attr('class', 'link')
  10684. .attr('d', path)
  10685. .style('stroke-width', function(d) { return Math.max(1, d.dy); })
  10686. .sort(function(a,b) { return b.dy - a.dy; });
  10687. // add the link titles
  10688. link.append('title')
  10689. .text(linkTitle);
  10690. // add in the nodes
  10691. var node = svg.append('g').selectAll('.node')
  10692. .data(data.nodes)
  10693. .enter().append('g')
  10694. .attr('class', 'node')
  10695. .attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; })
  10696. .call(
  10697. d3.behavior
  10698. .drag()
  10699. .origin(function(d) { return d; })
  10700. .on('dragstart', function() {
  10701. this.parentNode.appendChild(this);
  10702. })
  10703. .on('drag', dragmove)
  10704. );
  10705. // add the rectangles for the nodes
  10706. node.append('rect')
  10707. .attr('height', function(d) { return d.dy; })
  10708. .attr('width', sankey.nodeWidth())
  10709. .style('fill', nodeFillColor)
  10710. .style('stroke', nodeStrokeColor)
  10711. .append('title')
  10712. .text(nodeTitle);
  10713. // add in the title for the nodes
  10714. node.append('text')
  10715. .attr('x', -6)
  10716. .attr('y', function(d) { return d.dy / 2; })
  10717. .attr('dy', '.35em')
  10718. .attr('text-anchor', 'end')
  10719. .attr('transform', null)
  10720. .text(function(d) { return d.name; })
  10721. .filter(function(d) { return d.x < width / 2; })
  10722. .attr('x', 6 + sankey.nodeWidth())
  10723. .attr('text-anchor', 'start');
  10724. // the function for moving the nodes
  10725. function dragmove(d) {
  10726. d3.select(this).attr('transform',
  10727. 'translate(' + d.x + ',' + (
  10728. d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))
  10729. ) + ')');
  10730. sankey.relayout();
  10731. link.attr('d', path);
  10732. }
  10733. });
  10734. return chart;
  10735. }
  10736. //============================================================
  10737. // Expose Public Variables
  10738. //------------------------------------------------------------
  10739. chart.options = nv.utils.optionsFunc.bind(chart);
  10740. chart._options = Object.create({}, {
  10741. // simple options, just get/set the necessary values
  10742. units: {get: function(){return units;}, set: function(_){units=_;}},
  10743. width: {get: function(){return width;}, set: function(_){width=_;}},
  10744. height: {get: function(){return height;}, set: function(_){height=_;}},
  10745. format: {get: function(){return format;}, set: function(_){format=_;}},
  10746. linkTitle: {get: function(){return linkTitle;}, set: function(_){linkTitle=_;}},
  10747. nodeWidth: {get: function(){return nodeWidth;}, set: function(_){nodeWidth=_;}},
  10748. nodePadding: {get: function(){return nodePadding;}, set: function(_){nodePadding=_;}},
  10749. center: {get: function(){return center}, set: function(_){center=_}},
  10750. // options that require extra logic in the setter
  10751. margin: {get: function(){return margin;}, set: function(_){
  10752. margin.top = _.top !== undefined ? _.top : margin.top;
  10753. margin.right = _.right !== undefined ? _.right : margin.right;
  10754. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  10755. margin.left = _.left !== undefined ? _.left : margin.left;
  10756. }},
  10757. nodeStyle: {get: function(){return {};}, set: function(_){
  10758. nodeFillColor = _.fillColor !== undefined ? _.fillColor : nodeFillColor;
  10759. nodeStrokeColor = _.strokeColor !== undefined ? _.strokeColor : nodeStrokeColor;
  10760. nodeTitle = _.title !== undefined ? _.title : nodeTitle;
  10761. }}
  10762. });
  10763. nv.utils.initOptions(chart);
  10764. return chart;
  10765. };
  10766. nv.models.scatter = function() {
  10767. "use strict";
  10768. //============================================================
  10769. // Public Variables with Default Settings
  10770. //------------------------------------------------------------
  10771. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  10772. , width = null
  10773. , height = null
  10774. , color = nv.utils.defaultColor() // chooses color
  10775. , pointBorderColor = null
  10776. , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one
  10777. , container = null
  10778. , x = d3.scale.linear()
  10779. , y = d3.scale.linear()
  10780. , z = d3.scale.linear() //linear because d3.svg.shape.size is treated as area
  10781. , getX = function(d) { return d.x } // accessor to get the x value
  10782. , getY = function(d) { return d.y } // accessor to get the y value
  10783. , getSize = function(d) { return d.size || 1} // accessor to get the point size
  10784. , getShape = function(d) { return d.shape || 'circle' } // accessor to get point shape
  10785. , forceX = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
  10786. , forceY = [] // List of numbers to Force into the Y scale
  10787. , forceSize = [] // List of numbers to Force into the Size scale
  10788. , interactive = true // If true, plots a voronoi overlay for advanced point intersection
  10789. , pointActive = function(d) { return !d.notActive } // any points that return false will be filtered out
  10790. , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
  10791. , padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding
  10792. , clipEdge = false // if true, masks points within x and y scale
  10793. , clipVoronoi = true // if true, masks each point with a circle... can turn off to slightly increase performance
  10794. , showVoronoi = false // display the voronoi areas
  10795. , clipRadius = function() { return 25 } // function to get the radius for voronoi point clips
  10796. , xDomain = null // Override x domain (skips the calculation from data)
  10797. , yDomain = null // Override y domain
  10798. , xRange = null // Override x range
  10799. , yRange = null // Override y range
  10800. , sizeDomain = null // Override point size domain
  10801. , sizeRange = null
  10802. , singlePoint = false
  10803. , dispatch = d3.dispatch('elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
  10804. , useVoronoi = true
  10805. , duration = 250
  10806. , interactiveUpdateDelay = 300
  10807. , showLabels = false
  10808. ;
  10809. //============================================================
  10810. // Private Variables
  10811. //------------------------------------------------------------
  10812. var x0, y0, z0 // used to store previous scales
  10813. , width0
  10814. , height0
  10815. , timeoutID
  10816. , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips
  10817. , renderWatch = nv.utils.renderWatch(dispatch, duration)
  10818. , _sizeRange_def = [16, 256]
  10819. , _cache = {}
  10820. ;
  10821. function getCache(d) {
  10822. var key, val;
  10823. key = d[0].series + ':' + d[1];
  10824. val = _cache[key] = _cache[key] || {};
  10825. return val;
  10826. }
  10827. function delCache(d) {
  10828. var key, val;
  10829. key = d[0].series + ':' + d[1];
  10830. delete _cache[key];
  10831. }
  10832. function getDiffs(d) {
  10833. var i, key, val,
  10834. cache = getCache(d),
  10835. diffs = false;
  10836. for (i = 1; i < arguments.length; i += 2) {
  10837. key = arguments[i];
  10838. val = arguments[i + 1](d[0], d[1]);
  10839. if (cache[key] !== val || !cache.hasOwnProperty(key)) {
  10840. cache[key] = val;
  10841. diffs = true;
  10842. }
  10843. }
  10844. return diffs;
  10845. }
  10846. function chart(selection) {
  10847. renderWatch.reset();
  10848. selection.each(function(data) {
  10849. container = d3.select(this);
  10850. var availableWidth = nv.utils.availableWidth(width, container, margin),
  10851. availableHeight = nv.utils.availableHeight(height, container, margin);
  10852. nv.utils.initSVG(container);
  10853. //add series index to each data point for reference
  10854. data.forEach(function(series, i) {
  10855. series.values.forEach(function(point) {
  10856. point.series = i;
  10857. });
  10858. });
  10859. // Setup Scales
  10860. var logScale = chart.yScale().name === d3.scale.log().name ? true : false;
  10861. // remap and flatten the data for use in calculating the scales' domains
  10862. 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
  10863. d3.merge(
  10864. data.map(function(d) {
  10865. return d.values.map(function(d,i) {
  10866. return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) }
  10867. })
  10868. })
  10869. );
  10870. x .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX)))
  10871. if (padData && data[0])
  10872. x.range(xRange || [(availableWidth * padDataOuter + availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length) ]);
  10873. //x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
  10874. else
  10875. x.range(xRange || [0, availableWidth]);
  10876. if (logScale) {
  10877. var min = d3.min(seriesData.map(function(d) { if (d.y !== 0) return d.y; }));
  10878. y.clamp(true)
  10879. .domain(yDomain || d3.extent(seriesData.map(function(d) {
  10880. if (d.y !== 0) return d.y;
  10881. else return min * 0.1;
  10882. }).concat(forceY)))
  10883. .range(yRange || [availableHeight, 0]);
  10884. } else {
  10885. y.domain(yDomain || d3.extent(seriesData.map(function (d) { return d.y;}).concat(forceY)))
  10886. .range(yRange || [availableHeight, 0]);
  10887. }
  10888. z .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize)))
  10889. .range(sizeRange || _sizeRange_def);
  10890. // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
  10891. singlePoint = x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1];
  10892. if (x.domain()[0] === x.domain()[1])
  10893. x.domain()[0] ?
  10894. x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
  10895. : x.domain([-1,1]);
  10896. if (y.domain()[0] === y.domain()[1])
  10897. y.domain()[0] ?
  10898. y.domain([y.domain()[0] - y.domain()[0] * 0.01, y.domain()[1] + y.domain()[1] * 0.01])
  10899. : y.domain([-1,1]);
  10900. if ( isNaN(x.domain()[0])) {
  10901. x.domain([-1,1]);
  10902. }
  10903. if ( isNaN(y.domain()[0])) {
  10904. y.domain([-1,1]);
  10905. }
  10906. x0 = x0 || x;
  10907. y0 = y0 || y;
  10908. z0 = z0 || z;
  10909. var scaleDiff = x(1) !== x0(1) || y(1) !== y0(1) || z(1) !== z0(1);
  10910. width0 = width0 || width;
  10911. height0 = height0 || height;
  10912. var sizeDiff = width0 !== width || height0 !== height;
  10913. // Setup containers and skeleton of chart
  10914. var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]);
  10915. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id);
  10916. var defsEnter = wrapEnter.append('defs');
  10917. var gEnter = wrapEnter.append('g');
  10918. var g = wrap.select('g');
  10919. wrap.classed('nv-single-point', singlePoint);
  10920. gEnter.append('g').attr('class', 'nv-groups');
  10921. gEnter.append('g').attr('class', 'nv-point-paths');
  10922. wrapEnter.append('g').attr('class', 'nv-point-clips');
  10923. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  10924. defsEnter.append('clipPath')
  10925. .attr('id', 'nv-edge-clip-' + id)
  10926. .append('rect')
  10927. .attr('transform', 'translate( -10, -10)');
  10928. wrap.select('#nv-edge-clip-' + id + ' rect')
  10929. .attr('width', availableWidth + 20)
  10930. .attr('height', (availableHeight > 0) ? availableHeight + 20 : 0);
  10931. g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
  10932. function updateInteractiveLayer() {
  10933. // Always clear needs-update flag regardless of whether or not
  10934. // we will actually do anything (avoids needless invocations).
  10935. needsUpdate = false;
  10936. if (!interactive) return false;
  10937. // inject series and point index for reference into voronoi
  10938. if (useVoronoi === true) {
  10939. var vertices = d3.merge(data.map(function(group, groupIndex) {
  10940. return group.values
  10941. .map(function(point, pointIndex) {
  10942. // *Adding noise to make duplicates very unlikely
  10943. // *Injecting series and point index for reference
  10944. /* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi.
  10945. */
  10946. var pX = getX(point,pointIndex);
  10947. var pY = getY(point,pointIndex);
  10948. return [nv.utils.NaNtoZero(x(pX))+ Math.random() * 1e-4,
  10949. nv.utils.NaNtoZero(y(pY))+ Math.random() * 1e-4,
  10950. groupIndex,
  10951. pointIndex, point]; //temp hack to add noise until I think of a better way so there are no duplicates
  10952. })
  10953. .filter(function(pointArray, pointIndex) {
  10954. return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct!
  10955. })
  10956. })
  10957. );
  10958. if (vertices.length == 0) return false; // No active points, we're done
  10959. if (vertices.length < 3) {
  10960. // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work
  10961. vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]);
  10962. vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]);
  10963. vertices.push([x.range()[0] - 20, y.range()[0] + 20, null, null]);
  10964. vertices.push([x.range()[1] + 20, y.range()[1] - 20, null, null]);
  10965. }
  10966. // keep voronoi sections from going more than 10 outside of graph
  10967. // to avoid overlap with other things like legend etc
  10968. var bounds = d3.geom.polygon([
  10969. [-10,-10],
  10970. [-10,height + 10],
  10971. [width + 10,height + 10],
  10972. [width + 10,-10]
  10973. ]);
  10974. var voronoi = d3.geom.voronoi(vertices).map(function(d, i) {
  10975. return {
  10976. 'data': bounds.clip(d),
  10977. 'series': vertices[i][2],
  10978. 'point': vertices[i][3]
  10979. }
  10980. });
  10981. // nuke all voronoi paths on reload and recreate them
  10982. wrap.select('.nv-point-paths').selectAll('path').remove();
  10983. var pointPaths = wrap.select('.nv-point-paths').selectAll('path').data(voronoi);
  10984. var vPointPaths = pointPaths
  10985. .enter().append("svg:path")
  10986. .attr("d", function(d) {
  10987. if (!d || !d.data || d.data.length === 0)
  10988. return 'M 0 0';
  10989. else
  10990. return "M" + d.data.join(",") + "Z";
  10991. })
  10992. .attr("id", function(d,i) {
  10993. return "nv-path-"+i; })
  10994. .attr("clip-path", function(d,i) { return "url(#nv-clip-"+id+"-"+i+")"; })
  10995. ;
  10996. // good for debugging point hover issues
  10997. if (showVoronoi) {
  10998. vPointPaths.style("fill", d3.rgb(230, 230, 230))
  10999. .style('fill-opacity', 0.4)
  11000. .style('stroke-opacity', 1)
  11001. .style("stroke", d3.rgb(200,200,200));
  11002. }
  11003. if (clipVoronoi) {
  11004. // voronoi sections are already set to clip,
  11005. // just create the circles with the IDs they expect
  11006. wrap.select('.nv-point-clips').selectAll('*').remove(); // must do * since it has sub-dom
  11007. var pointClips = wrap.select('.nv-point-clips').selectAll('clipPath').data(vertices);
  11008. var vPointClips = pointClips
  11009. .enter().append("svg:clipPath")
  11010. .attr("id", function(d, i) { return "nv-clip-"+id+"-"+i;})
  11011. .append("svg:circle")
  11012. .attr('cx', function(d) { return d[0]; })
  11013. .attr('cy', function(d) { return d[1]; })
  11014. .attr('r', clipRadius);
  11015. }
  11016. var mouseEventCallback = function(el, d, mDispatch) {
  11017. if (needsUpdate) return 0;
  11018. var series = data[d.series];
  11019. if (series === undefined) return;
  11020. var point = series.values[d.point];
  11021. point['color'] = color(series, d.series);
  11022. // standardize attributes for tooltip.
  11023. point['x'] = getX(point);
  11024. point['y'] = getY(point);
  11025. // can't just get box of event node since it's actually a voronoi polygon
  11026. var box = container.node().getBoundingClientRect();
  11027. var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
  11028. var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
  11029. var pos = {
  11030. left: x(getX(point, d.point)) + box.left + scrollLeft + margin.left + 10,
  11031. top: y(getY(point, d.point)) + box.top + scrollTop + margin.top + 10
  11032. };
  11033. mDispatch({
  11034. point: point,
  11035. series: series,
  11036. pos: pos,
  11037. relativePos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top],
  11038. seriesIndex: d.series,
  11039. pointIndex: d.point,
  11040. event: d3.event,
  11041. element: el
  11042. });
  11043. };
  11044. pointPaths
  11045. .on('click', function(d) {
  11046. mouseEventCallback(this, d, dispatch.elementClick);
  11047. })
  11048. .on('dblclick', function(d) {
  11049. mouseEventCallback(this, d, dispatch.elementDblClick);
  11050. })
  11051. .on('mouseover', function(d) {
  11052. mouseEventCallback(this, d, dispatch.elementMouseover);
  11053. })
  11054. .on('mouseout', function(d, i) {
  11055. mouseEventCallback(this, d, dispatch.elementMouseout);
  11056. });
  11057. } else {
  11058. // add event handlers to points instead voronoi paths
  11059. wrap.select('.nv-groups').selectAll('.nv-group')
  11060. .selectAll('.nv-point')
  11061. //.data(dataWithPoints)
  11062. //.style('pointer-events', 'auto') // recativate events, disabled by css
  11063. .on('click', function(d,i) {
  11064. //nv.log('test', d, i);
  11065. if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
  11066. var series = data[d.series],
  11067. point = series.values[i];
  11068. var element = this;
  11069. dispatch.elementClick({
  11070. point: point,
  11071. series: series,
  11072. pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], //TODO: make this pos base on the page
  11073. relativePos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
  11074. seriesIndex: d.series,
  11075. pointIndex: i,
  11076. event: d3.event,
  11077. element: element
  11078. });
  11079. })
  11080. .on('dblclick', function(d,i) {
  11081. if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
  11082. var series = data[d.series],
  11083. point = series.values[i];
  11084. dispatch.elementDblClick({
  11085. point: point,
  11086. series: series,
  11087. pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],//TODO: make this pos base on the page
  11088. relativePos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
  11089. seriesIndex: d.series,
  11090. pointIndex: i
  11091. });
  11092. })
  11093. .on('mouseover', function(d,i) {
  11094. if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
  11095. var series = data[d.series],
  11096. point = series.values[i];
  11097. dispatch.elementMouseover({
  11098. point: point,
  11099. series: series,
  11100. pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],//TODO: make this pos base on the page
  11101. relativePos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
  11102. seriesIndex: d.series,
  11103. pointIndex: i,
  11104. color: color(d, i)
  11105. });
  11106. })
  11107. .on('mouseout', function(d,i) {
  11108. if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
  11109. var series = data[d.series],
  11110. point = series.values[i];
  11111. dispatch.elementMouseout({
  11112. point: point,
  11113. series: series,
  11114. pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],//TODO: make this pos base on the page
  11115. relativePos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
  11116. seriesIndex: d.series,
  11117. pointIndex: i,
  11118. color: color(d, i)
  11119. });
  11120. });
  11121. }
  11122. }
  11123. needsUpdate = true;
  11124. var groups = wrap.select('.nv-groups').selectAll('.nv-group')
  11125. .data(function(d) { return d }, function(d) { return d.key });
  11126. groups.enter().append('g')
  11127. .style('stroke-opacity', 1e-6)
  11128. .style('fill-opacity', 1e-6);
  11129. groups.exit()
  11130. .remove();
  11131. groups
  11132. .attr('class', function(d,i) {
  11133. return (d.classed || '') + ' nv-group nv-series-' + i;
  11134. })
  11135. .classed('nv-noninteractive', !interactive)
  11136. .classed('hover', function(d) { return d.hover });
  11137. groups.watchTransition(renderWatch, 'scatter: groups')
  11138. .style('fill', function(d,i) { return color(d, i) })
  11139. .style('stroke', function(d,i) { return d.pointBorderColor || pointBorderColor || color(d, i) })
  11140. .style('stroke-opacity', 1)
  11141. .style('fill-opacity', .5);
  11142. // create the points, maintaining their IDs from the original data set
  11143. var points = groups.selectAll('path.nv-point')
  11144. .data(function(d) {
  11145. return d.values.map(
  11146. function (point, pointIndex) {
  11147. return [point, pointIndex]
  11148. }).filter(
  11149. function(pointArray, pointIndex) {
  11150. return pointActive(pointArray[0], pointIndex)
  11151. })
  11152. });
  11153. points.enter().append('path')
  11154. .attr('class', function (d) {
  11155. return 'nv-point nv-point-' + d[1];
  11156. })
  11157. .style('fill', function (d) { return d.color })
  11158. .style('stroke', function (d) { return d.color })
  11159. .attr('transform', function(d) {
  11160. return 'translate(' + nv.utils.NaNtoZero(x0(getX(d[0],d[1]))) + ',' + nv.utils.NaNtoZero(y0(getY(d[0],d[1]))) + ')'
  11161. })
  11162. .attr('d',
  11163. nv.utils.symbol()
  11164. .type(function(d) { return getShape(d[0]); })
  11165. .size(function(d) { return z(getSize(d[0],d[1])) })
  11166. );
  11167. points.exit().each(delCache).remove();
  11168. groups.exit().selectAll('path.nv-point')
  11169. .watchTransition(renderWatch, 'scatter exit')
  11170. .attr('transform', function(d) {
  11171. return 'translate(' + nv.utils.NaNtoZero(x(getX(d[0],d[1]))) + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')'
  11172. })
  11173. .remove();
  11174. // Update points position only if "x" or "y" have changed
  11175. points.filter(function (d) { return scaleDiff || sizeDiff || getDiffs(d, 'x', getX, 'y', getY); })
  11176. .watchTransition(renderWatch, 'scatter points')
  11177. .attr('transform', function(d) {
  11178. //nv.log(d, getX(d[0],d[1]), x(getX(d[0],d[1])));
  11179. return 'translate(' + nv.utils.NaNtoZero(x(getX(d[0],d[1]))) + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')'
  11180. });
  11181. // Update points appearance only if "shape" or "size" have changed
  11182. points.filter(function (d) { return scaleDiff || sizeDiff || getDiffs(d, 'shape', getShape, 'size', getSize); })
  11183. .watchTransition(renderWatch, 'scatter points')
  11184. .attr('d',
  11185. nv.utils.symbol()
  11186. .type(function(d) { return getShape(d[0]); })
  11187. .size(function(d) { return z(getSize(d[0],d[1])) })
  11188. );
  11189. // add label a label to scatter chart
  11190. if(showLabels)
  11191. {
  11192. var titles = groups.selectAll('.nv-label')
  11193. .data(function(d) {
  11194. return d.values.map(
  11195. function (point, pointIndex) {
  11196. return [point, pointIndex]
  11197. }).filter(
  11198. function(pointArray, pointIndex) {
  11199. return pointActive(pointArray[0], pointIndex)
  11200. })
  11201. });
  11202. titles.enter().append('text')
  11203. .style('fill', function (d,i) {
  11204. return d.color })
  11205. .style('stroke-opacity', 0)
  11206. .style('fill-opacity', 1)
  11207. .attr('transform', function(d) {
  11208. var dx = nv.utils.NaNtoZero(x0(getX(d[0],d[1]))) + Math.sqrt(z(getSize(d[0],d[1]))/Math.PI) + 2;
  11209. return 'translate(' + dx + ',' + nv.utils.NaNtoZero(y0(getY(d[0],d[1]))) + ')';
  11210. })
  11211. .text(function(d,i){
  11212. return d[0].label;});
  11213. titles.exit().remove();
  11214. groups.exit().selectAll('path.nv-label')
  11215. .watchTransition(renderWatch, 'scatter exit')
  11216. .attr('transform', function(d) {
  11217. var dx = nv.utils.NaNtoZero(x(getX(d[0],d[1])))+ Math.sqrt(z(getSize(d[0],d[1]))/Math.PI)+2;
  11218. return 'translate(' + dx + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')';
  11219. })
  11220. .remove();
  11221. titles.each(function(d) {
  11222. d3.select(this)
  11223. .classed('nv-label', true)
  11224. .classed('nv-label-' + d[1], false)
  11225. .classed('hover',false);
  11226. });
  11227. titles.watchTransition(renderWatch, 'scatter labels')
  11228. .attr('transform', function(d) {
  11229. var dx = nv.utils.NaNtoZero(x(getX(d[0],d[1])))+ Math.sqrt(z(getSize(d[0],d[1]))/Math.PI)+2;
  11230. return 'translate(' + dx + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')'
  11231. });
  11232. }
  11233. // Delay updating the invisible interactive layer for smoother animation
  11234. if( interactiveUpdateDelay )
  11235. {
  11236. clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer
  11237. timeoutID = setTimeout(updateInteractiveLayer, interactiveUpdateDelay );
  11238. }
  11239. else
  11240. {
  11241. updateInteractiveLayer();
  11242. }
  11243. //store old scales for use in transitions on update
  11244. x0 = x.copy();
  11245. y0 = y.copy();
  11246. z0 = z.copy();
  11247. width0 = width;
  11248. height0 = height;
  11249. });
  11250. renderWatch.renderEnd('scatter immediate');
  11251. return chart;
  11252. }
  11253. //============================================================
  11254. // Expose Public Variables
  11255. //------------------------------------------------------------
  11256. chart.dispatch = dispatch;
  11257. chart.options = nv.utils.optionsFunc.bind(chart);
  11258. // utility function calls provided by this chart
  11259. chart._calls = new function() {
  11260. this.clearHighlights = function () {
  11261. nv.dom.write(function() {
  11262. container.selectAll(".nv-point.hover").classed("hover", false);
  11263. });
  11264. return null;
  11265. };
  11266. this.highlightPoint = function (seriesIndex, pointIndex, isHoverOver) {
  11267. nv.dom.write(function() {
  11268. container.select('.nv-groups')
  11269. .selectAll(".nv-series-" + seriesIndex)
  11270. .selectAll(".nv-point-" + pointIndex)
  11271. .classed("hover", isHoverOver);
  11272. });
  11273. };
  11274. };
  11275. // trigger calls from events too
  11276. dispatch.on('elementMouseover.point', function(d) {
  11277. if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,true);
  11278. });
  11279. dispatch.on('elementMouseout.point', function(d) {
  11280. if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,false);
  11281. });
  11282. chart._options = Object.create({}, {
  11283. // simple options, just get/set the necessary values
  11284. width: {get: function(){return width;}, set: function(_){width=_;}},
  11285. height: {get: function(){return height;}, set: function(_){height=_;}},
  11286. xScale: {get: function(){return x;}, set: function(_){x=_;}},
  11287. yScale: {get: function(){return y;}, set: function(_){y=_;}},
  11288. pointScale: {get: function(){return z;}, set: function(_){z=_;}},
  11289. xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
  11290. yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
  11291. pointDomain: {get: function(){return sizeDomain;}, set: function(_){sizeDomain=_;}},
  11292. xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
  11293. yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
  11294. pointRange: {get: function(){return sizeRange;}, set: function(_){sizeRange=_;}},
  11295. forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
  11296. forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
  11297. forcePoint: {get: function(){return forceSize;}, set: function(_){forceSize=_;}},
  11298. interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
  11299. pointActive: {get: function(){return pointActive;}, set: function(_){pointActive=_;}},
  11300. padDataOuter: {get: function(){return padDataOuter;}, set: function(_){padDataOuter=_;}},
  11301. padData: {get: function(){return padData;}, set: function(_){padData=_;}},
  11302. clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
  11303. clipVoronoi: {get: function(){return clipVoronoi;}, set: function(_){clipVoronoi=_;}},
  11304. clipRadius: {get: function(){return clipRadius;}, set: function(_){clipRadius=_;}},
  11305. showVoronoi: {get: function(){return showVoronoi;}, set: function(_){showVoronoi=_;}},
  11306. id: {get: function(){return id;}, set: function(_){id=_;}},
  11307. interactiveUpdateDelay: {get:function(){return interactiveUpdateDelay;}, set: function(_){interactiveUpdateDelay=_;}},
  11308. showLabels: {get: function(){return showLabels;}, set: function(_){ showLabels = _;}},
  11309. pointBorderColor: {get: function(){return pointBorderColor;}, set: function(_){pointBorderColor=_;}},
  11310. // simple functor options
  11311. x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}},
  11312. y: {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}},
  11313. pointSize: {get: function(){return getSize;}, set: function(_){getSize = d3.functor(_);}},
  11314. pointShape: {get: function(){return getShape;}, set: function(_){getShape = d3.functor(_);}},
  11315. // options that require extra logic in the setter
  11316. margin: {get: function(){return margin;}, set: function(_){
  11317. margin.top = _.top !== undefined ? _.top : margin.top;
  11318. margin.right = _.right !== undefined ? _.right : margin.right;
  11319. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  11320. margin.left = _.left !== undefined ? _.left : margin.left;
  11321. }},
  11322. duration: {get: function(){return duration;}, set: function(_){
  11323. duration = _;
  11324. renderWatch.reset(duration);
  11325. }},
  11326. color: {get: function(){return color;}, set: function(_){
  11327. color = nv.utils.getColor(_);
  11328. }},
  11329. useVoronoi: {get: function(){return useVoronoi;}, set: function(_){
  11330. useVoronoi = _;
  11331. if (useVoronoi === false) {
  11332. clipVoronoi = false;
  11333. }
  11334. }}
  11335. });
  11336. nv.utils.initOptions(chart);
  11337. return chart;
  11338. };
  11339. nv.models.scatterChart = function() {
  11340. "use strict";
  11341. //============================================================
  11342. // Public Variables with Default Settings
  11343. //------------------------------------------------------------
  11344. var scatter = nv.models.scatter()
  11345. , xAxis = nv.models.axis()
  11346. , yAxis = nv.models.axis()
  11347. , legend = nv.models.legend()
  11348. , distX = nv.models.distribution()
  11349. , distY = nv.models.distribution()
  11350. , tooltip = nv.models.tooltip()
  11351. ;
  11352. var margin = {top: 30, right: 20, bottom: 50, left: 75}
  11353. , marginTop = null
  11354. , width = null
  11355. , height = null
  11356. , container = null
  11357. , color = nv.utils.defaultColor()
  11358. , x = scatter.xScale()
  11359. , y = scatter.yScale()
  11360. , showDistX = false
  11361. , showDistY = false
  11362. , showLegend = true
  11363. , showXAxis = true
  11364. , showYAxis = true
  11365. , rightAlignYAxis = false
  11366. , state = nv.utils.state()
  11367. , defaultState = null
  11368. , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
  11369. , noData = null
  11370. , duration = 250
  11371. , showLabels = false
  11372. ;
  11373. scatter.xScale(x).yScale(y);
  11374. xAxis.orient('bottom').tickPadding(10);
  11375. yAxis
  11376. .orient((rightAlignYAxis) ? 'right' : 'left')
  11377. .tickPadding(10)
  11378. ;
  11379. distX.axis('x');
  11380. distY.axis('y');
  11381. tooltip
  11382. .headerFormatter(function(d, i) {
  11383. return xAxis.tickFormat()(d, i);
  11384. })
  11385. .valueFormatter(function(d, i) {
  11386. return yAxis.tickFormat()(d, i);
  11387. });
  11388. //============================================================
  11389. // Private Variables
  11390. //------------------------------------------------------------
  11391. var x0, y0
  11392. , renderWatch = nv.utils.renderWatch(dispatch, duration);
  11393. var stateGetter = function(data) {
  11394. return function(){
  11395. return {
  11396. active: data.map(function(d) { return !d.disabled })
  11397. };
  11398. }
  11399. };
  11400. var stateSetter = function(data) {
  11401. return function(state) {
  11402. if (state.active !== undefined)
  11403. data.forEach(function(series,i) {
  11404. series.disabled = !state.active[i];
  11405. });
  11406. }
  11407. };
  11408. function chart(selection) {
  11409. renderWatch.reset();
  11410. renderWatch.models(scatter);
  11411. if (showXAxis) renderWatch.models(xAxis);
  11412. if (showYAxis) renderWatch.models(yAxis);
  11413. if (showDistX) renderWatch.models(distX);
  11414. if (showDistY) renderWatch.models(distY);
  11415. selection.each(function(data) {
  11416. var that = this;
  11417. container = d3.select(this);
  11418. nv.utils.initSVG(container);
  11419. var availableWidth = nv.utils.availableWidth(width, container, margin),
  11420. availableHeight = nv.utils.availableHeight(height, container, margin);
  11421. chart.update = function() {
  11422. if (duration === 0)
  11423. container.call(chart);
  11424. else
  11425. container.transition().duration(duration).call(chart);
  11426. };
  11427. chart.container = this;
  11428. state
  11429. .setter(stateSetter(data), chart.update)
  11430. .getter(stateGetter(data))
  11431. .update();
  11432. // DEPRECATED set state.disableddisabled
  11433. state.disabled = data.map(function(d) { return !!d.disabled });
  11434. if (!defaultState) {
  11435. var key;
  11436. defaultState = {};
  11437. for (key in state) {
  11438. if (state[key] instanceof Array)
  11439. defaultState[key] = state[key].slice(0);
  11440. else
  11441. defaultState[key] = state[key];
  11442. }
  11443. }
  11444. // Display noData message if there's nothing to show.
  11445. if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
  11446. nv.utils.noData(chart, container);
  11447. renderWatch.renderEnd('scatter immediate');
  11448. return chart;
  11449. } else {
  11450. container.selectAll('.nv-noData').remove();
  11451. }
  11452. // Setup Scales
  11453. x = scatter.xScale();
  11454. y = scatter.yScale();
  11455. // Setup containers and skeleton of chart
  11456. var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
  11457. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
  11458. var gEnter = wrapEnter.append('g');
  11459. var g = wrap.select('g');
  11460. // background for pointer events
  11461. gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none");
  11462. gEnter.append('g').attr('class', 'nv-x nv-axis');
  11463. gEnter.append('g').attr('class', 'nv-y nv-axis');
  11464. gEnter.append('g').attr('class', 'nv-scatterWrap');
  11465. gEnter.append('g').attr('class', 'nv-regressionLinesWrap');
  11466. gEnter.append('g').attr('class', 'nv-distWrap');
  11467. gEnter.append('g').attr('class', 'nv-legendWrap');
  11468. if (rightAlignYAxis) {
  11469. g.select(".nv-y.nv-axis")
  11470. .attr("transform", "translate(" + availableWidth + ",0)");
  11471. }
  11472. // Legend
  11473. if (!showLegend) {
  11474. g.select('.nv-legendWrap').selectAll('*').remove();
  11475. } else {
  11476. var legendWidth = availableWidth;
  11477. legend.width(legendWidth);
  11478. wrap.select('.nv-legendWrap')
  11479. .datum(data)
  11480. .call(legend);
  11481. if (!marginTop && legend.height() !== margin.top) {
  11482. margin.top = legend.height();
  11483. availableHeight = nv.utils.availableHeight(height, container, margin);
  11484. }
  11485. wrap.select('.nv-legendWrap')
  11486. .attr('transform', 'translate(0' + ',' + (-margin.top) +')');
  11487. }
  11488. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  11489. // Main Chart Component(s)
  11490. scatter
  11491. .width(availableWidth)
  11492. .height(availableHeight)
  11493. .color(data.map(function(d,i) {
  11494. d.color = d.color || color(d, i);
  11495. return d.color;
  11496. }).filter(function(d,i) { return !data[i].disabled }))
  11497. .showLabels(showLabels);
  11498. wrap.select('.nv-scatterWrap')
  11499. .datum(data.filter(function(d) { return !d.disabled }))
  11500. .call(scatter);
  11501. wrap.select('.nv-regressionLinesWrap')
  11502. .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')');
  11503. var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines')
  11504. .data(function (d) {
  11505. return d;
  11506. });
  11507. regWrap.enter().append('g').attr('class', 'nv-regLines');
  11508. var regLine = regWrap.selectAll('.nv-regLine')
  11509. .data(function (d) {
  11510. return [d]
  11511. });
  11512. regLine.enter()
  11513. .append('line').attr('class', 'nv-regLine')
  11514. .style('stroke-opacity', 0);
  11515. // don't add lines unless we have slope and intercept to use
  11516. regLine.filter(function(d) {
  11517. return d.intercept && d.slope;
  11518. })
  11519. .watchTransition(renderWatch, 'scatterPlusLineChart: regline')
  11520. .attr('x1', x.range()[0])
  11521. .attr('x2', x.range()[1])
  11522. .attr('y1', function (d, i) {
  11523. return y(x.domain()[0] * d.slope + d.intercept)
  11524. })
  11525. .attr('y2', function (d, i) {
  11526. return y(x.domain()[1] * d.slope + d.intercept)
  11527. })
  11528. .style('stroke', function (d, i, j) {
  11529. return color(d, j)
  11530. })
  11531. .style('stroke-opacity', function (d, i) {
  11532. return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1
  11533. });
  11534. // Setup Axes
  11535. if (showXAxis) {
  11536. xAxis
  11537. .scale(x)
  11538. ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
  11539. .tickSize( -availableHeight , 0);
  11540. g.select('.nv-x.nv-axis')
  11541. .attr('transform', 'translate(0,' + y.range()[0] + ')')
  11542. .call(xAxis);
  11543. }
  11544. if (showYAxis) {
  11545. yAxis
  11546. .scale(y)
  11547. ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
  11548. .tickSize( -availableWidth, 0);
  11549. g.select('.nv-y.nv-axis')
  11550. .call(yAxis);
  11551. }
  11552. // Setup Distribution
  11553. if (showDistX) {
  11554. distX
  11555. .getData(scatter.x())
  11556. .scale(x)
  11557. .width(availableWidth)
  11558. .color(data.map(function(d,i) {
  11559. return d.color || color(d, i);
  11560. }).filter(function(d,i) { return !data[i].disabled }));
  11561. gEnter.select('.nv-distWrap').append('g')
  11562. .attr('class', 'nv-distributionX');
  11563. g.select('.nv-distributionX')
  11564. .attr('transform', 'translate(0,' + y.range()[0] + ')')
  11565. .datum(data.filter(function(d) { return !d.disabled }))
  11566. .call(distX);
  11567. }
  11568. if (showDistY) {
  11569. distY
  11570. .getData(scatter.y())
  11571. .scale(y)
  11572. .width(availableHeight)
  11573. .color(data.map(function(d,i) {
  11574. return d.color || color(d, i);
  11575. }).filter(function(d,i) { return !data[i].disabled }));
  11576. gEnter.select('.nv-distWrap').append('g')
  11577. .attr('class', 'nv-distributionY');
  11578. g.select('.nv-distributionY')
  11579. .attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
  11580. .datum(data.filter(function(d) { return !d.disabled }))
  11581. .call(distY);
  11582. }
  11583. //============================================================
  11584. // Event Handling/Dispatching (in chart's scope)
  11585. //------------------------------------------------------------
  11586. legend.dispatch.on('stateChange', function(newState) {
  11587. for (var key in newState)
  11588. state[key] = newState[key];
  11589. dispatch.stateChange(state);
  11590. chart.update();
  11591. });
  11592. // Update chart from a state object passed to event handler
  11593. dispatch.on('changeState', function(e) {
  11594. if (typeof e.disabled !== 'undefined') {
  11595. data.forEach(function(series,i) {
  11596. series.disabled = e.disabled[i];
  11597. });
  11598. state.disabled = e.disabled;
  11599. }
  11600. chart.update();
  11601. });
  11602. // mouseover needs availableHeight so we just keep scatter mouse events inside the chart block
  11603. scatter.dispatch.on('elementMouseout.tooltip', function(evt) {
  11604. tooltip.hidden(true);
  11605. container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex)
  11606. .attr('y1', 0);
  11607. container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
  11608. .attr('x2', distY.size());
  11609. });
  11610. scatter.dispatch.on('elementMouseover.tooltip', function(evt) {
  11611. container.select('.nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex)
  11612. .attr('y1', evt.relativePos[1] - availableHeight);
  11613. container.select('.nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
  11614. .attr('x2', evt.relativePos[0] + distX.size());
  11615. tooltip.data(evt).hidden(false);
  11616. });
  11617. //store old scales for use in transitions on update
  11618. x0 = x.copy();
  11619. y0 = y.copy();
  11620. });
  11621. renderWatch.renderEnd('scatter with line immediate');
  11622. return chart;
  11623. }
  11624. //============================================================
  11625. // Expose Public Variables
  11626. //------------------------------------------------------------
  11627. // expose chart's sub-components
  11628. chart.dispatch = dispatch;
  11629. chart.scatter = scatter;
  11630. chart.legend = legend;
  11631. chart.xAxis = xAxis;
  11632. chart.yAxis = yAxis;
  11633. chart.distX = distX;
  11634. chart.distY = distY;
  11635. chart.tooltip = tooltip;
  11636. chart.options = nv.utils.optionsFunc.bind(chart);
  11637. chart._options = Object.create({}, {
  11638. // simple options, just get/set the necessary values
  11639. width: {get: function(){return width;}, set: function(_){width=_;}},
  11640. height: {get: function(){return height;}, set: function(_){height=_;}},
  11641. container: {get: function(){return container;}, set: function(_){container=_;}},
  11642. showDistX: {get: function(){return showDistX;}, set: function(_){showDistX=_;}},
  11643. showDistY: {get: function(){return showDistY;}, set: function(_){showDistY=_;}},
  11644. showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
  11645. showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
  11646. showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
  11647. defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
  11648. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  11649. duration: {get: function(){return duration;}, set: function(_){duration=_;}},
  11650. showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}},
  11651. // options that require extra logic in the setter
  11652. margin: {get: function(){return margin;}, set: function(_){
  11653. if (_.top !== undefined) {
  11654. margin.top = _.top;
  11655. marginTop = _.top;
  11656. }
  11657. margin.right = _.right !== undefined ? _.right : margin.right;
  11658. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  11659. margin.left = _.left !== undefined ? _.left : margin.left;
  11660. }},
  11661. rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
  11662. rightAlignYAxis = _;
  11663. yAxis.orient( (_) ? 'right' : 'left');
  11664. }},
  11665. color: {get: function(){return color;}, set: function(_){
  11666. color = nv.utils.getColor(_);
  11667. legend.color(color);
  11668. distX.color(color);
  11669. distY.color(color);
  11670. }}
  11671. });
  11672. nv.utils.inheritOptions(chart, scatter);
  11673. nv.utils.initOptions(chart);
  11674. return chart;
  11675. };
  11676. nv.models.sparkline = function() {
  11677. "use strict";
  11678. //============================================================
  11679. // Public Variables with Default Settings
  11680. //------------------------------------------------------------
  11681. var margin = {top: 2, right: 0, bottom: 2, left: 0}
  11682. , width = 400
  11683. , height = 32
  11684. , container = null
  11685. , animate = true
  11686. , x = d3.scale.linear()
  11687. , y = d3.scale.linear()
  11688. , getX = function(d) { return d.x }
  11689. , getY = function(d) { return d.y }
  11690. , color = nv.utils.getColor(['#000'])
  11691. , xDomain
  11692. , yDomain
  11693. , xRange
  11694. , yRange
  11695. , showMinMaxPoints = true
  11696. , showCurrentPoint = true
  11697. , dispatch = d3.dispatch('renderEnd')
  11698. ;
  11699. //============================================================
  11700. // Private Variables
  11701. //------------------------------------------------------------
  11702. var renderWatch = nv.utils.renderWatch(dispatch);
  11703. function chart(selection) {
  11704. renderWatch.reset();
  11705. selection.each(function(data) {
  11706. var availableWidth = width - margin.left - margin.right,
  11707. availableHeight = height - margin.top - margin.bottom;
  11708. container = d3.select(this);
  11709. nv.utils.initSVG(container);
  11710. // Setup Scales
  11711. x .domain(xDomain || d3.extent(data, getX ))
  11712. .range(xRange || [0, availableWidth]);
  11713. y .domain(yDomain || d3.extent(data, getY ))
  11714. .range(yRange || [availableHeight, 0]);
  11715. // Setup containers and skeleton of chart
  11716. var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]);
  11717. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline');
  11718. var gEnter = wrapEnter.append('g');
  11719. var g = wrap.select('g');
  11720. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
  11721. var paths = wrap.selectAll('path')
  11722. .data(function(d) { return [d] });
  11723. paths.enter().append('path');
  11724. paths.exit().remove();
  11725. paths
  11726. .style('stroke', function(d,i) { return d.color || color(d, i) })
  11727. .attr('d', d3.svg.line()
  11728. .x(function(d,i) { return x(getX(d,i)) })
  11729. .y(function(d,i) { return y(getY(d,i)) })
  11730. );
  11731. // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent)
  11732. var points = wrap.selectAll('circle.nv-point')
  11733. .data(function(data) {
  11734. var yValues = data.map(function(d, i) { return getY(d,i); });
  11735. function pointIndex(index) {
  11736. if (index != -1) {
  11737. var result = data[index];
  11738. result.pointIndex = index;
  11739. return result;
  11740. } else {
  11741. return null;
  11742. }
  11743. }
  11744. var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])),
  11745. minPoint = pointIndex(yValues.indexOf(y.domain()[0])),
  11746. currentPoint = pointIndex(yValues.length - 1);
  11747. return [(showMinMaxPoints ? minPoint : null), (showMinMaxPoints ? maxPoint : null), (showCurrentPoint ? currentPoint : null)].filter(function (d) {return d != null;});
  11748. });
  11749. points.enter().append('circle');
  11750. points.exit().remove();
  11751. points
  11752. .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) })
  11753. .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) })
  11754. .attr('r', 2)
  11755. .attr('class', function(d,i) {
  11756. return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' :
  11757. getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue'
  11758. });
  11759. });
  11760. renderWatch.renderEnd('sparkline immediate');
  11761. return chart;
  11762. }
  11763. //============================================================
  11764. // Expose Public Variables
  11765. //------------------------------------------------------------
  11766. chart.options = nv.utils.optionsFunc.bind(chart);
  11767. chart._options = Object.create({}, {
  11768. // simple options, just get/set the necessary values
  11769. width: {get: function(){return width;}, set: function(_){width=_;}},
  11770. height: {get: function(){return height;}, set: function(_){height=_;}},
  11771. xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
  11772. yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
  11773. xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
  11774. yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
  11775. xScale: {get: function(){return x;}, set: function(_){x=_;}},
  11776. yScale: {get: function(){return y;}, set: function(_){y=_;}},
  11777. animate: {get: function(){return animate;}, set: function(_){animate=_;}},
  11778. showMinMaxPoints: {get: function(){return showMinMaxPoints;}, set: function(_){showMinMaxPoints=_;}},
  11779. showCurrentPoint: {get: function(){return showCurrentPoint;}, set: function(_){showCurrentPoint=_;}},
  11780. //functor options
  11781. x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}},
  11782. y: {get: function(){return getY;}, set: function(_){getY=d3.functor(_);}},
  11783. // options that require extra logic in the setter
  11784. margin: {get: function(){return margin;}, set: function(_){
  11785. margin.top = _.top !== undefined ? _.top : margin.top;
  11786. margin.right = _.right !== undefined ? _.right : margin.right;
  11787. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  11788. margin.left = _.left !== undefined ? _.left : margin.left;
  11789. }},
  11790. color: {get: function(){return color;}, set: function(_){
  11791. color = nv.utils.getColor(_);
  11792. }}
  11793. });
  11794. chart.dispatch = dispatch;
  11795. nv.utils.initOptions(chart);
  11796. return chart;
  11797. };
  11798. nv.models.sparklinePlus = function() {
  11799. "use strict";
  11800. //============================================================
  11801. // Public Variables with Default Settings
  11802. //------------------------------------------------------------
  11803. var sparkline = nv.models.sparkline();
  11804. var margin = {top: 15, right: 100, bottom: 10, left: 50}
  11805. , width = null
  11806. , height = null
  11807. , x
  11808. , y
  11809. , index = []
  11810. , paused = false
  11811. , xTickFormat = d3.format(',r')
  11812. , yTickFormat = d3.format(',.2f')
  11813. , showLastValue = true
  11814. , alignValue = true
  11815. , rightAlignValue = false
  11816. , noData = null
  11817. , dispatch = d3.dispatch('renderEnd')
  11818. ;
  11819. //============================================================
  11820. // Private Variables
  11821. //------------------------------------------------------------
  11822. var renderWatch = nv.utils.renderWatch(dispatch);
  11823. function chart(selection) {
  11824. renderWatch.reset();
  11825. renderWatch.models(sparkline);
  11826. selection.each(function(data) {
  11827. var container = d3.select(this);
  11828. nv.utils.initSVG(container);
  11829. var availableWidth = nv.utils.availableWidth(width, container, margin),
  11830. availableHeight = nv.utils.availableHeight(height, container, margin);
  11831. chart.update = function() { container.call(chart); };
  11832. chart.container = this;
  11833. // Display No Data message if there's nothing to show.
  11834. if (!data || !data.length) {
  11835. nv.utils.noData(chart, container)
  11836. return chart;
  11837. } else {
  11838. container.selectAll('.nv-noData').remove();
  11839. }
  11840. var currentValue = sparkline.y()(data[data.length-1], data.length-1);
  11841. // Setup Scales
  11842. x = sparkline.xScale();
  11843. y = sparkline.yScale();
  11844. // Setup containers and skeleton of chart
  11845. var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]);
  11846. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus');
  11847. var gEnter = wrapEnter.append('g');
  11848. var g = wrap.select('g');
  11849. gEnter.append('g').attr('class', 'nv-sparklineWrap');
  11850. gEnter.append('g').attr('class', 'nv-valueWrap');
  11851. gEnter.append('g').attr('class', 'nv-hoverArea');
  11852. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  11853. // Main Chart Component(s)
  11854. var sparklineWrap = g.select('.nv-sparklineWrap');
  11855. sparkline.width(availableWidth).height(availableHeight);
  11856. sparklineWrap.call(sparkline);
  11857. if (showLastValue) {
  11858. var valueWrap = g.select('.nv-valueWrap');
  11859. var value = valueWrap.selectAll('.nv-currentValue')
  11860. .data([currentValue]);
  11861. value.enter().append('text').attr('class', 'nv-currentValue')
  11862. .attr('dx', rightAlignValue ? -8 : 8)
  11863. .attr('dy', '.9em')
  11864. .style('text-anchor', rightAlignValue ? 'end' : 'start');
  11865. value
  11866. .attr('x', availableWidth + (rightAlignValue ? margin.right : 0))
  11867. .attr('y', alignValue ? function (d) {
  11868. return y(d)
  11869. } : 0)
  11870. .style('fill', sparkline.color()(data[data.length - 1], data.length - 1))
  11871. .text(yTickFormat(currentValue));
  11872. }
  11873. gEnter.select('.nv-hoverArea').append('rect')
  11874. .on('mousemove', sparklineHover)
  11875. .on('click', function() { paused = !paused })
  11876. .on('mouseout', function() { index = []; updateValueLine(); });
  11877. g.select('.nv-hoverArea rect')
  11878. .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' })
  11879. .attr('width', availableWidth + margin.left + margin.right)
  11880. .attr('height', availableHeight + margin.top);
  11881. //index is currently global (within the chart), may or may not keep it that way
  11882. function updateValueLine() {
  11883. if (paused) return;
  11884. var hoverValue = g.selectAll('.nv-hoverValue').data(index);
  11885. var hoverEnter = hoverValue.enter()
  11886. .append('g').attr('class', 'nv-hoverValue')
  11887. .style('stroke-opacity', 0)
  11888. .style('fill-opacity', 0);
  11889. hoverValue.exit()
  11890. .transition().duration(250)
  11891. .style('stroke-opacity', 0)
  11892. .style('fill-opacity', 0)
  11893. .remove();
  11894. hoverValue
  11895. .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' })
  11896. .transition().duration(250)
  11897. .style('stroke-opacity', 1)
  11898. .style('fill-opacity', 1);
  11899. if (!index.length) return;
  11900. hoverEnter.append('line')
  11901. .attr('x1', 0)
  11902. .attr('y1', -margin.top)
  11903. .attr('x2', 0)
  11904. .attr('y2', availableHeight);
  11905. hoverEnter.append('text').attr('class', 'nv-xValue')
  11906. .attr('x', -6)
  11907. .attr('y', -margin.top)
  11908. .attr('text-anchor', 'end')
  11909. .attr('dy', '.9em');
  11910. g.select('.nv-hoverValue .nv-xValue')
  11911. .text(xTickFormat(sparkline.x()(data[index[0]], index[0])));
  11912. hoverEnter.append('text').attr('class', 'nv-yValue')
  11913. .attr('x', 6)
  11914. .attr('y', -margin.top)
  11915. .attr('text-anchor', 'start')
  11916. .attr('dy', '.9em');
  11917. g.select('.nv-hoverValue .nv-yValue')
  11918. .text(yTickFormat(sparkline.y()(data[index[0]], index[0])));
  11919. }
  11920. function sparklineHover() {
  11921. if (paused) return;
  11922. var pos = d3.mouse(this)[0] - margin.left;
  11923. function getClosestIndex(data, x) {
  11924. var distance = Math.abs(sparkline.x()(data[0], 0) - x);
  11925. var closestIndex = 0;
  11926. for (var i = 0; i < data.length; i++){
  11927. if (Math.abs(sparkline.x()(data[i], i) - x) < distance) {
  11928. distance = Math.abs(sparkline.x()(data[i], i) - x);
  11929. closestIndex = i;
  11930. }
  11931. }
  11932. return closestIndex;
  11933. }
  11934. index = [getClosestIndex(data, Math.round(x.invert(pos)))];
  11935. updateValueLine();
  11936. }
  11937. });
  11938. renderWatch.renderEnd('sparklinePlus immediate');
  11939. return chart;
  11940. }
  11941. //============================================================
  11942. // Expose Public Variables
  11943. //------------------------------------------------------------
  11944. // expose chart's sub-components
  11945. chart.dispatch = dispatch;
  11946. chart.sparkline = sparkline;
  11947. chart.options = nv.utils.optionsFunc.bind(chart);
  11948. chart._options = Object.create({}, {
  11949. // simple options, just get/set the necessary values
  11950. width: {get: function(){return width;}, set: function(_){width=_;}},
  11951. height: {get: function(){return height;}, set: function(_){height=_;}},
  11952. xTickFormat: {get: function(){return xTickFormat;}, set: function(_){xTickFormat=_;}},
  11953. yTickFormat: {get: function(){return yTickFormat;}, set: function(_){yTickFormat=_;}},
  11954. showLastValue: {get: function(){return showLastValue;}, set: function(_){showLastValue=_;}},
  11955. alignValue: {get: function(){return alignValue;}, set: function(_){alignValue=_;}},
  11956. rightAlignValue: {get: function(){return rightAlignValue;}, set: function(_){rightAlignValue=_;}},
  11957. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  11958. // options that require extra logic in the setter
  11959. margin: {get: function(){return margin;}, set: function(_){
  11960. margin.top = _.top !== undefined ? _.top : margin.top;
  11961. margin.right = _.right !== undefined ? _.right : margin.right;
  11962. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  11963. margin.left = _.left !== undefined ? _.left : margin.left;
  11964. }}
  11965. });
  11966. nv.utils.inheritOptions(chart, sparkline);
  11967. nv.utils.initOptions(chart);
  11968. return chart;
  11969. };
  11970. nv.models.stackedArea = function() {
  11971. "use strict";
  11972. //============================================================
  11973. // Public Variables with Default Settings
  11974. //------------------------------------------------------------
  11975. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  11976. , width = 960
  11977. , height = 500
  11978. , color = nv.utils.defaultColor() // a function that computes the color
  11979. , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one
  11980. , container = null
  11981. , getX = function(d) { return d.x } // accessor to get the x value from a data point
  11982. , getY = function(d) { return d.y } // accessor to get the y value from a data point
  11983. , 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
  11984. , style = 'stack'
  11985. , offset = 'zero'
  11986. , order = 'default'
  11987. , interpolate = 'linear' // controls the line interpolation
  11988. , clipEdge = false // if true, masks lines within x and y scale
  11989. , x //can be accessed via chart.xScale()
  11990. , y //can be accessed via chart.yScale()
  11991. , scatter = nv.models.scatter()
  11992. , duration = 250
  11993. , dispatch = d3.dispatch('areaClick', 'areaMouseover', 'areaMouseout','renderEnd', 'elementClick', 'elementMouseover', 'elementMouseout')
  11994. ;
  11995. scatter
  11996. .pointSize(2.2) // default size
  11997. .pointDomain([2.2, 2.2]) // all the same size by default
  11998. ;
  11999. /************************************
  12000. * offset:
  12001. * 'wiggle' (stream)
  12002. * 'zero' (stacked)
  12003. * 'expand' (normalize to 100%)
  12004. * 'silhouette' (simple centered)
  12005. *
  12006. * order:
  12007. * 'inside-out' (stream)
  12008. * 'default' (input order)
  12009. ************************************/
  12010. var renderWatch = nv.utils.renderWatch(dispatch, duration);
  12011. function chart(selection) {
  12012. renderWatch.reset();
  12013. renderWatch.models(scatter);
  12014. selection.each(function(data) {
  12015. var availableWidth = width - margin.left - margin.right,
  12016. availableHeight = height - margin.top - margin.bottom;
  12017. container = d3.select(this);
  12018. nv.utils.initSVG(container);
  12019. // Setup Scales
  12020. x = scatter.xScale();
  12021. y = scatter.yScale();
  12022. var dataRaw = data;
  12023. // Injecting point index into each point because d3.layout.stack().out does not give index
  12024. data.forEach(function(aseries, i) {
  12025. aseries.seriesIndex = i;
  12026. aseries.values = aseries.values.map(function(d, j) {
  12027. d.index = j;
  12028. d.seriesIndex = i;
  12029. return d;
  12030. });
  12031. });
  12032. var dataFiltered = data.filter(function(series) {
  12033. return !series.disabled;
  12034. });
  12035. data = d3.layout.stack()
  12036. .order(order)
  12037. .offset(offset)
  12038. .values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion
  12039. .x(getX)
  12040. .y(getY)
  12041. .out(function(d, y0, y) {
  12042. d.display = {
  12043. y: y,
  12044. y0: y0
  12045. };
  12046. })
  12047. (dataFiltered);
  12048. // Setup containers and skeleton of chart
  12049. var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]);
  12050. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea');
  12051. var defsEnter = wrapEnter.append('defs');
  12052. var gEnter = wrapEnter.append('g');
  12053. var g = wrap.select('g');
  12054. gEnter.append('g').attr('class', 'nv-areaWrap');
  12055. gEnter.append('g').attr('class', 'nv-scatterWrap');
  12056. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  12057. // If the user has not specified forceY, make sure 0 is included in the domain
  12058. // Otherwise, use user-specified values for forceY
  12059. if (scatter.forceY().length == 0) {
  12060. scatter.forceY().push(0);
  12061. }
  12062. scatter
  12063. .width(availableWidth)
  12064. .height(availableHeight)
  12065. .x(getX)
  12066. .y(function(d) {
  12067. if (d.display !== undefined) { return d.display.y + d.display.y0; }
  12068. })
  12069. .color(data.map(function(d,i) {
  12070. d.color = d.color || color(d, d.seriesIndex);
  12071. return d.color;
  12072. }));
  12073. var scatterWrap = g.select('.nv-scatterWrap')
  12074. .datum(data);
  12075. scatterWrap.call(scatter);
  12076. defsEnter.append('clipPath')
  12077. .attr('id', 'nv-edge-clip-' + id)
  12078. .append('rect');
  12079. wrap.select('#nv-edge-clip-' + id + ' rect')
  12080. .attr('width', availableWidth)
  12081. .attr('height', availableHeight);
  12082. g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
  12083. var area = d3.svg.area()
  12084. .defined(defined)
  12085. .x(function(d,i) { return x(getX(d,i)) })
  12086. .y0(function(d) {
  12087. return y(d.display.y0)
  12088. })
  12089. .y1(function(d) {
  12090. return y(d.display.y + d.display.y0)
  12091. })
  12092. .interpolate(interpolate);
  12093. var zeroArea = d3.svg.area()
  12094. .defined(defined)
  12095. .x(function(d,i) { return x(getX(d,i)) })
  12096. .y0(function(d) { return y(d.display.y0) })
  12097. .y1(function(d) { return y(d.display.y0) });
  12098. var path = g.select('.nv-areaWrap').selectAll('path.nv-area')
  12099. .data(function(d) { return d });
  12100. path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i })
  12101. .attr('d', function(d,i){
  12102. return zeroArea(d.values, d.seriesIndex);
  12103. })
  12104. .on('mouseover', function(d,i) {
  12105. d3.select(this).classed('hover', true);
  12106. dispatch.areaMouseover({
  12107. point: d,
  12108. series: d.key,
  12109. pos: [d3.event.pageX, d3.event.pageY],
  12110. seriesIndex: d.seriesIndex
  12111. });
  12112. })
  12113. .on('mouseout', function(d,i) {
  12114. d3.select(this).classed('hover', false);
  12115. dispatch.areaMouseout({
  12116. point: d,
  12117. series: d.key,
  12118. pos: [d3.event.pageX, d3.event.pageY],
  12119. seriesIndex: d.seriesIndex
  12120. });
  12121. })
  12122. .on('click', function(d,i) {
  12123. d3.select(this).classed('hover', false);
  12124. dispatch.areaClick({
  12125. point: d,
  12126. series: d.key,
  12127. pos: [d3.event.pageX, d3.event.pageY],
  12128. seriesIndex: d.seriesIndex
  12129. });
  12130. });
  12131. path.exit().remove();
  12132. path.style('fill', function(d,i){
  12133. return d.color || color(d, d.seriesIndex)
  12134. })
  12135. .style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) });
  12136. path.watchTransition(renderWatch,'stackedArea path')
  12137. .attr('d', function(d,i) {
  12138. return area(d.values,i)
  12139. });
  12140. //============================================================
  12141. // Event Handling/Dispatching (in chart's scope)
  12142. //------------------------------------------------------------
  12143. scatter.dispatch.on('elementMouseover.area', function(e) {
  12144. g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true);
  12145. });
  12146. scatter.dispatch.on('elementMouseout.area', function(e) {
  12147. g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false);
  12148. });
  12149. //Special offset functions
  12150. chart.d3_stackedOffset_stackPercent = function(stackData) {
  12151. var n = stackData.length, //How many series
  12152. m = stackData[0].length, //how many points per series
  12153. i,
  12154. j,
  12155. o,
  12156. y0 = [];
  12157. for (j = 0; j < m; ++j) { //Looping through all points
  12158. for (i = 0, o = 0; i < dataRaw.length; i++) { //looping through all series
  12159. o += getY(dataRaw[i].values[j]); //total y value of all series at a certian point in time.
  12160. }
  12161. if (o) for (i = 0; i < n; i++) { //(total y value of all series at point in time i) != 0
  12162. stackData[i][j][1] /= o;
  12163. } else { //(total y value of all series at point in time i) == 0
  12164. for (i = 0; i < n; i++) {
  12165. stackData[i][j][1] = 0;
  12166. }
  12167. }
  12168. }
  12169. for (j = 0; j < m; ++j) y0[j] = 0;
  12170. return y0;
  12171. };
  12172. });
  12173. renderWatch.renderEnd('stackedArea immediate');
  12174. return chart;
  12175. }
  12176. //============================================================
  12177. // Global getters and setters
  12178. //------------------------------------------------------------
  12179. chart.dispatch = dispatch;
  12180. chart.scatter = scatter;
  12181. scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); });
  12182. scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); });
  12183. scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); });
  12184. chart.interpolate = function(_) {
  12185. if (!arguments.length) return interpolate;
  12186. interpolate = _;
  12187. return chart;
  12188. };
  12189. chart.duration = function(_) {
  12190. if (!arguments.length) return duration;
  12191. duration = _;
  12192. renderWatch.reset(duration);
  12193. scatter.duration(duration);
  12194. return chart;
  12195. };
  12196. chart.dispatch = dispatch;
  12197. chart.scatter = scatter;
  12198. chart.options = nv.utils.optionsFunc.bind(chart);
  12199. chart._options = Object.create({}, {
  12200. // simple options, just get/set the necessary values
  12201. width: {get: function(){return width;}, set: function(_){width=_;}},
  12202. height: {get: function(){return height;}, set: function(_){height=_;}},
  12203. defined: {get: function(){return defined;}, set: function(_){defined=_;}},
  12204. clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
  12205. offset: {get: function(){return offset;}, set: function(_){offset=_;}},
  12206. order: {get: function(){return order;}, set: function(_){order=_;}},
  12207. interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
  12208. // simple functor options
  12209. x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}},
  12210. y: {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}},
  12211. // options that require extra logic in the setter
  12212. margin: {get: function(){return margin;}, set: function(_){
  12213. margin.top = _.top !== undefined ? _.top : margin.top;
  12214. margin.right = _.right !== undefined ? _.right : margin.right;
  12215. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  12216. margin.left = _.left !== undefined ? _.left : margin.left;
  12217. }},
  12218. color: {get: function(){return color;}, set: function(_){
  12219. color = nv.utils.getColor(_);
  12220. }},
  12221. style: {get: function(){return style;}, set: function(_){
  12222. style = _;
  12223. switch (style) {
  12224. case 'stack':
  12225. chart.offset('zero');
  12226. chart.order('default');
  12227. break;
  12228. case 'stream':
  12229. chart.offset('wiggle');
  12230. chart.order('inside-out');
  12231. break;
  12232. case 'stream-center':
  12233. chart.offset('silhouette');
  12234. chart.order('inside-out');
  12235. break;
  12236. case 'expand':
  12237. chart.offset('expand');
  12238. chart.order('default');
  12239. break;
  12240. case 'stack_percent':
  12241. chart.offset(chart.d3_stackedOffset_stackPercent);
  12242. chart.order('default');
  12243. break;
  12244. }
  12245. }},
  12246. duration: {get: function(){return duration;}, set: function(_){
  12247. duration = _;
  12248. renderWatch.reset(duration);
  12249. scatter.duration(duration);
  12250. }}
  12251. });
  12252. nv.utils.inheritOptions(chart, scatter);
  12253. nv.utils.initOptions(chart);
  12254. return chart;
  12255. };
  12256. nv.models.stackedAreaChart = function() {
  12257. "use strict";
  12258. //============================================================
  12259. // Public Variables with Default Settings
  12260. //------------------------------------------------------------
  12261. var stacked = nv.models.stackedArea()
  12262. , xAxis = nv.models.axis()
  12263. , yAxis = nv.models.axis()
  12264. , legend = nv.models.legend()
  12265. , controls = nv.models.legend()
  12266. , interactiveLayer = nv.interactiveGuideline()
  12267. , tooltip = nv.models.tooltip()
  12268. , focus = nv.models.focus(nv.models.stackedArea())
  12269. ;
  12270. var margin = {top: 10, right: 25, bottom: 50, left: 60}
  12271. , marginTop = null
  12272. , width = null
  12273. , height = null
  12274. , color = nv.utils.defaultColor()
  12275. , showControls = true
  12276. , showLegend = true
  12277. , legendPosition = 'top'
  12278. , showXAxis = true
  12279. , showYAxis = true
  12280. , rightAlignYAxis = false
  12281. , focusEnable = false
  12282. , useInteractiveGuideline = false
  12283. , showTotalInTooltip = true
  12284. , totalLabel = 'TOTAL'
  12285. , x //can be accessed via chart.xScale()
  12286. , y //can be accessed via chart.yScale()
  12287. , state = nv.utils.state()
  12288. , defaultState = null
  12289. , noData = null
  12290. , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd')
  12291. , controlWidth = 250
  12292. , controlOptions = ['Stacked','Stream','Expanded']
  12293. , controlLabels = {}
  12294. , duration = 250
  12295. ;
  12296. state.style = stacked.style();
  12297. xAxis.orient('bottom').tickPadding(7);
  12298. yAxis.orient((rightAlignYAxis) ? 'right' : 'left');
  12299. tooltip
  12300. .headerFormatter(function(d, i) {
  12301. return xAxis.tickFormat()(d, i);
  12302. })
  12303. .valueFormatter(function(d, i) {
  12304. return yAxis.tickFormat()(d, i);
  12305. });
  12306. interactiveLayer.tooltip
  12307. .headerFormatter(function(d, i) {
  12308. return xAxis.tickFormat()(d, i);
  12309. })
  12310. .valueFormatter(function(d, i) {
  12311. return d == null ? "N/A" : yAxis.tickFormat()(d, i);
  12312. });
  12313. var oldYTickFormat = null,
  12314. oldValueFormatter = null;
  12315. controls.updateState(false);
  12316. //============================================================
  12317. // Private Variables
  12318. //------------------------------------------------------------
  12319. var renderWatch = nv.utils.renderWatch(dispatch);
  12320. var style = stacked.style();
  12321. var stateGetter = function(data) {
  12322. return function(){
  12323. return {
  12324. active: data.map(function(d) { return !d.disabled }),
  12325. style: stacked.style()
  12326. };
  12327. }
  12328. };
  12329. var stateSetter = function(data) {
  12330. return function(state) {
  12331. if (state.style !== undefined)
  12332. style = state.style;
  12333. if (state.active !== undefined)
  12334. data.forEach(function(series,i) {
  12335. series.disabled = !state.active[i];
  12336. });
  12337. }
  12338. };
  12339. var percentFormatter = d3.format('%');
  12340. function chart(selection) {
  12341. renderWatch.reset();
  12342. renderWatch.models(stacked);
  12343. if (showXAxis) renderWatch.models(xAxis);
  12344. if (showYAxis) renderWatch.models(yAxis);
  12345. selection.each(function(data) {
  12346. var container = d3.select(this),
  12347. that = this;
  12348. nv.utils.initSVG(container);
  12349. var availableWidth = nv.utils.availableWidth(width, container, margin),
  12350. availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0);
  12351. chart.update = function() { container.transition().duration(duration).call(chart); };
  12352. chart.container = this;
  12353. state
  12354. .setter(stateSetter(data), chart.update)
  12355. .getter(stateGetter(data))
  12356. .update();
  12357. // DEPRECATED set state.disabled
  12358. state.disabled = data.map(function(d) { return !!d.disabled });
  12359. if (!defaultState) {
  12360. var key;
  12361. defaultState = {};
  12362. for (key in state) {
  12363. if (state[key] instanceof Array)
  12364. defaultState[key] = state[key].slice(0);
  12365. else
  12366. defaultState[key] = state[key];
  12367. }
  12368. }
  12369. // Display No Data message if there's nothing to show.
  12370. if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
  12371. nv.utils.noData(chart, container)
  12372. return chart;
  12373. } else {
  12374. container.selectAll('.nv-noData').remove();
  12375. }
  12376. // Setup Scales
  12377. x = stacked.xScale();
  12378. y = stacked.yScale();
  12379. // Setup containers and skeleton of chart
  12380. var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]);
  12381. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g');
  12382. var g = wrap.select('g');
  12383. gEnter.append('g').attr('class', 'nv-legendWrap');
  12384. gEnter.append('g').attr('class', 'nv-controlsWrap');
  12385. var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
  12386. focusEnter.append('g').attr('class', 'nv-background').append('rect');
  12387. focusEnter.append('g').attr('class', 'nv-x nv-axis');
  12388. focusEnter.append('g').attr('class', 'nv-y nv-axis');
  12389. focusEnter.append('g').attr('class', 'nv-stackedWrap');
  12390. focusEnter.append('g').attr('class', 'nv-interactive');
  12391. // g.select("rect").attr("width",availableWidth).attr("height",availableHeight);
  12392. var contextEnter = gEnter.append('g').attr('class', 'nv-focusWrap');
  12393. // Legend
  12394. if (!showLegend) {
  12395. g.select('.nv-legendWrap').selectAll('*').remove();
  12396. } else {
  12397. var legendWidth = (showControls && legendPosition === 'top') ? availableWidth - controlWidth : availableWidth;
  12398. legend.width(legendWidth);
  12399. g.select('.nv-legendWrap').datum(data).call(legend);
  12400. if (legendPosition === 'bottom') {
  12401. // constant from axis.js, plus some margin for better layout
  12402. var xAxisHeight = (showXAxis ? 12 : 0) + 10;
  12403. margin.bottom = Math.max(legend.height() + xAxisHeight, margin.bottom);
  12404. availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0);
  12405. var legendTop = availableHeight + xAxisHeight;
  12406. g.select('.nv-legendWrap')
  12407. .attr('transform', 'translate(0,' + legendTop +')');
  12408. } else if (legendPosition === 'top') {
  12409. if (!marginTop && margin.top != legend.height()) {
  12410. margin.top = legend.height();
  12411. availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0);
  12412. }
  12413. g.select('.nv-legendWrap')
  12414. .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')');
  12415. }
  12416. }
  12417. // Controls
  12418. if (!showControls) {
  12419. g.select('.nv-controlsWrap').selectAll('*').remove();
  12420. } else {
  12421. var controlsData = [
  12422. {
  12423. key: controlLabels.stacked || 'Stacked',
  12424. metaKey: 'Stacked',
  12425. disabled: stacked.style() != 'stack',
  12426. style: 'stack'
  12427. },
  12428. {
  12429. key: controlLabels.stream || 'Stream',
  12430. metaKey: 'Stream',
  12431. disabled: stacked.style() != 'stream',
  12432. style: 'stream'
  12433. },
  12434. {
  12435. key: controlLabels.expanded || 'Expanded',
  12436. metaKey: 'Expanded',
  12437. disabled: stacked.style() != 'expand',
  12438. style: 'expand'
  12439. },
  12440. {
  12441. key: controlLabels.stack_percent || 'Stack %',
  12442. metaKey: 'Stack_Percent',
  12443. disabled: stacked.style() != 'stack_percent',
  12444. style: 'stack_percent'
  12445. }
  12446. ];
  12447. controlWidth = (controlOptions.length/3) * 260;
  12448. controlsData = controlsData.filter(function(d) {
  12449. return controlOptions.indexOf(d.metaKey) !== -1;
  12450. });
  12451. controls
  12452. .width( controlWidth )
  12453. .color(['#444', '#444', '#444']);
  12454. g.select('.nv-controlsWrap')
  12455. .datum(controlsData)
  12456. .call(controls);
  12457. var requiredTop = Math.max(controls.height(), showLegend && (legendPosition === 'top') ? legend.height() : 0);
  12458. if ( margin.top != requiredTop ) {
  12459. margin.top = requiredTop;
  12460. availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0);
  12461. }
  12462. g.select('.nv-controlsWrap')
  12463. .attr('transform', 'translate(0,' + (-margin.top) +')');
  12464. }
  12465. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  12466. if (rightAlignYAxis) {
  12467. g.select(".nv-y.nv-axis")
  12468. .attr("transform", "translate(" + availableWidth + ",0)");
  12469. }
  12470. //Set up interactive layer
  12471. if (useInteractiveGuideline) {
  12472. interactiveLayer
  12473. .width(availableWidth)
  12474. .height(availableHeight)
  12475. .margin({left: margin.left, top: margin.top})
  12476. .svgContainer(container)
  12477. .xScale(x);
  12478. wrap.select(".nv-interactive").call(interactiveLayer);
  12479. }
  12480. g.select('.nv-focus .nv-background rect')
  12481. .attr('width', availableWidth)
  12482. .attr('height', availableHeight);
  12483. stacked
  12484. .width(availableWidth)
  12485. .height(availableHeight)
  12486. .color(data.map(function(d,i) {
  12487. return d.color || color(d, i);
  12488. }).filter(function(d,i) { return !data[i].disabled; }));
  12489. var stackedWrap = g.select('.nv-focus .nv-stackedWrap')
  12490. .datum(data.filter(function(d) { return !d.disabled; }));
  12491. // Setup Axes
  12492. if (showXAxis) {
  12493. xAxis.scale(x)
  12494. ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
  12495. .tickSize( -availableHeight, 0);
  12496. }
  12497. if (showYAxis) {
  12498. var ticks;
  12499. if (stacked.offset() === 'wiggle') {
  12500. ticks = 0;
  12501. }
  12502. else {
  12503. ticks = nv.utils.calcTicksY(availableHeight/36, data);
  12504. }
  12505. yAxis.scale(y)
  12506. ._ticks(ticks)
  12507. .tickSize(-availableWidth, 0);
  12508. }
  12509. //============================================================
  12510. // Update Axes
  12511. //============================================================
  12512. function updateXAxis() {
  12513. if(showXAxis) {
  12514. g.select('.nv-focus .nv-x.nv-axis')
  12515. .attr('transform', 'translate(0,' + availableHeight + ')')
  12516. .transition()
  12517. .duration(duration)
  12518. .call(xAxis)
  12519. ;
  12520. }
  12521. }
  12522. function updateYAxis() {
  12523. if(showYAxis) {
  12524. if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') {
  12525. var currentFormat = yAxis.tickFormat();
  12526. if ( !oldYTickFormat || currentFormat !== percentFormatter )
  12527. oldYTickFormat = currentFormat;
  12528. //Forces the yAxis to use percentage in 'expand' mode.
  12529. yAxis.tickFormat(percentFormatter);
  12530. }
  12531. else {
  12532. if (oldYTickFormat) {
  12533. yAxis.tickFormat(oldYTickFormat);
  12534. oldYTickFormat = null;
  12535. }
  12536. }
  12537. g.select('.nv-focus .nv-y.nv-axis')
  12538. .transition().duration(0)
  12539. .call(yAxis);
  12540. }
  12541. }
  12542. //============================================================
  12543. // Update Focus
  12544. //============================================================
  12545. if(!focusEnable) {
  12546. stackedWrap.transition().call(stacked);
  12547. updateXAxis();
  12548. updateYAxis();
  12549. } else {
  12550. focus.width(availableWidth);
  12551. g.select('.nv-focusWrap')
  12552. .attr('transform', 'translate(0,' + ( availableHeight + margin.bottom + focus.margin().top) + ')')
  12553. .datum(data.filter(function(d) { return !d.disabled; }))
  12554. .call(focus);
  12555. var extent = focus.brush.empty() ? focus.xDomain() : focus.brush.extent();
  12556. if(extent !== null){
  12557. onBrush(extent);
  12558. }
  12559. }
  12560. //============================================================
  12561. // Event Handling/Dispatching (in chart's scope)
  12562. //------------------------------------------------------------
  12563. stacked.dispatch.on('areaClick.toggle', function(e) {
  12564. if (data.filter(function(d) { return !d.disabled }).length === 1)
  12565. data.forEach(function(d) {
  12566. d.disabled = false;
  12567. });
  12568. else
  12569. data.forEach(function(d,i) {
  12570. d.disabled = (i != e.seriesIndex);
  12571. });
  12572. state.disabled = data.map(function(d) { return !!d.disabled });
  12573. dispatch.stateChange(state);
  12574. chart.update();
  12575. });
  12576. legend.dispatch.on('stateChange', function(newState) {
  12577. for (var key in newState)
  12578. state[key] = newState[key];
  12579. dispatch.stateChange(state);
  12580. chart.update();
  12581. });
  12582. controls.dispatch.on('legendClick', function(d,i) {
  12583. if (!d.disabled) return;
  12584. controlsData = controlsData.map(function(s) {
  12585. s.disabled = true;
  12586. return s;
  12587. });
  12588. d.disabled = false;
  12589. stacked.style(d.style);
  12590. state.style = stacked.style();
  12591. dispatch.stateChange(state);
  12592. chart.update();
  12593. });
  12594. interactiveLayer.dispatch.on('elementMousemove', function(e) {
  12595. stacked.clearHighlights();
  12596. var singlePoint, pointIndex, pointXLocation, allData = [], valueSum = 0, allNullValues = true;
  12597. data
  12598. .filter(function(series, i) {
  12599. series.seriesIndex = i;
  12600. return !series.disabled;
  12601. })
  12602. .forEach(function(series,i) {
  12603. pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
  12604. var point = series.values[pointIndex];
  12605. var pointYValue = chart.y()(point, pointIndex);
  12606. if (pointYValue != null) {
  12607. stacked.highlightPoint(i, pointIndex, true);
  12608. }
  12609. if (typeof point === 'undefined') return;
  12610. if (typeof singlePoint === 'undefined') singlePoint = point;
  12611. if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
  12612. //If we are in 'expand' mode, use the stacked percent value instead of raw value.
  12613. var tooltipValue = (stacked.style() == 'expand') ? point.display.y : chart.y()(point,pointIndex);
  12614. allData.push({
  12615. key: series.key,
  12616. value: tooltipValue,
  12617. color: color(series,series.seriesIndex),
  12618. point: point
  12619. });
  12620. if (showTotalInTooltip && stacked.style() != 'expand' && tooltipValue != null) {
  12621. valueSum += tooltipValue;
  12622. allNullValues = false;
  12623. };
  12624. });
  12625. allData.reverse();
  12626. //Highlight the tooltip entry based on which stack the mouse is closest to.
  12627. if (allData.length > 2) {
  12628. var yValue = chart.yScale().invert(e.mouseY);
  12629. var yDistMax = Infinity, indexToHighlight = null;
  12630. allData.forEach(function(series,i) {
  12631. //To handle situation where the stacked area chart is negative, we need to use absolute values
  12632. //when checking if the mouse Y value is within the stack area.
  12633. yValue = Math.abs(yValue);
  12634. var stackedY0 = Math.abs(series.point.display.y0);
  12635. var stackedY = Math.abs(series.point.display.y);
  12636. if ( yValue >= stackedY0 && yValue <= (stackedY + stackedY0))
  12637. {
  12638. indexToHighlight = i;
  12639. return;
  12640. }
  12641. });
  12642. if (indexToHighlight != null)
  12643. allData[indexToHighlight].highlight = true;
  12644. }
  12645. //If we are not in 'expand' mode, add a 'Total' row to the tooltip.
  12646. if (showTotalInTooltip && stacked.style() != 'expand' && allData.length >= 2 && !allNullValues) {
  12647. allData.push({
  12648. key: totalLabel,
  12649. value: valueSum,
  12650. total: true
  12651. });
  12652. }
  12653. var xValue = chart.x()(singlePoint,pointIndex);
  12654. var valueFormatter = interactiveLayer.tooltip.valueFormatter();
  12655. // Keeps track of the tooltip valueFormatter if the chart changes to expanded view
  12656. if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') {
  12657. if ( !oldValueFormatter ) {
  12658. oldValueFormatter = valueFormatter;
  12659. }
  12660. //Forces the tooltip to use percentage in 'expand' mode.
  12661. valueFormatter = d3.format(".1%");
  12662. }
  12663. else {
  12664. if (oldValueFormatter) {
  12665. valueFormatter = oldValueFormatter;
  12666. oldValueFormatter = null;
  12667. }
  12668. }
  12669. interactiveLayer.tooltip
  12670. .valueFormatter(valueFormatter)
  12671. .data(
  12672. {
  12673. value: xValue,
  12674. series: allData
  12675. }
  12676. )();
  12677. interactiveLayer.renderGuideLine(pointXLocation);
  12678. });
  12679. interactiveLayer.dispatch.on("elementMouseout",function(e) {
  12680. stacked.clearHighlights();
  12681. });
  12682. /* Update `main' graph on brush update. */
  12683. focus.dispatch.on("onBrush", function(extent) {
  12684. onBrush(extent);
  12685. });
  12686. // Update chart from a state object passed to event handler
  12687. dispatch.on('changeState', function(e) {
  12688. if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
  12689. data.forEach(function(series,i) {
  12690. series.disabled = e.disabled[i];
  12691. });
  12692. state.disabled = e.disabled;
  12693. }
  12694. if (typeof e.style !== 'undefined') {
  12695. stacked.style(e.style);
  12696. style = e.style;
  12697. }
  12698. chart.update();
  12699. });
  12700. //============================================================
  12701. // Functions
  12702. //------------------------------------------------------------
  12703. function onBrush(extent) {
  12704. // Update Main (Focus)
  12705. var stackedWrap = g.select('.nv-focus .nv-stackedWrap')
  12706. .datum(
  12707. data.filter(function(d) { return !d.disabled; })
  12708. .map(function(d,i) {
  12709. return {
  12710. key: d.key,
  12711. area: d.area,
  12712. classed: d.classed,
  12713. values: d.values.filter(function(d,i) {
  12714. return stacked.x()(d,i) >= extent[0] && stacked.x()(d,i) <= extent[1];
  12715. }),
  12716. disableTooltip: d.disableTooltip
  12717. };
  12718. })
  12719. );
  12720. stackedWrap.transition().duration(duration).call(stacked);
  12721. // Update Main (Focus) Axes
  12722. updateXAxis();
  12723. updateYAxis();
  12724. }
  12725. });
  12726. renderWatch.renderEnd('stacked Area chart immediate');
  12727. return chart;
  12728. }
  12729. //============================================================
  12730. // Event Handling/Dispatching (out of chart's scope)
  12731. //------------------------------------------------------------
  12732. stacked.dispatch.on('elementMouseover.tooltip', function(evt) {
  12733. evt.point['x'] = stacked.x()(evt.point);
  12734. evt.point['y'] = stacked.y()(evt.point);
  12735. tooltip.data(evt).hidden(false);
  12736. });
  12737. stacked.dispatch.on('elementMouseout.tooltip', function(evt) {
  12738. tooltip.hidden(true)
  12739. });
  12740. //============================================================
  12741. // Expose Public Variables
  12742. //------------------------------------------------------------
  12743. // expose chart's sub-components
  12744. chart.dispatch = dispatch;
  12745. chart.stacked = stacked;
  12746. chart.legend = legend;
  12747. chart.controls = controls;
  12748. chart.xAxis = xAxis;
  12749. chart.x2Axis = focus.xAxis;
  12750. chart.yAxis = yAxis;
  12751. chart.y2Axis = focus.yAxis;
  12752. chart.interactiveLayer = interactiveLayer;
  12753. chart.tooltip = tooltip;
  12754. chart.focus = focus;
  12755. chart.dispatch = dispatch;
  12756. chart.options = nv.utils.optionsFunc.bind(chart);
  12757. chart._options = Object.create({}, {
  12758. // simple options, just get/set the necessary values
  12759. width: {get: function(){return width;}, set: function(_){width=_;}},
  12760. height: {get: function(){return height;}, set: function(_){height=_;}},
  12761. showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
  12762. legendPosition: {get: function(){return legendPosition;}, set: function(_){legendPosition=_;}},
  12763. showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
  12764. showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
  12765. defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
  12766. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  12767. showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
  12768. controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
  12769. controlOptions: {get: function(){return controlOptions;}, set: function(_){controlOptions=_;}},
  12770. showTotalInTooltip: {get: function(){return showTotalInTooltip;}, set: function(_){showTotalInTooltip=_;}},
  12771. totalLabel: {get: function(){return totalLabel;}, set: function(_){totalLabel=_;}},
  12772. focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}},
  12773. focusHeight: {get: function(){return focus.height();}, set: function(_){focus.height(_);}},
  12774. brushExtent: {get: function(){return focus.brushExtent();}, set: function(_){focus.brushExtent(_);}},
  12775. // options that require extra logic in the setter
  12776. margin: {get: function(){return margin;}, set: function(_){
  12777. if (_.top !== undefined) {
  12778. margin.top = _.top;
  12779. marginTop = _.top;
  12780. }
  12781. margin.right = _.right !== undefined ? _.right : margin.right;
  12782. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  12783. margin.left = _.left !== undefined ? _.left : margin.left;
  12784. }},
  12785. focusMargin: {get: function(){return focus.margin}, set: function(_){
  12786. focus.margin.top = _.top !== undefined ? _.top : focus.margin.top;
  12787. focus.margin.right = _.right !== undefined ? _.right : focus.margin.right;
  12788. focus.margin.bottom = _.bottom !== undefined ? _.bottom : focus.margin.bottom;
  12789. focus.margin.left = _.left !== undefined ? _.left : focus.margin.left;
  12790. }},
  12791. duration: {get: function(){return duration;}, set: function(_){
  12792. duration = _;
  12793. renderWatch.reset(duration);
  12794. stacked.duration(duration);
  12795. xAxis.duration(duration);
  12796. yAxis.duration(duration);
  12797. }},
  12798. color: {get: function(){return color;}, set: function(_){
  12799. color = nv.utils.getColor(_);
  12800. legend.color(color);
  12801. stacked.color(color);
  12802. focus.color(color);
  12803. }},
  12804. x: {get: function(){return stacked.x();}, set: function(_){
  12805. stacked.x(_);
  12806. focus.x(_);
  12807. }},
  12808. y: {get: function(){return stacked.y();}, set: function(_){
  12809. stacked.y(_);
  12810. focus.y(_);
  12811. }},
  12812. rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
  12813. rightAlignYAxis = _;
  12814. yAxis.orient( rightAlignYAxis ? 'right' : 'left');
  12815. }},
  12816. useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
  12817. useInteractiveGuideline = !!_;
  12818. chart.interactive(!_);
  12819. chart.useVoronoi(!_);
  12820. stacked.scatter.interactive(!_);
  12821. }}
  12822. });
  12823. nv.utils.inheritOptions(chart, stacked);
  12824. nv.utils.initOptions(chart);
  12825. return chart;
  12826. };
  12827. nv.models.stackedAreaWithFocusChart = function() {
  12828. return nv.models.stackedAreaChart()
  12829. .margin({ bottom: 30 })
  12830. .focusEnable( true );
  12831. };
  12832. // based on http://bl.ocks.org/kerryrodden/477c1bfb081b783f80ad
  12833. nv.models.sunburst = function() {
  12834. "use strict";
  12835. //============================================================
  12836. // Public Variables with Default Settings
  12837. //------------------------------------------------------------
  12838. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  12839. , width = 600
  12840. , height = 600
  12841. , mode = "count"
  12842. , modes = {count: function(d) { return 1; }, value: function(d) { return d.value || d.size }, size: function(d) { return d.value || d.size }}
  12843. , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
  12844. , container = null
  12845. , color = nv.utils.defaultColor()
  12846. , showLabels = false
  12847. , labelFormat = function(d){if(mode === 'count'){return d.name + ' #' + d.value}else{return d.name + ' ' + (d.value || d.size)}}
  12848. , labelThreshold = 0.02
  12849. , sort = function(d1, d2){return d1.name > d2.name;}
  12850. , key = function(d,i){return d.name;}
  12851. , groupColorByParent = true
  12852. , duration = 500
  12853. , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMousemove', 'elementMouseover', 'elementMouseout', 'renderEnd');
  12854. //============================================================
  12855. // aux functions and setup
  12856. //------------------------------------------------------------
  12857. var x = d3.scale.linear().range([0, 2 * Math.PI]);
  12858. var y = d3.scale.sqrt();
  12859. var partition = d3.layout.partition().sort(sort);
  12860. var node, availableWidth, availableHeight, radius;
  12861. var prevPositions = {};
  12862. var arc = d3.svg.arc()
  12863. .startAngle(function(d) {return Math.max(0, Math.min(2 * Math.PI, x(d.x))) })
  12864. .endAngle(function(d) {return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))) })
  12865. .innerRadius(function(d) {return Math.max(0, y(d.y)) })
  12866. .outerRadius(function(d) {return Math.max(0, y(d.y + d.dy)) });
  12867. function rotationToAvoidUpsideDown(d) {
  12868. var centerAngle = computeCenterAngle(d);
  12869. if(centerAngle > 90){
  12870. return 180;
  12871. }
  12872. else {
  12873. return 0;
  12874. }
  12875. }
  12876. function computeCenterAngle(d) {
  12877. var startAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x)));
  12878. var endAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
  12879. var centerAngle = (((startAngle + endAngle) / 2) * (180 / Math.PI)) - 90;
  12880. return centerAngle;
  12881. }
  12882. function computeNodePercentage(d) {
  12883. var startAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x)));
  12884. var endAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
  12885. return (endAngle - startAngle) / (2 * Math.PI);
  12886. }
  12887. function labelThresholdMatched(d) {
  12888. var startAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x)));
  12889. var endAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
  12890. var size = endAngle - startAngle;
  12891. return size > labelThreshold;
  12892. }
  12893. // When zooming: interpolate the scales.
  12894. function arcTweenZoom(e,i) {
  12895. var xd = d3.interpolate(x.domain(), [node.x, node.x + node.dx]),
  12896. yd = d3.interpolate(y.domain(), [node.y, 1]),
  12897. yr = d3.interpolate(y.range(), [node.y ? 20 : 0, radius]);
  12898. if (i === 0) {
  12899. return function() {return arc(e);}
  12900. }
  12901. else {
  12902. return function (t) {
  12903. x.domain(xd(t));
  12904. y.domain(yd(t)).range(yr(t));
  12905. return arc(e);
  12906. }
  12907. };
  12908. }
  12909. function arcTweenUpdate(d) {
  12910. var ipo = d3.interpolate({x: d.x0, dx: d.dx0, y: d.y0, dy: d.dy0}, d);
  12911. return function (t) {
  12912. var b = ipo(t);
  12913. d.x0 = b.x;
  12914. d.dx0 = b.dx;
  12915. d.y0 = b.y;
  12916. d.dy0 = b.dy;
  12917. return arc(b);
  12918. };
  12919. }
  12920. function updatePrevPosition(node) {
  12921. var k = key(node);
  12922. if(! prevPositions[k]) prevPositions[k] = {};
  12923. var pP = prevPositions[k];
  12924. pP.dx = node.dx;
  12925. pP.x = node.x;
  12926. pP.dy = node.dy;
  12927. pP.y = node.y;
  12928. }
  12929. function storeRetrievePrevPositions(nodes) {
  12930. nodes.forEach(function(n){
  12931. var k = key(n);
  12932. var pP = prevPositions[k];
  12933. //console.log(k,n,pP);
  12934. if( pP ){
  12935. n.dx0 = pP.dx;
  12936. n.x0 = pP.x;
  12937. n.dy0 = pP.dy;
  12938. n.y0 = pP.y;
  12939. }
  12940. else {
  12941. n.dx0 = n.dx;
  12942. n.x0 = n.x;
  12943. n.dy0 = n.dy;
  12944. n.y0 = n.y;
  12945. }
  12946. updatePrevPosition(n);
  12947. });
  12948. }
  12949. function zoomClick(d) {
  12950. var labels = container.selectAll('text')
  12951. var path = container.selectAll('path')
  12952. // fade out all text elements
  12953. labels.transition().attr("opacity",0);
  12954. // to allow reference to the new center node
  12955. node = d;
  12956. path.transition()
  12957. .duration(duration)
  12958. .attrTween("d", arcTweenZoom)
  12959. .each('end', function(e) {
  12960. // partially taken from here: http://bl.ocks.org/metmajer/5480307
  12961. // check if the animated element's data e lies within the visible angle span given in d
  12962. if(e.x >= d.x && e.x < (d.x + d.dx) ){
  12963. if(e.depth >= d.depth){
  12964. // get a selection of the associated text element
  12965. var parentNode = d3.select(this.parentNode);
  12966. var arcText = parentNode.select('text');
  12967. // fade in the text element and recalculate positions
  12968. arcText.transition().duration(duration)
  12969. .text( function(e){return labelFormat(e) })
  12970. .attr("opacity", function(d){
  12971. if(labelThresholdMatched(d)) {
  12972. return 1;
  12973. }
  12974. else {
  12975. return 0;
  12976. }
  12977. })
  12978. .attr("transform", function() {
  12979. var width = this.getBBox().width;
  12980. if(e.depth === 0)
  12981. return "translate(" + (width / 2 * - 1) + ",0)";
  12982. else if(e.depth === d.depth){
  12983. return "translate(" + (y(e.y) + 5) + ",0)";
  12984. }
  12985. else {
  12986. var centerAngle = computeCenterAngle(e);
  12987. var rotation = rotationToAvoidUpsideDown(e);
  12988. if (rotation === 0) {
  12989. return 'rotate('+ centerAngle +')translate(' + (y(e.y) + 5) + ',0)';
  12990. }
  12991. else {
  12992. return 'rotate('+ centerAngle +')translate(' + (y(e.y) + width + 5) + ',0)rotate(' + rotation + ')';
  12993. }
  12994. }
  12995. });
  12996. }
  12997. }
  12998. })
  12999. }
  13000. //============================================================
  13001. // chart function
  13002. //------------------------------------------------------------
  13003. var renderWatch = nv.utils.renderWatch(dispatch);
  13004. function chart(selection) {
  13005. renderWatch.reset();
  13006. selection.each(function(data) {
  13007. container = d3.select(this);
  13008. availableWidth = nv.utils.availableWidth(width, container, margin);
  13009. availableHeight = nv.utils.availableHeight(height, container, margin);
  13010. radius = Math.min(availableWidth, availableHeight) / 2;
  13011. y.range([0, radius]);
  13012. // Setup containers and skeleton of chart
  13013. var wrap = container.select('g.nvd3.nv-wrap.nv-sunburst');
  13014. if( !wrap[0][0] ) {
  13015. wrap = container.append('g')
  13016. .attr('class', 'nvd3 nv-wrap nv-sunburst nv-chart-' + id)
  13017. .attr('transform', 'translate(' + ((availableWidth / 2) + margin.left + margin.right) + ',' + ((availableHeight / 2) + margin.top + margin.bottom) + ')');
  13018. } else {
  13019. wrap.attr('transform', 'translate(' + ((availableWidth / 2) + margin.left + margin.right) + ',' + ((availableHeight / 2) + margin.top + margin.bottom) + ')');
  13020. }
  13021. container.on('click', function (d, i) {
  13022. dispatch.chartClick({
  13023. data: d,
  13024. index: i,
  13025. pos: d3.event,
  13026. id: id
  13027. });
  13028. });
  13029. partition.value(modes[mode] || modes["count"]);
  13030. //reverse the drawing order so that the labels of inner
  13031. //arcs are drawn on top of the outer arcs.
  13032. var nodes = partition.nodes(data[0]).reverse()
  13033. storeRetrievePrevPositions(nodes);
  13034. var cG = wrap.selectAll('.arc-container').data(nodes, key)
  13035. //handle new datapoints
  13036. var cGE = cG.enter()
  13037. .append("g")
  13038. .attr("class",'arc-container')
  13039. cGE.append("path")
  13040. .attr("d", arc)
  13041. .style("fill", function (d) {
  13042. if (d.color) {
  13043. return d.color;
  13044. }
  13045. else if (groupColorByParent) {
  13046. return color((d.children ? d : d.parent).name);
  13047. }
  13048. else {
  13049. return color(d.name);
  13050. }
  13051. })
  13052. .style("stroke", "#FFF")
  13053. .on("click", function(d,i){
  13054. zoomClick(d);
  13055. dispatch.elementClick({
  13056. data: d,
  13057. index: i
  13058. })
  13059. })
  13060. .on('mouseover', function(d,i){
  13061. d3.select(this).classed('hover', true).style('opacity', 0.8);
  13062. dispatch.elementMouseover({
  13063. data: d,
  13064. color: d3.select(this).style("fill"),
  13065. percent: computeNodePercentage(d)
  13066. });
  13067. })
  13068. .on('mouseout', function(d,i){
  13069. d3.select(this).classed('hover', false).style('opacity', 1);
  13070. dispatch.elementMouseout({
  13071. data: d
  13072. });
  13073. })
  13074. .on('mousemove', function(d,i){
  13075. dispatch.elementMousemove({
  13076. data: d
  13077. });
  13078. });
  13079. ///Iterating via each and selecting based on the this
  13080. ///makes it work ... a cG.selectAll('path') doesn't.
  13081. ///Without iteration the data (in the element) didn't update.
  13082. cG.each(function(d){
  13083. d3.select(this).select('path')
  13084. .transition()
  13085. .duration(duration)
  13086. .attrTween('d', arcTweenUpdate);
  13087. });
  13088. if(showLabels){
  13089. //remove labels first and add them back
  13090. cG.selectAll('text').remove();
  13091. //this way labels are on top of newly added arcs
  13092. cG.append('text')
  13093. .text( function(e){ return labelFormat(e)})
  13094. .transition()
  13095. .duration(duration)
  13096. .attr("opacity", function(d){
  13097. if(labelThresholdMatched(d)) {
  13098. return 1;
  13099. }
  13100. else {
  13101. return 0;
  13102. }
  13103. })
  13104. .attr("transform", function(d) {
  13105. var width = this.getBBox().width;
  13106. if(d.depth === 0){
  13107. return "rotate(0)translate(" + (width / 2 * -1) + ",0)";
  13108. }
  13109. else {
  13110. var centerAngle = computeCenterAngle(d);
  13111. var rotation = rotationToAvoidUpsideDown(d);
  13112. if (rotation === 0) {
  13113. return 'rotate('+ centerAngle +')translate(' + (y(d.y) + 5) + ',0)';
  13114. }
  13115. else {
  13116. return 'rotate('+ centerAngle +')translate(' + (y(d.y) + width + 5) + ',0)rotate(' + rotation + ')';
  13117. }
  13118. }
  13119. });
  13120. }
  13121. //zoom out to the center when the data is updated.
  13122. zoomClick(nodes[nodes.length - 1])
  13123. //remove unmatched elements ...
  13124. cG.exit()
  13125. .transition()
  13126. .duration(duration)
  13127. .attr('opacity',0)
  13128. .each('end',function(d){
  13129. var k = key(d);
  13130. prevPositions[k] = undefined;
  13131. })
  13132. .remove();
  13133. });
  13134. renderWatch.renderEnd('sunburst immediate');
  13135. return chart;
  13136. }
  13137. //============================================================
  13138. // Expose Public Variables
  13139. //------------------------------------------------------------
  13140. chart.dispatch = dispatch;
  13141. chart.options = nv.utils.optionsFunc.bind(chart);
  13142. chart._options = Object.create({}, {
  13143. // simple options, just get/set the necessary values
  13144. width: {get: function(){return width;}, set: function(_){width=_;}},
  13145. height: {get: function(){return height;}, set: function(_){height=_;}},
  13146. mode: {get: function(){return mode;}, set: function(_){mode=_;}},
  13147. id: {get: function(){return id;}, set: function(_){id=_;}},
  13148. duration: {get: function(){return duration;}, set: function(_){duration=_;}},
  13149. groupColorByParent: {get: function(){return groupColorByParent;}, set: function(_){groupColorByParent=!!_;}},
  13150. showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=!!_}},
  13151. labelFormat: {get: function(){return labelFormat;}, set: function(_){labelFormat=_}},
  13152. labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_}},
  13153. sort: {get: function(){return sort;}, set: function(_){sort=_}},
  13154. key: {get: function(){return key;}, set: function(_){key=_}},
  13155. // options that require extra logic in the setter
  13156. margin: {get: function(){return margin;}, set: function(_){
  13157. margin.top = _.top != undefined ? _.top : margin.top;
  13158. margin.right = _.right != undefined ? _.right : margin.right;
  13159. margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
  13160. margin.left = _.left != undefined ? _.left : margin.left;
  13161. }},
  13162. color: {get: function(){return color;}, set: function(_){
  13163. color=nv.utils.getColor(_);
  13164. }}
  13165. });
  13166. nv.utils.initOptions(chart);
  13167. return chart;
  13168. };
  13169. nv.models.sunburstChart = function() {
  13170. "use strict";
  13171. //============================================================
  13172. // Public Variables with Default Settings
  13173. //------------------------------------------------------------
  13174. var sunburst = nv.models.sunburst();
  13175. var tooltip = nv.models.tooltip();
  13176. var margin = {top: 30, right: 20, bottom: 20, left: 20}
  13177. , width = null
  13178. , height = null
  13179. , color = nv.utils.defaultColor()
  13180. , showTooltipPercent = false
  13181. , id = Math.round(Math.random() * 100000)
  13182. , defaultState = null
  13183. , noData = null
  13184. , duration = 250
  13185. , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd');
  13186. //============================================================
  13187. // Private Variables
  13188. //------------------------------------------------------------
  13189. var renderWatch = nv.utils.renderWatch(dispatch);
  13190. tooltip
  13191. .duration(0)
  13192. .headerEnabled(false)
  13193. .valueFormatter(function(d){return d;});
  13194. //============================================================
  13195. // Chart function
  13196. //------------------------------------------------------------
  13197. function chart(selection) {
  13198. renderWatch.reset();
  13199. renderWatch.models(sunburst);
  13200. selection.each(function(data) {
  13201. var container = d3.select(this);
  13202. nv.utils.initSVG(container);
  13203. var availableWidth = nv.utils.availableWidth(width, container, margin);
  13204. var availableHeight = nv.utils.availableHeight(height, container, margin);
  13205. chart.update = function() {
  13206. if (duration === 0) {
  13207. container.call(chart);
  13208. } else {
  13209. container.transition().duration(duration).call(chart);
  13210. }
  13211. };
  13212. chart.container = container;
  13213. // Display No Data message if there's nothing to show.
  13214. if (!data || !data.length) {
  13215. nv.utils.noData(chart, container);
  13216. return chart;
  13217. } else {
  13218. container.selectAll('.nv-noData').remove();
  13219. }
  13220. sunburst.width(availableWidth).height(availableHeight).margin(margin);
  13221. container.call(sunburst);
  13222. });
  13223. renderWatch.renderEnd('sunburstChart immediate');
  13224. return chart;
  13225. }
  13226. //============================================================
  13227. // Event Handling/Dispatching (out of chart's scope)
  13228. //------------------------------------------------------------
  13229. sunburst.dispatch.on('elementMouseover.tooltip', function(evt) {
  13230. evt.series = {
  13231. key: evt.data.name,
  13232. value: (evt.data.value || evt.data.size),
  13233. color: evt.color,
  13234. percent: evt.percent
  13235. };
  13236. if (!showTooltipPercent) {
  13237. delete evt.percent;
  13238. delete evt.series.percent;
  13239. }
  13240. tooltip.data(evt).hidden(false);
  13241. });
  13242. sunburst.dispatch.on('elementMouseout.tooltip', function(evt) {
  13243. tooltip.hidden(true);
  13244. });
  13245. sunburst.dispatch.on('elementMousemove.tooltip', function(evt) {
  13246. tooltip();
  13247. });
  13248. //============================================================
  13249. // Expose Public Variables
  13250. //------------------------------------------------------------
  13251. // expose chart's sub-components
  13252. chart.dispatch = dispatch;
  13253. chart.sunburst = sunburst;
  13254. chart.tooltip = tooltip;
  13255. chart.options = nv.utils.optionsFunc.bind(chart);
  13256. // use Object get/set functionality to map between vars and chart functions
  13257. chart._options = Object.create({}, {
  13258. // simple options, just get/set the necessary values
  13259. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  13260. defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
  13261. showTooltipPercent: {get: function(){return showTooltipPercent;}, set: function(_){showTooltipPercent=_;}},
  13262. // options that require extra logic in the setter
  13263. color: {get: function(){return color;}, set: function(_){
  13264. color = _;
  13265. sunburst.color(color);
  13266. }},
  13267. duration: {get: function(){return duration;}, set: function(_){
  13268. duration = _;
  13269. renderWatch.reset(duration);
  13270. sunburst.duration(duration);
  13271. }},
  13272. margin: {get: function(){return margin;}, set: function(_){
  13273. margin.top = _.top !== undefined ? _.top : margin.top;
  13274. margin.right = _.right !== undefined ? _.right : margin.right;
  13275. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  13276. margin.left = _.left !== undefined ? _.left : margin.left;
  13277. sunburst.margin(margin);
  13278. }}
  13279. });
  13280. nv.utils.inheritOptions(chart, sunburst);
  13281. nv.utils.initOptions(chart);
  13282. return chart;
  13283. };
  13284. nv.version = "1.8.5";
  13285. })();
  13286. //# sourceMappingURL=nv.d3.js.map