nv.d3.js 596 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399144001440114402144031440414405144061440714408144091441014411144121441314414144151441614417144181441914420144211442214423144241442514426144271442814429144301443114432144331443414435144361443714438144391444014441144421444314444144451444614447144481444914450144511445214453144541445514456144571445814459144601446114462144631446414465144661446714468144691447014471144721447314474144751447614477144781447914480144811448214483144841448514486144871448814489144901449114492144931449414495144961449714498144991450014501145021450314504145051450614507145081450914510145111451214513145141451514516145171451814519145201452114522145231452414525145261452714528145291453014531145321453314534145351453614537145381453914540145411454214543145441454514546145471454814549145501455114552145531455414555145561455714558145591456014561145621456314564145651456614567145681456914570145711457214573145741457514576145771457814579145801458114582145831458414585145861458714588145891459014591145921459314594145951459614597145981459914600146011460214603146041460514606146071460814609146101461114612146131461414615146161461714618146191462014621146221462314624146251462614627146281462914630146311463214633146341463514636146371463814639146401464114642146431464414645146461464714648146491465014651146521465314654146551465614657146581465914660146611466214663146641466514666146671466814669146701467114672146731467414675146761467714678146791468014681146821468314684146851468614687146881468914690146911469214693146941469514696146971469814699147001470114702147031470414705147061470714708147091471014711
  1. /* nvd3 version 1.8.4 (https://github.com/novus/nvd3) 2016-07-03 */
  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).selectAll('.nvtooltip').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)) {
  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. ;
  2247. function sortLabels(labels, values){
  2248. var lz = labels.slice();
  2249. labels.sort(function(a, b){
  2250. var iA = lz.indexOf(a);
  2251. var iB = lz.indexOf(b);
  2252. return d3.descending(values[iA], values[iB]);
  2253. });
  2254. };
  2255. function chart(selection) {
  2256. selection.each(function(d, i) {
  2257. var availableWidth = width - margin.left - margin.right,
  2258. availableHeight = height - margin.top - margin.bottom;
  2259. container = d3.select(this);
  2260. nv.utils.initSVG(container);
  2261. var rangez = ranges.call(this, d, i).slice(),
  2262. markerz = markers.call(this, d, i).slice(),
  2263. markerLinez = markerLines.call(this, d, i).slice().sort(d3.descending),
  2264. measurez = measures.call(this, d, i).slice(),
  2265. rangeLabelz = rangeLabels.call(this, d, i).slice(),
  2266. markerLabelz = markerLabels.call(this, d, i).slice(),
  2267. markerLineLabelz = markerLineLabels.call(this, d, i).slice(),
  2268. measureLabelz = measureLabels.call(this, d, i).slice();
  2269. // Sort labels according to their sorted values
  2270. sortLabels(rangeLabelz, rangez);
  2271. sortLabels(markerLabelz, markerz);
  2272. sortLabels(markerLineLabelz, markerLinez);
  2273. sortLabels(measureLabelz, measurez);
  2274. // sort values descending
  2275. rangez.sort(d3.descending);
  2276. markerz.sort(d3.descending);
  2277. measurez.sort(d3.descending);
  2278. // Setup Scales
  2279. // Compute the new x-scale.
  2280. var x1 = d3.scale.linear()
  2281. .domain( d3.extent(d3.merge([forceX, rangez])) )
  2282. .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
  2283. // Retrieve the old x-scale, if this is an update.
  2284. var x0 = this.__chart__ || d3.scale.linear()
  2285. .domain([0, Infinity])
  2286. .range(x1.range());
  2287. // Stash the new scale.
  2288. this.__chart__ = x1;
  2289. var rangeMin = d3.min(rangez), //rangez[2]
  2290. rangeMax = d3.max(rangez), //rangez[0]
  2291. rangeAvg = rangez[1];
  2292. // Setup containers and skeleton of chart
  2293. var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]);
  2294. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet');
  2295. var gEnter = wrapEnter.append('g');
  2296. var g = wrap.select('g');
  2297. for(var i=0,il=rangez.length; i<il; i++){
  2298. var rangeClassNames = 'nv-range nv-range'+i;
  2299. if(i <= 2){
  2300. rangeClassNames = rangeClassNames + ' nv-range'+legacyRangeClassNames[i];
  2301. }
  2302. gEnter.append('rect').attr('class', rangeClassNames);
  2303. }
  2304. gEnter.append('rect').attr('class', 'nv-measure');
  2305. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  2306. var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
  2307. w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
  2308. var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) },
  2309. xp1 = function(d) { return d < 0 ? x1(d) : x1(0) };
  2310. for(var i=0,il=rangez.length; i<il; i++){
  2311. var range = rangez[i];
  2312. g.select('rect.nv-range'+i)
  2313. .attr('height', availableHeight)
  2314. .attr('width', w1(range))
  2315. .attr('x', xp1(range))
  2316. .datum(range)
  2317. }
  2318. g.select('rect.nv-measure')
  2319. .style('fill', color)
  2320. .attr('height', availableHeight / 3)
  2321. .attr('y', availableHeight / 3)
  2322. .attr('width', measurez < 0 ?
  2323. x1(0) - x1(measurez[0])
  2324. : x1(measurez[0]) - x1(0))
  2325. .attr('x', xp1(measurez))
  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. var h3 = availableHeight / 6;
  2348. var markerData = markerz.map( function(marker, index) {
  2349. return {value: marker, label: markerLabelz[index]}
  2350. });
  2351. gEnter
  2352. .selectAll("path.nv-markerTriangle")
  2353. .data(markerData)
  2354. .enter()
  2355. .append('path')
  2356. .attr('class', 'nv-markerTriangle')
  2357. .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
  2358. .on('mouseover', function(d) {
  2359. dispatch.elementMouseover({
  2360. value: d.value,
  2361. label: d.label || 'Previous',
  2362. color: d3.select(this).style("fill"),
  2363. pos: [x1(d.value), availableHeight/2]
  2364. })
  2365. })
  2366. .on('mousemove', function(d) {
  2367. dispatch.elementMousemove({
  2368. value: d.value,
  2369. label: d.label || 'Previous',
  2370. color: d3.select(this).style("fill")
  2371. })
  2372. })
  2373. .on('mouseout', function(d, i) {
  2374. dispatch.elementMouseout({
  2375. value: d.value,
  2376. label: d.label || 'Previous',
  2377. color: d3.select(this).style("fill")
  2378. })
  2379. });
  2380. g.selectAll("path.nv-markerTriangle")
  2381. .data(markerData)
  2382. .attr('transform', function(d) { return 'translate(' + x1(d.value) + ',' + (availableHeight / 2) + ')' });
  2383. var markerLinesData = markerLinez.map( function(marker, index) {
  2384. return {value: marker, label: markerLineLabelz[index]}
  2385. });
  2386. gEnter
  2387. .selectAll("path.nv-markerLine")
  2388. .data(markerLinesData)
  2389. .enter()
  2390. .append('line')
  2391. .attr('cursor', '')
  2392. .attr('class', 'nv-markerLine')
  2393. .attr('x1', function(d) { return x1(d.value) })
  2394. .attr('y1', '2')
  2395. .attr('x2', function(d) { return x1(d.value) })
  2396. .attr('y2', availableHeight - 2)
  2397. .on('mouseover', function(d) {
  2398. dispatch.elementMouseover({
  2399. value: d.value,
  2400. label: d.label || 'Previous',
  2401. color: d3.select(this).style("fill"),
  2402. pos: [x1(d.value), availableHeight/2]
  2403. })
  2404. })
  2405. .on('mousemove', function(d) {
  2406. dispatch.elementMousemove({
  2407. value: d.value,
  2408. label: d.label || 'Previous',
  2409. color: d3.select(this).style("fill")
  2410. })
  2411. })
  2412. .on('mouseout', function(d, i) {
  2413. dispatch.elementMouseout({
  2414. value: d.value,
  2415. label: d.label || 'Previous',
  2416. color: d3.select(this).style("fill")
  2417. })
  2418. });
  2419. g.selectAll("path.nv-markerLines")
  2420. .data(markerLinesData)
  2421. .attr('transform', function(d) { return 'translate(' + x1(d.value) + ',' + (availableHeight / 2) + ')' });
  2422. wrap.selectAll('.nv-range')
  2423. .on('mouseover', function(d,i) {
  2424. var label = rangeLabelz[i] || defaultRangeLabels[i];
  2425. dispatch.elementMouseover({
  2426. value: d,
  2427. label: label,
  2428. color: d3.select(this).style("fill")
  2429. })
  2430. })
  2431. .on('mousemove', function() {
  2432. dispatch.elementMousemove({
  2433. value: measurez[0],
  2434. label: measureLabelz[0] || 'Previous',
  2435. color: d3.select(this).style("fill")
  2436. })
  2437. })
  2438. .on('mouseout', function(d,i) {
  2439. var label = rangeLabelz[i] || defaultRangeLabels[i];
  2440. dispatch.elementMouseout({
  2441. value: d,
  2442. label: label,
  2443. color: d3.select(this).style("fill")
  2444. })
  2445. });
  2446. });
  2447. return chart;
  2448. }
  2449. //============================================================
  2450. // Expose Public Variables
  2451. //------------------------------------------------------------
  2452. chart.dispatch = dispatch;
  2453. chart.options = nv.utils.optionsFunc.bind(chart);
  2454. chart._options = Object.create({}, {
  2455. // simple options, just get/set the necessary values
  2456. ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good)
  2457. markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal)
  2458. measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast)
  2459. forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
  2460. width: {get: function(){return width;}, set: function(_){width=_;}},
  2461. height: {get: function(){return height;}, set: function(_){height=_;}},
  2462. tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}},
  2463. // options that require extra logic in the setter
  2464. margin: {get: function(){return margin;}, set: function(_){
  2465. margin.top = _.top !== undefined ? _.top : margin.top;
  2466. margin.right = _.right !== undefined ? _.right : margin.right;
  2467. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  2468. margin.left = _.left !== undefined ? _.left : margin.left;
  2469. }},
  2470. orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
  2471. orient = _;
  2472. reverse = orient == 'right' || orient == 'bottom';
  2473. }},
  2474. color: {get: function(){return color;}, set: function(_){
  2475. color = nv.utils.getColor(_);
  2476. }}
  2477. });
  2478. nv.utils.initOptions(chart);
  2479. return chart;
  2480. };
  2481. // Chart design based on the recommendations of Stephen Few. Implementation
  2482. // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
  2483. // http://projects.instantcognition.com/protovis/bulletchart/
  2484. nv.models.bulletChart = function() {
  2485. "use strict";
  2486. //============================================================
  2487. // Public Variables with Default Settings
  2488. //------------------------------------------------------------
  2489. var bullet = nv.models.bullet();
  2490. var tooltip = nv.models.tooltip();
  2491. var orient = 'left' // TODO top & bottom
  2492. , reverse = false
  2493. , margin = {top: 5, right: 40, bottom: 20, left: 120}
  2494. , ranges = function(d) { return d.ranges }
  2495. , markers = function(d) { return d.markers ? d.markers : [] }
  2496. , measures = function(d) { return d.measures }
  2497. , width = null
  2498. , height = 55
  2499. , tickFormat = null
  2500. , ticks = null
  2501. , noData = null
  2502. , dispatch = d3.dispatch()
  2503. ;
  2504. tooltip
  2505. .duration(0)
  2506. .headerEnabled(false);
  2507. function chart(selection) {
  2508. selection.each(function(d, i) {
  2509. var container = d3.select(this);
  2510. nv.utils.initSVG(container);
  2511. var availableWidth = nv.utils.availableWidth(width, container, margin),
  2512. availableHeight = height - margin.top - margin.bottom,
  2513. that = this;
  2514. chart.update = function() { chart(selection) };
  2515. chart.container = this;
  2516. // Display No Data message if there's nothing to show.
  2517. if (!d || !ranges.call(this, d, i)) {
  2518. nv.utils.noData(chart, container)
  2519. return chart;
  2520. } else {
  2521. container.selectAll('.nv-noData').remove();
  2522. }
  2523. var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
  2524. markerz = markers.call(this, d, i).slice().sort(d3.descending),
  2525. measurez = measures.call(this, d, i).slice().sort(d3.descending);
  2526. // Setup containers and skeleton of chart
  2527. var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]);
  2528. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart');
  2529. var gEnter = wrapEnter.append('g');
  2530. var g = wrap.select('g');
  2531. gEnter.append('g').attr('class', 'nv-bulletWrap');
  2532. gEnter.append('g').attr('class', 'nv-titles');
  2533. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  2534. // Compute the new x-scale.
  2535. var x1 = d3.scale.linear()
  2536. .domain([0, Math.max(rangez[0], (markerz[0] || 0), measurez[0])]) // TODO: need to allow forceX and forceY, and xDomain, yDomain
  2537. .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
  2538. // Retrieve the old x-scale, if this is an update.
  2539. var x0 = this.__chart__ || d3.scale.linear()
  2540. .domain([0, Infinity])
  2541. .range(x1.range());
  2542. // Stash the new scale.
  2543. this.__chart__ = x1;
  2544. var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
  2545. w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
  2546. var title = gEnter.select('.nv-titles').append('g')
  2547. .attr('text-anchor', 'end')
  2548. .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')');
  2549. title.append('text')
  2550. .attr('class', 'nv-title')
  2551. .text(function(d) { return d.title; });
  2552. title.append('text')
  2553. .attr('class', 'nv-subtitle')
  2554. .attr('dy', '1em')
  2555. .text(function(d) { return d.subtitle; });
  2556. bullet
  2557. .width(availableWidth)
  2558. .height(availableHeight)
  2559. var bulletWrap = g.select('.nv-bulletWrap');
  2560. d3.transition(bulletWrap).call(bullet);
  2561. // Compute the tick format.
  2562. var format = tickFormat || x1.tickFormat( availableWidth / 100 );
  2563. // Update the tick groups.
  2564. var tick = g.selectAll('g.nv-tick')
  2565. .data(x1.ticks( ticks ? ticks : (availableWidth / 50) ), function(d) {
  2566. return this.textContent || format(d);
  2567. });
  2568. // Initialize the ticks with the old scale, x0.
  2569. var tickEnter = tick.enter().append('g')
  2570. .attr('class', 'nv-tick')
  2571. .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' })
  2572. .style('opacity', 1e-6);
  2573. tickEnter.append('line')
  2574. .attr('y1', availableHeight)
  2575. .attr('y2', availableHeight * 7 / 6);
  2576. tickEnter.append('text')
  2577. .attr('text-anchor', 'middle')
  2578. .attr('dy', '1em')
  2579. .attr('y', availableHeight * 7 / 6)
  2580. .text(format);
  2581. // Transition the updating ticks to the new scale, x1.
  2582. var tickUpdate = d3.transition(tick)
  2583. .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
  2584. .style('opacity', 1);
  2585. tickUpdate.select('line')
  2586. .attr('y1', availableHeight)
  2587. .attr('y2', availableHeight * 7 / 6);
  2588. tickUpdate.select('text')
  2589. .attr('y', availableHeight * 7 / 6);
  2590. // Transition the exiting ticks to the new scale, x1.
  2591. d3.transition(tick.exit())
  2592. .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
  2593. .style('opacity', 1e-6)
  2594. .remove();
  2595. });
  2596. d3.timer.flush();
  2597. return chart;
  2598. }
  2599. //============================================================
  2600. // Event Handling/Dispatching (out of chart's scope)
  2601. //------------------------------------------------------------
  2602. bullet.dispatch.on('elementMouseover.tooltip', function(evt) {
  2603. evt['series'] = {
  2604. key: evt.label,
  2605. value: evt.value,
  2606. color: evt.color
  2607. };
  2608. tooltip.data(evt).hidden(false);
  2609. });
  2610. bullet.dispatch.on('elementMouseout.tooltip', function(evt) {
  2611. tooltip.hidden(true);
  2612. });
  2613. bullet.dispatch.on('elementMousemove.tooltip', function(evt) {
  2614. tooltip();
  2615. });
  2616. //============================================================
  2617. // Expose Public Variables
  2618. //------------------------------------------------------------
  2619. chart.bullet = bullet;
  2620. chart.dispatch = dispatch;
  2621. chart.tooltip = tooltip;
  2622. chart.options = nv.utils.optionsFunc.bind(chart);
  2623. chart._options = Object.create({}, {
  2624. // simple options, just get/set the necessary values
  2625. ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good)
  2626. markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal)
  2627. measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast)
  2628. width: {get: function(){return width;}, set: function(_){width=_;}},
  2629. height: {get: function(){return height;}, set: function(_){height=_;}},
  2630. tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}},
  2631. ticks: {get: function(){return ticks;}, set: function(_){ticks=_;}},
  2632. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  2633. // options that require extra logic in the setter
  2634. margin: {get: function(){return margin;}, set: function(_){
  2635. margin.top = _.top !== undefined ? _.top : margin.top;
  2636. margin.right = _.right !== undefined ? _.right : margin.right;
  2637. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  2638. margin.left = _.left !== undefined ? _.left : margin.left;
  2639. }},
  2640. orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
  2641. orient = _;
  2642. reverse = orient == 'right' || orient == 'bottom';
  2643. }}
  2644. });
  2645. nv.utils.inheritOptions(chart, bullet);
  2646. nv.utils.initOptions(chart);
  2647. return chart;
  2648. };
  2649. nv.models.candlestickBar = function() {
  2650. "use strict";
  2651. //============================================================
  2652. // Public Variables with Default Settings
  2653. //------------------------------------------------------------
  2654. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  2655. , width = null
  2656. , height = null
  2657. , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
  2658. , container
  2659. , x = d3.scale.linear()
  2660. , y = d3.scale.linear()
  2661. , getX = function(d) { return d.x }
  2662. , getY = function(d) { return d.y }
  2663. , getOpen = function(d) { return d.open }
  2664. , getClose = function(d) { return d.close }
  2665. , getHigh = function(d) { return d.high }
  2666. , getLow = function(d) { return d.low }
  2667. , forceX = []
  2668. , forceY = []
  2669. , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
  2670. , clipEdge = true
  2671. , color = nv.utils.defaultColor()
  2672. , interactive = false
  2673. , xDomain
  2674. , yDomain
  2675. , xRange
  2676. , yRange
  2677. , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove')
  2678. ;
  2679. //============================================================
  2680. // Private Variables
  2681. //------------------------------------------------------------
  2682. function chart(selection) {
  2683. selection.each(function(data) {
  2684. container = d3.select(this);
  2685. var availableWidth = nv.utils.availableWidth(width, container, margin),
  2686. availableHeight = nv.utils.availableHeight(height, container, margin);
  2687. nv.utils.initSVG(container);
  2688. // Width of the candlestick bars.
  2689. var barWidth = (availableWidth / data[0].values.length) * .45;
  2690. // Setup Scales
  2691. x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
  2692. if (padData)
  2693. x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
  2694. else
  2695. x.range(xRange || [5 + barWidth / 2, availableWidth - barWidth / 2 - 5]);
  2696. y.domain(yDomain || [
  2697. d3.min(data[0].values.map(getLow).concat(forceY)),
  2698. d3.max(data[0].values.map(getHigh).concat(forceY))
  2699. ]
  2700. ).range(yRange || [availableHeight, 0]);
  2701. // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
  2702. if (x.domain()[0] === x.domain()[1])
  2703. x.domain()[0] ?
  2704. x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
  2705. : x.domain([-1,1]);
  2706. if (y.domain()[0] === y.domain()[1])
  2707. y.domain()[0] ?
  2708. y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
  2709. : y.domain([-1,1]);
  2710. // Setup containers and skeleton of chart
  2711. var wrap = d3.select(this).selectAll('g.nv-wrap.nv-candlestickBar').data([data[0].values]);
  2712. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-candlestickBar');
  2713. var defsEnter = wrapEnter.append('defs');
  2714. var gEnter = wrapEnter.append('g');
  2715. var g = wrap.select('g');
  2716. gEnter.append('g').attr('class', 'nv-ticks');
  2717. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  2718. container
  2719. .on('click', function(d,i) {
  2720. dispatch.chartClick({
  2721. data: d,
  2722. index: i,
  2723. pos: d3.event,
  2724. id: id
  2725. });
  2726. });
  2727. defsEnter.append('clipPath')
  2728. .attr('id', 'nv-chart-clip-path-' + id)
  2729. .append('rect');
  2730. wrap.select('#nv-chart-clip-path-' + id + ' rect')
  2731. .attr('width', availableWidth)
  2732. .attr('height', availableHeight);
  2733. g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
  2734. var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
  2735. .data(function(d) { return d });
  2736. ticks.exit().remove();
  2737. var tickGroups = ticks.enter().append('g');
  2738. // The colors are currently controlled by CSS.
  2739. ticks
  2740. .attr('class', function(d, i, j) { return (getOpen(d, i) > getClose(d, i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i});
  2741. var lines = tickGroups.append('line')
  2742. .attr('class', 'nv-candlestick-lines')
  2743. .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; })
  2744. .attr('x1', 0)
  2745. .attr('y1', function(d, i) { return y(getHigh(d, i)); })
  2746. .attr('x2', 0)
  2747. .attr('y2', function(d, i) { return y(getLow(d, i)); });
  2748. var rects = tickGroups.append('rect')
  2749. .attr('class', 'nv-candlestick-rects nv-bars')
  2750. .attr('transform', function(d, i) {
  2751. return 'translate(' + (x(getX(d, i)) - barWidth/2) + ','
  2752. + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0))
  2753. + ')';
  2754. })
  2755. .attr('x', 0)
  2756. .attr('y', 0)
  2757. .attr('width', barWidth)
  2758. .attr('height', function(d, i) {
  2759. var open = getOpen(d, i);
  2760. var close = getClose(d, i);
  2761. return open > close ? y(close) - y(open) : y(open) - y(close);
  2762. });
  2763. ticks.select('.nv-candlestick-lines').transition()
  2764. .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; })
  2765. .attr('x1', 0)
  2766. .attr('y1', function(d, i) { return y(getHigh(d, i)); })
  2767. .attr('x2', 0)
  2768. .attr('y2', function(d, i) { return y(getLow(d, i)); });
  2769. ticks.select('.nv-candlestick-rects').transition()
  2770. .attr('transform', function(d, i) {
  2771. return 'translate(' + (x(getX(d, i)) - barWidth/2) + ','
  2772. + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0))
  2773. + ')';
  2774. })
  2775. .attr('x', 0)
  2776. .attr('y', 0)
  2777. .attr('width', barWidth)
  2778. .attr('height', function(d, i) {
  2779. var open = getOpen(d, i);
  2780. var close = getClose(d, i);
  2781. return open > close ? y(close) - y(open) : y(open) - y(close);
  2782. });
  2783. });
  2784. return chart;
  2785. }
  2786. //Create methods to allow outside functions to highlight a specific bar.
  2787. chart.highlightPoint = function(pointIndex, isHoverOver) {
  2788. chart.clearHighlights();
  2789. container.select(".nv-candlestickBar .nv-tick-0-" + pointIndex)
  2790. .classed("hover", isHoverOver)
  2791. ;
  2792. };
  2793. chart.clearHighlights = function() {
  2794. container.select(".nv-candlestickBar .nv-tick.hover")
  2795. .classed("hover", false)
  2796. ;
  2797. };
  2798. //============================================================
  2799. // Expose Public Variables
  2800. //------------------------------------------------------------
  2801. chart.dispatch = dispatch;
  2802. chart.options = nv.utils.optionsFunc.bind(chart);
  2803. chart._options = Object.create({}, {
  2804. // simple options, just get/set the necessary values
  2805. width: {get: function(){return width;}, set: function(_){width=_;}},
  2806. height: {get: function(){return height;}, set: function(_){height=_;}},
  2807. xScale: {get: function(){return x;}, set: function(_){x=_;}},
  2808. yScale: {get: function(){return y;}, set: function(_){y=_;}},
  2809. xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
  2810. yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
  2811. xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
  2812. yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
  2813. forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
  2814. forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
  2815. padData: {get: function(){return padData;}, set: function(_){padData=_;}},
  2816. clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
  2817. id: {get: function(){return id;}, set: function(_){id=_;}},
  2818. interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
  2819. x: {get: function(){return getX;}, set: function(_){getX=_;}},
  2820. y: {get: function(){return getY;}, set: function(_){getY=_;}},
  2821. open: {get: function(){return getOpen();}, set: function(_){getOpen=_;}},
  2822. close: {get: function(){return getClose();}, set: function(_){getClose=_;}},
  2823. high: {get: function(){return getHigh;}, set: function(_){getHigh=_;}},
  2824. low: {get: function(){return getLow;}, set: function(_){getLow=_;}},
  2825. // options that require extra logic in the setter
  2826. margin: {get: function(){return margin;}, set: function(_){
  2827. margin.top = _.top != undefined ? _.top : margin.top;
  2828. margin.right = _.right != undefined ? _.right : margin.right;
  2829. margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
  2830. margin.left = _.left != undefined ? _.left : margin.left;
  2831. }},
  2832. color: {get: function(){return color;}, set: function(_){
  2833. color = nv.utils.getColor(_);
  2834. }}
  2835. });
  2836. nv.utils.initOptions(chart);
  2837. return chart;
  2838. };
  2839. nv.models.cumulativeLineChart = function() {
  2840. "use strict";
  2841. //============================================================
  2842. // Public Variables with Default Settings
  2843. //------------------------------------------------------------
  2844. var lines = nv.models.line()
  2845. , xAxis = nv.models.axis()
  2846. , yAxis = nv.models.axis()
  2847. , legend = nv.models.legend()
  2848. , controls = nv.models.legend()
  2849. , interactiveLayer = nv.interactiveGuideline()
  2850. , tooltip = nv.models.tooltip()
  2851. ;
  2852. var margin = {top: 30, right: 30, bottom: 50, left: 60}
  2853. , color = nv.utils.defaultColor()
  2854. , width = null
  2855. , height = null
  2856. , showLegend = true
  2857. , showXAxis = true
  2858. , showYAxis = true
  2859. , rightAlignYAxis = false
  2860. , showControls = true
  2861. , useInteractiveGuideline = false
  2862. , rescaleY = true
  2863. , x //can be accessed via chart.xScale()
  2864. , y //can be accessed via chart.yScale()
  2865. , id = lines.id()
  2866. , state = nv.utils.state()
  2867. , defaultState = null
  2868. , noData = null
  2869. , average = function(d) { return d.average }
  2870. , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
  2871. , transitionDuration = 250
  2872. , duration = 250
  2873. , noErrorCheck = false //if set to TRUE, will bypass an error check in the indexify function.
  2874. ;
  2875. state.index = 0;
  2876. state.rescaleY = rescaleY;
  2877. xAxis.orient('bottom').tickPadding(7);
  2878. yAxis.orient((rightAlignYAxis) ? 'right' : 'left');
  2879. tooltip.valueFormatter(function(d, i) {
  2880. return yAxis.tickFormat()(d, i);
  2881. }).headerFormatter(function(d, i) {
  2882. return xAxis.tickFormat()(d, i);
  2883. });
  2884. controls.updateState(false);
  2885. //============================================================
  2886. // Private Variables
  2887. //------------------------------------------------------------
  2888. var dx = d3.scale.linear()
  2889. , index = {i: 0, x: 0}
  2890. , renderWatch = nv.utils.renderWatch(dispatch, duration)
  2891. ;
  2892. var stateGetter = function(data) {
  2893. return function(){
  2894. return {
  2895. active: data.map(function(d) { return !d.disabled }),
  2896. index: index.i,
  2897. rescaleY: rescaleY
  2898. };
  2899. }
  2900. };
  2901. var stateSetter = function(data) {
  2902. return function(state) {
  2903. if (state.index !== undefined)
  2904. index.i = state.index;
  2905. if (state.rescaleY !== undefined)
  2906. rescaleY = state.rescaleY;
  2907. if (state.active !== undefined)
  2908. data.forEach(function(series,i) {
  2909. series.disabled = !state.active[i];
  2910. });
  2911. }
  2912. };
  2913. function chart(selection) {
  2914. renderWatch.reset();
  2915. renderWatch.models(lines);
  2916. if (showXAxis) renderWatch.models(xAxis);
  2917. if (showYAxis) renderWatch.models(yAxis);
  2918. selection.each(function(data) {
  2919. var container = d3.select(this);
  2920. nv.utils.initSVG(container);
  2921. container.classed('nv-chart-' + id, true);
  2922. var that = this;
  2923. var availableWidth = nv.utils.availableWidth(width, container, margin),
  2924. availableHeight = nv.utils.availableHeight(height, container, margin);
  2925. chart.update = function() {
  2926. if (duration === 0)
  2927. container.call(chart);
  2928. else
  2929. container.transition().duration(duration).call(chart)
  2930. };
  2931. chart.container = this;
  2932. state
  2933. .setter(stateSetter(data), chart.update)
  2934. .getter(stateGetter(data))
  2935. .update();
  2936. // DEPRECATED set state.disableddisabled
  2937. state.disabled = data.map(function(d) { return !!d.disabled });
  2938. if (!defaultState) {
  2939. var key;
  2940. defaultState = {};
  2941. for (key in state) {
  2942. if (state[key] instanceof Array)
  2943. defaultState[key] = state[key].slice(0);
  2944. else
  2945. defaultState[key] = state[key];
  2946. }
  2947. }
  2948. var indexDrag = d3.behavior.drag()
  2949. .on('dragstart', dragStart)
  2950. .on('drag', dragMove)
  2951. .on('dragend', dragEnd);
  2952. function dragStart(d,i) {
  2953. d3.select(chart.container)
  2954. .style('cursor', 'ew-resize');
  2955. }
  2956. function dragMove(d,i) {
  2957. index.x = d3.event.x;
  2958. index.i = Math.round(dx.invert(index.x));
  2959. updateZero();
  2960. }
  2961. function dragEnd(d,i) {
  2962. d3.select(chart.container)
  2963. .style('cursor', 'auto');
  2964. // update state and send stateChange with new index
  2965. state.index = index.i;
  2966. dispatch.stateChange(state);
  2967. }
  2968. // Display No Data message if there's nothing to show.
  2969. if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
  2970. nv.utils.noData(chart, container)
  2971. return chart;
  2972. } else {
  2973. container.selectAll('.nv-noData').remove();
  2974. }
  2975. // Setup Scales
  2976. x = lines.xScale();
  2977. y = lines.yScale();
  2978. if (!rescaleY) {
  2979. var seriesDomains = data
  2980. .filter(function(series) { return !series.disabled })
  2981. .map(function(series,i) {
  2982. var initialDomain = d3.extent(series.values, lines.y());
  2983. //account for series being disabled when losing 95% or more
  2984. if (initialDomain[0] < -.95) initialDomain[0] = -.95;
  2985. return [
  2986. (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]),
  2987. (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0])
  2988. ];
  2989. });
  2990. var completeDomain = [
  2991. d3.min(seriesDomains, function(d) { return d[0] }),
  2992. d3.max(seriesDomains, function(d) { return d[1] })
  2993. ];
  2994. lines.yDomain(completeDomain);
  2995. } else {
  2996. lines.yDomain(null);
  2997. }
  2998. dx.domain([0, data[0].values.length - 1]) //Assumes all series have same length
  2999. .range([0, availableWidth])
  3000. .clamp(true);
  3001. var data = indexify(index.i, data);
  3002. // Setup containers and skeleton of chart
  3003. var interactivePointerEvents = (useInteractiveGuideline) ? "none" : "all";
  3004. var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]);
  3005. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g');
  3006. var g = wrap.select('g');
  3007. gEnter.append('g').attr('class', 'nv-interactive');
  3008. gEnter.append('g').attr('class', 'nv-x nv-axis').style("pointer-events","none");
  3009. gEnter.append('g').attr('class', 'nv-y nv-axis');
  3010. gEnter.append('g').attr('class', 'nv-background');
  3011. gEnter.append('g').attr('class', 'nv-linesWrap').style("pointer-events",interactivePointerEvents);
  3012. gEnter.append('g').attr('class', 'nv-avgLinesWrap').style("pointer-events","none");
  3013. gEnter.append('g').attr('class', 'nv-legendWrap');
  3014. gEnter.append('g').attr('class', 'nv-controlsWrap');
  3015. // Legend
  3016. if (!showLegend) {
  3017. g.select('.nv-legendWrap').selectAll('*').remove();
  3018. } else {
  3019. legend.width(availableWidth);
  3020. g.select('.nv-legendWrap')
  3021. .datum(data)
  3022. .call(legend);
  3023. if (legend.height() > margin.top) {
  3024. margin.top = legend.height();
  3025. availableHeight = nv.utils.availableHeight(height, container, margin);
  3026. }
  3027. g.select('.nv-legendWrap')
  3028. .attr('transform', 'translate(0,' + (-margin.top) +')')
  3029. }
  3030. // Controls
  3031. if (!showControls) {
  3032. g.select('.nv-controlsWrap').selectAll('*').remove();
  3033. } else {
  3034. var controlsData = [
  3035. { key: 'Re-scale y-axis', disabled: !rescaleY }
  3036. ];
  3037. controls
  3038. .width(140)
  3039. .color(['#444', '#444', '#444'])
  3040. .rightAlign(false)
  3041. .margin({top: 5, right: 0, bottom: 5, left: 20})
  3042. ;
  3043. g.select('.nv-controlsWrap')
  3044. .datum(controlsData)
  3045. .attr('transform', 'translate(0,' + (-margin.top) +')')
  3046. .call(controls);
  3047. }
  3048. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  3049. if (rightAlignYAxis) {
  3050. g.select(".nv-y.nv-axis")
  3051. .attr("transform", "translate(" + availableWidth + ",0)");
  3052. }
  3053. // Show error if series goes below 100%
  3054. var tempDisabled = data.filter(function(d) { return d.tempDisabled });
  3055. wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates
  3056. if (tempDisabled.length) {
  3057. wrap.append('text').attr('class', 'tempDisabled')
  3058. .attr('x', availableWidth / 2)
  3059. .attr('y', '-.71em')
  3060. .style('text-anchor', 'end')
  3061. .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.');
  3062. }
  3063. //Set up interactive layer
  3064. if (useInteractiveGuideline) {
  3065. interactiveLayer
  3066. .width(availableWidth)
  3067. .height(availableHeight)
  3068. .margin({left:margin.left,top:margin.top})
  3069. .svgContainer(container)
  3070. .xScale(x);
  3071. wrap.select(".nv-interactive").call(interactiveLayer);
  3072. }
  3073. gEnter.select('.nv-background')
  3074. .append('rect');
  3075. g.select('.nv-background rect')
  3076. .attr('width', availableWidth)
  3077. .attr('height', availableHeight);
  3078. lines
  3079. //.x(function(d) { return d.x })
  3080. .y(function(d) { return d.display.y })
  3081. .width(availableWidth)
  3082. .height(availableHeight)
  3083. .color(data.map(function(d,i) {
  3084. return d.color || color(d, i);
  3085. }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; }));
  3086. var linesWrap = g.select('.nv-linesWrap')
  3087. .datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled }));
  3088. linesWrap.call(lines);
  3089. //Store a series index number in the data array.
  3090. data.forEach(function(d,i) {
  3091. d.seriesIndex = i;
  3092. });
  3093. var avgLineData = data.filter(function(d) {
  3094. return !d.disabled && !!average(d);
  3095. });
  3096. var avgLines = g.select(".nv-avgLinesWrap").selectAll("line")
  3097. .data(avgLineData, function(d) { return d.key; });
  3098. var getAvgLineY = function(d) {
  3099. //If average lines go off the svg element, clamp them to the svg bounds.
  3100. var yVal = y(average(d));
  3101. if (yVal < 0) return 0;
  3102. if (yVal > availableHeight) return availableHeight;
  3103. return yVal;
  3104. };
  3105. avgLines.enter()
  3106. .append('line')
  3107. .style('stroke-width',2)
  3108. .style('stroke-dasharray','10,10')
  3109. .style('stroke',function (d,i) {
  3110. return lines.color()(d,d.seriesIndex);
  3111. })
  3112. .attr('x1',0)
  3113. .attr('x2',availableWidth)
  3114. .attr('y1', getAvgLineY)
  3115. .attr('y2', getAvgLineY);
  3116. avgLines
  3117. .style('stroke-opacity',function(d){
  3118. //If average lines go offscreen, make them transparent
  3119. var yVal = y(average(d));
  3120. if (yVal < 0 || yVal > availableHeight) return 0;
  3121. return 1;
  3122. })
  3123. .attr('x1',0)
  3124. .attr('x2',availableWidth)
  3125. .attr('y1', getAvgLineY)
  3126. .attr('y2', getAvgLineY);
  3127. avgLines.exit().remove();
  3128. //Create index line
  3129. var indexLine = linesWrap.selectAll('.nv-indexLine')
  3130. .data([index]);
  3131. indexLine.enter().append('rect').attr('class', 'nv-indexLine')
  3132. .attr('width', 3)
  3133. .attr('x', -2)
  3134. .attr('fill', 'red')
  3135. .attr('fill-opacity', .5)
  3136. .style("pointer-events","all")
  3137. .call(indexDrag);
  3138. indexLine
  3139. .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' })
  3140. .attr('height', availableHeight);
  3141. // Setup Axes
  3142. if (showXAxis) {
  3143. xAxis
  3144. .scale(x)
  3145. ._ticks( nv.utils.calcTicksX(availableWidth/70, data) )
  3146. .tickSize(-availableHeight, 0);
  3147. g.select('.nv-x.nv-axis')
  3148. .attr('transform', 'translate(0,' + y.range()[0] + ')');
  3149. g.select('.nv-x.nv-axis')
  3150. .call(xAxis);
  3151. }
  3152. if (showYAxis) {
  3153. yAxis
  3154. .scale(y)
  3155. ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
  3156. .tickSize( -availableWidth, 0);
  3157. g.select('.nv-y.nv-axis')
  3158. .call(yAxis);
  3159. }
  3160. //============================================================
  3161. // Event Handling/Dispatching (in chart's scope)
  3162. //------------------------------------------------------------
  3163. function updateZero() {
  3164. indexLine
  3165. .data([index]);
  3166. //When dragging the index line, turn off line transitions.
  3167. // Then turn them back on when done dragging.
  3168. var oldDuration = chart.duration();
  3169. chart.duration(0);
  3170. chart.update();
  3171. chart.duration(oldDuration);
  3172. }
  3173. g.select('.nv-background rect')
  3174. .on('click', function() {
  3175. index.x = d3.mouse(this)[0];
  3176. index.i = Math.round(dx.invert(index.x));
  3177. // update state and send stateChange with new index
  3178. state.index = index.i;
  3179. dispatch.stateChange(state);
  3180. updateZero();
  3181. });
  3182. lines.dispatch.on('elementClick', function(e) {
  3183. index.i = e.pointIndex;
  3184. index.x = dx(index.i);
  3185. // update state and send stateChange with new index
  3186. state.index = index.i;
  3187. dispatch.stateChange(state);
  3188. updateZero();
  3189. });
  3190. controls.dispatch.on('legendClick', function(d,i) {
  3191. d.disabled = !d.disabled;
  3192. rescaleY = !d.disabled;
  3193. state.rescaleY = rescaleY;
  3194. dispatch.stateChange(state);
  3195. chart.update();
  3196. });
  3197. legend.dispatch.on('stateChange', function(newState) {
  3198. for (var key in newState)
  3199. state[key] = newState[key];
  3200. dispatch.stateChange(state);
  3201. chart.update();
  3202. });
  3203. interactiveLayer.dispatch.on('elementMousemove', function(e) {
  3204. lines.clearHighlights();
  3205. var singlePoint, pointIndex, pointXLocation, allData = [];
  3206. data
  3207. .filter(function(series, i) {
  3208. series.seriesIndex = i;
  3209. return !series.disabled;
  3210. })
  3211. .forEach(function(series,i) {
  3212. pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
  3213. lines.highlightPoint(i, pointIndex, true);
  3214. var point = series.values[pointIndex];
  3215. if (typeof point === 'undefined') return;
  3216. if (typeof singlePoint === 'undefined') singlePoint = point;
  3217. if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
  3218. allData.push({
  3219. key: series.key,
  3220. value: chart.y()(point, pointIndex),
  3221. color: color(series,series.seriesIndex)
  3222. });
  3223. });
  3224. //Highlight the tooltip entry based on which point the mouse is closest to.
  3225. if (allData.length > 2) {
  3226. var yValue = chart.yScale().invert(e.mouseY);
  3227. var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
  3228. var threshold = 0.03 * domainExtent;
  3229. var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
  3230. if (indexToHighlight !== null)
  3231. allData[indexToHighlight].highlight = true;
  3232. }
  3233. var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex), pointIndex);
  3234. interactiveLayer.tooltip
  3235. .valueFormatter(function(d,i) {
  3236. return yAxis.tickFormat()(d);
  3237. })
  3238. .data(
  3239. {
  3240. value: xValue,
  3241. series: allData
  3242. }
  3243. )();
  3244. interactiveLayer.renderGuideLine(pointXLocation);
  3245. });
  3246. interactiveLayer.dispatch.on("elementMouseout",function(e) {
  3247. lines.clearHighlights();
  3248. });
  3249. // Update chart from a state object passed to event handler
  3250. dispatch.on('changeState', function(e) {
  3251. if (typeof e.disabled !== 'undefined') {
  3252. data.forEach(function(series,i) {
  3253. series.disabled = e.disabled[i];
  3254. });
  3255. state.disabled = e.disabled;
  3256. }
  3257. if (typeof e.index !== 'undefined') {
  3258. index.i = e.index;
  3259. index.x = dx(index.i);
  3260. state.index = e.index;
  3261. indexLine
  3262. .data([index]);
  3263. }
  3264. if (typeof e.rescaleY !== 'undefined') {
  3265. rescaleY = e.rescaleY;
  3266. }
  3267. chart.update();
  3268. });
  3269. });
  3270. renderWatch.renderEnd('cumulativeLineChart immediate');
  3271. return chart;
  3272. }
  3273. //============================================================
  3274. // Event Handling/Dispatching (out of chart's scope)
  3275. //------------------------------------------------------------
  3276. lines.dispatch.on('elementMouseover.tooltip', function(evt) {
  3277. var point = {
  3278. x: chart.x()(evt.point),
  3279. y: chart.y()(evt.point),
  3280. color: evt.point.color
  3281. };
  3282. evt.point = point;
  3283. tooltip.data(evt).hidden(false);
  3284. });
  3285. lines.dispatch.on('elementMouseout.tooltip', function(evt) {
  3286. tooltip.hidden(true)
  3287. });
  3288. //============================================================
  3289. // Functions
  3290. //------------------------------------------------------------
  3291. var indexifyYGetter = null;
  3292. /* Normalize the data according to an index point. */
  3293. function indexify(idx, data) {
  3294. if (!indexifyYGetter) indexifyYGetter = lines.y();
  3295. return data.map(function(line, i) {
  3296. if (!line.values) {
  3297. return line;
  3298. }
  3299. var indexValue = line.values[idx];
  3300. if (indexValue == null) {
  3301. return line;
  3302. }
  3303. var v = indexifyYGetter(indexValue, idx);
  3304. //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue
  3305. if (v < -.95 && !noErrorCheck) {
  3306. //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100)
  3307. line.tempDisabled = true;
  3308. return line;
  3309. }
  3310. line.tempDisabled = false;
  3311. line.values = line.values.map(function(point, pointIndex) {
  3312. point.display = {'y': (indexifyYGetter(point, pointIndex) - v) / (1 + v) };
  3313. return point;
  3314. });
  3315. return line;
  3316. })
  3317. }
  3318. //============================================================
  3319. // Expose Public Variables
  3320. //------------------------------------------------------------
  3321. // expose chart's sub-components
  3322. chart.dispatch = dispatch;
  3323. chart.lines = lines;
  3324. chart.legend = legend;
  3325. chart.controls = controls;
  3326. chart.xAxis = xAxis;
  3327. chart.yAxis = yAxis;
  3328. chart.interactiveLayer = interactiveLayer;
  3329. chart.state = state;
  3330. chart.tooltip = tooltip;
  3331. chart.options = nv.utils.optionsFunc.bind(chart);
  3332. chart._options = Object.create({}, {
  3333. // simple options, just get/set the necessary values
  3334. width: {get: function(){return width;}, set: function(_){width=_;}},
  3335. height: {get: function(){return height;}, set: function(_){height=_;}},
  3336. rescaleY: {get: function(){return rescaleY;}, set: function(_){rescaleY=_;}},
  3337. showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
  3338. showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
  3339. average: {get: function(){return average;}, set: function(_){average=_;}},
  3340. defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
  3341. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  3342. showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
  3343. showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
  3344. noErrorCheck: {get: function(){return noErrorCheck;}, set: function(_){noErrorCheck=_;}},
  3345. // options that require extra logic in the setter
  3346. margin: {get: function(){return margin;}, set: function(_){
  3347. margin.top = _.top !== undefined ? _.top : margin.top;
  3348. margin.right = _.right !== undefined ? _.right : margin.right;
  3349. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  3350. margin.left = _.left !== undefined ? _.left : margin.left;
  3351. }},
  3352. color: {get: function(){return color;}, set: function(_){
  3353. color = nv.utils.getColor(_);
  3354. legend.color(color);
  3355. }},
  3356. useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
  3357. useInteractiveGuideline = _;
  3358. if (_ === true) {
  3359. chart.interactive(false);
  3360. chart.useVoronoi(false);
  3361. }
  3362. }},
  3363. rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
  3364. rightAlignYAxis = _;
  3365. yAxis.orient( (_) ? 'right' : 'left');
  3366. }},
  3367. duration: {get: function(){return duration;}, set: function(_){
  3368. duration = _;
  3369. lines.duration(duration);
  3370. xAxis.duration(duration);
  3371. yAxis.duration(duration);
  3372. renderWatch.reset(duration);
  3373. }}
  3374. });
  3375. nv.utils.inheritOptions(chart, lines);
  3376. nv.utils.initOptions(chart);
  3377. return chart;
  3378. };
  3379. //TODO: consider deprecating by adding necessary features to multiBar model
  3380. nv.models.discreteBar = function() {
  3381. "use strict";
  3382. //============================================================
  3383. // Public Variables with Default Settings
  3384. //------------------------------------------------------------
  3385. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  3386. , width = 960
  3387. , height = 500
  3388. , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
  3389. , container
  3390. , x = d3.scale.ordinal()
  3391. , y = d3.scale.linear()
  3392. , getX = function(d) { return d.x }
  3393. , getY = function(d) { return d.y }
  3394. , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
  3395. , color = nv.utils.defaultColor()
  3396. , showValues = false
  3397. , valueFormat = d3.format(',.2f')
  3398. , xDomain
  3399. , yDomain
  3400. , xRange
  3401. , yRange
  3402. , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
  3403. , rectClass = 'discreteBar'
  3404. , duration = 250
  3405. ;
  3406. //============================================================
  3407. // Private Variables
  3408. //------------------------------------------------------------
  3409. var x0, y0;
  3410. var renderWatch = nv.utils.renderWatch(dispatch, duration);
  3411. function chart(selection) {
  3412. renderWatch.reset();
  3413. selection.each(function(data) {
  3414. var availableWidth = width - margin.left - margin.right,
  3415. availableHeight = height - margin.top - margin.bottom;
  3416. container = d3.select(this);
  3417. nv.utils.initSVG(container);
  3418. //add series index to each data point for reference
  3419. data.forEach(function(series, i) {
  3420. series.values.forEach(function(point) {
  3421. point.series = i;
  3422. });
  3423. });
  3424. // Setup Scales
  3425. // remap and flatten the data for use in calculating the scales' domains
  3426. var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
  3427. data.map(function(d) {
  3428. return d.values.map(function(d,i) {
  3429. return { x: getX(d,i), y: getY(d,i), y0: d.y0 }
  3430. })
  3431. });
  3432. x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
  3433. .rangeBands(xRange || [0, availableWidth], .1);
  3434. y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY)));
  3435. // If showValues, pad the Y axis range to account for label height
  3436. if (showValues) y.range(yRange || [availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]);
  3437. else y.range(yRange || [availableHeight, 0]);
  3438. //store old scales if they exist
  3439. x0 = x0 || x;
  3440. y0 = y0 || y.copy().range([y(0),y(0)]);
  3441. // Setup containers and skeleton of chart
  3442. var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]);
  3443. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar');
  3444. var gEnter = wrapEnter.append('g');
  3445. var g = wrap.select('g');
  3446. gEnter.append('g').attr('class', 'nv-groups');
  3447. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  3448. //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later
  3449. var groups = wrap.select('.nv-groups').selectAll('.nv-group')
  3450. .data(function(d) { return d }, function(d) { return d.key });
  3451. groups.enter().append('g')
  3452. .style('stroke-opacity', 1e-6)
  3453. .style('fill-opacity', 1e-6);
  3454. groups.exit()
  3455. .watchTransition(renderWatch, 'discreteBar: exit groups')
  3456. .style('stroke-opacity', 1e-6)
  3457. .style('fill-opacity', 1e-6)
  3458. .remove();
  3459. groups
  3460. .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
  3461. .classed('hover', function(d) { return d.hover });
  3462. groups
  3463. .watchTransition(renderWatch, 'discreteBar: groups')
  3464. .style('stroke-opacity', 1)
  3465. .style('fill-opacity', .75);
  3466. var bars = groups.selectAll('g.nv-bar')
  3467. .data(function(d) { return d.values });
  3468. bars.exit().remove();
  3469. var barsEnter = bars.enter().append('g')
  3470. .attr('transform', function(d,i,j) {
  3471. return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')'
  3472. })
  3473. .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
  3474. d3.select(this).classed('hover', true);
  3475. dispatch.elementMouseover({
  3476. data: d,
  3477. index: i,
  3478. color: d3.select(this).style("fill")
  3479. });
  3480. })
  3481. .on('mouseout', function(d,i) {
  3482. d3.select(this).classed('hover', false);
  3483. dispatch.elementMouseout({
  3484. data: d,
  3485. index: i,
  3486. color: d3.select(this).style("fill")
  3487. });
  3488. })
  3489. .on('mousemove', function(d,i) {
  3490. dispatch.elementMousemove({
  3491. data: d,
  3492. index: i,
  3493. color: d3.select(this).style("fill")
  3494. });
  3495. })
  3496. .on('click', function(d,i) {
  3497. var element = this;
  3498. dispatch.elementClick({
  3499. data: d,
  3500. index: i,
  3501. color: d3.select(this).style("fill"),
  3502. event: d3.event,
  3503. element: element
  3504. });
  3505. d3.event.stopPropagation();
  3506. })
  3507. .on('dblclick', function(d,i) {
  3508. dispatch.elementDblClick({
  3509. data: d,
  3510. index: i,
  3511. color: d3.select(this).style("fill")
  3512. });
  3513. d3.event.stopPropagation();
  3514. });
  3515. barsEnter.append('rect')
  3516. .attr('height', 0)
  3517. .attr('width', x.rangeBand() * .9 / data.length )
  3518. if (showValues) {
  3519. barsEnter.append('text')
  3520. .attr('text-anchor', 'middle')
  3521. ;
  3522. bars.select('text')
  3523. .text(function(d,i) { return valueFormat(getY(d,i)) })
  3524. .watchTransition(renderWatch, 'discreteBar: bars text')
  3525. .attr('x', x.rangeBand() * .9 / 2)
  3526. .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 })
  3527. ;
  3528. } else {
  3529. bars.selectAll('text').remove();
  3530. }
  3531. bars
  3532. .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' })
  3533. .style('fill', function(d,i) { return d.color || color(d,i) })
  3534. .style('stroke', function(d,i) { return d.color || color(d,i) })
  3535. .select('rect')
  3536. .attr('class', rectClass)
  3537. .watchTransition(renderWatch, 'discreteBar: bars rect')
  3538. .attr('width', x.rangeBand() * .9 / data.length);
  3539. bars.watchTransition(renderWatch, 'discreteBar: bars')
  3540. //.delay(function(d,i) { return i * 1200 / data[0].values.length })
  3541. .attr('transform', function(d,i) {
  3542. var left = x(getX(d,i)) + x.rangeBand() * .05,
  3543. top = getY(d,i) < 0 ?
  3544. y(0) :
  3545. y(0) - y(getY(d,i)) < 1 ?
  3546. y(0) - 1 : //make 1 px positive bars show up above y=0
  3547. y(getY(d,i));
  3548. return 'translate(' + left + ', ' + top + ')'
  3549. })
  3550. .select('rect')
  3551. .attr('height', function(d,i) {
  3552. return Math.max(Math.abs(y(getY(d,i)) - y(0)), 1)
  3553. });
  3554. //store old scales for use in transitions on update
  3555. x0 = x.copy();
  3556. y0 = y.copy();
  3557. });
  3558. renderWatch.renderEnd('discreteBar immediate');
  3559. return chart;
  3560. }
  3561. //============================================================
  3562. // Expose Public Variables
  3563. //------------------------------------------------------------
  3564. chart.dispatch = dispatch;
  3565. chart.options = nv.utils.optionsFunc.bind(chart);
  3566. chart._options = Object.create({}, {
  3567. // simple options, just get/set the necessary values
  3568. width: {get: function(){return width;}, set: function(_){width=_;}},
  3569. height: {get: function(){return height;}, set: function(_){height=_;}},
  3570. forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
  3571. showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}},
  3572. x: {get: function(){return getX;}, set: function(_){getX=_;}},
  3573. y: {get: function(){return getY;}, set: function(_){getY=_;}},
  3574. xScale: {get: function(){return x;}, set: function(_){x=_;}},
  3575. yScale: {get: function(){return y;}, set: function(_){y=_;}},
  3576. xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
  3577. yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
  3578. xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
  3579. yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
  3580. valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
  3581. id: {get: function(){return id;}, set: function(_){id=_;}},
  3582. rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}},
  3583. // options that require extra logic in the setter
  3584. margin: {get: function(){return margin;}, set: function(_){
  3585. margin.top = _.top !== undefined ? _.top : margin.top;
  3586. margin.right = _.right !== undefined ? _.right : margin.right;
  3587. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  3588. margin.left = _.left !== undefined ? _.left : margin.left;
  3589. }},
  3590. color: {get: function(){return color;}, set: function(_){
  3591. color = nv.utils.getColor(_);
  3592. }},
  3593. duration: {get: function(){return duration;}, set: function(_){
  3594. duration = _;
  3595. renderWatch.reset(duration);
  3596. }}
  3597. });
  3598. nv.utils.initOptions(chart);
  3599. return chart;
  3600. };
  3601. nv.models.discreteBarChart = function() {
  3602. "use strict";
  3603. //============================================================
  3604. // Public Variables with Default Settings
  3605. //------------------------------------------------------------
  3606. var discretebar = nv.models.discreteBar()
  3607. , xAxis = nv.models.axis()
  3608. , yAxis = nv.models.axis()
  3609. , legend = nv.models.legend()
  3610. , tooltip = nv.models.tooltip()
  3611. ;
  3612. var margin = {top: 15, right: 10, bottom: 50, left: 60}
  3613. , width = null
  3614. , height = null
  3615. , color = nv.utils.getColor()
  3616. , showLegend = false
  3617. , showXAxis = true
  3618. , showYAxis = true
  3619. , rightAlignYAxis = false
  3620. , staggerLabels = false
  3621. , wrapLabels = false
  3622. , rotateLabels = 0
  3623. , x
  3624. , y
  3625. , noData = null
  3626. , dispatch = d3.dispatch('beforeUpdate','renderEnd')
  3627. , duration = 250
  3628. ;
  3629. xAxis
  3630. .orient('bottom')
  3631. .showMaxMin(false)
  3632. .tickFormat(function(d) { return d })
  3633. ;
  3634. yAxis
  3635. .orient((rightAlignYAxis) ? 'right' : 'left')
  3636. .tickFormat(d3.format(',.1f'))
  3637. ;
  3638. tooltip
  3639. .duration(0)
  3640. .headerEnabled(false)
  3641. .valueFormatter(function(d, i) {
  3642. return yAxis.tickFormat()(d, i);
  3643. })
  3644. .keyFormatter(function(d, i) {
  3645. return xAxis.tickFormat()(d, i);
  3646. });
  3647. //============================================================
  3648. // Private Variables
  3649. //------------------------------------------------------------
  3650. var renderWatch = nv.utils.renderWatch(dispatch, duration);
  3651. function chart(selection) {
  3652. renderWatch.reset();
  3653. renderWatch.models(discretebar);
  3654. if (showXAxis) renderWatch.models(xAxis);
  3655. if (showYAxis) renderWatch.models(yAxis);
  3656. selection.each(function(data) {
  3657. var container = d3.select(this),
  3658. that = this;
  3659. nv.utils.initSVG(container);
  3660. var availableWidth = nv.utils.availableWidth(width, container, margin),
  3661. availableHeight = nv.utils.availableHeight(height, container, margin);
  3662. chart.update = function() {
  3663. dispatch.beforeUpdate();
  3664. container.transition().duration(duration).call(chart);
  3665. };
  3666. chart.container = this;
  3667. // Display No Data message if there's nothing to show.
  3668. if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
  3669. nv.utils.noData(chart, container);
  3670. return chart;
  3671. } else {
  3672. container.selectAll('.nv-noData').remove();
  3673. }
  3674. // Setup Scales
  3675. x = discretebar.xScale();
  3676. y = discretebar.yScale().clamp(true);
  3677. // Setup containers and skeleton of chart
  3678. var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]);
  3679. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g');
  3680. var defsEnter = gEnter.append('defs');
  3681. var g = wrap.select('g');
  3682. gEnter.append('g').attr('class', 'nv-x nv-axis');
  3683. gEnter.append('g').attr('class', 'nv-y nv-axis')
  3684. .append('g').attr('class', 'nv-zeroLine')
  3685. .append('line');
  3686. gEnter.append('g').attr('class', 'nv-barsWrap');
  3687. gEnter.append('g').attr('class', 'nv-legendWrap');
  3688. g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  3689. // Legend
  3690. if (!showLegend) {
  3691. g.select('.nv-legendWrap').selectAll('*').remove();
  3692. } else {
  3693. legend.width(availableWidth);
  3694. g.select('.nv-legendWrap')
  3695. .datum(data)
  3696. .call(legend);
  3697. if (legend.height() > margin.top) {
  3698. margin.top = legend.height();
  3699. availableHeight = nv.utils.availableHeight(height, container, margin);
  3700. }
  3701. wrap.select('.nv-legendWrap')
  3702. .attr('transform', 'translate(0,' + (-margin.top) +')')
  3703. }
  3704. if (rightAlignYAxis) {
  3705. g.select(".nv-y.nv-axis")
  3706. .attr("transform", "translate(" + availableWidth + ",0)");
  3707. }
  3708. // Main Chart Component(s)
  3709. discretebar
  3710. .width(availableWidth)
  3711. .height(availableHeight);
  3712. var barsWrap = g.select('.nv-barsWrap')
  3713. .datum(data.filter(function(d) { return !d.disabled }));
  3714. barsWrap.transition().call(discretebar);
  3715. defsEnter.append('clipPath')
  3716. .attr('id', 'nv-x-label-clip-' + discretebar.id())
  3717. .append('rect');
  3718. g.select('#nv-x-label-clip-' + discretebar.id() + ' rect')
  3719. .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
  3720. .attr('height', 16)
  3721. .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
  3722. // Setup Axes
  3723. if (showXAxis) {
  3724. xAxis
  3725. .scale(x)
  3726. ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
  3727. .tickSize(-availableHeight, 0);
  3728. g.select('.nv-x.nv-axis')
  3729. .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')');
  3730. g.select('.nv-x.nv-axis').call(xAxis);
  3731. var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
  3732. if (staggerLabels) {
  3733. xTicks
  3734. .selectAll('text')
  3735. .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
  3736. }
  3737. if (rotateLabels) {
  3738. xTicks
  3739. .selectAll('.tick text')
  3740. .attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
  3741. .style('text-anchor', rotateLabels > 0 ? 'start' : 'end');
  3742. }
  3743. if (wrapLabels) {
  3744. g.selectAll('.tick text')
  3745. .call(nv.utils.wrapTicks, chart.xAxis.rangeBand())
  3746. }
  3747. }
  3748. if (showYAxis) {
  3749. yAxis
  3750. .scale(y)
  3751. ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
  3752. .tickSize( -availableWidth, 0);
  3753. g.select('.nv-y.nv-axis').call(yAxis);
  3754. }
  3755. // Zero line
  3756. g.select(".nv-zeroLine line")
  3757. .attr("x1",0)
  3758. .attr("x2",(rightAlignYAxis) ? -availableWidth : availableWidth)
  3759. .attr("y1", y(0))
  3760. .attr("y2", y(0))
  3761. ;
  3762. });
  3763. renderWatch.renderEnd('discreteBar chart immediate');
  3764. return chart;
  3765. }
  3766. //============================================================
  3767. // Event Handling/Dispatching (out of chart's scope)
  3768. //------------------------------------------------------------
  3769. discretebar.dispatch.on('elementMouseover.tooltip', function(evt) {
  3770. evt['series'] = {
  3771. key: chart.x()(evt.data),
  3772. value: chart.y()(evt.data),
  3773. color: evt.color
  3774. };
  3775. tooltip.data(evt).hidden(false);
  3776. });
  3777. discretebar.dispatch.on('elementMouseout.tooltip', function(evt) {
  3778. tooltip.hidden(true);
  3779. });
  3780. discretebar.dispatch.on('elementMousemove.tooltip', function(evt) {
  3781. tooltip();
  3782. });
  3783. //============================================================
  3784. // Expose Public Variables
  3785. //------------------------------------------------------------
  3786. chart.dispatch = dispatch;
  3787. chart.discretebar = discretebar;
  3788. chart.legend = legend;
  3789. chart.xAxis = xAxis;
  3790. chart.yAxis = yAxis;
  3791. chart.tooltip = tooltip;
  3792. chart.options = nv.utils.optionsFunc.bind(chart);
  3793. chart._options = Object.create({}, {
  3794. // simple options, just get/set the necessary values
  3795. width: {get: function(){return width;}, set: function(_){width=_;}},
  3796. height: {get: function(){return height;}, set: function(_){height=_;}},
  3797. showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
  3798. staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
  3799. rotateLabels: {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
  3800. wrapLabels: {get: function(){return wrapLabels;}, set: function(_){wrapLabels=!!_;}},
  3801. showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
  3802. showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
  3803. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  3804. // options that require extra logic in the setter
  3805. margin: {get: function(){return margin;}, set: function(_){
  3806. margin.top = _.top !== undefined ? _.top : margin.top;
  3807. margin.right = _.right !== undefined ? _.right : margin.right;
  3808. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  3809. margin.left = _.left !== undefined ? _.left : margin.left;
  3810. }},
  3811. duration: {get: function(){return duration;}, set: function(_){
  3812. duration = _;
  3813. renderWatch.reset(duration);
  3814. discretebar.duration(duration);
  3815. xAxis.duration(duration);
  3816. yAxis.duration(duration);
  3817. }},
  3818. color: {get: function(){return color;}, set: function(_){
  3819. color = nv.utils.getColor(_);
  3820. discretebar.color(color);
  3821. legend.color(color);
  3822. }},
  3823. rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
  3824. rightAlignYAxis = _;
  3825. yAxis.orient( (_) ? 'right' : 'left');
  3826. }}
  3827. });
  3828. nv.utils.inheritOptions(chart, discretebar);
  3829. nv.utils.initOptions(chart);
  3830. return chart;
  3831. }
  3832. nv.models.distribution = function() {
  3833. "use strict";
  3834. //============================================================
  3835. // Public Variables with Default Settings
  3836. //------------------------------------------------------------
  3837. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  3838. , width = 400 //technically width or height depending on x or y....
  3839. , size = 8
  3840. , axis = 'x' // 'x' or 'y'... horizontal or vertical
  3841. , getData = function(d) { return d[axis] } // defaults d.x or d.y
  3842. , color = nv.utils.defaultColor()
  3843. , scale = d3.scale.linear()
  3844. , domain
  3845. , duration = 250
  3846. , dispatch = d3.dispatch('renderEnd')
  3847. ;
  3848. //============================================================
  3849. //============================================================
  3850. // Private Variables
  3851. //------------------------------------------------------------
  3852. var scale0;
  3853. var renderWatch = nv.utils.renderWatch(dispatch, duration);
  3854. //============================================================
  3855. function chart(selection) {
  3856. renderWatch.reset();
  3857. selection.each(function(data) {
  3858. var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom),
  3859. naxis = axis == 'x' ? 'y' : 'x',
  3860. container = d3.select(this);
  3861. nv.utils.initSVG(container);
  3862. //------------------------------------------------------------
  3863. // Setup Scales
  3864. scale0 = scale0 || scale;
  3865. //------------------------------------------------------------
  3866. //------------------------------------------------------------
  3867. // Setup containers and skeleton of chart
  3868. var wrap = container.selectAll('g.nv-distribution').data([data]);
  3869. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution');
  3870. var gEnter = wrapEnter.append('g');
  3871. var g = wrap.select('g');
  3872. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
  3873. //------------------------------------------------------------
  3874. var distWrap = g.selectAll('g.nv-dist')
  3875. .data(function(d) { return d }, function(d) { return d.key });
  3876. distWrap.enter().append('g');
  3877. distWrap
  3878. .attr('class', function(d,i) { return 'nv-dist nv-series-' + i })
  3879. .style('stroke', function(d,i) { return color(d, i) });
  3880. var dist = distWrap.selectAll('line.nv-dist' + axis)
  3881. .data(function(d) { return d.values })
  3882. dist.enter().append('line')
  3883. .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) })
  3884. .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) })
  3885. renderWatch.transition(distWrap.exit().selectAll('line.nv-dist' + axis), 'dist exit')
  3886. // .transition()
  3887. .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
  3888. .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
  3889. .style('stroke-opacity', 0)
  3890. .remove();
  3891. dist
  3892. .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i })
  3893. .attr(naxis + '1', 0)
  3894. .attr(naxis + '2', size);
  3895. renderWatch.transition(dist, 'dist')
  3896. // .transition()
  3897. .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
  3898. .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
  3899. scale0 = scale.copy();
  3900. });
  3901. renderWatch.renderEnd('distribution immediate');
  3902. return chart;
  3903. }
  3904. //============================================================
  3905. // Expose Public Variables
  3906. //------------------------------------------------------------
  3907. chart.options = nv.utils.optionsFunc.bind(chart);
  3908. chart.dispatch = dispatch;
  3909. chart.margin = function(_) {
  3910. if (!arguments.length) return margin;
  3911. margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
  3912. margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
  3913. margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
  3914. margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
  3915. return chart;
  3916. };
  3917. chart.width = function(_) {
  3918. if (!arguments.length) return width;
  3919. width = _;
  3920. return chart;
  3921. };
  3922. chart.axis = function(_) {
  3923. if (!arguments.length) return axis;
  3924. axis = _;
  3925. return chart;
  3926. };
  3927. chart.size = function(_) {
  3928. if (!arguments.length) return size;
  3929. size = _;
  3930. return chart;
  3931. };
  3932. chart.getData = function(_) {
  3933. if (!arguments.length) return getData;
  3934. getData = d3.functor(_);
  3935. return chart;
  3936. };
  3937. chart.scale = function(_) {
  3938. if (!arguments.length) return scale;
  3939. scale = _;
  3940. return chart;
  3941. };
  3942. chart.color = function(_) {
  3943. if (!arguments.length) return color;
  3944. color = nv.utils.getColor(_);
  3945. return chart;
  3946. };
  3947. chart.duration = function(_) {
  3948. if (!arguments.length) return duration;
  3949. duration = _;
  3950. renderWatch.reset(duration);
  3951. return chart;
  3952. };
  3953. //============================================================
  3954. return chart;
  3955. }
  3956. nv.models.focus = function(content) {
  3957. "use strict";
  3958. //============================================================
  3959. // Public Variables with Default Settings
  3960. //------------------------------------------------------------
  3961. var content = content || nv.models.line()
  3962. , xAxis = nv.models.axis()
  3963. , yAxis = nv.models.axis()
  3964. , brush = d3.svg.brush()
  3965. ;
  3966. var margin = {top: 10, right: 0, bottom: 30, left: 0}
  3967. , color = nv.utils.defaultColor()
  3968. , width = null
  3969. , height = 70
  3970. , showXAxis = true
  3971. , showYAxis = false
  3972. , rightAlignYAxis = false
  3973. , ticks = null
  3974. , x
  3975. , y
  3976. , brushExtent = null
  3977. , duration = 250
  3978. , dispatch = d3.dispatch('brush', 'onBrush', 'renderEnd')
  3979. ;
  3980. content.interactive(false);
  3981. content.pointActive(function(d) { return false; });
  3982. //============================================================
  3983. // Private Variables
  3984. //------------------------------------------------------------
  3985. var renderWatch = nv.utils.renderWatch(dispatch, duration);
  3986. function chart(selection) {
  3987. renderWatch.reset();
  3988. renderWatch.models(content);
  3989. if (showXAxis) renderWatch.models(xAxis);
  3990. if (showYAxis) renderWatch.models(yAxis);
  3991. selection.each(function(data) {
  3992. var container = d3.select(this);
  3993. nv.utils.initSVG(container);
  3994. var availableWidth = nv.utils.availableWidth(width, container, margin),
  3995. availableHeight = height - margin.top - margin.bottom;
  3996. chart.update = function() {
  3997. if( duration === 0 ) {
  3998. container.call( chart );
  3999. } else {
  4000. container.transition().duration(duration).call(chart);
  4001. }
  4002. };
  4003. chart.container = this;
  4004. // Setup Scales
  4005. x = content.xScale();
  4006. y = content.yScale();
  4007. // Setup containers and skeleton of chart
  4008. var wrap = container.selectAll('g.nv-focus').data([data]);
  4009. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-focus').append('g');
  4010. var g = wrap.select('g');
  4011. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  4012. gEnter.append('g').attr('class', 'nv-background').append('rect');
  4013. gEnter.append('g').attr('class', 'nv-x nv-axis');
  4014. gEnter.append('g').attr('class', 'nv-y nv-axis');
  4015. gEnter.append('g').attr('class', 'nv-contentWrap');
  4016. gEnter.append('g').attr('class', 'nv-brushBackground');
  4017. gEnter.append('g').attr('class', 'nv-x nv-brush');
  4018. if (rightAlignYAxis) {
  4019. g.select(".nv-y.nv-axis")
  4020. .attr("transform", "translate(" + availableWidth + ",0)");
  4021. }
  4022. g.select('.nv-background rect')
  4023. .attr('width', availableWidth)
  4024. .attr('height', availableHeight);
  4025. content
  4026. .width(availableWidth)
  4027. .height(availableHeight)
  4028. .color(data.map(function(d,i) {
  4029. return d.color || color(d, i);
  4030. }).filter(function(d,i) { return !data[i].disabled; }));
  4031. var contentWrap = g.select('.nv-contentWrap')
  4032. .datum(data.filter(function(d) { return !d.disabled; }));
  4033. d3.transition(contentWrap).call(content);
  4034. // Setup Brush
  4035. brush
  4036. .x(x)
  4037. .on('brush', function() {
  4038. onBrush();
  4039. });
  4040. if (brushExtent) brush.extent(brushExtent);
  4041. var brushBG = g.select('.nv-brushBackground').selectAll('g')
  4042. .data([brushExtent || brush.extent()]);
  4043. var brushBGenter = brushBG.enter()
  4044. .append('g');
  4045. brushBGenter.append('rect')
  4046. .attr('class', 'left')
  4047. .attr('x', 0)
  4048. .attr('y', 0)
  4049. .attr('height', availableHeight);
  4050. brushBGenter.append('rect')
  4051. .attr('class', 'right')
  4052. .attr('x', 0)
  4053. .attr('y', 0)
  4054. .attr('height', availableHeight);
  4055. var gBrush = g.select('.nv-x.nv-brush')
  4056. .call(brush);
  4057. gBrush.selectAll('rect')
  4058. .attr('height', availableHeight);
  4059. gBrush.selectAll('.resize').append('path').attr('d', resizePath);
  4060. onBrush();
  4061. g.select('.nv-background rect')
  4062. .attr('width', availableWidth)
  4063. .attr('height', availableHeight);
  4064. if (showXAxis) {
  4065. xAxis.scale(x)
  4066. ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
  4067. .tickSize(-availableHeight, 0);
  4068. g.select('.nv-x.nv-axis')
  4069. .attr('transform', 'translate(0,' + y.range()[0] + ')');
  4070. d3.transition(g.select('.nv-x.nv-axis'))
  4071. .call(xAxis);
  4072. }
  4073. if (showYAxis) {
  4074. yAxis
  4075. .scale(y)
  4076. ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
  4077. .tickSize( -availableWidth, 0);
  4078. d3.transition(g.select('.nv-y.nv-axis'))
  4079. .call(yAxis);
  4080. }
  4081. g.select('.nv-x.nv-axis')
  4082. .attr('transform', 'translate(0,' + y.range()[0] + ')');
  4083. //============================================================
  4084. // Event Handling/Dispatching (in chart's scope)
  4085. //------------------------------------------------------------
  4086. //============================================================
  4087. // Functions
  4088. //------------------------------------------------------------
  4089. // Taken from crossfilter (http://square.github.com/crossfilter/)
  4090. function resizePath(d) {
  4091. var e = +(d == 'e'),
  4092. x = e ? 1 : -1,
  4093. y = availableHeight / 3;
  4094. return 'M' + (0.5 * x) + ',' + y
  4095. + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
  4096. + 'V' + (2 * y - 6)
  4097. + 'A6,6 0 0 ' + e + ' ' + (0.5 * x) + ',' + (2 * y)
  4098. + 'Z'
  4099. + 'M' + (2.5 * x) + ',' + (y + 8)
  4100. + 'V' + (2 * y - 8)
  4101. + 'M' + (4.5 * x) + ',' + (y + 8)
  4102. + 'V' + (2 * y - 8);
  4103. }
  4104. function updateBrushBG() {
  4105. if (!brush.empty()) brush.extent(brushExtent);
  4106. brushBG
  4107. .data([brush.empty() ? x.domain() : brushExtent])
  4108. .each(function(d,i) {
  4109. var leftWidth = x(d[0]) - x.range()[0],
  4110. rightWidth = availableWidth - x(d[1]);
  4111. d3.select(this).select('.left')
  4112. .attr('width', leftWidth < 0 ? 0 : leftWidth);
  4113. d3.select(this).select('.right')
  4114. .attr('x', x(d[1]))
  4115. .attr('width', rightWidth < 0 ? 0 : rightWidth);
  4116. });
  4117. }
  4118. function onBrush() {
  4119. brushExtent = brush.empty() ? null : brush.extent();
  4120. var extent = brush.empty() ? x.domain() : brush.extent();
  4121. //The brush extent cannot be less than one. If it is, don't update the line chart.
  4122. if (Math.abs(extent[0] - extent[1]) <= 1) {
  4123. return;
  4124. }
  4125. dispatch.brush({extent: extent, brush: brush});
  4126. updateBrushBG();
  4127. dispatch.onBrush(extent);
  4128. }
  4129. });
  4130. renderWatch.renderEnd('focus immediate');
  4131. return chart;
  4132. }
  4133. //============================================================
  4134. // Event Handling/Dispatching (out of chart's scope)
  4135. //------------------------------------------------------------
  4136. //============================================================
  4137. // Expose Public Variables
  4138. //------------------------------------------------------------
  4139. // expose chart's sub-components
  4140. chart.dispatch = dispatch;
  4141. chart.content = content;
  4142. chart.brush = brush;
  4143. chart.xAxis = xAxis;
  4144. chart.yAxis = yAxis;
  4145. chart.options = nv.utils.optionsFunc.bind(chart);
  4146. chart._options = Object.create({}, {
  4147. // simple options, just get/set the necessary values
  4148. width: {get: function(){return width;}, set: function(_){width=_;}},
  4149. height: {get: function(){return height;}, set: function(_){height=_;}},
  4150. showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
  4151. showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
  4152. brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
  4153. // options that require extra logic in the setter
  4154. margin: {get: function(){return margin;}, set: function(_){
  4155. margin.top = _.top !== undefined ? _.top : margin.top;
  4156. margin.right = _.right !== undefined ? _.right : margin.right;
  4157. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  4158. margin.left = _.left !== undefined ? _.left : margin.left;
  4159. }},
  4160. duration: {get: function(){return duration;}, set: function(_){
  4161. duration = _;
  4162. renderWatch.reset(duration);
  4163. content.duration(duration);
  4164. xAxis.duration(duration);
  4165. yAxis.duration(duration);
  4166. }},
  4167. color: {get: function(){return color;}, set: function(_){
  4168. color = nv.utils.getColor(_);
  4169. content.color(color);
  4170. }},
  4171. interpolate: {get: function(){return content.interpolate();}, set: function(_){
  4172. content.interpolate(_);
  4173. }},
  4174. xTickFormat: {get: function(){return xAxis.tickFormat();}, set: function(_){
  4175. xAxis.tickFormat(_);
  4176. }},
  4177. yTickFormat: {get: function(){return yAxis.tickFormat();}, set: function(_){
  4178. yAxis.tickFormat(_);
  4179. }},
  4180. x: {get: function(){return content.x();}, set: function(_){
  4181. content.x(_);
  4182. }},
  4183. y: {get: function(){return content.y();}, set: function(_){
  4184. content.y(_);
  4185. }},
  4186. rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
  4187. rightAlignYAxis = _;
  4188. yAxis.orient( rightAlignYAxis ? 'right' : 'left');
  4189. }},
  4190. });
  4191. nv.utils.inheritOptions(chart, content);
  4192. nv.utils.initOptions(chart);
  4193. return chart;
  4194. };
  4195. nv.models.forceDirectedGraph = function() {
  4196. "use strict";
  4197. //============================================================
  4198. // Public Variables with Default Settings
  4199. //------------------------------------------------------------
  4200. var margin = {top: 2, right: 0, bottom: 2, left: 0}
  4201. , width = 400
  4202. , height = 32
  4203. , container = null
  4204. , dispatch = d3.dispatch('renderEnd')
  4205. , color = nv.utils.getColor(['#000'])
  4206. , tooltip = nv.models.tooltip()
  4207. , noData = null
  4208. // Force directed graph specific parameters [default values]
  4209. , linkStrength = 0.1
  4210. , friction = 0.9
  4211. , linkDist = 30
  4212. , charge = -120
  4213. , gravity = 0.1
  4214. , theta = 0.8
  4215. , alpha = 0.1
  4216. , radius = 5
  4217. // These functions allow to add extra attributes to ndes and links
  4218. ,nodeExtras = function(nodes) { /* Do nothing */ }
  4219. ,linkExtras = function(links) { /* Do nothing */ }
  4220. ;
  4221. //============================================================
  4222. // Private Variables
  4223. //------------------------------------------------------------
  4224. var renderWatch = nv.utils.renderWatch(dispatch);
  4225. function chart(selection) {
  4226. renderWatch.reset();
  4227. selection.each(function(data) {
  4228. container = d3.select(this);
  4229. nv.utils.initSVG(container);
  4230. var availableWidth = nv.utils.availableWidth(width, container, margin),
  4231. availableHeight = nv.utils.availableHeight(height, container, margin);
  4232. container
  4233. .attr("width", availableWidth)
  4234. .attr("height", availableHeight);
  4235. // Display No Data message if there's nothing to show.
  4236. if (!data || !data.links || !data.nodes) {
  4237. nv.utils.noData(chart, container)
  4238. return chart;
  4239. } else {
  4240. container.selectAll('.nv-noData').remove();
  4241. }
  4242. container.selectAll('*').remove();
  4243. // Collect names of all fields in the nodes
  4244. var nodeFieldSet = new Set();
  4245. data.nodes.forEach(function(node) {
  4246. var keys = Object.keys(node);
  4247. keys.forEach(function(key) {
  4248. nodeFieldSet.add(key);
  4249. });
  4250. });
  4251. var force = d3.layout.force()
  4252. .nodes(data.nodes)
  4253. .links(data.links)
  4254. .size([availableWidth, availableHeight])
  4255. .linkStrength(linkStrength)
  4256. .friction(friction)
  4257. .linkDistance(linkDist)
  4258. .charge(charge)
  4259. .gravity(gravity)
  4260. .theta(theta)
  4261. .alpha(alpha)
  4262. .start();
  4263. var link = container.selectAll(".link")
  4264. .data(data.links)
  4265. .enter().append("line")
  4266. .attr("class", "nv-force-link")
  4267. .style("stroke-width", function(d) { return Math.sqrt(d.value); });
  4268. var node = container.selectAll(".node")
  4269. .data(data.nodes)
  4270. .enter()
  4271. .append("g")
  4272. .attr("class", "nv-force-node")
  4273. .call(force.drag);
  4274. node
  4275. .append("circle")
  4276. .attr("r", radius)
  4277. .style("fill", function(d) { return color(d) } )
  4278. .on("mouseover", function(evt) {
  4279. container.select('.nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex)
  4280. .attr('y1', evt.py);
  4281. container.select('.nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
  4282. .attr('x2', evt.px);
  4283. // Add 'series' object to
  4284. var nodeColor = color(evt);
  4285. evt.series = [];
  4286. nodeFieldSet.forEach(function(field) {
  4287. evt.series.push({
  4288. color: nodeColor,
  4289. key: field,
  4290. value: evt[field]
  4291. });
  4292. });
  4293. tooltip.data(evt).hidden(false);
  4294. })
  4295. .on("mouseout", function(d) {
  4296. tooltip.hidden(true);
  4297. });
  4298. tooltip.headerFormatter(function(d) {return "Node";});
  4299. // Apply extra attributes to nodes and links (if any)
  4300. linkExtras(link);
  4301. nodeExtras(node);
  4302. force.on("tick", function() {
  4303. link.attr("x1", function(d) { return d.source.x; })
  4304. .attr("y1", function(d) { return d.source.y; })
  4305. .attr("x2", function(d) { return d.target.x; })
  4306. .attr("y2", function(d) { return d.target.y; });
  4307. node.attr("transform", function(d) {
  4308. return "translate(" + d.x + ", " + d.y + ")";
  4309. });
  4310. });
  4311. });
  4312. return chart;
  4313. }
  4314. //============================================================
  4315. // Expose Public Variables
  4316. //------------------------------------------------------------
  4317. chart.options = nv.utils.optionsFunc.bind(chart);
  4318. chart._options = Object.create({}, {
  4319. // simple options, just get/set the necessary values
  4320. width: {get: function(){return width;}, set: function(_){width=_;}},
  4321. height: {get: function(){return height;}, set: function(_){height=_;}},
  4322. // Force directed graph specific parameters
  4323. linkStrength:{get: function(){return linkStrength;}, set: function(_){linkStrength=_;}},
  4324. friction: {get: function(){return friction;}, set: function(_){friction=_;}},
  4325. linkDist: {get: function(){return linkDist;}, set: function(_){linkDist=_;}},
  4326. charge: {get: function(){return charge;}, set: function(_){charge=_;}},
  4327. gravity: {get: function(){return gravity;}, set: function(_){gravity=_;}},
  4328. theta: {get: function(){return theta;}, set: function(_){theta=_;}},
  4329. alpha: {get: function(){return alpha;}, set: function(_){alpha=_;}},
  4330. radius: {get: function(){return radius;}, set: function(_){radius=_;}},
  4331. //functor options
  4332. x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}},
  4333. y: {get: function(){return getY;}, set: function(_){getY=d3.functor(_);}},
  4334. // options that require extra logic in the setter
  4335. margin: {get: function(){return margin;}, set: function(_){
  4336. margin.top = _.top !== undefined ? _.top : margin.top;
  4337. margin.right = _.right !== undefined ? _.right : margin.right;
  4338. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  4339. margin.left = _.left !== undefined ? _.left : margin.left;
  4340. }},
  4341. color: {get: function(){return color;}, set: function(_){
  4342. color = nv.utils.getColor(_);
  4343. }},
  4344. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  4345. nodeExtras: {get: function(){return nodeExtras;}, set: function(_){
  4346. nodeExtras = _;
  4347. }},
  4348. linkExtras: {get: function(){return linkExtras;}, set: function(_){
  4349. linkExtras = _;
  4350. }}
  4351. });
  4352. chart.dispatch = dispatch;
  4353. chart.tooltip = tooltip;
  4354. nv.utils.initOptions(chart);
  4355. return chart;
  4356. };
  4357. nv.models.furiousLegend = function() {
  4358. "use strict";
  4359. //============================================================
  4360. // Public Variables with Default Settings
  4361. //------------------------------------------------------------
  4362. var margin = {top: 5, right: 0, bottom: 5, left: 0}
  4363. , width = 400
  4364. , height = 20
  4365. , getKey = function(d) { return d.key }
  4366. , keyFormatter = function (d) { return d }
  4367. , color = nv.utils.getColor()
  4368. , maxKeyLength = 20 //default value for key lengths
  4369. , align = true
  4370. , padding = 28 //define how much space between legend items. - recommend 32 for furious version
  4371. , rightAlign = true
  4372. , updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
  4373. , radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
  4374. , expanded = false
  4375. , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
  4376. , vers = 'classic' //Options are "classic" and "furious"
  4377. ;
  4378. function chart(selection) {
  4379. selection.each(function(data) {
  4380. var availableWidth = width - margin.left - margin.right,
  4381. container = d3.select(this);
  4382. nv.utils.initSVG(container);
  4383. // Setup containers and skeleton of chart
  4384. var wrap = container.selectAll('g.nv-legend').data([data]);
  4385. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
  4386. var g = wrap.select('g');
  4387. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  4388. var series = g.selectAll('.nv-series')
  4389. .data(function(d) {
  4390. if(vers != 'furious') return d;
  4391. return d.filter(function(n) {
  4392. return expanded ? true : !n.disengaged;
  4393. });
  4394. });
  4395. var seriesEnter = series.enter().append('g').attr('class', 'nv-series')
  4396. var seriesShape;
  4397. if(vers == 'classic') {
  4398. seriesEnter.append('circle')
  4399. .style('stroke-width', 2)
  4400. .attr('class','nv-legend-symbol')
  4401. .attr('r', 5);
  4402. seriesShape = series.select('circle');
  4403. } else if (vers == 'furious') {
  4404. seriesEnter.append('rect')
  4405. .style('stroke-width', 2)
  4406. .attr('class','nv-legend-symbol')
  4407. .attr('rx', 3)
  4408. .attr('ry', 3);
  4409. seriesShape = series.select('rect');
  4410. seriesEnter.append('g')
  4411. .attr('class', 'nv-check-box')
  4412. .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>')
  4413. .attr('transform', 'translate(-10,-8)scale(0.5)');
  4414. var seriesCheckbox = series.select('.nv-check-box');
  4415. seriesCheckbox.each(function(d,i) {
  4416. d3.select(this).selectAll('path')
  4417. .attr('stroke', setTextColor(d,i));
  4418. });
  4419. }
  4420. seriesEnter.append('text')
  4421. .attr('text-anchor', 'start')
  4422. .attr('class','nv-legend-text')
  4423. .attr('dy', '.32em')
  4424. .attr('dx', '8');
  4425. var seriesText = series.select('text.nv-legend-text');
  4426. series
  4427. .on('mouseover', function(d,i) {
  4428. dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects
  4429. })
  4430. .on('mouseout', function(d,i) {
  4431. dispatch.legendMouseout(d,i);
  4432. })
  4433. .on('click', function(d,i) {
  4434. dispatch.legendClick(d,i);
  4435. // make sure we re-get data in case it was modified
  4436. var data = series.data();
  4437. if (updateState) {
  4438. if(vers =='classic') {
  4439. if (radioButtonMode) {
  4440. //Radio button mode: set every series to disabled,
  4441. // and enable the clicked series.
  4442. data.forEach(function(series) { series.disabled = true});
  4443. d.disabled = false;
  4444. }
  4445. else {
  4446. d.disabled = !d.disabled;
  4447. if (data.every(function(series) { return series.disabled})) {
  4448. //the default behavior of NVD3 legends is, if every single series
  4449. // is disabled, turn all series' back on.
  4450. data.forEach(function(series) { series.disabled = false});
  4451. }
  4452. }
  4453. } else if(vers == 'furious') {
  4454. if(expanded) {
  4455. d.disengaged = !d.disengaged;
  4456. d.userDisabled = d.userDisabled == undefined ? !!d.disabled : d.userDisabled;
  4457. d.disabled = d.disengaged || d.userDisabled;
  4458. } else if (!expanded) {
  4459. d.disabled = !d.disabled;
  4460. d.userDisabled = d.disabled;
  4461. var engaged = data.filter(function(d) { return !d.disengaged; });
  4462. if (engaged.every(function(series) { return series.userDisabled })) {
  4463. //the default behavior of NVD3 legends is, if every single series
  4464. // is disabled, turn all series' back on.
  4465. data.forEach(function(series) {
  4466. series.disabled = series.userDisabled = false;
  4467. });
  4468. }
  4469. }
  4470. }
  4471. dispatch.stateChange({
  4472. disabled: data.map(function(d) { return !!d.disabled }),
  4473. disengaged: data.map(function(d) { return !!d.disengaged })
  4474. });
  4475. }
  4476. })
  4477. .on('dblclick', function(d,i) {
  4478. if(vers == 'furious' && expanded) return;
  4479. dispatch.legendDblclick(d,i);
  4480. if (updateState) {
  4481. // make sure we re-get data in case it was modified
  4482. var data = series.data();
  4483. //the default behavior of NVD3 legends, when double clicking one,
  4484. // is to set all other series' to false, and make the double clicked series enabled.
  4485. data.forEach(function(series) {
  4486. series.disabled = true;
  4487. if(vers == 'furious') series.userDisabled = series.disabled;
  4488. });
  4489. d.disabled = false;
  4490. if(vers == 'furious') d.userDisabled = d.disabled;
  4491. dispatch.stateChange({
  4492. disabled: data.map(function(d) { return !!d.disabled })
  4493. });
  4494. }
  4495. });
  4496. series.classed('nv-disabled', function(d) { return d.userDisabled });
  4497. series.exit().remove();
  4498. seriesText
  4499. .attr('fill', setTextColor)
  4500. .text(function (d) { return keyFormatter(getKey(d)) });
  4501. //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
  4502. // NEW ALIGNING CODE, TODO: clean up
  4503. var versPadding;
  4504. switch(vers) {
  4505. case 'furious' :
  4506. versPadding = 23;
  4507. break;
  4508. case 'classic' :
  4509. versPadding = 20;
  4510. }
  4511. if (align) {
  4512. var seriesWidths = [];
  4513. series.each(function(d,i) {
  4514. var legendText;
  4515. if (keyFormatter(getKey(d)) && keyFormatter(getKey(d)).length > maxKeyLength) {
  4516. var trimmedKey = keyFormatter(getKey(d)).substring(0, maxKeyLength);
  4517. legendText = d3.select(this).select('text').text(trimmedKey + "...");
  4518. d3.select(this).append("svg:title").text(keyFormatter(getKey(d)));
  4519. } else {
  4520. legendText = d3.select(this).select('text');
  4521. }
  4522. var nodeTextLength;
  4523. try {
  4524. nodeTextLength = legendText.node().getComputedTextLength();
  4525. // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead
  4526. if(nodeTextLength <= 0) throw Error();
  4527. }
  4528. catch(e) {
  4529. nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
  4530. }
  4531. seriesWidths.push(nodeTextLength + padding);
  4532. });
  4533. var seriesPerRow = 0;
  4534. var legendWidth = 0;
  4535. var columnWidths = [];
  4536. while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
  4537. columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
  4538. legendWidth += seriesWidths[seriesPerRow++];
  4539. }
  4540. if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
  4541. while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
  4542. columnWidths = [];
  4543. seriesPerRow--;
  4544. for (var k = 0; k < seriesWidths.length; k++) {
  4545. if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
  4546. columnWidths[k % seriesPerRow] = seriesWidths[k];
  4547. }
  4548. legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
  4549. return prev + cur;
  4550. });
  4551. }
  4552. var xPositions = [];
  4553. for (var i = 0, curX = 0; i < seriesPerRow; i++) {
  4554. xPositions[i] = curX;
  4555. curX += columnWidths[i];
  4556. }
  4557. series
  4558. .attr('transform', function(d, i) {
  4559. return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')';
  4560. });
  4561. //position legend as far right as possible within the total width
  4562. if (rightAlign) {
  4563. g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
  4564. }
  4565. else {
  4566. g.attr('transform', 'translate(0' + ',' + margin.top + ')');
  4567. }
  4568. height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding);
  4569. } else {
  4570. var ypos = 5,
  4571. newxpos = 5,
  4572. maxwidth = 0,
  4573. xpos;
  4574. series
  4575. .attr('transform', function(d, i) {
  4576. var length = d3.select(this).select('text').node().getComputedTextLength() + padding;
  4577. xpos = newxpos;
  4578. if (width < margin.left + margin.right + xpos + length) {
  4579. newxpos = xpos = 5;
  4580. ypos += versPadding;
  4581. }
  4582. newxpos += length;
  4583. if (newxpos > maxwidth) maxwidth = newxpos;
  4584. return 'translate(' + xpos + ',' + ypos + ')';
  4585. });
  4586. //position legend as far right as possible within the total width
  4587. g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
  4588. height = margin.top + margin.bottom + ypos + 15;
  4589. }
  4590. if(vers == 'furious') {
  4591. // Size rectangles after text is placed
  4592. seriesShape
  4593. .attr('width', function(d,i) {
  4594. return seriesText[0][i].getComputedTextLength() + 27;
  4595. })
  4596. .attr('height', 18)
  4597. .attr('y', -9)
  4598. .attr('x', -15)
  4599. }
  4600. seriesShape
  4601. .style('fill', setBGColor)
  4602. .style('stroke', function(d,i) { return d.color || color(d, i) });
  4603. });
  4604. function setTextColor(d,i) {
  4605. if(vers != 'furious') return '#000';
  4606. if(expanded) {
  4607. return d.disengaged ? color(d,i) : '#fff';
  4608. } else if (!expanded) {
  4609. return !!d.disabled ? color(d,i) : '#fff';
  4610. }
  4611. }
  4612. function setBGColor(d,i) {
  4613. if(expanded && vers == 'furious') {
  4614. return d.disengaged ? '#fff' : color(d,i);
  4615. } else {
  4616. return !!d.disabled ? '#fff' : color(d,i);
  4617. }
  4618. }
  4619. return chart;
  4620. }
  4621. //============================================================
  4622. // Expose Public Variables
  4623. //------------------------------------------------------------
  4624. chart.dispatch = dispatch;
  4625. chart.options = nv.utils.optionsFunc.bind(chart);
  4626. chart._options = Object.create({}, {
  4627. // simple options, just get/set the necessary values
  4628. width: {get: function(){return width;}, set: function(_){width=_;}},
  4629. height: {get: function(){return height;}, set: function(_){height=_;}},
  4630. key: {get: function(){return getKey;}, set: function(_){getKey=_;}},
  4631. keyFormatter: {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}},
  4632. align: {get: function(){return align;}, set: function(_){align=_;}},
  4633. rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}},
  4634. maxKeyLength: {get: function(){return maxKeyLength;}, set: function(_){maxKeyLength=_;}},
  4635. padding: {get: function(){return padding;}, set: function(_){padding=_;}},
  4636. updateState: {get: function(){return updateState;}, set: function(_){updateState=_;}},
  4637. radioButtonMode:{get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}},
  4638. expanded: {get: function(){return expanded;}, set: function(_){expanded=_;}},
  4639. vers: {get: function(){return vers;}, set: function(_){vers=_;}},
  4640. // options that require extra logic in the setter
  4641. margin: {get: function(){return margin;}, set: function(_){
  4642. margin.top = _.top !== undefined ? _.top : margin.top;
  4643. margin.right = _.right !== undefined ? _.right : margin.right;
  4644. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  4645. margin.left = _.left !== undefined ? _.left : margin.left;
  4646. }},
  4647. color: {get: function(){return color;}, set: function(_){
  4648. color = nv.utils.getColor(_);
  4649. }}
  4650. });
  4651. nv.utils.initOptions(chart);
  4652. return chart;
  4653. };
  4654. //TODO: consider deprecating and using multibar with single series for this
  4655. nv.models.historicalBar = function() {
  4656. "use strict";
  4657. //============================================================
  4658. // Public Variables with Default Settings
  4659. //------------------------------------------------------------
  4660. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  4661. , width = null
  4662. , height = null
  4663. , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
  4664. , container = null
  4665. , x = d3.scale.linear()
  4666. , y = d3.scale.linear()
  4667. , getX = function(d) { return d.x }
  4668. , getY = function(d) { return d.y }
  4669. , forceX = []
  4670. , forceY = [0]
  4671. , padData = false
  4672. , clipEdge = true
  4673. , color = nv.utils.defaultColor()
  4674. , xDomain
  4675. , yDomain
  4676. , xRange
  4677. , yRange
  4678. , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
  4679. , interactive = true
  4680. ;
  4681. var renderWatch = nv.utils.renderWatch(dispatch, 0);
  4682. function chart(selection) {
  4683. selection.each(function(data) {
  4684. renderWatch.reset();
  4685. container = d3.select(this);
  4686. var availableWidth = nv.utils.availableWidth(width, container, margin),
  4687. availableHeight = nv.utils.availableHeight(height, container, margin);
  4688. nv.utils.initSVG(container);
  4689. // Setup Scales
  4690. x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
  4691. if (padData)
  4692. x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
  4693. else
  4694. x.range(xRange || [0, availableWidth]);
  4695. y.domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) ))
  4696. .range(yRange || [availableHeight, 0]);
  4697. // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
  4698. if (x.domain()[0] === x.domain()[1])
  4699. x.domain()[0] ?
  4700. x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
  4701. : x.domain([-1,1]);
  4702. if (y.domain()[0] === y.domain()[1])
  4703. y.domain()[0] ?
  4704. y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
  4705. : y.domain([-1,1]);
  4706. // Setup containers and skeleton of chart
  4707. var wrap = container.selectAll('g.nv-wrap.nv-historicalBar-' + id).data([data[0].values]);
  4708. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBar-' + id);
  4709. var defsEnter = wrapEnter.append('defs');
  4710. var gEnter = wrapEnter.append('g');
  4711. var g = wrap.select('g');
  4712. gEnter.append('g').attr('class', 'nv-bars');
  4713. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  4714. container
  4715. .on('click', function(d,i) {
  4716. dispatch.chartClick({
  4717. data: d,
  4718. index: i,
  4719. pos: d3.event,
  4720. id: id
  4721. });
  4722. });
  4723. defsEnter.append('clipPath')
  4724. .attr('id', 'nv-chart-clip-path-' + id)
  4725. .append('rect');
  4726. wrap.select('#nv-chart-clip-path-' + id + ' rect')
  4727. .attr('width', availableWidth)
  4728. .attr('height', availableHeight);
  4729. g.attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
  4730. var bars = wrap.select('.nv-bars').selectAll('.nv-bar')
  4731. .data(function(d) { return d }, function(d,i) {return getX(d,i)});
  4732. bars.exit().remove();
  4733. bars.enter().append('rect')
  4734. .attr('x', 0 )
  4735. .attr('y', function(d,i) { return nv.utils.NaNtoZero(y(Math.max(0, getY(d,i)))) })
  4736. .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.abs(y(getY(d,i)) - y(0))) })
  4737. .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
  4738. .on('mouseover', function(d,i) {
  4739. if (!interactive) return;
  4740. d3.select(this).classed('hover', true);
  4741. dispatch.elementMouseover({
  4742. data: d,
  4743. index: i,
  4744. color: d3.select(this).style("fill")
  4745. });
  4746. })
  4747. .on('mouseout', function(d,i) {
  4748. if (!interactive) return;
  4749. d3.select(this).classed('hover', false);
  4750. dispatch.elementMouseout({
  4751. data: d,
  4752. index: i,
  4753. color: d3.select(this).style("fill")
  4754. });
  4755. })
  4756. .on('mousemove', function(d,i) {
  4757. if (!interactive) return;
  4758. dispatch.elementMousemove({
  4759. data: d,
  4760. index: i,
  4761. color: d3.select(this).style("fill")
  4762. });
  4763. })
  4764. .on('click', function(d,i) {
  4765. if (!interactive) return;
  4766. dispatch.elementClick({
  4767. data: d,
  4768. index: i,
  4769. color: d3.select(this).style("fill")
  4770. });
  4771. d3.event.stopPropagation();
  4772. })
  4773. .on('dblclick', function(d,i) {
  4774. if (!interactive) return;
  4775. dispatch.elementDblClick({
  4776. data: d,
  4777. index: i,
  4778. color: d3.select(this).style("fill")
  4779. });
  4780. d3.event.stopPropagation();
  4781. });
  4782. bars
  4783. .attr('fill', function(d,i) { return color(d, i); })
  4784. .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
  4785. .watchTransition(renderWatch, 'bars')
  4786. .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
  4787. //TODO: better width calculations that don't assume always uniform data spacing;w
  4788. .attr('width', (availableWidth / data[0].values.length) * .9 );
  4789. bars.watchTransition(renderWatch, 'bars')
  4790. .attr('y', function(d,i) {
  4791. var rval = getY(d,i) < 0 ?
  4792. y(0) :
  4793. y(0) - y(getY(d,i)) < 1 ?
  4794. y(0) - 1 :
  4795. y(getY(d,i));
  4796. return nv.utils.NaNtoZero(rval);
  4797. })
  4798. .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) });
  4799. });
  4800. renderWatch.renderEnd('historicalBar immediate');
  4801. return chart;
  4802. }
  4803. //Create methods to allow outside functions to highlight a specific bar.
  4804. chart.highlightPoint = function(pointIndex, isHoverOver) {
  4805. container
  4806. .select(".nv-bars .nv-bar-0-" + pointIndex)
  4807. .classed("hover", isHoverOver)
  4808. ;
  4809. };
  4810. chart.clearHighlights = function() {
  4811. container
  4812. .select(".nv-bars .nv-bar.hover")
  4813. .classed("hover", false)
  4814. ;
  4815. };
  4816. //============================================================
  4817. // Expose Public Variables
  4818. //------------------------------------------------------------
  4819. chart.dispatch = dispatch;
  4820. chart.options = nv.utils.optionsFunc.bind(chart);
  4821. chart._options = Object.create({}, {
  4822. // simple options, just get/set the necessary values
  4823. width: {get: function(){return width;}, set: function(_){width=_;}},
  4824. height: {get: function(){return height;}, set: function(_){height=_;}},
  4825. forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
  4826. forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
  4827. padData: {get: function(){return padData;}, set: function(_){padData=_;}},
  4828. x: {get: function(){return getX;}, set: function(_){getX=_;}},
  4829. y: {get: function(){return getY;}, set: function(_){getY=_;}},
  4830. xScale: {get: function(){return x;}, set: function(_){x=_;}},
  4831. yScale: {get: function(){return y;}, set: function(_){y=_;}},
  4832. xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
  4833. yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
  4834. xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
  4835. yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
  4836. clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
  4837. id: {get: function(){return id;}, set: function(_){id=_;}},
  4838. interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
  4839. // options that require extra logic in the setter
  4840. margin: {get: function(){return margin;}, set: function(_){
  4841. margin.top = _.top !== undefined ? _.top : margin.top;
  4842. margin.right = _.right !== undefined ? _.right : margin.right;
  4843. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  4844. margin.left = _.left !== undefined ? _.left : margin.left;
  4845. }},
  4846. color: {get: function(){return color;}, set: function(_){
  4847. color = nv.utils.getColor(_);
  4848. }}
  4849. });
  4850. nv.utils.initOptions(chart);
  4851. return chart;
  4852. };
  4853. nv.models.historicalBarChart = function(bar_model) {
  4854. "use strict";
  4855. //============================================================
  4856. // Public Variables with Default Settings
  4857. //------------------------------------------------------------
  4858. var bars = bar_model || nv.models.historicalBar()
  4859. , xAxis = nv.models.axis()
  4860. , yAxis = nv.models.axis()
  4861. , legend = nv.models.legend()
  4862. , interactiveLayer = nv.interactiveGuideline()
  4863. , tooltip = nv.models.tooltip()
  4864. ;
  4865. var margin = {top: 30, right: 90, bottom: 50, left: 90}
  4866. , color = nv.utils.defaultColor()
  4867. , width = null
  4868. , height = null
  4869. , showLegend = false
  4870. , showXAxis = true
  4871. , showYAxis = true
  4872. , rightAlignYAxis = false
  4873. , useInteractiveGuideline = false
  4874. , x
  4875. , y
  4876. , state = {}
  4877. , defaultState = null
  4878. , noData = null
  4879. , dispatch = d3.dispatch('tooltipHide', 'stateChange', 'changeState', 'renderEnd')
  4880. , transitionDuration = 250
  4881. ;
  4882. xAxis.orient('bottom').tickPadding(7);
  4883. yAxis.orient( (rightAlignYAxis) ? 'right' : 'left');
  4884. tooltip
  4885. .duration(0)
  4886. .headerEnabled(false)
  4887. .valueFormatter(function(d, i) {
  4888. return yAxis.tickFormat()(d, i);
  4889. })
  4890. .headerFormatter(function(d, i) {
  4891. return xAxis.tickFormat()(d, i);
  4892. });
  4893. //============================================================
  4894. // Private Variables
  4895. //------------------------------------------------------------
  4896. var renderWatch = nv.utils.renderWatch(dispatch, 0);
  4897. function chart(selection) {
  4898. selection.each(function(data) {
  4899. renderWatch.reset();
  4900. renderWatch.models(bars);
  4901. if (showXAxis) renderWatch.models(xAxis);
  4902. if (showYAxis) renderWatch.models(yAxis);
  4903. var container = d3.select(this),
  4904. that = this;
  4905. nv.utils.initSVG(container);
  4906. var availableWidth = nv.utils.availableWidth(width, container, margin),
  4907. availableHeight = nv.utils.availableHeight(height, container, margin);
  4908. chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
  4909. chart.container = this;
  4910. //set state.disabled
  4911. state.disabled = data.map(function(d) { return !!d.disabled });
  4912. if (!defaultState) {
  4913. var key;
  4914. defaultState = {};
  4915. for (key in state) {
  4916. if (state[key] instanceof Array)
  4917. defaultState[key] = state[key].slice(0);
  4918. else
  4919. defaultState[key] = state[key];
  4920. }
  4921. }
  4922. // Display noData message if there's nothing to show.
  4923. if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
  4924. nv.utils.noData(chart, container)
  4925. return chart;
  4926. } else {
  4927. container.selectAll('.nv-noData').remove();
  4928. }
  4929. // Setup Scales
  4930. x = bars.xScale();
  4931. y = bars.yScale();
  4932. // Setup containers and skeleton of chart
  4933. var wrap = container.selectAll('g.nv-wrap.nv-historicalBarChart').data([data]);
  4934. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBarChart').append('g');
  4935. var g = wrap.select('g');
  4936. gEnter.append('g').attr('class', 'nv-x nv-axis');
  4937. gEnter.append('g').attr('class', 'nv-y nv-axis');
  4938. gEnter.append('g').attr('class', 'nv-barsWrap');
  4939. gEnter.append('g').attr('class', 'nv-legendWrap');
  4940. gEnter.append('g').attr('class', 'nv-interactive');
  4941. // Legend
  4942. if (!showLegend) {
  4943. g.select('.nv-legendWrap').selectAll('*').remove();
  4944. } else {
  4945. legend.width(availableWidth);
  4946. g.select('.nv-legendWrap')
  4947. .datum(data)
  4948. .call(legend);
  4949. if (legend.height() > margin.top) {
  4950. margin.top = legend.height();
  4951. availableHeight = nv.utils.availableHeight(height, container, margin);
  4952. }
  4953. wrap.select('.nv-legendWrap')
  4954. .attr('transform', 'translate(0,' + (-margin.top) +')')
  4955. }
  4956. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  4957. if (rightAlignYAxis) {
  4958. g.select(".nv-y.nv-axis")
  4959. .attr("transform", "translate(" + availableWidth + ",0)");
  4960. }
  4961. //Set up interactive layer
  4962. if (useInteractiveGuideline) {
  4963. interactiveLayer
  4964. .width(availableWidth)
  4965. .height(availableHeight)
  4966. .margin({left:margin.left, top:margin.top})
  4967. .svgContainer(container)
  4968. .xScale(x);
  4969. wrap.select(".nv-interactive").call(interactiveLayer);
  4970. }
  4971. bars
  4972. .width(availableWidth)
  4973. .height(availableHeight)
  4974. .color(data.map(function(d,i) {
  4975. return d.color || color(d, i);
  4976. }).filter(function(d,i) { return !data[i].disabled }));
  4977. var barsWrap = g.select('.nv-barsWrap')
  4978. .datum(data.filter(function(d) { return !d.disabled }));
  4979. barsWrap.transition().call(bars);
  4980. // Setup Axes
  4981. if (showXAxis) {
  4982. xAxis
  4983. .scale(x)
  4984. ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
  4985. .tickSize(-availableHeight, 0);
  4986. g.select('.nv-x.nv-axis')
  4987. .attr('transform', 'translate(0,' + y.range()[0] + ')');
  4988. g.select('.nv-x.nv-axis')
  4989. .transition()
  4990. .call(xAxis);
  4991. }
  4992. if (showYAxis) {
  4993. yAxis
  4994. .scale(y)
  4995. ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
  4996. .tickSize( -availableWidth, 0);
  4997. g.select('.nv-y.nv-axis')
  4998. .transition()
  4999. .call(yAxis);
  5000. }
  5001. //============================================================
  5002. // Event Handling/Dispatching (in chart's scope)
  5003. //------------------------------------------------------------
  5004. interactiveLayer.dispatch.on('elementMousemove', function(e) {
  5005. bars.clearHighlights();
  5006. var singlePoint, pointIndex, pointXLocation, allData = [];
  5007. data
  5008. .filter(function(series, i) {
  5009. series.seriesIndex = i;
  5010. return !series.disabled;
  5011. })
  5012. .forEach(function(series,i) {
  5013. pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
  5014. bars.highlightPoint(pointIndex,true);
  5015. var point = series.values[pointIndex];
  5016. if (point === undefined) return;
  5017. if (singlePoint === undefined) singlePoint = point;
  5018. if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
  5019. allData.push({
  5020. key: series.key,
  5021. value: chart.y()(point, pointIndex),
  5022. color: color(series,series.seriesIndex),
  5023. data: series.values[pointIndex]
  5024. });
  5025. });
  5026. var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
  5027. interactiveLayer.tooltip
  5028. .valueFormatter(function(d,i) {
  5029. return yAxis.tickFormat()(d);
  5030. })
  5031. .data({
  5032. value: xValue,
  5033. index: pointIndex,
  5034. series: allData
  5035. })();
  5036. interactiveLayer.renderGuideLine(pointXLocation);
  5037. });
  5038. interactiveLayer.dispatch.on("elementMouseout",function(e) {
  5039. dispatch.tooltipHide();
  5040. bars.clearHighlights();
  5041. });
  5042. legend.dispatch.on('legendClick', function(d,i) {
  5043. d.disabled = !d.disabled;
  5044. if (!data.filter(function(d) { return !d.disabled }).length) {
  5045. data.map(function(d) {
  5046. d.disabled = false;
  5047. wrap.selectAll('.nv-series').classed('disabled', false);
  5048. return d;
  5049. });
  5050. }
  5051. state.disabled = data.map(function(d) { return !!d.disabled });
  5052. dispatch.stateChange(state);
  5053. selection.transition().call(chart);
  5054. });
  5055. legend.dispatch.on('legendDblclick', function(d) {
  5056. //Double clicking should always enable current series, and disabled all others.
  5057. data.forEach(function(d) {
  5058. d.disabled = true;
  5059. });
  5060. d.disabled = false;
  5061. state.disabled = data.map(function(d) { return !!d.disabled });
  5062. dispatch.stateChange(state);
  5063. chart.update();
  5064. });
  5065. dispatch.on('changeState', function(e) {
  5066. if (typeof e.disabled !== 'undefined') {
  5067. data.forEach(function(series,i) {
  5068. series.disabled = e.disabled[i];
  5069. });
  5070. state.disabled = e.disabled;
  5071. }
  5072. chart.update();
  5073. });
  5074. });
  5075. renderWatch.renderEnd('historicalBarChart immediate');
  5076. return chart;
  5077. }
  5078. //============================================================
  5079. // Event Handling/Dispatching (out of chart's scope)
  5080. //------------------------------------------------------------
  5081. bars.dispatch.on('elementMouseover.tooltip', function(evt) {
  5082. evt['series'] = {
  5083. key: chart.x()(evt.data),
  5084. value: chart.y()(evt.data),
  5085. color: evt.color
  5086. };
  5087. tooltip.data(evt).hidden(false);
  5088. });
  5089. bars.dispatch.on('elementMouseout.tooltip', function(evt) {
  5090. tooltip.hidden(true);
  5091. });
  5092. bars.dispatch.on('elementMousemove.tooltip', function(evt) {
  5093. tooltip();
  5094. });
  5095. //============================================================
  5096. // Expose Public Variables
  5097. //------------------------------------------------------------
  5098. // expose chart's sub-components
  5099. chart.dispatch = dispatch;
  5100. chart.bars = bars;
  5101. chart.legend = legend;
  5102. chart.xAxis = xAxis;
  5103. chart.yAxis = yAxis;
  5104. chart.interactiveLayer = interactiveLayer;
  5105. chart.tooltip = tooltip;
  5106. chart.options = nv.utils.optionsFunc.bind(chart);
  5107. chart._options = Object.create({}, {
  5108. // simple options, just get/set the necessary values
  5109. width: {get: function(){return width;}, set: function(_){width=_;}},
  5110. height: {get: function(){return height;}, set: function(_){height=_;}},
  5111. showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
  5112. showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
  5113. showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
  5114. defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
  5115. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  5116. // options that require extra logic in the setter
  5117. margin: {get: function(){return margin;}, set: function(_){
  5118. margin.top = _.top !== undefined ? _.top : margin.top;
  5119. margin.right = _.right !== undefined ? _.right : margin.right;
  5120. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  5121. margin.left = _.left !== undefined ? _.left : margin.left;
  5122. }},
  5123. color: {get: function(){return color;}, set: function(_){
  5124. color = nv.utils.getColor(_);
  5125. legend.color(color);
  5126. bars.color(color);
  5127. }},
  5128. duration: {get: function(){return transitionDuration;}, set: function(_){
  5129. transitionDuration=_;
  5130. renderWatch.reset(transitionDuration);
  5131. yAxis.duration(transitionDuration);
  5132. xAxis.duration(transitionDuration);
  5133. }},
  5134. rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
  5135. rightAlignYAxis = _;
  5136. yAxis.orient( (_) ? 'right' : 'left');
  5137. }},
  5138. useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
  5139. useInteractiveGuideline = _;
  5140. if (_ === true) {
  5141. chart.interactive(false);
  5142. }
  5143. }}
  5144. });
  5145. nv.utils.inheritOptions(chart, bars);
  5146. nv.utils.initOptions(chart);
  5147. return chart;
  5148. };
  5149. // ohlcChart is just a historical chart with ohlc bars and some tweaks
  5150. nv.models.ohlcBarChart = function() {
  5151. var chart = nv.models.historicalBarChart(nv.models.ohlcBar());
  5152. // special default tooltip since we show multiple values per x
  5153. chart.useInteractiveGuideline(true);
  5154. chart.interactiveLayer.tooltip.contentGenerator(function(data) {
  5155. // we assume only one series exists for this chart
  5156. var d = data.series[0].data;
  5157. // match line colors as defined in nv.d3.css
  5158. var color = d.open < d.close ? "2ca02c" : "d62728";
  5159. return '' +
  5160. '<h3 style="color: #' + color + '">' + data.value + '</h3>' +
  5161. '<table>' +
  5162. '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' +
  5163. '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' +
  5164. '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' +
  5165. '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' +
  5166. '</table>';
  5167. });
  5168. return chart;
  5169. };
  5170. // candlestickChart is just a historical chart with candlestick bars and some tweaks
  5171. nv.models.candlestickBarChart = function() {
  5172. var chart = nv.models.historicalBarChart(nv.models.candlestickBar());
  5173. // special default tooltip since we show multiple values per x
  5174. chart.useInteractiveGuideline(true);
  5175. chart.interactiveLayer.tooltip.contentGenerator(function(data) {
  5176. // we assume only one series exists for this chart
  5177. var d = data.series[0].data;
  5178. // match line colors as defined in nv.d3.css
  5179. var color = d.open < d.close ? "2ca02c" : "d62728";
  5180. return '' +
  5181. '<h3 style="color: #' + color + '">' + data.value + '</h3>' +
  5182. '<table>' +
  5183. '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' +
  5184. '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' +
  5185. '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' +
  5186. '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' +
  5187. '</table>';
  5188. });
  5189. return chart;
  5190. };
  5191. nv.models.legend = function() {
  5192. "use strict";
  5193. //============================================================
  5194. // Public Variables with Default Settings
  5195. //------------------------------------------------------------
  5196. var margin = {top: 5, right: 0, bottom: 5, left: 0}
  5197. , width = 400
  5198. , height = 20
  5199. , getKey = function(d) { return d.key }
  5200. , keyFormatter = function (d) { return d }
  5201. , color = nv.utils.getColor()
  5202. , maxKeyLength = 20 //default value for key lengths
  5203. , align = true
  5204. , padding = 32 //define how much space between legend items. - recommend 32 for furious version
  5205. , rightAlign = true
  5206. , updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
  5207. , radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
  5208. , expanded = false
  5209. , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
  5210. , vers = 'classic' //Options are "classic" and "furious"
  5211. ;
  5212. function chart(selection) {
  5213. selection.each(function(data) {
  5214. var availableWidth = width - margin.left - margin.right,
  5215. container = d3.select(this);
  5216. nv.utils.initSVG(container);
  5217. // Setup containers and skeleton of chart
  5218. var wrap = container.selectAll('g.nv-legend').data([data]);
  5219. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
  5220. var g = wrap.select('g');
  5221. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  5222. var series = g.selectAll('.nv-series')
  5223. .data(function(d) {
  5224. if(vers != 'furious') return d;
  5225. return d.filter(function(n) {
  5226. return expanded ? true : !n.disengaged;
  5227. });
  5228. });
  5229. var seriesEnter = series.enter().append('g').attr('class', 'nv-series');
  5230. var seriesShape;
  5231. var versPadding;
  5232. switch(vers) {
  5233. case 'furious' :
  5234. versPadding = 23;
  5235. break;
  5236. case 'classic' :
  5237. versPadding = 20;
  5238. }
  5239. if(vers == 'classic') {
  5240. seriesEnter.append('circle')
  5241. .style('stroke-width', 2)
  5242. .attr('class','nv-legend-symbol')
  5243. .attr('r', 5);
  5244. seriesShape = series.select('.nv-legend-symbol');
  5245. } else if (vers == 'furious') {
  5246. seriesEnter.append('rect')
  5247. .style('stroke-width', 2)
  5248. .attr('class','nv-legend-symbol')
  5249. .attr('rx', 3)
  5250. .attr('ry', 3);
  5251. seriesShape = series.select('.nv-legend-symbol');
  5252. seriesEnter.append('g')
  5253. .attr('class', 'nv-check-box')
  5254. .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>')
  5255. .attr('transform', 'translate(-10,-8)scale(0.5)');
  5256. var seriesCheckbox = series.select('.nv-check-box');
  5257. seriesCheckbox.each(function(d,i) {
  5258. d3.select(this).selectAll('path')
  5259. .attr('stroke', setTextColor(d,i));
  5260. });
  5261. }
  5262. seriesEnter.append('text')
  5263. .attr('text-anchor', 'start')
  5264. .attr('class','nv-legend-text')
  5265. .attr('dy', '.32em')
  5266. .attr('dx', '8');
  5267. var seriesText = series.select('text.nv-legend-text');
  5268. series
  5269. .on('mouseover', function(d,i) {
  5270. dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects
  5271. })
  5272. .on('mouseout', function(d,i) {
  5273. dispatch.legendMouseout(d,i);
  5274. })
  5275. .on('click', function(d,i) {
  5276. dispatch.legendClick(d,i);
  5277. // make sure we re-get data in case it was modified
  5278. var data = series.data();
  5279. if (updateState) {
  5280. if(vers =='classic') {
  5281. if (radioButtonMode) {
  5282. //Radio button mode: set every series to disabled,
  5283. // and enable the clicked series.
  5284. data.forEach(function(series) { series.disabled = true});
  5285. d.disabled = false;
  5286. }
  5287. else {
  5288. d.disabled = !d.disabled;
  5289. if (data.every(function(series) { return series.disabled})) {
  5290. //the default behavior of NVD3 legends is, if every single series
  5291. // is disabled, turn all series' back on.
  5292. data.forEach(function(series) { series.disabled = false});
  5293. }
  5294. }
  5295. } else if(vers == 'furious') {
  5296. if(expanded) {
  5297. d.disengaged = !d.disengaged;
  5298. d.userDisabled = d.userDisabled == undefined ? !!d.disabled : d.userDisabled;
  5299. d.disabled = d.disengaged || d.userDisabled;
  5300. } else if (!expanded) {
  5301. d.disabled = !d.disabled;
  5302. d.userDisabled = d.disabled;
  5303. var engaged = data.filter(function(d) { return !d.disengaged; });
  5304. if (engaged.every(function(series) { return series.userDisabled })) {
  5305. //the default behavior of NVD3 legends is, if every single series
  5306. // is disabled, turn all series' back on.
  5307. data.forEach(function(series) {
  5308. series.disabled = series.userDisabled = false;
  5309. });
  5310. }
  5311. }
  5312. }
  5313. dispatch.stateChange({
  5314. disabled: data.map(function(d) { return !!d.disabled }),
  5315. disengaged: data.map(function(d) { return !!d.disengaged })
  5316. });
  5317. }
  5318. })
  5319. .on('dblclick', function(d,i) {
  5320. if(vers == 'furious' && expanded) return;
  5321. dispatch.legendDblclick(d,i);
  5322. if (updateState) {
  5323. // make sure we re-get data in case it was modified
  5324. var data = series.data();
  5325. //the default behavior of NVD3 legends, when double clicking one,
  5326. // is to set all other series' to false, and make the double clicked series enabled.
  5327. data.forEach(function(series) {
  5328. series.disabled = true;
  5329. if(vers == 'furious') series.userDisabled = series.disabled;
  5330. });
  5331. d.disabled = false;
  5332. if(vers == 'furious') d.userDisabled = d.disabled;
  5333. dispatch.stateChange({
  5334. disabled: data.map(function(d) { return !!d.disabled })
  5335. });
  5336. }
  5337. });
  5338. series.classed('nv-disabled', function(d) { return d.userDisabled });
  5339. series.exit().remove();
  5340. seriesText
  5341. .attr('fill', setTextColor)
  5342. .text(function (d) { return keyFormatter(getKey(d)) });
  5343. //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
  5344. // NEW ALIGNING CODE, TODO: clean up
  5345. var legendWidth = 0;
  5346. if (align) {
  5347. var seriesWidths = [];
  5348. series.each(function(d,i) {
  5349. var legendText;
  5350. if (keyFormatter(getKey(d)) && keyFormatter(getKey(d)).length > maxKeyLength) {
  5351. var trimmedKey = keyFormatter(getKey(d)).substring(0, maxKeyLength);
  5352. legendText = d3.select(this).select('text').text(trimmedKey + "...");
  5353. d3.select(this).append("svg:title").text(keyFormatter(getKey(d)));
  5354. } else {
  5355. legendText = d3.select(this).select('text');
  5356. }
  5357. var nodeTextLength;
  5358. try {
  5359. nodeTextLength = legendText.node().getComputedTextLength();
  5360. // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead
  5361. if(nodeTextLength <= 0) throw Error();
  5362. }
  5363. catch(e) {
  5364. nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
  5365. }
  5366. seriesWidths.push(nodeTextLength + padding);
  5367. });
  5368. var seriesPerRow = 0;
  5369. var columnWidths = [];
  5370. legendWidth = 0;
  5371. while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
  5372. columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
  5373. legendWidth += seriesWidths[seriesPerRow++];
  5374. }
  5375. if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
  5376. while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
  5377. columnWidths = [];
  5378. seriesPerRow--;
  5379. for (var k = 0; k < seriesWidths.length; k++) {
  5380. if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
  5381. columnWidths[k % seriesPerRow] = seriesWidths[k];
  5382. }
  5383. legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
  5384. return prev + cur;
  5385. });
  5386. }
  5387. var xPositions = [];
  5388. for (var i = 0, curX = 0; i < seriesPerRow; i++) {
  5389. xPositions[i] = curX;
  5390. curX += columnWidths[i];
  5391. }
  5392. series
  5393. .attr('transform', function(d, i) {
  5394. return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')';
  5395. });
  5396. //position legend as far right as possible within the total width
  5397. if (rightAlign) {
  5398. g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
  5399. }
  5400. else {
  5401. g.attr('transform', 'translate(0' + ',' + margin.top + ')');
  5402. }
  5403. height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding);
  5404. } else {
  5405. var ypos = 5,
  5406. newxpos = 5,
  5407. maxwidth = 0,
  5408. xpos;
  5409. series
  5410. .attr('transform', function(d, i) {
  5411. var length = d3.select(this).select('text').node().getComputedTextLength() + padding;
  5412. xpos = newxpos;
  5413. if (width < margin.left + margin.right + xpos + length) {
  5414. newxpos = xpos = 5;
  5415. ypos += versPadding;
  5416. }
  5417. newxpos += length;
  5418. if (newxpos > maxwidth) maxwidth = newxpos;
  5419. if(legendWidth < xpos + maxwidth) {
  5420. legendWidth = xpos + maxwidth;
  5421. }
  5422. return 'translate(' + xpos + ',' + ypos + ')';
  5423. });
  5424. //position legend as far right as possible within the total width
  5425. g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
  5426. height = margin.top + margin.bottom + ypos + 15;
  5427. }
  5428. if(vers == 'furious') {
  5429. // Size rectangles after text is placed
  5430. seriesShape
  5431. .attr('width', function(d,i) {
  5432. return seriesText[0][i].getComputedTextLength() + 27;
  5433. })
  5434. .attr('height', 18)
  5435. .attr('y', -9)
  5436. .attr('x', -15);
  5437. // The background for the expanded legend (UI)
  5438. gEnter.insert('rect',':first-child')
  5439. .attr('class', 'nv-legend-bg')
  5440. .attr('fill', '#eee')
  5441. // .attr('stroke', '#444')
  5442. .attr('opacity',0);
  5443. var seriesBG = g.select('.nv-legend-bg');
  5444. seriesBG
  5445. .transition().duration(300)
  5446. .attr('x', -versPadding )
  5447. .attr('width', legendWidth + versPadding - 12)
  5448. .attr('height', height + 10)
  5449. .attr('y', -margin.top - 10)
  5450. .attr('opacity', expanded ? 1 : 0);
  5451. }
  5452. seriesShape
  5453. .style('fill', setBGColor)
  5454. .style('fill-opacity', setBGOpacity)
  5455. .style('stroke', setBGColor);
  5456. });
  5457. function setTextColor(d,i) {
  5458. if(vers != 'furious') return '#000';
  5459. if(expanded) {
  5460. return d.disengaged ? '#000' : '#fff';
  5461. } else if (!expanded) {
  5462. if(!d.color) d.color = color(d,i);
  5463. return !!d.disabled ? d.color : '#fff';
  5464. }
  5465. }
  5466. function setBGColor(d,i) {
  5467. if(expanded && vers == 'furious') {
  5468. return d.disengaged ? '#eee' : d.color || color(d,i);
  5469. } else {
  5470. return d.color || color(d,i);
  5471. }
  5472. }
  5473. function setBGOpacity(d,i) {
  5474. if(expanded && vers == 'furious') {
  5475. return 1;
  5476. } else {
  5477. return !!d.disabled ? 0 : 1;
  5478. }
  5479. }
  5480. return chart;
  5481. }
  5482. //============================================================
  5483. // Expose Public Variables
  5484. //------------------------------------------------------------
  5485. chart.dispatch = dispatch;
  5486. chart.options = nv.utils.optionsFunc.bind(chart);
  5487. chart._options = Object.create({}, {
  5488. // simple options, just get/set the necessary values
  5489. width: {get: function(){return width;}, set: function(_){width=_;}},
  5490. height: {get: function(){return height;}, set: function(_){height=_;}},
  5491. key: {get: function(){return getKey;}, set: function(_){getKey=_;}},
  5492. keyFormatter: {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}},
  5493. align: {get: function(){return align;}, set: function(_){align=_;}},
  5494. maxKeyLength: {get: function(){return maxKeyLength;}, set: function(_){maxKeyLength=_;}},
  5495. rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}},
  5496. padding: {get: function(){return padding;}, set: function(_){padding=_;}},
  5497. updateState: {get: function(){return updateState;}, set: function(_){updateState=_;}},
  5498. radioButtonMode:{get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}},
  5499. expanded: {get: function(){return expanded;}, set: function(_){expanded=_;}},
  5500. vers: {get: function(){return vers;}, set: function(_){vers=_;}},
  5501. // options that require extra logic in the setter
  5502. margin: {get: function(){return margin;}, set: function(_){
  5503. margin.top = _.top !== undefined ? _.top : margin.top;
  5504. margin.right = _.right !== undefined ? _.right : margin.right;
  5505. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  5506. margin.left = _.left !== undefined ? _.left : margin.left;
  5507. }},
  5508. color: {get: function(){return color;}, set: function(_){
  5509. color = nv.utils.getColor(_);
  5510. }}
  5511. });
  5512. nv.utils.initOptions(chart);
  5513. return chart;
  5514. };
  5515. nv.models.line = function() {
  5516. "use strict";
  5517. //============================================================
  5518. // Public Variables with Default Settings
  5519. //------------------------------------------------------------
  5520. var scatter = nv.models.scatter()
  5521. ;
  5522. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  5523. , width = 960
  5524. , height = 500
  5525. , container = null
  5526. , strokeWidth = 1.5
  5527. , color = nv.utils.defaultColor() // a function that returns a color
  5528. , getX = function(d) { return d.x } // accessor to get the x value from a data point
  5529. , getY = function(d) { return d.y } // accessor to get the y value from a data point
  5530. , 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
  5531. , isArea = function(d) { return d.area } // decides if a line is an area or just a line
  5532. , clipEdge = false // if true, masks lines within x and y scale
  5533. , x //can be accessed via chart.xScale()
  5534. , y //can be accessed via chart.yScale()
  5535. , interpolate = "linear" // controls the line interpolation
  5536. , duration = 250
  5537. , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
  5538. ;
  5539. scatter
  5540. .pointSize(16) // default size
  5541. .pointDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor
  5542. ;
  5543. //============================================================
  5544. //============================================================
  5545. // Private Variables
  5546. //------------------------------------------------------------
  5547. var x0, y0 //used to store previous scales
  5548. , renderWatch = nv.utils.renderWatch(dispatch, duration)
  5549. ;
  5550. //============================================================
  5551. function chart(selection) {
  5552. renderWatch.reset();
  5553. renderWatch.models(scatter);
  5554. selection.each(function(data) {
  5555. container = d3.select(this);
  5556. var availableWidth = nv.utils.availableWidth(width, container, margin),
  5557. availableHeight = nv.utils.availableHeight(height, container, margin);
  5558. nv.utils.initSVG(container);
  5559. // Setup Scales
  5560. x = scatter.xScale();
  5561. y = scatter.yScale();
  5562. x0 = x0 || x;
  5563. y0 = y0 || y;
  5564. // Setup containers and skeleton of chart
  5565. var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]);
  5566. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line');
  5567. var defsEnter = wrapEnter.append('defs');
  5568. var gEnter = wrapEnter.append('g');
  5569. var g = wrap.select('g');
  5570. gEnter.append('g').attr('class', 'nv-groups');
  5571. gEnter.append('g').attr('class', 'nv-scatterWrap');
  5572. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  5573. scatter
  5574. .width(availableWidth)
  5575. .height(availableHeight);
  5576. var scatterWrap = wrap.select('.nv-scatterWrap');
  5577. scatterWrap.call(scatter);
  5578. defsEnter.append('clipPath')
  5579. .attr('id', 'nv-edge-clip-' + scatter.id())
  5580. .append('rect');
  5581. wrap.select('#nv-edge-clip-' + scatter.id() + ' rect')
  5582. .attr('width', availableWidth)
  5583. .attr('height', (availableHeight > 0) ? availableHeight : 0);
  5584. g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
  5585. scatterWrap
  5586. .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
  5587. var groups = wrap.select('.nv-groups').selectAll('.nv-group')
  5588. .data(function(d) { return d }, function(d) { return d.key });
  5589. groups.enter().append('g')
  5590. .style('stroke-opacity', 1e-6)
  5591. .style('stroke-width', function(d) { return d.strokeWidth || strokeWidth })
  5592. .style('fill-opacity', 1e-6);
  5593. groups.exit().remove();
  5594. groups
  5595. .attr('class', function(d,i) {
  5596. return (d.classed || '') + ' nv-group nv-series-' + i;
  5597. })
  5598. .classed('hover', function(d) { return d.hover })
  5599. .style('fill', function(d,i){ return color(d, i) })
  5600. .style('stroke', function(d,i){ return color(d, i)});
  5601. groups.watchTransition(renderWatch, 'line: groups')
  5602. .style('stroke-opacity', 1)
  5603. .style('fill-opacity', function(d) { return d.fillOpacity || .5});
  5604. var areaPaths = groups.selectAll('path.nv-area')
  5605. .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area
  5606. areaPaths.enter().append('path')
  5607. .attr('class', 'nv-area')
  5608. .attr('d', function(d) {
  5609. return d3.svg.area()
  5610. .interpolate(interpolate)
  5611. .defined(defined)
  5612. .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
  5613. .y0(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
  5614. .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
  5615. //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
  5616. .apply(this, [d.values])
  5617. });
  5618. groups.exit().selectAll('path.nv-area')
  5619. .remove();
  5620. areaPaths.watchTransition(renderWatch, 'line: areaPaths')
  5621. .attr('d', function(d) {
  5622. return d3.svg.area()
  5623. .interpolate(interpolate)
  5624. .defined(defined)
  5625. .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
  5626. .y0(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
  5627. .y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
  5628. //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
  5629. .apply(this, [d.values])
  5630. });
  5631. var linePaths = groups.selectAll('path.nv-line')
  5632. .data(function(d) { return [d.values] });
  5633. linePaths.enter().append('path')
  5634. .attr('class', 'nv-line')
  5635. .attr('d',
  5636. d3.svg.line()
  5637. .interpolate(interpolate)
  5638. .defined(defined)
  5639. .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
  5640. .y(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
  5641. );
  5642. linePaths.watchTransition(renderWatch, 'line: linePaths')
  5643. .attr('d',
  5644. d3.svg.line()
  5645. .interpolate(interpolate)
  5646. .defined(defined)
  5647. .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
  5648. .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
  5649. );
  5650. //store old scales for use in transitions on update
  5651. x0 = x.copy();
  5652. y0 = y.copy();
  5653. });
  5654. renderWatch.renderEnd('line immediate');
  5655. return chart;
  5656. }
  5657. //============================================================
  5658. // Expose Public Variables
  5659. //------------------------------------------------------------
  5660. chart.dispatch = dispatch;
  5661. chart.scatter = scatter;
  5662. // Pass through events
  5663. scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); });
  5664. scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); });
  5665. scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); });
  5666. chart.options = nv.utils.optionsFunc.bind(chart);
  5667. chart._options = Object.create({}, {
  5668. // simple options, just get/set the necessary values
  5669. width: {get: function(){return width;}, set: function(_){width=_;}},
  5670. height: {get: function(){return height;}, set: function(_){height=_;}},
  5671. defined: {get: function(){return defined;}, set: function(_){defined=_;}},
  5672. interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
  5673. clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
  5674. // options that require extra logic in the setter
  5675. margin: {get: function(){return margin;}, set: function(_){
  5676. margin.top = _.top !== undefined ? _.top : margin.top;
  5677. margin.right = _.right !== undefined ? _.right : margin.right;
  5678. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  5679. margin.left = _.left !== undefined ? _.left : margin.left;
  5680. }},
  5681. duration: {get: function(){return duration;}, set: function(_){
  5682. duration = _;
  5683. renderWatch.reset(duration);
  5684. scatter.duration(duration);
  5685. }},
  5686. isArea: {get: function(){return isArea;}, set: function(_){
  5687. isArea = d3.functor(_);
  5688. }},
  5689. x: {get: function(){return getX;}, set: function(_){
  5690. getX = _;
  5691. scatter.x(_);
  5692. }},
  5693. y: {get: function(){return getY;}, set: function(_){
  5694. getY = _;
  5695. scatter.y(_);
  5696. }},
  5697. color: {get: function(){return color;}, set: function(_){
  5698. color = nv.utils.getColor(_);
  5699. scatter.color(color);
  5700. }}
  5701. });
  5702. nv.utils.inheritOptions(chart, scatter);
  5703. nv.utils.initOptions(chart);
  5704. return chart;
  5705. };
  5706. nv.models.lineChart = function() {
  5707. "use strict";
  5708. //============================================================
  5709. // Public Variables with Default Settings
  5710. //------------------------------------------------------------
  5711. var lines = nv.models.line()
  5712. , xAxis = nv.models.axis()
  5713. , yAxis = nv.models.axis()
  5714. , legend = nv.models.legend()
  5715. , interactiveLayer = nv.interactiveGuideline()
  5716. , tooltip = nv.models.tooltip()
  5717. , focus = nv.models.focus(nv.models.line())
  5718. ;
  5719. var margin = {top: 30, right: 20, bottom: 50, left: 60}
  5720. , color = nv.utils.defaultColor()
  5721. , width = null
  5722. , height = null
  5723. , showLegend = true
  5724. , legendPosition = 'top'
  5725. , showXAxis = true
  5726. , showYAxis = true
  5727. , rightAlignYAxis = false
  5728. , useInteractiveGuideline = false
  5729. , x
  5730. , y
  5731. , focusEnable = false
  5732. , state = nv.utils.state()
  5733. , defaultState = null
  5734. , noData = null
  5735. , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
  5736. , duration = 250
  5737. ;
  5738. // set options on sub-objects for this chart
  5739. xAxis.orient('bottom').tickPadding(7);
  5740. yAxis.orient(rightAlignYAxis ? 'right' : 'left');
  5741. lines.clipEdge(true).duration(0);
  5742. tooltip.valueFormatter(function(d, i) {
  5743. return yAxis.tickFormat()(d, i);
  5744. }).headerFormatter(function(d, i) {
  5745. return xAxis.tickFormat()(d, i);
  5746. });
  5747. interactiveLayer.tooltip.valueFormatter(function(d, i) {
  5748. return yAxis.tickFormat()(d, i);
  5749. }).headerFormatter(function(d, i) {
  5750. return xAxis.tickFormat()(d, i);
  5751. });
  5752. //============================================================
  5753. // Private Variables
  5754. //------------------------------------------------------------
  5755. var renderWatch = nv.utils.renderWatch(dispatch, duration);
  5756. var stateGetter = function(data) {
  5757. return function(){
  5758. return {
  5759. active: data.map(function(d) { return !d.disabled; })
  5760. };
  5761. };
  5762. };
  5763. var stateSetter = function(data) {
  5764. return function(state) {
  5765. if (state.active !== undefined)
  5766. data.forEach(function(series,i) {
  5767. series.disabled = !state.active[i];
  5768. });
  5769. };
  5770. };
  5771. function chart(selection) {
  5772. renderWatch.reset();
  5773. renderWatch.models(lines);
  5774. if (showXAxis) renderWatch.models(xAxis);
  5775. if (showYAxis) renderWatch.models(yAxis);
  5776. selection.each(function(data) {
  5777. var container = d3.select(this);
  5778. nv.utils.initSVG(container);
  5779. var availableWidth = nv.utils.availableWidth(width, container, margin),
  5780. availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0);
  5781. chart.update = function() {
  5782. if( duration === 0 ) {
  5783. container.call( chart );
  5784. } else {
  5785. container.transition().duration(duration).call(chart);
  5786. }
  5787. };
  5788. chart.container = this;
  5789. state
  5790. .setter(stateSetter(data), chart.update)
  5791. .getter(stateGetter(data))
  5792. .update();
  5793. // DEPRECATED set state.disabled
  5794. state.disabled = data.map(function(d) { return !!d.disabled; });
  5795. if (!defaultState) {
  5796. var key;
  5797. defaultState = {};
  5798. for (key in state) {
  5799. if (state[key] instanceof Array)
  5800. defaultState[key] = state[key].slice(0);
  5801. else
  5802. defaultState[key] = state[key];
  5803. }
  5804. }
  5805. // Display noData message if there's nothing to show.
  5806. if (!data || !data.length || !data.filter(function(d) { return d.values.length; }).length) {
  5807. nv.utils.noData(chart, container);
  5808. return chart;
  5809. } else {
  5810. container.selectAll('.nv-noData').remove();
  5811. }
  5812. /* Update `main' graph on brush update. */
  5813. focus.dispatch.on("onBrush", function(extent) {
  5814. onBrush(extent);
  5815. });
  5816. // Setup Scales
  5817. x = lines.xScale();
  5818. y = lines.yScale();
  5819. // Setup containers and skeleton of chart
  5820. var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]);
  5821. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g');
  5822. var g = wrap.select('g');
  5823. gEnter.append('g').attr('class', 'nv-legendWrap');
  5824. var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
  5825. focusEnter.append('g').attr('class', 'nv-background').append('rect');
  5826. focusEnter.append('g').attr('class', 'nv-x nv-axis');
  5827. focusEnter.append('g').attr('class', 'nv-y nv-axis');
  5828. focusEnter.append('g').attr('class', 'nv-linesWrap');
  5829. focusEnter.append('g').attr('class', 'nv-interactive');
  5830. var contextEnter = gEnter.append('g').attr('class', 'nv-focusWrap');
  5831. // Legend
  5832. if (!showLegend) {
  5833. g.select('.nv-legendWrap').selectAll('*').remove();
  5834. } else {
  5835. legend.width(availableWidth);
  5836. g.select('.nv-legendWrap')
  5837. .datum(data)
  5838. .call(legend);
  5839. if (legendPosition === 'bottom') {
  5840. wrap.select('.nv-legendWrap')
  5841. .attr('transform', 'translate(0,' + availableHeight +')');
  5842. } else if (legendPosition === 'top') {
  5843. if (legend.height() > margin.top) {
  5844. margin.top = legend.height();
  5845. availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0);
  5846. }
  5847. wrap.select('.nv-legendWrap')
  5848. .attr('transform', 'translate(0,' + (-margin.top) +')');
  5849. }
  5850. }
  5851. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  5852. if (rightAlignYAxis) {
  5853. g.select(".nv-y.nv-axis")
  5854. .attr("transform", "translate(" + availableWidth + ",0)");
  5855. }
  5856. //Set up interactive layer
  5857. if (useInteractiveGuideline) {
  5858. interactiveLayer
  5859. .width(availableWidth)
  5860. .height(availableHeight)
  5861. .margin({left:margin.left, top:margin.top})
  5862. .svgContainer(container)
  5863. .xScale(x);
  5864. wrap.select(".nv-interactive").call(interactiveLayer);
  5865. }
  5866. g.select('.nv-focus .nv-background rect')
  5867. .attr('width', availableWidth)
  5868. .attr('height', availableHeight);
  5869. lines
  5870. .width(availableWidth)
  5871. .height(availableHeight)
  5872. .color(data.map(function(d,i) {
  5873. return d.color || color(d, i);
  5874. }).filter(function(d,i) { return !data[i].disabled; }));
  5875. var linesWrap = g.select('.nv-linesWrap')
  5876. .datum(data.filter(function(d) { return !d.disabled; }));
  5877. // Setup Main (Focus) Axes
  5878. if (showXAxis) {
  5879. xAxis
  5880. .scale(x)
  5881. ._ticks(nv.utils.calcTicksX(availableWidth/100, data) )
  5882. .tickSize(-availableHeight, 0);
  5883. }
  5884. if (showYAxis) {
  5885. yAxis
  5886. .scale(y)
  5887. ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
  5888. .tickSize( -availableWidth, 0);
  5889. }
  5890. //============================================================
  5891. // Update Axes
  5892. //============================================================
  5893. function updateXAxis() {
  5894. if(showXAxis) {
  5895. g.select('.nv-focus .nv-x.nv-axis')
  5896. .transition()
  5897. .duration(duration)
  5898. .call(xAxis)
  5899. ;
  5900. }
  5901. }
  5902. function updateYAxis() {
  5903. if(showYAxis) {
  5904. g.select('.nv-focus .nv-y.nv-axis')
  5905. .transition()
  5906. .duration(duration)
  5907. .call(yAxis)
  5908. ;
  5909. }
  5910. }
  5911. g.select('.nv-focus .nv-x.nv-axis')
  5912. .attr('transform', 'translate(0,' + availableHeight + ')');
  5913. //============================================================
  5914. // Update Focus
  5915. //============================================================
  5916. if(!focusEnable) {
  5917. linesWrap.call(lines);
  5918. updateXAxis();
  5919. updateYAxis();
  5920. } else {
  5921. focus.width(availableWidth);
  5922. g.select('.nv-focusWrap')
  5923. .attr('transform', 'translate(0,' + ( availableHeight + margin.bottom + focus.margin().top) + ')')
  5924. .datum(data.filter(function(d) { return !d.disabled; }))
  5925. .call(focus);
  5926. var extent = focus.brush.empty() ? focus.xDomain() : focus.brush.extent();
  5927. if(extent !== null){
  5928. onBrush(extent);
  5929. }
  5930. }
  5931. //============================================================
  5932. // Event Handling/Dispatching (in chart's scope)
  5933. //------------------------------------------------------------
  5934. legend.dispatch.on('stateChange', function(newState) {
  5935. for (var key in newState)
  5936. state[key] = newState[key];
  5937. dispatch.stateChange(state);
  5938. chart.update();
  5939. });
  5940. interactiveLayer.dispatch.on('elementMousemove', function(e) {
  5941. lines.clearHighlights();
  5942. var singlePoint, pointIndex, pointXLocation, allData = [];
  5943. data
  5944. .filter(function(series, i) {
  5945. series.seriesIndex = i;
  5946. return !series.disabled && !series.disableTooltip;
  5947. })
  5948. .forEach(function(series,i) {
  5949. var extent = focusEnable ? (focus.brush.empty() ? focus.xScale().domain() : focus.brush.extent()) : x.domain();
  5950. var currentValues = series.values.filter(function(d,i) {
  5951. return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
  5952. });
  5953. pointIndex = nv.interactiveBisect(currentValues, e.pointXValue, lines.x());
  5954. var point = currentValues[pointIndex];
  5955. var pointYValue = chart.y()(point, pointIndex);
  5956. if (pointYValue !== null) {
  5957. lines.highlightPoint(series.seriesIndex, pointIndex, true);
  5958. }
  5959. if (point === undefined) return;
  5960. if (singlePoint === undefined) singlePoint = point;
  5961. if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
  5962. allData.push({
  5963. key: series.key,
  5964. value: pointYValue,
  5965. color: color(series,series.seriesIndex),
  5966. data: point
  5967. });
  5968. });
  5969. //Highlight the tooltip entry based on which point the mouse is closest to.
  5970. if (allData.length > 2) {
  5971. var yValue = chart.yScale().invert(e.mouseY);
  5972. var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
  5973. var threshold = 0.03 * domainExtent;
  5974. var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value;}),yValue,threshold);
  5975. if (indexToHighlight !== null)
  5976. allData[indexToHighlight].highlight = true;
  5977. }
  5978. var defaultValueFormatter = function(d,i) {
  5979. return d == null ? "N/A" : yAxis.tickFormat()(d);
  5980. };
  5981. interactiveLayer.tooltip
  5982. .valueFormatter(interactiveLayer.tooltip.valueFormatter() || defaultValueFormatter)
  5983. .data({
  5984. value: chart.x()( singlePoint,pointIndex ),
  5985. index: pointIndex,
  5986. series: allData
  5987. })();
  5988. interactiveLayer.renderGuideLine(pointXLocation);
  5989. });
  5990. interactiveLayer.dispatch.on('elementClick', function(e) {
  5991. var pointXLocation, allData = [];
  5992. data.filter(function(series, i) {
  5993. series.seriesIndex = i;
  5994. return !series.disabled;
  5995. }).forEach(function(series) {
  5996. var pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
  5997. var point = series.values[pointIndex];
  5998. if (typeof point === 'undefined') return;
  5999. if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
  6000. var yPos = chart.yScale()(chart.y()(point,pointIndex));
  6001. allData.push({
  6002. point: point,
  6003. pointIndex: pointIndex,
  6004. pos: [pointXLocation, yPos],
  6005. seriesIndex: series.seriesIndex,
  6006. series: series
  6007. });
  6008. });
  6009. lines.dispatch.elementClick(allData);
  6010. });
  6011. interactiveLayer.dispatch.on("elementMouseout",function(e) {
  6012. lines.clearHighlights();
  6013. });
  6014. dispatch.on('changeState', function(e) {
  6015. if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
  6016. data.forEach(function(series,i) {
  6017. series.disabled = e.disabled[i];
  6018. });
  6019. state.disabled = e.disabled;
  6020. }
  6021. chart.update();
  6022. });
  6023. //============================================================
  6024. // Functions
  6025. //------------------------------------------------------------
  6026. // Taken from crossfilter (http://square.github.com/crossfilter/)
  6027. function resizePath(d) {
  6028. var e = +(d == 'e'),
  6029. x = e ? 1 : -1,
  6030. y = availableHeight / 3;
  6031. return 'M' + (0.5 * x) + ',' + y
  6032. + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
  6033. + 'V' + (2 * y - 6)
  6034. + 'A6,6 0 0 ' + e + ' ' + (0.5 * x) + ',' + (2 * y)
  6035. + 'Z'
  6036. + 'M' + (2.5 * x) + ',' + (y + 8)
  6037. + 'V' + (2 * y - 8)
  6038. + 'M' + (4.5 * x) + ',' + (y + 8)
  6039. + 'V' + (2 * y - 8);
  6040. }
  6041. function onBrush(extent) {
  6042. // Update Main (Focus)
  6043. var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
  6044. .datum(
  6045. data.filter(function(d) { return !d.disabled; })
  6046. .map(function(d,i) {
  6047. return {
  6048. key: d.key,
  6049. area: d.area,
  6050. classed: d.classed,
  6051. values: d.values.filter(function(d,i) {
  6052. return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
  6053. }),
  6054. disableTooltip: d.disableTooltip
  6055. };
  6056. })
  6057. );
  6058. focusLinesWrap.transition().duration(duration).call(lines);
  6059. // Update Main (Focus) Axes
  6060. updateXAxis();
  6061. updateYAxis();
  6062. }
  6063. });
  6064. renderWatch.renderEnd('lineChart immediate');
  6065. return chart;
  6066. }
  6067. //============================================================
  6068. // Event Handling/Dispatching (out of chart's scope)
  6069. //------------------------------------------------------------
  6070. lines.dispatch.on('elementMouseover.tooltip', function(evt) {
  6071. if(!evt.series.disableTooltip){
  6072. tooltip.data(evt).hidden(false);
  6073. }
  6074. });
  6075. lines.dispatch.on('elementMouseout.tooltip', function(evt) {
  6076. tooltip.hidden(true);
  6077. });
  6078. //============================================================
  6079. // Expose Public Variables
  6080. //------------------------------------------------------------
  6081. // expose chart's sub-components
  6082. chart.dispatch = dispatch;
  6083. chart.lines = lines;
  6084. chart.legend = legend;
  6085. chart.focus = focus;
  6086. chart.xAxis = xAxis;
  6087. chart.x2Axis = focus.xAxis
  6088. chart.yAxis = yAxis;
  6089. chart.y2Axis = focus.yAxis
  6090. chart.interactiveLayer = interactiveLayer;
  6091. chart.tooltip = tooltip;
  6092. chart.state = state;
  6093. chart.dispatch = dispatch;
  6094. chart.options = nv.utils.optionsFunc.bind(chart);
  6095. chart._options = Object.create({}, {
  6096. // simple options, just get/set the necessary values
  6097. width: {get: function(){return width;}, set: function(_){width=_;}},
  6098. height: {get: function(){return height;}, set: function(_){height=_;}},
  6099. showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
  6100. legendPosition: {get: function(){return legendPosition;}, set: function(_){legendPosition=_;}},
  6101. showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
  6102. showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
  6103. defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
  6104. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  6105. // Focus options, mostly passed onto focus model.
  6106. focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}},
  6107. focusHeight: {get: function(){return focus.height();}, set: function(_){focus.height(_);}},
  6108. focusShowAxisX: {get: function(){return focus.showXAxis();}, set: function(_){focus.showXAxis(_);}},
  6109. focusShowAxisY: {get: function(){return focus.showYAxis();}, set: function(_){focus.showYAxis(_);}},
  6110. brushExtent: {get: function(){return focus.brushExtent();}, set: function(_){focus.brushExtent(_);}},
  6111. // options that require extra logic in the setter
  6112. focusMargin: {get: function(){return focus.margin}, set: function(_){
  6113. focus.margin.top = _.top !== undefined ? _.top : focus.margin.top;
  6114. focus.margin.right = _.right !== undefined ? _.right : focus.margin.right;
  6115. focus.margin.bottom = _.bottom !== undefined ? _.bottom : focus.margin.bottom;
  6116. focus.margin.left = _.left !== undefined ? _.left : focus.margin.left;
  6117. }},
  6118. margin: {get: function(){return margin;}, set: function(_){
  6119. margin.top = _.top !== undefined ? _.top : margin.top;
  6120. margin.right = _.right !== undefined ? _.right : margin.right;
  6121. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  6122. margin.left = _.left !== undefined ? _.left : margin.left;
  6123. }},
  6124. duration: {get: function(){return duration;}, set: function(_){
  6125. duration = _;
  6126. renderWatch.reset(duration);
  6127. lines.duration(duration);
  6128. focus.duration(duration);
  6129. xAxis.duration(duration);
  6130. yAxis.duration(duration);
  6131. }},
  6132. color: {get: function(){return color;}, set: function(_){
  6133. color = nv.utils.getColor(_);
  6134. legend.color(color);
  6135. lines.color(color);
  6136. focus.color(color);
  6137. }},
  6138. interpolate: {get: function(){return lines.interpolate();}, set: function(_){
  6139. lines.interpolate(_);
  6140. focus.interpolate(_);
  6141. }},
  6142. xTickFormat: {get: function(){return xAxis.tickFormat();}, set: function(_){
  6143. xAxis.tickFormat(_);
  6144. focus.xTickFormat(_);
  6145. }},
  6146. yTickFormat: {get: function(){return yAxis.tickFormat();}, set: function(_){
  6147. yAxis.tickFormat(_);
  6148. focus.yTickFormat(_);
  6149. }},
  6150. x: {get: function(){return lines.x();}, set: function(_){
  6151. lines.x(_);
  6152. focus.x(_);
  6153. }},
  6154. y: {get: function(){return lines.y();}, set: function(_){
  6155. lines.y(_);
  6156. focus.y(_);
  6157. }},
  6158. rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
  6159. rightAlignYAxis = _;
  6160. yAxis.orient( rightAlignYAxis ? 'right' : 'left');
  6161. }},
  6162. useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
  6163. useInteractiveGuideline = _;
  6164. if (useInteractiveGuideline) {
  6165. lines.interactive(false);
  6166. lines.useVoronoi(false);
  6167. }
  6168. }}
  6169. });
  6170. nv.utils.inheritOptions(chart, lines);
  6171. nv.utils.initOptions(chart);
  6172. return chart;
  6173. };
  6174. nv.models.lineWithFocusChart = function() {
  6175. return nv.models.lineChart()
  6176. .margin({ bottom: 30 })
  6177. .focusEnable( true );
  6178. };
  6179. nv.models.linePlusBarChart = function() {
  6180. "use strict";
  6181. //============================================================
  6182. // Public Variables with Default Settings
  6183. //------------------------------------------------------------
  6184. var lines = nv.models.line()
  6185. , lines2 = nv.models.line()
  6186. , bars = nv.models.historicalBar()
  6187. , bars2 = nv.models.historicalBar()
  6188. , xAxis = nv.models.axis()
  6189. , x2Axis = nv.models.axis()
  6190. , y1Axis = nv.models.axis()
  6191. , y2Axis = nv.models.axis()
  6192. , y3Axis = nv.models.axis()
  6193. , y4Axis = nv.models.axis()
  6194. , legend = nv.models.legend()
  6195. , brush = d3.svg.brush()
  6196. , tooltip = nv.models.tooltip()
  6197. ;
  6198. var margin = {top: 30, right: 30, bottom: 30, left: 60}
  6199. , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
  6200. , width = null
  6201. , height = null
  6202. , getX = function(d) { return d.x }
  6203. , getY = function(d) { return d.y }
  6204. , color = nv.utils.defaultColor()
  6205. , showLegend = true
  6206. , focusEnable = true
  6207. , focusShowAxisY = false
  6208. , focusShowAxisX = true
  6209. , focusHeight = 50
  6210. , extent
  6211. , brushExtent = null
  6212. , x
  6213. , x2
  6214. , y1
  6215. , y2
  6216. , y3
  6217. , y4
  6218. , noData = null
  6219. , dispatch = d3.dispatch('brush', 'stateChange', 'changeState')
  6220. , transitionDuration = 0
  6221. , state = nv.utils.state()
  6222. , defaultState = null
  6223. , legendLeftAxisHint = ' (left axis)'
  6224. , legendRightAxisHint = ' (right axis)'
  6225. , switchYAxisOrder = false
  6226. ;
  6227. lines.clipEdge(true);
  6228. lines2.interactive(false);
  6229. // We don't want any points emitted for the focus chart's scatter graph.
  6230. lines2.pointActive(function(d) { return false });
  6231. xAxis.orient('bottom').tickPadding(5);
  6232. y1Axis.orient('left');
  6233. y2Axis.orient('right');
  6234. x2Axis.orient('bottom').tickPadding(5);
  6235. y3Axis.orient('left');
  6236. y4Axis.orient('right');
  6237. tooltip.headerEnabled(true).headerFormatter(function(d, i) {
  6238. return xAxis.tickFormat()(d, i);
  6239. });
  6240. //============================================================
  6241. // Private Variables
  6242. //------------------------------------------------------------
  6243. var getBarsAxis = function() {
  6244. return switchYAxisOrder
  6245. ? { main: y2Axis, focus: y4Axis }
  6246. : { main: y1Axis, focus: y3Axis }
  6247. }
  6248. var getLinesAxis = function() {
  6249. return switchYAxisOrder
  6250. ? { main: y1Axis, focus: y3Axis }
  6251. : { main: y2Axis, focus: y4Axis }
  6252. }
  6253. var stateGetter = function(data) {
  6254. return function(){
  6255. return {
  6256. active: data.map(function(d) { return !d.disabled })
  6257. };
  6258. }
  6259. };
  6260. var stateSetter = function(data) {
  6261. return function(state) {
  6262. if (state.active !== undefined)
  6263. data.forEach(function(series,i) {
  6264. series.disabled = !state.active[i];
  6265. });
  6266. }
  6267. };
  6268. var allDisabled = function(data) {
  6269. return data.every(function(series) {
  6270. return series.disabled;
  6271. });
  6272. }
  6273. function chart(selection) {
  6274. selection.each(function(data) {
  6275. var container = d3.select(this),
  6276. that = this;
  6277. nv.utils.initSVG(container);
  6278. var availableWidth = nv.utils.availableWidth(width, container, margin),
  6279. availableHeight1 = nv.utils.availableHeight(height, container, margin)
  6280. - (focusEnable ? focusHeight : 0),
  6281. availableHeight2 = focusHeight - margin2.top - margin2.bottom;
  6282. chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
  6283. chart.container = this;
  6284. state
  6285. .setter(stateSetter(data), chart.update)
  6286. .getter(stateGetter(data))
  6287. .update();
  6288. // DEPRECATED set state.disableddisabled
  6289. state.disabled = data.map(function(d) { return !!d.disabled });
  6290. if (!defaultState) {
  6291. var key;
  6292. defaultState = {};
  6293. for (key in state) {
  6294. if (state[key] instanceof Array)
  6295. defaultState[key] = state[key].slice(0);
  6296. else
  6297. defaultState[key] = state[key];
  6298. }
  6299. }
  6300. // Display No Data message if there's nothing to show.
  6301. if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
  6302. nv.utils.noData(chart, container)
  6303. return chart;
  6304. } else {
  6305. container.selectAll('.nv-noData').remove();
  6306. }
  6307. // Setup Scales
  6308. var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
  6309. var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
  6310. if (dataBars.length && !switchYAxisOrder) {
  6311. x = bars.xScale();
  6312. } else {
  6313. x = lines.xScale();
  6314. }
  6315. x2 = x2Axis.scale();
  6316. // select the scales and series based on the position of the yAxis
  6317. y1 = switchYAxisOrder ? lines.yScale() : bars.yScale();
  6318. y2 = switchYAxisOrder ? bars.yScale() : lines.yScale();
  6319. y3 = switchYAxisOrder ? lines2.yScale() : bars2.yScale();
  6320. y4 = switchYAxisOrder ? bars2.yScale() : lines2.yScale();
  6321. var series1 = data
  6322. .filter(function(d) { return !d.disabled && (switchYAxisOrder ? !d.bar : d.bar) })
  6323. .map(function(d) {
  6324. return d.values.map(function(d,i) {
  6325. return { x: getX(d,i), y: getY(d,i) }
  6326. })
  6327. });
  6328. var series2 = data
  6329. .filter(function(d) { return !d.disabled && (switchYAxisOrder ? d.bar : !d.bar) })
  6330. .map(function(d) {
  6331. return d.values.map(function(d,i) {
  6332. return { x: getX(d,i), y: getY(d,i) }
  6333. })
  6334. });
  6335. x.range([0, availableWidth]);
  6336. x2 .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
  6337. .range([0, availableWidth]);
  6338. // Setup containers and skeleton of chart
  6339. var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
  6340. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
  6341. var g = wrap.select('g');
  6342. gEnter.append('g').attr('class', 'nv-legendWrap');
  6343. // this is the main chart
  6344. var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
  6345. focusEnter.append('g').attr('class', 'nv-x nv-axis');
  6346. focusEnter.append('g').attr('class', 'nv-y1 nv-axis');
  6347. focusEnter.append('g').attr('class', 'nv-y2 nv-axis');
  6348. focusEnter.append('g').attr('class', 'nv-barsWrap');
  6349. focusEnter.append('g').attr('class', 'nv-linesWrap');
  6350. // context chart is where you can focus in
  6351. var contextEnter = gEnter.append('g').attr('class', 'nv-context');
  6352. contextEnter.append('g').attr('class', 'nv-x nv-axis');
  6353. contextEnter.append('g').attr('class', 'nv-y1 nv-axis');
  6354. contextEnter.append('g').attr('class', 'nv-y2 nv-axis');
  6355. contextEnter.append('g').attr('class', 'nv-barsWrap');
  6356. contextEnter.append('g').attr('class', 'nv-linesWrap');
  6357. contextEnter.append('g').attr('class', 'nv-brushBackground');
  6358. contextEnter.append('g').attr('class', 'nv-x nv-brush');
  6359. //============================================================
  6360. // Legend
  6361. //------------------------------------------------------------
  6362. if (!showLegend) {
  6363. g.select('.nv-legendWrap').selectAll('*').remove();
  6364. } else {
  6365. var legendWidth = legend.align() ? availableWidth / 2 : availableWidth;
  6366. var legendXPosition = legend.align() ? legendWidth : 0;
  6367. legend.width(legendWidth);
  6368. g.select('.nv-legendWrap')
  6369. .datum(data.map(function(series) {
  6370. series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
  6371. if(switchYAxisOrder) {
  6372. series.key = series.originalKey + (series.bar ? legendRightAxisHint : legendLeftAxisHint);
  6373. } else {
  6374. series.key = series.originalKey + (series.bar ? legendLeftAxisHint : legendRightAxisHint);
  6375. }
  6376. return series;
  6377. }))
  6378. .call(legend);
  6379. if (legend.height() > margin.top) {
  6380. margin.top = legend.height();
  6381. // FIXME: shouldn't this be "- (focusEnabled ? focusHeight : 0)"?
  6382. availableHeight1 = nv.utils.availableHeight(height, container, margin) - focusHeight;
  6383. }
  6384. g.select('.nv-legendWrap')
  6385. .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')');
  6386. }
  6387. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  6388. //============================================================
  6389. // Context chart (focus chart) components
  6390. //------------------------------------------------------------
  6391. // hide or show the focus context chart
  6392. g.select('.nv-context').style('display', focusEnable ? 'initial' : 'none');
  6393. bars2
  6394. .width(availableWidth)
  6395. .height(availableHeight2)
  6396. .color(data.map(function (d, i) {
  6397. return d.color || color(d, i);
  6398. }).filter(function (d, i) {
  6399. return !data[i].disabled && data[i].bar
  6400. }));
  6401. lines2
  6402. .width(availableWidth)
  6403. .height(availableHeight2)
  6404. .color(data.map(function (d, i) {
  6405. return d.color || color(d, i);
  6406. }).filter(function (d, i) {
  6407. return !data[i].disabled && !data[i].bar
  6408. }));
  6409. var bars2Wrap = g.select('.nv-context .nv-barsWrap')
  6410. .datum(dataBars.length ? dataBars : [
  6411. {values: []}
  6412. ]);
  6413. var lines2Wrap = g.select('.nv-context .nv-linesWrap')
  6414. .datum(allDisabled(dataLines) ?
  6415. [{values: []}] :
  6416. dataLines.filter(function(dataLine) {
  6417. return !dataLine.disabled;
  6418. }));
  6419. g.select('.nv-context')
  6420. .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')');
  6421. bars2Wrap.transition().call(bars2);
  6422. lines2Wrap.transition().call(lines2);
  6423. // context (focus chart) axis controls
  6424. if (focusShowAxisX) {
  6425. x2Axis
  6426. ._ticks( nv.utils.calcTicksX(availableWidth / 100, data))
  6427. .tickSize(-availableHeight2, 0);
  6428. g.select('.nv-context .nv-x.nv-axis')
  6429. .attr('transform', 'translate(0,' + y3.range()[0] + ')');
  6430. g.select('.nv-context .nv-x.nv-axis').transition()
  6431. .call(x2Axis);
  6432. }
  6433. if (focusShowAxisY) {
  6434. y3Axis
  6435. .scale(y3)
  6436. ._ticks( availableHeight2 / 36 )
  6437. .tickSize( -availableWidth, 0);
  6438. y4Axis
  6439. .scale(y4)
  6440. ._ticks( availableHeight2 / 36 )
  6441. .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
  6442. g.select('.nv-context .nv-y3.nv-axis')
  6443. .style('opacity', dataBars.length ? 1 : 0)
  6444. .attr('transform', 'translate(0,' + x2.range()[0] + ')');
  6445. g.select('.nv-context .nv-y2.nv-axis')
  6446. .style('opacity', dataLines.length ? 1 : 0)
  6447. .attr('transform', 'translate(' + x2.range()[1] + ',0)');
  6448. g.select('.nv-context .nv-y1.nv-axis').transition()
  6449. .call(y3Axis);
  6450. g.select('.nv-context .nv-y2.nv-axis').transition()
  6451. .call(y4Axis);
  6452. }
  6453. // Setup Brush
  6454. brush.x(x2).on('brush', onBrush);
  6455. if (brushExtent) brush.extent(brushExtent);
  6456. var brushBG = g.select('.nv-brushBackground').selectAll('g')
  6457. .data([brushExtent || brush.extent()]);
  6458. var brushBGenter = brushBG.enter()
  6459. .append('g');
  6460. brushBGenter.append('rect')
  6461. .attr('class', 'left')
  6462. .attr('x', 0)
  6463. .attr('y', 0)
  6464. .attr('height', availableHeight2);
  6465. brushBGenter.append('rect')
  6466. .attr('class', 'right')
  6467. .attr('x', 0)
  6468. .attr('y', 0)
  6469. .attr('height', availableHeight2);
  6470. var gBrush = g.select('.nv-x.nv-brush')
  6471. .call(brush);
  6472. gBrush.selectAll('rect')
  6473. //.attr('y', -5)
  6474. .attr('height', availableHeight2);
  6475. gBrush.selectAll('.resize').append('path').attr('d', resizePath);
  6476. //============================================================
  6477. // Event Handling/Dispatching (in chart's scope)
  6478. //------------------------------------------------------------
  6479. legend.dispatch.on('stateChange', function(newState) {
  6480. for (var key in newState)
  6481. state[key] = newState[key];
  6482. dispatch.stateChange(state);
  6483. chart.update();
  6484. });
  6485. // Update chart from a state object passed to event handler
  6486. dispatch.on('changeState', function(e) {
  6487. if (typeof e.disabled !== 'undefined') {
  6488. data.forEach(function(series,i) {
  6489. series.disabled = e.disabled[i];
  6490. });
  6491. state.disabled = e.disabled;
  6492. }
  6493. chart.update();
  6494. });
  6495. //============================================================
  6496. // Functions
  6497. //------------------------------------------------------------
  6498. // Taken from crossfilter (http://square.github.com/crossfilter/)
  6499. function resizePath(d) {
  6500. var e = +(d == 'e'),
  6501. x = e ? 1 : -1,
  6502. y = availableHeight2 / 3;
  6503. return 'M' + (.5 * x) + ',' + y
  6504. + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
  6505. + 'V' + (2 * y - 6)
  6506. + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
  6507. + 'Z'
  6508. + 'M' + (2.5 * x) + ',' + (y + 8)
  6509. + 'V' + (2 * y - 8)
  6510. + 'M' + (4.5 * x) + ',' + (y + 8)
  6511. + 'V' + (2 * y - 8);
  6512. }
  6513. function updateBrushBG() {
  6514. if (!brush.empty()) brush.extent(brushExtent);
  6515. brushBG
  6516. .data([brush.empty() ? x2.domain() : brushExtent])
  6517. .each(function(d,i) {
  6518. var leftWidth = x2(d[0]) - x2.range()[0],
  6519. rightWidth = x2.range()[1] - x2(d[1]);
  6520. d3.select(this).select('.left')
  6521. .attr('width', leftWidth < 0 ? 0 : leftWidth);
  6522. d3.select(this).select('.right')
  6523. .attr('x', x2(d[1]))
  6524. .attr('width', rightWidth < 0 ? 0 : rightWidth);
  6525. });
  6526. }
  6527. function onBrush() {
  6528. brushExtent = brush.empty() ? null : brush.extent();
  6529. extent = brush.empty() ? x2.domain() : brush.extent();
  6530. dispatch.brush({extent: extent, brush: brush});
  6531. updateBrushBG();
  6532. // Prepare Main (Focus) Bars and Lines
  6533. bars
  6534. .width(availableWidth)
  6535. .height(availableHeight1)
  6536. .color(data.map(function(d,i) {
  6537. return d.color || color(d, i);
  6538. }).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
  6539. lines
  6540. .width(availableWidth)
  6541. .height(availableHeight1)
  6542. .color(data.map(function(d,i) {
  6543. return d.color || color(d, i);
  6544. }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
  6545. var focusBarsWrap = g.select('.nv-focus .nv-barsWrap')
  6546. .datum(!dataBars.length ? [{values:[]}] :
  6547. dataBars
  6548. .map(function(d,i) {
  6549. return {
  6550. key: d.key,
  6551. values: d.values.filter(function(d,i) {
  6552. return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1];
  6553. })
  6554. }
  6555. })
  6556. );
  6557. var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
  6558. .datum(allDisabled(dataLines) ? [{values:[]}] :
  6559. dataLines
  6560. .filter(function(dataLine) { return !dataLine.disabled; })
  6561. .map(function(d,i) {
  6562. return {
  6563. area: d.area,
  6564. fillOpacity: d.fillOpacity,
  6565. strokeWidth: d.strokeWidth,
  6566. key: d.key,
  6567. values: d.values.filter(function(d,i) {
  6568. return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
  6569. })
  6570. }
  6571. })
  6572. );
  6573. // Update Main (Focus) X Axis
  6574. if (dataBars.length && !switchYAxisOrder) {
  6575. x = bars.xScale();
  6576. } else {
  6577. x = lines.xScale();
  6578. }
  6579. xAxis
  6580. .scale(x)
  6581. ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
  6582. .tickSize(-availableHeight1, 0);
  6583. xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]);
  6584. g.select('.nv-x.nv-axis').transition().duration(transitionDuration)
  6585. .call(xAxis);
  6586. // Update Main (Focus) Bars and Lines
  6587. focusBarsWrap.transition().duration(transitionDuration).call(bars);
  6588. focusLinesWrap.transition().duration(transitionDuration).call(lines);
  6589. // Setup and Update Main (Focus) Y Axes
  6590. g.select('.nv-focus .nv-x.nv-axis')
  6591. .attr('transform', 'translate(0,' + y1.range()[0] + ')');
  6592. y1Axis
  6593. .scale(y1)
  6594. ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
  6595. .tickSize(-availableWidth, 0);
  6596. y2Axis
  6597. .scale(y2)
  6598. ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) );
  6599. // Show the y2 rules only if y1 has none
  6600. if(!switchYAxisOrder) {
  6601. y2Axis.tickSize(dataBars.length ? 0 : -availableWidth, 0);
  6602. } else {
  6603. y2Axis.tickSize(dataLines.length ? 0 : -availableWidth, 0);
  6604. }
  6605. // Calculate opacity of the axis
  6606. var barsOpacity = dataBars.length ? 1 : 0;
  6607. var linesOpacity = dataLines.length && !allDisabled(dataLines) ? 1 : 0;
  6608. var y1Opacity = switchYAxisOrder ? linesOpacity : barsOpacity;
  6609. var y2Opacity = switchYAxisOrder ? barsOpacity : linesOpacity;
  6610. g.select('.nv-focus .nv-y1.nv-axis')
  6611. .style('opacity', y1Opacity);
  6612. g.select('.nv-focus .nv-y2.nv-axis')
  6613. .style('opacity', y2Opacity)
  6614. .attr('transform', 'translate(' + x.range()[1] + ',0)');
  6615. g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration)
  6616. .call(y1Axis);
  6617. g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration)
  6618. .call(y2Axis);
  6619. }
  6620. onBrush();
  6621. });
  6622. return chart;
  6623. }
  6624. //============================================================
  6625. // Event Handling/Dispatching (out of chart's scope)
  6626. //------------------------------------------------------------
  6627. lines.dispatch.on('elementMouseover.tooltip', function(evt) {
  6628. tooltip
  6629. .duration(100)
  6630. .valueFormatter(function(d, i) {
  6631. return getLinesAxis().main.tickFormat()(d, i);
  6632. })
  6633. .data(evt)
  6634. .hidden(false);
  6635. });
  6636. lines.dispatch.on('elementMouseout.tooltip', function(evt) {
  6637. tooltip.hidden(true)
  6638. });
  6639. bars.dispatch.on('elementMouseover.tooltip', function(evt) {
  6640. evt.value = chart.x()(evt.data);
  6641. evt['series'] = {
  6642. value: chart.y()(evt.data),
  6643. color: evt.color
  6644. };
  6645. tooltip
  6646. .duration(0)
  6647. .valueFormatter(function(d, i) {
  6648. return getBarsAxis().main.tickFormat()(d, i);
  6649. })
  6650. .data(evt)
  6651. .hidden(false);
  6652. });
  6653. bars.dispatch.on('elementMouseout.tooltip', function(evt) {
  6654. tooltip.hidden(true);
  6655. });
  6656. bars.dispatch.on('elementMousemove.tooltip', function(evt) {
  6657. tooltip();
  6658. });
  6659. //============================================================
  6660. //============================================================
  6661. // Expose Public Variables
  6662. //------------------------------------------------------------
  6663. // expose chart's sub-components
  6664. chart.dispatch = dispatch;
  6665. chart.legend = legend;
  6666. chart.lines = lines;
  6667. chart.lines2 = lines2;
  6668. chart.bars = bars;
  6669. chart.bars2 = bars2;
  6670. chart.xAxis = xAxis;
  6671. chart.x2Axis = x2Axis;
  6672. chart.y1Axis = y1Axis;
  6673. chart.y2Axis = y2Axis;
  6674. chart.y3Axis = y3Axis;
  6675. chart.y4Axis = y4Axis;
  6676. chart.tooltip = tooltip;
  6677. chart.options = nv.utils.optionsFunc.bind(chart);
  6678. chart._options = Object.create({}, {
  6679. // simple options, just get/set the necessary values
  6680. width: {get: function(){return width;}, set: function(_){width=_;}},
  6681. height: {get: function(){return height;}, set: function(_){height=_;}},
  6682. showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
  6683. brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
  6684. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  6685. focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}},
  6686. focusHeight: {get: function(){return focusHeight;}, set: function(_){focusHeight=_;}},
  6687. focusShowAxisX: {get: function(){return focusShowAxisX;}, set: function(_){focusShowAxisX=_;}},
  6688. focusShowAxisY: {get: function(){return focusShowAxisY;}, set: function(_){focusShowAxisY=_;}},
  6689. legendLeftAxisHint: {get: function(){return legendLeftAxisHint;}, set: function(_){legendLeftAxisHint=_;}},
  6690. legendRightAxisHint: {get: function(){return legendRightAxisHint;}, set: function(_){legendRightAxisHint=_;}},
  6691. // options that require extra logic in the setter
  6692. margin: {get: function(){return margin;}, set: function(_){
  6693. margin.top = _.top !== undefined ? _.top : margin.top;
  6694. margin.right = _.right !== undefined ? _.right : margin.right;
  6695. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  6696. margin.left = _.left !== undefined ? _.left : margin.left;
  6697. }},
  6698. focusMargin: {get: function(){return margin2;}, set: function(_){
  6699. margin2.top = _.top !== undefined ? _.top : margin2.top;
  6700. margin2.right = _.right !== undefined ? _.right : margin2.right;
  6701. margin2.bottom = _.bottom !== undefined ? _.bottom : margin2.bottom;
  6702. margin2.left = _.left !== undefined ? _.left : margin2.left;
  6703. }},
  6704. duration: {get: function(){return transitionDuration;}, set: function(_){
  6705. transitionDuration = _;
  6706. }},
  6707. color: {get: function(){return color;}, set: function(_){
  6708. color = nv.utils.getColor(_);
  6709. legend.color(color);
  6710. }},
  6711. x: {get: function(){return getX;}, set: function(_){
  6712. getX = _;
  6713. lines.x(_);
  6714. lines2.x(_);
  6715. bars.x(_);
  6716. bars2.x(_);
  6717. }},
  6718. y: {get: function(){return getY;}, set: function(_){
  6719. getY = _;
  6720. lines.y(_);
  6721. lines2.y(_);
  6722. bars.y(_);
  6723. bars2.y(_);
  6724. }},
  6725. switchYAxisOrder: {get: function(){return switchYAxisOrder;}, set: function(_){
  6726. // Switch the tick format for the yAxis
  6727. if(switchYAxisOrder !== _) {
  6728. var y1 = y1Axis;
  6729. y1Axis = y2Axis;
  6730. y2Axis = y1;
  6731. var y3 = y3Axis;
  6732. y3Axis = y4Axis;
  6733. y4Axis = y3;
  6734. }
  6735. switchYAxisOrder=_;
  6736. y1Axis.orient('left');
  6737. y2Axis.orient('right');
  6738. y3Axis.orient('left');
  6739. y4Axis.orient('right');
  6740. }}
  6741. });
  6742. nv.utils.inheritOptions(chart, lines);
  6743. nv.utils.initOptions(chart);
  6744. return chart;
  6745. };
  6746. nv.models.multiBar = function() {
  6747. "use strict";
  6748. //============================================================
  6749. // Public Variables with Default Settings
  6750. //------------------------------------------------------------
  6751. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  6752. , width = 960
  6753. , height = 500
  6754. , x = d3.scale.ordinal()
  6755. , y = d3.scale.linear()
  6756. , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
  6757. , container = null
  6758. , getX = function(d) { return d.x }
  6759. , getY = function(d) { return d.y }
  6760. , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
  6761. , clipEdge = true
  6762. , stacked = false
  6763. , stackOffset = 'zero' // options include 'silhouette', 'wiggle', 'expand', 'zero', or a custom function
  6764. , color = nv.utils.defaultColor()
  6765. , hideable = false
  6766. , barColor = null // adding the ability to set the color for each rather than the whole group
  6767. , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
  6768. , duration = 500
  6769. , xDomain
  6770. , yDomain
  6771. , xRange
  6772. , yRange
  6773. , groupSpacing = 0.1
  6774. , fillOpacity = 0.75
  6775. , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
  6776. ;
  6777. //============================================================
  6778. // Private Variables
  6779. //------------------------------------------------------------
  6780. var x0, y0 //used to store previous scales
  6781. , renderWatch = nv.utils.renderWatch(dispatch, duration)
  6782. ;
  6783. var last_datalength = 0;
  6784. function chart(selection) {
  6785. renderWatch.reset();
  6786. selection.each(function(data) {
  6787. var availableWidth = width - margin.left - margin.right,
  6788. availableHeight = height - margin.top - margin.bottom;
  6789. container = d3.select(this);
  6790. nv.utils.initSVG(container);
  6791. var nonStackableCount = 0;
  6792. // This function defines the requirements for render complete
  6793. var endFn = function(d, i) {
  6794. if (d.series === data.length - 1 && i === data[0].values.length - 1)
  6795. return true;
  6796. return false;
  6797. };
  6798. if(hideable && data.length) hideable = [{
  6799. values: data[0].values.map(function(d) {
  6800. return {
  6801. x: d.x,
  6802. y: 0,
  6803. series: d.series,
  6804. size: 0.01
  6805. };}
  6806. )}];
  6807. if (stacked) {
  6808. var parsed = d3.layout.stack()
  6809. .offset(stackOffset)
  6810. .values(function(d){ return d.values })
  6811. .y(getY)
  6812. (!data.length && hideable ? hideable : data);
  6813. parsed.forEach(function(series, i){
  6814. // if series is non-stackable, use un-parsed data
  6815. if (series.nonStackable) {
  6816. data[i].nonStackableSeries = nonStackableCount++;
  6817. parsed[i] = data[i];
  6818. } else {
  6819. // don't stack this seires on top of the nonStackable seriees
  6820. if (i > 0 && parsed[i - 1].nonStackable){
  6821. parsed[i].values.map(function(d,j){
  6822. d.y0 -= parsed[i - 1].values[j].y;
  6823. d.y1 = d.y0 + d.y;
  6824. });
  6825. }
  6826. }
  6827. });
  6828. data = parsed;
  6829. }
  6830. //add series index and key to each data point for reference
  6831. data.forEach(function(series, i) {
  6832. series.values.forEach(function(point) {
  6833. point.series = i;
  6834. point.key = series.key;
  6835. });
  6836. });
  6837. // HACK for negative value stacking
  6838. if (stacked && data.length > 0) {
  6839. data[0].values.map(function(d,i) {
  6840. var posBase = 0, negBase = 0;
  6841. data.map(function(d, idx) {
  6842. if (!data[idx].nonStackable) {
  6843. var f = d.values[i]
  6844. f.size = Math.abs(f.y);
  6845. if (f.y<0) {
  6846. f.y1 = negBase;
  6847. negBase = negBase - f.size;
  6848. } else
  6849. {
  6850. f.y1 = f.size + posBase;
  6851. posBase = posBase + f.size;
  6852. }
  6853. }
  6854. });
  6855. });
  6856. }
  6857. // Setup Scales
  6858. // remap and flatten the data for use in calculating the scales' domains
  6859. var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
  6860. data.map(function(d, idx) {
  6861. return d.values.map(function(d,i) {
  6862. return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1, idx:idx }
  6863. })
  6864. });
  6865. x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
  6866. .rangeBands(xRange || [0, availableWidth], groupSpacing);
  6867. y.domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) {
  6868. var domain = d.y;
  6869. // increase the domain range if this series is stackable
  6870. if (stacked && !data[d.idx].nonStackable) {
  6871. if (d.y > 0){
  6872. domain = d.y1
  6873. } else {
  6874. domain = d.y1 + d.y
  6875. }
  6876. }
  6877. return domain;
  6878. }).concat(forceY)))
  6879. .range(yRange || [availableHeight, 0]);
  6880. // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
  6881. if (x.domain()[0] === x.domain()[1])
  6882. x.domain()[0] ?
  6883. x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
  6884. : x.domain([-1,1]);
  6885. if (y.domain()[0] === y.domain()[1])
  6886. y.domain()[0] ?
  6887. y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
  6888. : y.domain([-1,1]);
  6889. x0 = x0 || x;
  6890. y0 = y0 || y;
  6891. // Setup containers and skeleton of chart
  6892. var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]);
  6893. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar');
  6894. var defsEnter = wrapEnter.append('defs');
  6895. var gEnter = wrapEnter.append('g');
  6896. var g = wrap.select('g');
  6897. gEnter.append('g').attr('class', 'nv-groups');
  6898. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  6899. defsEnter.append('clipPath')
  6900. .attr('id', 'nv-edge-clip-' + id)
  6901. .append('rect');
  6902. wrap.select('#nv-edge-clip-' + id + ' rect')
  6903. .attr('width', availableWidth)
  6904. .attr('height', availableHeight);
  6905. g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
  6906. var groups = wrap.select('.nv-groups').selectAll('.nv-group')
  6907. .data(function(d) { return d }, function(d,i) { return i });
  6908. groups.enter().append('g')
  6909. .style('stroke-opacity', 1e-6)
  6910. .style('fill-opacity', 1e-6);
  6911. var exitTransition = renderWatch
  6912. .transition(groups.exit().selectAll('rect.nv-bar'), 'multibarExit', Math.min(100, duration))
  6913. .attr('y', function(d, i, j) {
  6914. var yVal = y0(0) || 0;
  6915. if (stacked) {
  6916. if (data[d.series] && !data[d.series].nonStackable) {
  6917. yVal = y0(d.y0);
  6918. }
  6919. }
  6920. return yVal;
  6921. })
  6922. .attr('height', 0)
  6923. .remove();
  6924. if (exitTransition.delay)
  6925. exitTransition.delay(function(d,i) {
  6926. var delay = i * (duration / (last_datalength + 1)) - i;
  6927. return delay;
  6928. });
  6929. groups
  6930. .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
  6931. .classed('hover', function(d) { return d.hover })
  6932. .style('fill', function(d,i){ return color(d, i) })
  6933. .style('stroke', function(d,i){ return color(d, i) });
  6934. groups
  6935. .style('stroke-opacity', 1)
  6936. .style('fill-opacity', fillOpacity);
  6937. var bars = groups.selectAll('rect.nv-bar')
  6938. .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values });
  6939. bars.exit().remove();
  6940. var barsEnter = bars.enter().append('rect')
  6941. .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
  6942. .attr('x', function(d,i,j) {
  6943. return stacked && !data[j].nonStackable ? 0 : (j * x.rangeBand() / data.length )
  6944. })
  6945. .attr('y', function(d,i,j) { return y0(stacked && !data[j].nonStackable ? d.y0 : 0) || 0 })
  6946. .attr('height', 0)
  6947. .attr('width', function(d,i,j) { return x.rangeBand() / (stacked && !data[j].nonStackable ? 1 : data.length) })
  6948. .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
  6949. ;
  6950. bars
  6951. .style('fill', function(d,i,j){ return color(d, j, i); })
  6952. .style('stroke', function(d,i,j){ return color(d, j, i); })
  6953. .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
  6954. d3.select(this).classed('hover', true);
  6955. dispatch.elementMouseover({
  6956. data: d,
  6957. index: i,
  6958. color: d3.select(this).style("fill")
  6959. });
  6960. })
  6961. .on('mouseout', function(d,i) {
  6962. d3.select(this).classed('hover', false);
  6963. dispatch.elementMouseout({
  6964. data: d,
  6965. index: i,
  6966. color: d3.select(this).style("fill")
  6967. });
  6968. })
  6969. .on('mousemove', function(d,i) {
  6970. dispatch.elementMousemove({
  6971. data: d,
  6972. index: i,
  6973. color: d3.select(this).style("fill")
  6974. });
  6975. })
  6976. .on('click', function(d,i) {
  6977. var element = this;
  6978. dispatch.elementClick({
  6979. data: d,
  6980. index: i,
  6981. color: d3.select(this).style("fill"),
  6982. event: d3.event,
  6983. element: element
  6984. });
  6985. d3.event.stopPropagation();
  6986. })
  6987. .on('dblclick', function(d,i) {
  6988. dispatch.elementDblClick({
  6989. data: d,
  6990. index: i,
  6991. color: d3.select(this).style("fill")
  6992. });
  6993. d3.event.stopPropagation();
  6994. });
  6995. bars
  6996. .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
  6997. .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
  6998. if (barColor) {
  6999. if (!disabled) disabled = data.map(function() { return true });
  7000. bars
  7001. .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(); })
  7002. .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(); });
  7003. }
  7004. var barSelection =
  7005. bars.watchTransition(renderWatch, 'multibar', Math.min(250, duration))
  7006. .delay(function(d,i) {
  7007. return i * duration / data[0].values.length;
  7008. });
  7009. if (stacked){
  7010. barSelection
  7011. .attr('y', function(d,i,j) {
  7012. var yVal = 0;
  7013. // if stackable, stack it on top of the previous series
  7014. if (!data[j].nonStackable) {
  7015. yVal = y(d.y1);
  7016. } else {
  7017. if (getY(d,i) < 0){
  7018. yVal = y(0);
  7019. } else {
  7020. if (y(0) - y(getY(d,i)) < -1){
  7021. yVal = y(0) - 1;
  7022. } else {
  7023. yVal = y(getY(d, i)) || 0;
  7024. }
  7025. }
  7026. }
  7027. return yVal;
  7028. })
  7029. .attr('height', function(d,i,j) {
  7030. if (!data[j].nonStackable) {
  7031. return Math.max(Math.abs(y(d.y+d.y0) - y(d.y0)), 0);
  7032. } else {
  7033. return Math.max(Math.abs(y(getY(d,i)) - y(0)), 0) || 0;
  7034. }
  7035. })
  7036. .attr('x', function(d,i,j) {
  7037. var width = 0;
  7038. if (data[j].nonStackable) {
  7039. width = d.series * x.rangeBand() / data.length;
  7040. if (data.length !== nonStackableCount){
  7041. width = data[j].nonStackableSeries * x.rangeBand()/(nonStackableCount*2);
  7042. }
  7043. }
  7044. return width;
  7045. })
  7046. .attr('width', function(d,i,j){
  7047. if (!data[j].nonStackable) {
  7048. return x.rangeBand();
  7049. } else {
  7050. // if all series are nonStacable, take the full width
  7051. var width = (x.rangeBand() / nonStackableCount);
  7052. // otherwise, nonStackable graph will be only taking the half-width
  7053. // of the x rangeBand
  7054. if (data.length !== nonStackableCount) {
  7055. width = x.rangeBand()/(nonStackableCount*2);
  7056. }
  7057. return width;
  7058. }
  7059. });
  7060. }
  7061. else {
  7062. barSelection
  7063. .attr('x', function(d,i) {
  7064. return d.series * x.rangeBand() / data.length;
  7065. })
  7066. .attr('width', x.rangeBand() / data.length)
  7067. .attr('y', function(d,i) {
  7068. return getY(d,i) < 0 ?
  7069. y(0) :
  7070. y(0) - y(getY(d,i)) < 1 ?
  7071. y(0) - 1 :
  7072. y(getY(d,i)) || 0;
  7073. })
  7074. .attr('height', function(d,i) {
  7075. return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
  7076. });
  7077. }
  7078. //store old scales for use in transitions on update
  7079. x0 = x.copy();
  7080. y0 = y.copy();
  7081. // keep track of the last data value length for transition calculations
  7082. if (data[0] && data[0].values) {
  7083. last_datalength = data[0].values.length;
  7084. }
  7085. });
  7086. renderWatch.renderEnd('multibar immediate');
  7087. return chart;
  7088. }
  7089. //============================================================
  7090. // Expose Public Variables
  7091. //------------------------------------------------------------
  7092. chart.dispatch = dispatch;
  7093. chart.options = nv.utils.optionsFunc.bind(chart);
  7094. chart._options = Object.create({}, {
  7095. // simple options, just get/set the necessary values
  7096. width: {get: function(){return width;}, set: function(_){width=_;}},
  7097. height: {get: function(){return height;}, set: function(_){height=_;}},
  7098. x: {get: function(){return getX;}, set: function(_){getX=_;}},
  7099. y: {get: function(){return getY;}, set: function(_){getY=_;}},
  7100. xScale: {get: function(){return x;}, set: function(_){x=_;}},
  7101. yScale: {get: function(){return y;}, set: function(_){y=_;}},
  7102. xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
  7103. yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
  7104. xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
  7105. yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
  7106. forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
  7107. stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}},
  7108. stackOffset: {get: function(){return stackOffset;}, set: function(_){stackOffset=_;}},
  7109. clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
  7110. disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}},
  7111. id: {get: function(){return id;}, set: function(_){id=_;}},
  7112. hideable: {get: function(){return hideable;}, set: function(_){hideable=_;}},
  7113. groupSpacing:{get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}},
  7114. fillOpacity: {get: function(){return fillOpacity;}, set: function(_){fillOpacity=_;}},
  7115. // options that require extra logic in the setter
  7116. margin: {get: function(){return margin;}, set: function(_){
  7117. margin.top = _.top !== undefined ? _.top : margin.top;
  7118. margin.right = _.right !== undefined ? _.right : margin.right;
  7119. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  7120. margin.left = _.left !== undefined ? _.left : margin.left;
  7121. }},
  7122. duration: {get: function(){return duration;}, set: function(_){
  7123. duration = _;
  7124. renderWatch.reset(duration);
  7125. }},
  7126. color: {get: function(){return color;}, set: function(_){
  7127. color = nv.utils.getColor(_);
  7128. }},
  7129. barColor: {get: function(){return barColor;}, set: function(_){
  7130. barColor = _ ? nv.utils.getColor(_) : null;
  7131. }}
  7132. });
  7133. nv.utils.initOptions(chart);
  7134. return chart;
  7135. };
  7136. nv.models.multiBarChart = function() {
  7137. "use strict";
  7138. //============================================================
  7139. // Public Variables with Default Settings
  7140. //------------------------------------------------------------
  7141. var multibar = nv.models.multiBar()
  7142. , xAxis = nv.models.axis()
  7143. , yAxis = nv.models.axis()
  7144. , interactiveLayer = nv.interactiveGuideline()
  7145. , legend = nv.models.legend()
  7146. , controls = nv.models.legend()
  7147. , tooltip = nv.models.tooltip()
  7148. ;
  7149. var margin = {top: 30, right: 20, bottom: 50, left: 60}
  7150. , width = null
  7151. , height = null
  7152. , color = nv.utils.defaultColor()
  7153. , showControls = true
  7154. , controlLabels = {}
  7155. , showLegend = true
  7156. , showXAxis = true
  7157. , showYAxis = true
  7158. , rightAlignYAxis = false
  7159. , reduceXTicks = true // if false a tick will show for every data point
  7160. , staggerLabels = false
  7161. , wrapLabels = false
  7162. , rotateLabels = 0
  7163. , x //can be accessed via chart.xScale()
  7164. , y //can be accessed via chart.yScale()
  7165. , state = nv.utils.state()
  7166. , defaultState = null
  7167. , noData = null
  7168. , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
  7169. , controlWidth = function() { return showControls ? 180 : 0 }
  7170. , duration = 250
  7171. , useInteractiveGuideline = false
  7172. ;
  7173. state.stacked = false // DEPRECATED Maintained for backward compatibility
  7174. multibar.stacked(false);
  7175. xAxis
  7176. .orient('bottom')
  7177. .tickPadding(7)
  7178. .showMaxMin(false)
  7179. .tickFormat(function(d) { return d })
  7180. ;
  7181. yAxis
  7182. .orient((rightAlignYAxis) ? 'right' : 'left')
  7183. .tickFormat(d3.format(',.1f'))
  7184. ;
  7185. tooltip
  7186. .duration(0)
  7187. .valueFormatter(function(d, i) {
  7188. return yAxis.tickFormat()(d, i);
  7189. })
  7190. .headerFormatter(function(d, i) {
  7191. return xAxis.tickFormat()(d, i);
  7192. });
  7193. controls.updateState(false);
  7194. //============================================================
  7195. // Private Variables
  7196. //------------------------------------------------------------
  7197. var renderWatch = nv.utils.renderWatch(dispatch);
  7198. var stacked = false;
  7199. var stateGetter = function(data) {
  7200. return function(){
  7201. return {
  7202. active: data.map(function(d) { return !d.disabled }),
  7203. stacked: stacked
  7204. };
  7205. }
  7206. };
  7207. var stateSetter = function(data) {
  7208. return function(state) {
  7209. if (state.stacked !== undefined)
  7210. stacked = state.stacked;
  7211. if (state.active !== undefined)
  7212. data.forEach(function(series,i) {
  7213. series.disabled = !state.active[i];
  7214. });
  7215. }
  7216. };
  7217. function chart(selection) {
  7218. renderWatch.reset();
  7219. renderWatch.models(multibar);
  7220. if (showXAxis) renderWatch.models(xAxis);
  7221. if (showYAxis) renderWatch.models(yAxis);
  7222. selection.each(function(data) {
  7223. var container = d3.select(this),
  7224. that = this;
  7225. nv.utils.initSVG(container);
  7226. var availableWidth = nv.utils.availableWidth(width, container, margin),
  7227. availableHeight = nv.utils.availableHeight(height, container, margin);
  7228. chart.update = function() {
  7229. if (duration === 0)
  7230. container.call(chart);
  7231. else
  7232. container.transition()
  7233. .duration(duration)
  7234. .call(chart);
  7235. };
  7236. chart.container = this;
  7237. state
  7238. .setter(stateSetter(data), chart.update)
  7239. .getter(stateGetter(data))
  7240. .update();
  7241. // DEPRECATED set state.disableddisabled
  7242. state.disabled = data.map(function(d) { return !!d.disabled });
  7243. if (!defaultState) {
  7244. var key;
  7245. defaultState = {};
  7246. for (key in state) {
  7247. if (state[key] instanceof Array)
  7248. defaultState[key] = state[key].slice(0);
  7249. else
  7250. defaultState[key] = state[key];
  7251. }
  7252. }
  7253. // Display noData message if there's nothing to show.
  7254. if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
  7255. nv.utils.noData(chart, container)
  7256. return chart;
  7257. } else {
  7258. container.selectAll('.nv-noData').remove();
  7259. }
  7260. // Setup Scales
  7261. x = multibar.xScale();
  7262. y = multibar.yScale();
  7263. // Setup containers and skeleton of chart
  7264. var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]);
  7265. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g');
  7266. var g = wrap.select('g');
  7267. gEnter.append('g').attr('class', 'nv-x nv-axis');
  7268. gEnter.append('g').attr('class', 'nv-y nv-axis');
  7269. gEnter.append('g').attr('class', 'nv-barsWrap');
  7270. gEnter.append('g').attr('class', 'nv-legendWrap');
  7271. gEnter.append('g').attr('class', 'nv-controlsWrap');
  7272. gEnter.append('g').attr('class', 'nv-interactive');
  7273. // Legend
  7274. if (!showLegend) {
  7275. g.select('.nv-legendWrap').selectAll('*').remove();
  7276. } else {
  7277. legend.width(availableWidth - controlWidth());
  7278. g.select('.nv-legendWrap')
  7279. .datum(data)
  7280. .call(legend);
  7281. if (legend.height() > margin.top) {
  7282. margin.top = legend.height();
  7283. availableHeight = nv.utils.availableHeight(height, container, margin);
  7284. }
  7285. g.select('.nv-legendWrap')
  7286. .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
  7287. }
  7288. // Controls
  7289. if (!showControls) {
  7290. g.select('.nv-controlsWrap').selectAll('*').remove();
  7291. } else {
  7292. var controlsData = [
  7293. { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() },
  7294. { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() }
  7295. ];
  7296. controls.width(controlWidth()).color(['#444', '#444', '#444']);
  7297. g.select('.nv-controlsWrap')
  7298. .datum(controlsData)
  7299. .attr('transform', 'translate(0,' + (-margin.top) +')')
  7300. .call(controls);
  7301. }
  7302. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  7303. if (rightAlignYAxis) {
  7304. g.select(".nv-y.nv-axis")
  7305. .attr("transform", "translate(" + availableWidth + ",0)");
  7306. }
  7307. // Main Chart Component(s)
  7308. multibar
  7309. .disabled(data.map(function(series) { return series.disabled }))
  7310. .width(availableWidth)
  7311. .height(availableHeight)
  7312. .color(data.map(function(d,i) {
  7313. return d.color || color(d, i);
  7314. }).filter(function(d,i) { return !data[i].disabled }));
  7315. var barsWrap = g.select('.nv-barsWrap')
  7316. .datum(data.filter(function(d) { return !d.disabled }));
  7317. barsWrap.call(multibar);
  7318. // Setup Axes
  7319. if (showXAxis) {
  7320. xAxis
  7321. .scale(x)
  7322. ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
  7323. .tickSize(-availableHeight, 0);
  7324. g.select('.nv-x.nv-axis')
  7325. .attr('transform', 'translate(0,' + y.range()[0] + ')');
  7326. g.select('.nv-x.nv-axis')
  7327. .call(xAxis);
  7328. var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g');
  7329. xTicks
  7330. .selectAll('line, text')
  7331. .style('opacity', 1)
  7332. if (staggerLabels) {
  7333. var getTranslate = function(x,y) {
  7334. return "translate(" + x + "," + y + ")";
  7335. };
  7336. var staggerUp = 5, staggerDown = 17; //pixels to stagger by
  7337. // Issue #140
  7338. xTicks
  7339. .selectAll("text")
  7340. .attr('transform', function(d,i,j) {
  7341. return getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown));
  7342. });
  7343. var totalInBetweenTicks = d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;
  7344. g.selectAll(".nv-x.nv-axis .nv-axisMaxMin text")
  7345. .attr("transform", function(d,i) {
  7346. return getTranslate(0, (i === 0 || totalInBetweenTicks % 2 !== 0) ? staggerDown : staggerUp);
  7347. });
  7348. }
  7349. if (wrapLabels) {
  7350. g.selectAll('.tick text')
  7351. .call(nv.utils.wrapTicks, chart.xAxis.rangeBand())
  7352. }
  7353. if (reduceXTicks)
  7354. xTicks
  7355. .filter(function(d,i) {
  7356. return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0;
  7357. })
  7358. .selectAll('text, line')
  7359. .style('opacity', 0);
  7360. if(rotateLabels)
  7361. xTicks
  7362. .selectAll('.tick text')
  7363. .attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
  7364. .style('text-anchor', rotateLabels > 0 ? 'start' : 'end');
  7365. g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text')
  7366. .style('opacity', 1);
  7367. }
  7368. if (showYAxis) {
  7369. yAxis
  7370. .scale(y)
  7371. ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
  7372. .tickSize( -availableWidth, 0);
  7373. g.select('.nv-y.nv-axis')
  7374. .call(yAxis);
  7375. }
  7376. //Set up interactive layer
  7377. if (useInteractiveGuideline) {
  7378. interactiveLayer
  7379. .width(availableWidth)
  7380. .height(availableHeight)
  7381. .margin({left:margin.left, top:margin.top})
  7382. .svgContainer(container)
  7383. .xScale(x);
  7384. wrap.select(".nv-interactive").call(interactiveLayer);
  7385. }
  7386. //============================================================
  7387. // Event Handling/Dispatching (in chart's scope)
  7388. //------------------------------------------------------------
  7389. legend.dispatch.on('stateChange', function(newState) {
  7390. for (var key in newState)
  7391. state[key] = newState[key];
  7392. dispatch.stateChange(state);
  7393. chart.update();
  7394. });
  7395. controls.dispatch.on('legendClick', function(d,i) {
  7396. if (!d.disabled) return;
  7397. controlsData = controlsData.map(function(s) {
  7398. s.disabled = true;
  7399. return s;
  7400. });
  7401. d.disabled = false;
  7402. switch (d.key) {
  7403. case 'Grouped':
  7404. case controlLabels.grouped:
  7405. multibar.stacked(false);
  7406. break;
  7407. case 'Stacked':
  7408. case controlLabels.stacked:
  7409. multibar.stacked(true);
  7410. break;
  7411. }
  7412. state.stacked = multibar.stacked();
  7413. dispatch.stateChange(state);
  7414. chart.update();
  7415. });
  7416. // Update chart from a state object passed to event handler
  7417. dispatch.on('changeState', function(e) {
  7418. if (typeof e.disabled !== 'undefined') {
  7419. data.forEach(function(series,i) {
  7420. series.disabled = e.disabled[i];
  7421. });
  7422. state.disabled = e.disabled;
  7423. }
  7424. if (typeof e.stacked !== 'undefined') {
  7425. multibar.stacked(e.stacked);
  7426. state.stacked = e.stacked;
  7427. stacked = e.stacked;
  7428. }
  7429. chart.update();
  7430. });
  7431. if (useInteractiveGuideline) {
  7432. interactiveLayer.dispatch.on('elementMousemove', function(e) {
  7433. if (e.pointXValue == undefined) return;
  7434. var singlePoint, pointIndex, pointXLocation, xValue, allData = [];
  7435. data
  7436. .filter(function(series, i) {
  7437. series.seriesIndex = i;
  7438. return !series.disabled;
  7439. })
  7440. .forEach(function(series,i) {
  7441. pointIndex = x.domain().indexOf(e.pointXValue)
  7442. var point = series.values[pointIndex];
  7443. if (point === undefined) return;
  7444. xValue = point.x;
  7445. if (singlePoint === undefined) singlePoint = point;
  7446. if (pointXLocation === undefined) pointXLocation = e.mouseX
  7447. allData.push({
  7448. key: series.key,
  7449. value: chart.y()(point, pointIndex),
  7450. color: color(series,series.seriesIndex),
  7451. data: series.values[pointIndex]
  7452. });
  7453. });
  7454. interactiveLayer.tooltip
  7455. .data({
  7456. value: xValue,
  7457. index: pointIndex,
  7458. series: allData
  7459. })();
  7460. interactiveLayer.renderGuideLine(pointXLocation);
  7461. });
  7462. interactiveLayer.dispatch.on("elementMouseout",function(e) {
  7463. interactiveLayer.tooltip.hidden(true);
  7464. });
  7465. }
  7466. else {
  7467. multibar.dispatch.on('elementMouseover.tooltip', function(evt) {
  7468. evt.value = chart.x()(evt.data);
  7469. evt['series'] = {
  7470. key: evt.data.key,
  7471. value: chart.y()(evt.data),
  7472. color: evt.color
  7473. };
  7474. tooltip.data(evt).hidden(false);
  7475. });
  7476. multibar.dispatch.on('elementMouseout.tooltip', function(evt) {
  7477. tooltip.hidden(true);
  7478. });
  7479. multibar.dispatch.on('elementMousemove.tooltip', function(evt) {
  7480. tooltip();
  7481. });
  7482. }
  7483. });
  7484. renderWatch.renderEnd('multibarchart immediate');
  7485. return chart;
  7486. }
  7487. //============================================================
  7488. // Expose Public Variables
  7489. //------------------------------------------------------------
  7490. // expose chart's sub-components
  7491. chart.dispatch = dispatch;
  7492. chart.multibar = multibar;
  7493. chart.legend = legend;
  7494. chart.controls = controls;
  7495. chart.xAxis = xAxis;
  7496. chart.yAxis = yAxis;
  7497. chart.state = state;
  7498. chart.tooltip = tooltip;
  7499. chart.interactiveLayer = interactiveLayer;
  7500. chart.options = nv.utils.optionsFunc.bind(chart);
  7501. chart._options = Object.create({}, {
  7502. // simple options, just get/set the necessary values
  7503. width: {get: function(){return width;}, set: function(_){width=_;}},
  7504. height: {get: function(){return height;}, set: function(_){height=_;}},
  7505. showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
  7506. showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
  7507. controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
  7508. showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
  7509. showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
  7510. defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
  7511. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  7512. reduceXTicks: {get: function(){return reduceXTicks;}, set: function(_){reduceXTicks=_;}},
  7513. rotateLabels: {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
  7514. staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
  7515. wrapLabels: {get: function(){return wrapLabels;}, set: function(_){wrapLabels=!!_;}},
  7516. // options that require extra logic in the setter
  7517. margin: {get: function(){return margin;}, set: function(_){
  7518. margin.top = _.top !== undefined ? _.top : margin.top;
  7519. margin.right = _.right !== undefined ? _.right : margin.right;
  7520. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  7521. margin.left = _.left !== undefined ? _.left : margin.left;
  7522. }},
  7523. duration: {get: function(){return duration;}, set: function(_){
  7524. duration = _;
  7525. multibar.duration(duration);
  7526. xAxis.duration(duration);
  7527. yAxis.duration(duration);
  7528. renderWatch.reset(duration);
  7529. }},
  7530. color: {get: function(){return color;}, set: function(_){
  7531. color = nv.utils.getColor(_);
  7532. legend.color(color);
  7533. }},
  7534. rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
  7535. rightAlignYAxis = _;
  7536. yAxis.orient( rightAlignYAxis ? 'right' : 'left');
  7537. }},
  7538. useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
  7539. useInteractiveGuideline = _;
  7540. }},
  7541. barColor: {get: function(){return multibar.barColor;}, set: function(_){
  7542. multibar.barColor(_);
  7543. legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();})
  7544. }}
  7545. });
  7546. nv.utils.inheritOptions(chart, multibar);
  7547. nv.utils.initOptions(chart);
  7548. return chart;
  7549. };
  7550. nv.models.multiBarHorizontal = function() {
  7551. "use strict";
  7552. //============================================================
  7553. // Public Variables with Default Settings
  7554. //------------------------------------------------------------
  7555. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  7556. , width = 960
  7557. , height = 500
  7558. , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
  7559. , container = null
  7560. , x = d3.scale.ordinal()
  7561. , y = d3.scale.linear()
  7562. , getX = function(d) { return d.x }
  7563. , getY = function(d) { return d.y }
  7564. , getYerr = function(d) { return d.yErr }
  7565. , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
  7566. , color = nv.utils.defaultColor()
  7567. , barColor = null // adding the ability to set the color for each rather than the whole group
  7568. , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
  7569. , stacked = false
  7570. , showValues = false
  7571. , showBarLabels = false
  7572. , valuePadding = 60
  7573. , groupSpacing = 0.1
  7574. , fillOpacity = 0.75
  7575. , valueFormat = d3.format(',.2f')
  7576. , delay = 1200
  7577. , xDomain
  7578. , yDomain
  7579. , xRange
  7580. , yRange
  7581. , duration = 250
  7582. , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
  7583. ;
  7584. //============================================================
  7585. // Private Variables
  7586. //------------------------------------------------------------
  7587. var x0, y0; //used to store previous scales
  7588. var renderWatch = nv.utils.renderWatch(dispatch, duration);
  7589. function chart(selection) {
  7590. renderWatch.reset();
  7591. selection.each(function(data) {
  7592. var availableWidth = width - margin.left - margin.right,
  7593. availableHeight = height - margin.top - margin.bottom;
  7594. container = d3.select(this);
  7595. nv.utils.initSVG(container);
  7596. if (stacked)
  7597. data = d3.layout.stack()
  7598. .offset('zero')
  7599. .values(function(d){ return d.values })
  7600. .y(getY)
  7601. (data);
  7602. //add series index and key to each data point for reference
  7603. data.forEach(function(series, i) {
  7604. series.values.forEach(function(point) {
  7605. point.series = i;
  7606. point.key = series.key;
  7607. });
  7608. });
  7609. // HACK for negative value stacking
  7610. if (stacked)
  7611. data[0].values.map(function(d,i) {
  7612. var posBase = 0, negBase = 0;
  7613. data.map(function(d) {
  7614. var f = d.values[i]
  7615. f.size = Math.abs(f.y);
  7616. if (f.y<0) {
  7617. f.y1 = negBase - f.size;
  7618. negBase = negBase - f.size;
  7619. } else
  7620. {
  7621. f.y1 = posBase;
  7622. posBase = posBase + f.size;
  7623. }
  7624. });
  7625. });
  7626. // Setup Scales
  7627. // remap and flatten the data for use in calculating the scales' domains
  7628. var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
  7629. data.map(function(d) {
  7630. return d.values.map(function(d,i) {
  7631. return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
  7632. })
  7633. });
  7634. x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
  7635. .rangeBands(xRange || [0, availableHeight], groupSpacing);
  7636. 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)))
  7637. if (showValues && !stacked)
  7638. y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]);
  7639. else
  7640. y.range(yRange || [0, availableWidth]);
  7641. x0 = x0 || x;
  7642. y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]);
  7643. // Setup containers and skeleton of chart
  7644. var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]);
  7645. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal');
  7646. var defsEnter = wrapEnter.append('defs');
  7647. var gEnter = wrapEnter.append('g');
  7648. var g = wrap.select('g');
  7649. gEnter.append('g').attr('class', 'nv-groups');
  7650. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  7651. var groups = wrap.select('.nv-groups').selectAll('.nv-group')
  7652. .data(function(d) { return d }, function(d,i) { return i });
  7653. groups.enter().append('g')
  7654. .style('stroke-opacity', 1e-6)
  7655. .style('fill-opacity', 1e-6);
  7656. groups.exit().watchTransition(renderWatch, 'multibarhorizontal: exit groups')
  7657. .style('stroke-opacity', 1e-6)
  7658. .style('fill-opacity', 1e-6)
  7659. .remove();
  7660. groups
  7661. .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
  7662. .classed('hover', function(d) { return d.hover })
  7663. .style('fill', function(d,i){ return color(d, i) })
  7664. .style('stroke', function(d,i){ return color(d, i) });
  7665. groups.watchTransition(renderWatch, 'multibarhorizontal: groups')
  7666. .style('stroke-opacity', 1)
  7667. .style('fill-opacity', fillOpacity);
  7668. var bars = groups.selectAll('g.nv-bar')
  7669. .data(function(d) { return d.values });
  7670. bars.exit().remove();
  7671. var barsEnter = bars.enter().append('g')
  7672. .attr('transform', function(d,i,j) {
  7673. return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')'
  7674. });
  7675. barsEnter.append('rect')
  7676. .attr('width', 0)
  7677. .attr('height', x.rangeBand() / (stacked ? 1 : data.length) )
  7678. bars
  7679. .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
  7680. d3.select(this).classed('hover', true);
  7681. dispatch.elementMouseover({
  7682. data: d,
  7683. index: i,
  7684. color: d3.select(this).style("fill")
  7685. });
  7686. })
  7687. .on('mouseout', function(d,i) {
  7688. d3.select(this).classed('hover', false);
  7689. dispatch.elementMouseout({
  7690. data: d,
  7691. index: i,
  7692. color: d3.select(this).style("fill")
  7693. });
  7694. })
  7695. .on('mouseout', function(d,i) {
  7696. dispatch.elementMouseout({
  7697. data: d,
  7698. index: i,
  7699. color: d3.select(this).style("fill")
  7700. });
  7701. })
  7702. .on('mousemove', function(d,i) {
  7703. dispatch.elementMousemove({
  7704. data: d,
  7705. index: i,
  7706. color: d3.select(this).style("fill")
  7707. });
  7708. })
  7709. .on('click', function(d,i) {
  7710. var element = this;
  7711. dispatch.elementClick({
  7712. data: d,
  7713. index: i,
  7714. color: d3.select(this).style("fill"),
  7715. event: d3.event,
  7716. element: element
  7717. });
  7718. d3.event.stopPropagation();
  7719. })
  7720. .on('dblclick', function(d,i) {
  7721. dispatch.elementDblClick({
  7722. data: d,
  7723. index: i,
  7724. color: d3.select(this).style("fill")
  7725. });
  7726. d3.event.stopPropagation();
  7727. });
  7728. if (getYerr(data[0],0)) {
  7729. barsEnter.append('polyline');
  7730. bars.select('polyline')
  7731. .attr('fill', 'none')
  7732. .attr('points', function(d,i) {
  7733. var xerr = getYerr(d,i)
  7734. , mid = 0.8 * x.rangeBand() / ((stacked ? 1 : data.length) * 2);
  7735. xerr = xerr.length ? xerr : [-Math.abs(xerr), Math.abs(xerr)];
  7736. xerr = xerr.map(function(e) { return y(e) - y(0); });
  7737. var a = [[xerr[0],-mid], [xerr[0],mid], [xerr[0],0], [xerr[1],0], [xerr[1],-mid], [xerr[1],mid]];
  7738. return a.map(function (path) { return path.join(',') }).join(' ');
  7739. })
  7740. .attr('transform', function(d,i) {
  7741. var mid = x.rangeBand() / ((stacked ? 1 : data.length) * 2);
  7742. return 'translate(' + (getY(d,i) < 0 ? 0 : y(getY(d,i)) - y(0)) + ', ' + mid + ')'
  7743. });
  7744. }
  7745. barsEnter.append('text');
  7746. if (showValues && !stacked) {
  7747. bars.select('text')
  7748. .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' })
  7749. .attr('y', x.rangeBand() / (data.length * 2))
  7750. .attr('dy', '.32em')
  7751. .text(function(d,i) {
  7752. var t = valueFormat(getY(d,i))
  7753. , yerr = getYerr(d,i);
  7754. if (yerr === undefined)
  7755. return t;
  7756. if (!yerr.length)
  7757. return t + '±' + valueFormat(Math.abs(yerr));
  7758. return t + '+' + valueFormat(Math.abs(yerr[1])) + '-' + valueFormat(Math.abs(yerr[0]));
  7759. });
  7760. bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
  7761. .select('text')
  7762. .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 })
  7763. } else {
  7764. bars.selectAll('text').text('');
  7765. }
  7766. if (showBarLabels && !stacked) {
  7767. barsEnter.append('text').classed('nv-bar-label',true);
  7768. bars.select('text.nv-bar-label')
  7769. .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'start' : 'end' })
  7770. .attr('y', x.rangeBand() / (data.length * 2))
  7771. .attr('dy', '.32em')
  7772. .text(function(d,i) { return getX(d,i) });
  7773. bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
  7774. .select('text.nv-bar-label')
  7775. .attr('x', function(d,i) { return getY(d,i) < 0 ? y(0) - y(getY(d,i)) + 4 : -4 });
  7776. }
  7777. else {
  7778. bars.selectAll('text.nv-bar-label').text('');
  7779. }
  7780. bars
  7781. .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
  7782. if (barColor) {
  7783. if (!disabled) disabled = data.map(function() { return true });
  7784. bars
  7785. .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(); })
  7786. .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(); });
  7787. }
  7788. if (stacked)
  7789. bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
  7790. .attr('transform', function(d,i) {
  7791. return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')'
  7792. })
  7793. .select('rect')
  7794. .attr('width', function(d,i) {
  7795. return Math.abs(y(getY(d,i) + d.y0) - y(d.y0)) || 0
  7796. })
  7797. .attr('height', x.rangeBand() );
  7798. else
  7799. bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
  7800. .attr('transform', function(d,i) {
  7801. //TODO: stacked must be all positive or all negative, not both?
  7802. return 'translate(' +
  7803. (getY(d,i) < 0 ? y(getY(d,i)) : y(0))
  7804. + ',' +
  7805. (d.series * x.rangeBand() / data.length
  7806. +
  7807. x(getX(d,i)) )
  7808. + ')'
  7809. })
  7810. .select('rect')
  7811. .attr('height', x.rangeBand() / data.length )
  7812. .attr('width', function(d,i) {
  7813. return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0
  7814. });
  7815. //store old scales for use in transitions on update
  7816. x0 = x.copy();
  7817. y0 = y.copy();
  7818. });
  7819. renderWatch.renderEnd('multibarHorizontal immediate');
  7820. return chart;
  7821. }
  7822. //============================================================
  7823. // Expose Public Variables
  7824. //------------------------------------------------------------
  7825. chart.dispatch = dispatch;
  7826. chart.options = nv.utils.optionsFunc.bind(chart);
  7827. chart._options = Object.create({}, {
  7828. // simple options, just get/set the necessary values
  7829. width: {get: function(){return width;}, set: function(_){width=_;}},
  7830. height: {get: function(){return height;}, set: function(_){height=_;}},
  7831. x: {get: function(){return getX;}, set: function(_){getX=_;}},
  7832. y: {get: function(){return getY;}, set: function(_){getY=_;}},
  7833. yErr: {get: function(){return getYerr;}, set: function(_){getYerr=_;}},
  7834. xScale: {get: function(){return x;}, set: function(_){x=_;}},
  7835. yScale: {get: function(){return y;}, set: function(_){y=_;}},
  7836. xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
  7837. yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
  7838. xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
  7839. yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
  7840. forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
  7841. stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}},
  7842. showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}},
  7843. // this shows the group name, seems pointless?
  7844. //showBarLabels: {get: function(){return showBarLabels;}, set: function(_){showBarLabels=_;}},
  7845. disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}},
  7846. id: {get: function(){return id;}, set: function(_){id=_;}},
  7847. valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
  7848. valuePadding: {get: function(){return valuePadding;}, set: function(_){valuePadding=_;}},
  7849. groupSpacing: {get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}},
  7850. fillOpacity: {get: function(){return fillOpacity;}, set: function(_){fillOpacity=_;}},
  7851. // options that require extra logic in the setter
  7852. margin: {get: function(){return margin;}, set: function(_){
  7853. margin.top = _.top !== undefined ? _.top : margin.top;
  7854. margin.right = _.right !== undefined ? _.right : margin.right;
  7855. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  7856. margin.left = _.left !== undefined ? _.left : margin.left;
  7857. }},
  7858. duration: {get: function(){return duration;}, set: function(_){
  7859. duration = _;
  7860. renderWatch.reset(duration);
  7861. }},
  7862. color: {get: function(){return color;}, set: function(_){
  7863. color = nv.utils.getColor(_);
  7864. }},
  7865. barColor: {get: function(){return barColor;}, set: function(_){
  7866. barColor = _ ? nv.utils.getColor(_) : null;
  7867. }}
  7868. });
  7869. nv.utils.initOptions(chart);
  7870. return chart;
  7871. };
  7872. nv.models.multiBarHorizontalChart = function() {
  7873. "use strict";
  7874. //============================================================
  7875. // Public Variables with Default Settings
  7876. //------------------------------------------------------------
  7877. var multibar = nv.models.multiBarHorizontal()
  7878. , xAxis = nv.models.axis()
  7879. , yAxis = nv.models.axis()
  7880. , legend = nv.models.legend().height(30)
  7881. , controls = nv.models.legend().height(30)
  7882. , tooltip = nv.models.tooltip()
  7883. ;
  7884. var margin = {top: 30, right: 20, bottom: 50, left: 60}
  7885. , width = null
  7886. , height = null
  7887. , color = nv.utils.defaultColor()
  7888. , showControls = true
  7889. , controlLabels = {}
  7890. , showLegend = true
  7891. , showXAxis = true
  7892. , showYAxis = true
  7893. , stacked = false
  7894. , x //can be accessed via chart.xScale()
  7895. , y //can be accessed via chart.yScale()
  7896. , state = nv.utils.state()
  7897. , defaultState = null
  7898. , noData = null
  7899. , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd')
  7900. , controlWidth = function() { return showControls ? 180 : 0 }
  7901. , duration = 250
  7902. ;
  7903. state.stacked = false; // DEPRECATED Maintained for backward compatibility
  7904. multibar.stacked(stacked);
  7905. xAxis
  7906. .orient('left')
  7907. .tickPadding(5)
  7908. .showMaxMin(false)
  7909. .tickFormat(function(d) { return d })
  7910. ;
  7911. yAxis
  7912. .orient('bottom')
  7913. .tickFormat(d3.format(',.1f'))
  7914. ;
  7915. tooltip
  7916. .duration(0)
  7917. .valueFormatter(function(d, i) {
  7918. return yAxis.tickFormat()(d, i);
  7919. })
  7920. .headerFormatter(function(d, i) {
  7921. return xAxis.tickFormat()(d, i);
  7922. });
  7923. controls.updateState(false);
  7924. //============================================================
  7925. // Private Variables
  7926. //------------------------------------------------------------
  7927. var stateGetter = function(data) {
  7928. return function(){
  7929. return {
  7930. active: data.map(function(d) { return !d.disabled }),
  7931. stacked: stacked
  7932. };
  7933. }
  7934. };
  7935. var stateSetter = function(data) {
  7936. return function(state) {
  7937. if (state.stacked !== undefined)
  7938. stacked = state.stacked;
  7939. if (state.active !== undefined)
  7940. data.forEach(function(series,i) {
  7941. series.disabled = !state.active[i];
  7942. });
  7943. }
  7944. };
  7945. var renderWatch = nv.utils.renderWatch(dispatch, duration);
  7946. function chart(selection) {
  7947. renderWatch.reset();
  7948. renderWatch.models(multibar);
  7949. if (showXAxis) renderWatch.models(xAxis);
  7950. if (showYAxis) renderWatch.models(yAxis);
  7951. selection.each(function(data) {
  7952. var container = d3.select(this),
  7953. that = this;
  7954. nv.utils.initSVG(container);
  7955. var availableWidth = nv.utils.availableWidth(width, container, margin),
  7956. availableHeight = nv.utils.availableHeight(height, container, margin);
  7957. chart.update = function() { container.transition().duration(duration).call(chart) };
  7958. chart.container = this;
  7959. stacked = multibar.stacked();
  7960. state
  7961. .setter(stateSetter(data), chart.update)
  7962. .getter(stateGetter(data))
  7963. .update();
  7964. // DEPRECATED set state.disableddisabled
  7965. state.disabled = data.map(function(d) { return !!d.disabled });
  7966. if (!defaultState) {
  7967. var key;
  7968. defaultState = {};
  7969. for (key in state) {
  7970. if (state[key] instanceof Array)
  7971. defaultState[key] = state[key].slice(0);
  7972. else
  7973. defaultState[key] = state[key];
  7974. }
  7975. }
  7976. // Display No Data message if there's nothing to show.
  7977. if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
  7978. nv.utils.noData(chart, container)
  7979. return chart;
  7980. } else {
  7981. container.selectAll('.nv-noData').remove();
  7982. }
  7983. // Setup Scales
  7984. x = multibar.xScale();
  7985. y = multibar.yScale().clamp(true);
  7986. // Setup containers and skeleton of chart
  7987. var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]);
  7988. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g');
  7989. var g = wrap.select('g');
  7990. gEnter.append('g').attr('class', 'nv-x nv-axis');
  7991. gEnter.append('g').attr('class', 'nv-y nv-axis')
  7992. .append('g').attr('class', 'nv-zeroLine')
  7993. .append('line');
  7994. gEnter.append('g').attr('class', 'nv-barsWrap');
  7995. gEnter.append('g').attr('class', 'nv-legendWrap');
  7996. gEnter.append('g').attr('class', 'nv-controlsWrap');
  7997. // Legend
  7998. if (!showLegend) {
  7999. g.select('.nv-legendWrap').selectAll('*').remove();
  8000. } else {
  8001. legend.width(availableWidth - controlWidth());
  8002. g.select('.nv-legendWrap')
  8003. .datum(data)
  8004. .call(legend);
  8005. if (legend.height() > margin.top) {
  8006. margin.top = legend.height();
  8007. availableHeight = nv.utils.availableHeight(height, container, margin);
  8008. }
  8009. g.select('.nv-legendWrap')
  8010. .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
  8011. }
  8012. // Controls
  8013. if (!showControls) {
  8014. g.select('.nv-controlsWrap').selectAll('*').remove();
  8015. } else {
  8016. var controlsData = [
  8017. { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() },
  8018. { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() }
  8019. ];
  8020. controls.width(controlWidth()).color(['#444', '#444', '#444']);
  8021. g.select('.nv-controlsWrap')
  8022. .datum(controlsData)
  8023. .attr('transform', 'translate(0,' + (-margin.top) +')')
  8024. .call(controls);
  8025. }
  8026. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  8027. // Main Chart Component(s)
  8028. multibar
  8029. .disabled(data.map(function(series) { return series.disabled }))
  8030. .width(availableWidth)
  8031. .height(availableHeight)
  8032. .color(data.map(function(d,i) {
  8033. return d.color || color(d, i);
  8034. }).filter(function(d,i) { return !data[i].disabled }));
  8035. var barsWrap = g.select('.nv-barsWrap')
  8036. .datum(data.filter(function(d) { return !d.disabled }));
  8037. barsWrap.transition().call(multibar);
  8038. // Setup Axes
  8039. if (showXAxis) {
  8040. xAxis
  8041. .scale(x)
  8042. ._ticks( nv.utils.calcTicksY(availableHeight/24, data) )
  8043. .tickSize(-availableWidth, 0);
  8044. g.select('.nv-x.nv-axis').call(xAxis);
  8045. var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
  8046. xTicks
  8047. .selectAll('line, text');
  8048. }
  8049. if (showYAxis) {
  8050. yAxis
  8051. .scale(y)
  8052. ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
  8053. .tickSize( -availableHeight, 0);
  8054. g.select('.nv-y.nv-axis')
  8055. .attr('transform', 'translate(0,' + availableHeight + ')');
  8056. g.select('.nv-y.nv-axis').call(yAxis);
  8057. }
  8058. // Zero line
  8059. g.select(".nv-zeroLine line")
  8060. .attr("x1", y(0))
  8061. .attr("x2", y(0))
  8062. .attr("y1", 0)
  8063. .attr("y2", -availableHeight)
  8064. ;
  8065. //============================================================
  8066. // Event Handling/Dispatching (in chart's scope)
  8067. //------------------------------------------------------------
  8068. legend.dispatch.on('stateChange', function(newState) {
  8069. for (var key in newState)
  8070. state[key] = newState[key];
  8071. dispatch.stateChange(state);
  8072. chart.update();
  8073. });
  8074. controls.dispatch.on('legendClick', function(d,i) {
  8075. if (!d.disabled) return;
  8076. controlsData = controlsData.map(function(s) {
  8077. s.disabled = true;
  8078. return s;
  8079. });
  8080. d.disabled = false;
  8081. switch (d.key) {
  8082. case 'Grouped':
  8083. case controlLabels.grouped:
  8084. multibar.stacked(false);
  8085. break;
  8086. case 'Stacked':
  8087. case controlLabels.stacked:
  8088. multibar.stacked(true);
  8089. break;
  8090. }
  8091. state.stacked = multibar.stacked();
  8092. dispatch.stateChange(state);
  8093. stacked = multibar.stacked();
  8094. chart.update();
  8095. });
  8096. // Update chart from a state object passed to event handler
  8097. dispatch.on('changeState', function(e) {
  8098. if (typeof e.disabled !== 'undefined') {
  8099. data.forEach(function(series,i) {
  8100. series.disabled = e.disabled[i];
  8101. });
  8102. state.disabled = e.disabled;
  8103. }
  8104. if (typeof e.stacked !== 'undefined') {
  8105. multibar.stacked(e.stacked);
  8106. state.stacked = e.stacked;
  8107. stacked = e.stacked;
  8108. }
  8109. chart.update();
  8110. });
  8111. });
  8112. renderWatch.renderEnd('multibar horizontal chart immediate');
  8113. return chart;
  8114. }
  8115. //============================================================
  8116. // Event Handling/Dispatching (out of chart's scope)
  8117. //------------------------------------------------------------
  8118. multibar.dispatch.on('elementMouseover.tooltip', function(evt) {
  8119. evt.value = chart.x()(evt.data);
  8120. evt['series'] = {
  8121. key: evt.data.key,
  8122. value: chart.y()(evt.data),
  8123. color: evt.color
  8124. };
  8125. tooltip.data(evt).hidden(false);
  8126. });
  8127. multibar.dispatch.on('elementMouseout.tooltip', function(evt) {
  8128. tooltip.hidden(true);
  8129. });
  8130. multibar.dispatch.on('elementMousemove.tooltip', function(evt) {
  8131. tooltip();
  8132. });
  8133. //============================================================
  8134. // Expose Public Variables
  8135. //------------------------------------------------------------
  8136. // expose chart's sub-components
  8137. chart.dispatch = dispatch;
  8138. chart.multibar = multibar;
  8139. chart.legend = legend;
  8140. chart.controls = controls;
  8141. chart.xAxis = xAxis;
  8142. chart.yAxis = yAxis;
  8143. chart.state = state;
  8144. chart.tooltip = tooltip;
  8145. chart.options = nv.utils.optionsFunc.bind(chart);
  8146. chart._options = Object.create({}, {
  8147. // simple options, just get/set the necessary values
  8148. width: {get: function(){return width;}, set: function(_){width=_;}},
  8149. height: {get: function(){return height;}, set: function(_){height=_;}},
  8150. showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
  8151. showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
  8152. controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
  8153. showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
  8154. showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
  8155. defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
  8156. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  8157. // options that require extra logic in the setter
  8158. margin: {get: function(){return margin;}, set: function(_){
  8159. margin.top = _.top !== undefined ? _.top : margin.top;
  8160. margin.right = _.right !== undefined ? _.right : margin.right;
  8161. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  8162. margin.left = _.left !== undefined ? _.left : margin.left;
  8163. }},
  8164. duration: {get: function(){return duration;}, set: function(_){
  8165. duration = _;
  8166. renderWatch.reset(duration);
  8167. multibar.duration(duration);
  8168. xAxis.duration(duration);
  8169. yAxis.duration(duration);
  8170. }},
  8171. color: {get: function(){return color;}, set: function(_){
  8172. color = nv.utils.getColor(_);
  8173. legend.color(color);
  8174. }},
  8175. barColor: {get: function(){return multibar.barColor;}, set: function(_){
  8176. multibar.barColor(_);
  8177. legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();})
  8178. }}
  8179. });
  8180. nv.utils.inheritOptions(chart, multibar);
  8181. nv.utils.initOptions(chart);
  8182. return chart;
  8183. };
  8184. nv.models.multiChart = function() {
  8185. "use strict";
  8186. //============================================================
  8187. // Public Variables with Default Settings
  8188. //------------------------------------------------------------
  8189. var margin = {top: 30, right: 20, bottom: 50, left: 60},
  8190. color = nv.utils.defaultColor(),
  8191. width = null,
  8192. height = null,
  8193. showLegend = true,
  8194. noData = null,
  8195. yDomain1,
  8196. yDomain2,
  8197. getX = function(d) { return d.x },
  8198. getY = function(d) { return d.y},
  8199. interpolate = 'linear',
  8200. useVoronoi = true,
  8201. interactiveLayer = nv.interactiveGuideline(),
  8202. useInteractiveGuideline = false,
  8203. legendRightAxisHint = ' (right axis)'
  8204. ;
  8205. //============================================================
  8206. // Private Variables
  8207. //------------------------------------------------------------
  8208. var x = d3.scale.linear(),
  8209. yScale1 = d3.scale.linear(),
  8210. yScale2 = d3.scale.linear(),
  8211. lines1 = nv.models.line().yScale(yScale1),
  8212. lines2 = nv.models.line().yScale(yScale2),
  8213. scatters1 = nv.models.scatter().yScale(yScale1),
  8214. scatters2 = nv.models.scatter().yScale(yScale2),
  8215. bars1 = nv.models.multiBar().stacked(false).yScale(yScale1),
  8216. bars2 = nv.models.multiBar().stacked(false).yScale(yScale2),
  8217. stack1 = nv.models.stackedArea().yScale(yScale1),
  8218. stack2 = nv.models.stackedArea().yScale(yScale2),
  8219. xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5),
  8220. yAxis1 = nv.models.axis().scale(yScale1).orient('left'),
  8221. yAxis2 = nv.models.axis().scale(yScale2).orient('right'),
  8222. legend = nv.models.legend().height(30),
  8223. tooltip = nv.models.tooltip(),
  8224. dispatch = d3.dispatch();
  8225. var charts = [lines1, lines2, scatters1, scatters2, bars1, bars2, stack1, stack2];
  8226. function chart(selection) {
  8227. selection.each(function(data) {
  8228. var container = d3.select(this),
  8229. that = this;
  8230. nv.utils.initSVG(container);
  8231. chart.update = function() { container.transition().call(chart); };
  8232. chart.container = this;
  8233. var availableWidth = nv.utils.availableWidth(width, container, margin),
  8234. availableHeight = nv.utils.availableHeight(height, container, margin);
  8235. var dataLines1 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 1});
  8236. var dataLines2 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 2});
  8237. var dataScatters1 = data.filter(function(d) {return d.type == 'scatter' && d.yAxis == 1});
  8238. var dataScatters2 = data.filter(function(d) {return d.type == 'scatter' && d.yAxis == 2});
  8239. var dataBars1 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 1});
  8240. var dataBars2 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 2});
  8241. var dataStack1 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 1});
  8242. var dataStack2 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 2});
  8243. // Display noData message if there's nothing to show.
  8244. if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
  8245. nv.utils.noData(chart, container);
  8246. return chart;
  8247. } else {
  8248. container.selectAll('.nv-noData').remove();
  8249. }
  8250. var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1})
  8251. .map(function(d) {
  8252. return d.values.map(function(d,i) {
  8253. return { x: getX(d), y: getY(d) }
  8254. })
  8255. });
  8256. var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2})
  8257. .map(function(d) {
  8258. return d.values.map(function(d,i) {
  8259. return { x: getX(d), y: getY(d) }
  8260. })
  8261. });
  8262. x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x }))
  8263. .range([0, availableWidth]);
  8264. var wrap = container.selectAll('g.wrap.multiChart').data([data]);
  8265. var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g');
  8266. gEnter.append('g').attr('class', 'nv-x nv-axis');
  8267. gEnter.append('g').attr('class', 'nv-y1 nv-axis');
  8268. gEnter.append('g').attr('class', 'nv-y2 nv-axis');
  8269. gEnter.append('g').attr('class', 'stack1Wrap');
  8270. gEnter.append('g').attr('class', 'stack2Wrap');
  8271. gEnter.append('g').attr('class', 'bars1Wrap');
  8272. gEnter.append('g').attr('class', 'bars2Wrap');
  8273. gEnter.append('g').attr('class', 'scatters1Wrap');
  8274. gEnter.append('g').attr('class', 'scatters2Wrap');
  8275. gEnter.append('g').attr('class', 'lines1Wrap');
  8276. gEnter.append('g').attr('class', 'lines2Wrap');
  8277. gEnter.append('g').attr('class', 'legendWrap');
  8278. gEnter.append('g').attr('class', 'nv-interactive');
  8279. var g = wrap.select('g');
  8280. var color_array = data.map(function(d,i) {
  8281. return data[i].color || color(d, i);
  8282. });
  8283. // Legend
  8284. if (!showLegend) {
  8285. g.select('.legendWrap').selectAll('*').remove();
  8286. } else {
  8287. var legendWidth = legend.align() ? availableWidth / 2 : availableWidth;
  8288. var legendXPosition = legend.align() ? legendWidth : 0;
  8289. legend.width(legendWidth);
  8290. legend.color(color_array);
  8291. g.select('.legendWrap')
  8292. .datum(data.map(function(series) {
  8293. series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
  8294. series.key = series.originalKey + (series.yAxis == 1 ? '' : legendRightAxisHint);
  8295. return series;
  8296. }))
  8297. .call(legend);
  8298. if (legend.height() > margin.top) {
  8299. margin.top = legend.height();
  8300. availableHeight = nv.utils.availableHeight(height, container, margin);
  8301. }
  8302. g.select('.legendWrap')
  8303. .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')');
  8304. }
  8305. lines1
  8306. .width(availableWidth)
  8307. .height(availableHeight)
  8308. .interpolate(interpolate)
  8309. .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'}));
  8310. lines2
  8311. .width(availableWidth)
  8312. .height(availableHeight)
  8313. .interpolate(interpolate)
  8314. .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'}));
  8315. scatters1
  8316. .width(availableWidth)
  8317. .height(availableHeight)
  8318. .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'scatter'}));
  8319. scatters2
  8320. .width(availableWidth)
  8321. .height(availableHeight)
  8322. .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'scatter'}));
  8323. bars1
  8324. .width(availableWidth)
  8325. .height(availableHeight)
  8326. .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'}));
  8327. bars2
  8328. .width(availableWidth)
  8329. .height(availableHeight)
  8330. .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'}));
  8331. stack1
  8332. .width(availableWidth)
  8333. .height(availableHeight)
  8334. .interpolate(interpolate)
  8335. .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'}));
  8336. stack2
  8337. .width(availableWidth)
  8338. .height(availableHeight)
  8339. .interpolate(interpolate)
  8340. .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'}));
  8341. g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  8342. var lines1Wrap = g.select('.lines1Wrap')
  8343. .datum(dataLines1.filter(function(d){return !d.disabled}));
  8344. var scatters1Wrap = g.select('.scatters1Wrap')
  8345. .datum(dataScatters1.filter(function(d){return !d.disabled}));
  8346. var bars1Wrap = g.select('.bars1Wrap')
  8347. .datum(dataBars1.filter(function(d){return !d.disabled}));
  8348. var stack1Wrap = g.select('.stack1Wrap')
  8349. .datum(dataStack1.filter(function(d){return !d.disabled}));
  8350. var lines2Wrap = g.select('.lines2Wrap')
  8351. .datum(dataLines2.filter(function(d){return !d.disabled}));
  8352. var scatters2Wrap = g.select('.scatters2Wrap')
  8353. .datum(dataScatters2.filter(function(d){return !d.disabled}));
  8354. var bars2Wrap = g.select('.bars2Wrap')
  8355. .datum(dataBars2.filter(function(d){return !d.disabled}));
  8356. var stack2Wrap = g.select('.stack2Wrap')
  8357. .datum(dataStack2.filter(function(d){return !d.disabled}));
  8358. var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){
  8359. return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
  8360. }).concat([{x:0, y:0}]) : [];
  8361. var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){
  8362. return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
  8363. }).concat([{x:0, y:0}]) : [];
  8364. yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } ))
  8365. .range([0, availableHeight]);
  8366. yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } ))
  8367. .range([0, availableHeight]);
  8368. lines1.yDomain(yScale1.domain());
  8369. scatters1.yDomain(yScale1.domain());
  8370. bars1.yDomain(yScale1.domain());
  8371. stack1.yDomain(yScale1.domain());
  8372. lines2.yDomain(yScale2.domain());
  8373. scatters2.yDomain(yScale2.domain());
  8374. bars2.yDomain(yScale2.domain());
  8375. stack2.yDomain(yScale2.domain());
  8376. if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);}
  8377. if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);}
  8378. if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);}
  8379. if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);}
  8380. if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);}
  8381. if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);}
  8382. if(dataScatters1.length){d3.transition(scatters1Wrap).call(scatters1);}
  8383. if(dataScatters2.length){d3.transition(scatters2Wrap).call(scatters2);}
  8384. xAxis
  8385. ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
  8386. .tickSize(-availableHeight, 0);
  8387. g.select('.nv-x.nv-axis')
  8388. .attr('transform', 'translate(0,' + availableHeight + ')');
  8389. d3.transition(g.select('.nv-x.nv-axis'))
  8390. .call(xAxis);
  8391. yAxis1
  8392. ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
  8393. .tickSize( -availableWidth, 0);
  8394. d3.transition(g.select('.nv-y1.nv-axis'))
  8395. .call(yAxis1);
  8396. yAxis2
  8397. ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
  8398. .tickSize( -availableWidth, 0);
  8399. d3.transition(g.select('.nv-y2.nv-axis'))
  8400. .call(yAxis2);
  8401. g.select('.nv-y1.nv-axis')
  8402. .classed('nv-disabled', series1.length ? false : true)
  8403. .attr('transform', 'translate(' + x.range()[0] + ',0)');
  8404. g.select('.nv-y2.nv-axis')
  8405. .classed('nv-disabled', series2.length ? false : true)
  8406. .attr('transform', 'translate(' + x.range()[1] + ',0)');
  8407. legend.dispatch.on('stateChange', function(newState) {
  8408. chart.update();
  8409. });
  8410. if(useInteractiveGuideline){
  8411. interactiveLayer
  8412. .width(availableWidth)
  8413. .height(availableHeight)
  8414. .margin({left:margin.left, top:margin.top})
  8415. .svgContainer(container)
  8416. .xScale(x);
  8417. wrap.select(".nv-interactive").call(interactiveLayer);
  8418. }
  8419. //============================================================
  8420. // Event Handling/Dispatching
  8421. //------------------------------------------------------------
  8422. function mouseover_line(evt) {
  8423. var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1;
  8424. evt.value = evt.point.x;
  8425. evt.series = {
  8426. value: evt.point.y,
  8427. color: evt.point.color,
  8428. key: evt.series.key
  8429. };
  8430. tooltip
  8431. .duration(0)
  8432. .headerFormatter(function(d, i) {
  8433. return xAxis.tickFormat()(d, i);
  8434. })
  8435. .valueFormatter(function(d, i) {
  8436. return yaxis.tickFormat()(d, i);
  8437. })
  8438. .data(evt)
  8439. .hidden(false);
  8440. }
  8441. function mouseover_scatter(evt) {
  8442. var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1;
  8443. evt.value = evt.point.x;
  8444. evt.series = {
  8445. value: evt.point.y,
  8446. color: evt.point.color,
  8447. key: evt.series.key
  8448. };
  8449. tooltip
  8450. .duration(100)
  8451. .headerFormatter(function(d, i) {
  8452. return xAxis.tickFormat()(d, i);
  8453. })
  8454. .valueFormatter(function(d, i) {
  8455. return yaxis.tickFormat()(d, i);
  8456. })
  8457. .data(evt)
  8458. .hidden(false);
  8459. }
  8460. function mouseover_stack(evt) {
  8461. var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1;
  8462. evt.point['x'] = stack1.x()(evt.point);
  8463. evt.point['y'] = stack1.y()(evt.point);
  8464. tooltip
  8465. .duration(0)
  8466. .headerFormatter(function(d, i) {
  8467. return xAxis.tickFormat()(d, i);
  8468. })
  8469. .valueFormatter(function(d, i) {
  8470. return yaxis.tickFormat()(d, i);
  8471. })
  8472. .data(evt)
  8473. .hidden(false);
  8474. }
  8475. function mouseover_bar(evt) {
  8476. var yaxis = data[evt.data.series].yAxis === 2 ? yAxis2 : yAxis1;
  8477. evt.value = bars1.x()(evt.data);
  8478. evt['series'] = {
  8479. value: bars1.y()(evt.data),
  8480. color: evt.color,
  8481. key: evt.data.key
  8482. };
  8483. tooltip
  8484. .duration(0)
  8485. .headerFormatter(function(d, i) {
  8486. return xAxis.tickFormat()(d, i);
  8487. })
  8488. .valueFormatter(function(d, i) {
  8489. return yaxis.tickFormat()(d, i);
  8490. })
  8491. .data(evt)
  8492. .hidden(false);
  8493. }
  8494. function clearHighlights() {
  8495. for(var i=0, il=charts.length; i < il; i++){
  8496. var chart = charts[i];
  8497. try {
  8498. chart.clearHighlights();
  8499. } catch(e){}
  8500. }
  8501. }
  8502. function highlightPoint(serieIndex, pointIndex, b){
  8503. for(var i=0, il=charts.length; i < il; i++){
  8504. var chart = charts[i];
  8505. try {
  8506. chart.highlightPoint(serieIndex, pointIndex, b);
  8507. } catch(e){}
  8508. }
  8509. }
  8510. if(useInteractiveGuideline){
  8511. interactiveLayer.dispatch.on('elementMousemove', function(e) {
  8512. clearHighlights();
  8513. var singlePoint, pointIndex, pointXLocation, allData = [];
  8514. data
  8515. .filter(function(series, i) {
  8516. series.seriesIndex = i;
  8517. return !series.disabled;
  8518. })
  8519. .forEach(function(series,i) {
  8520. var extent = x.domain();
  8521. var currentValues = series.values.filter(function(d,i) {
  8522. return chart.x()(d,i) >= extent[0] && chart.x()(d,i) <= extent[1];
  8523. });
  8524. pointIndex = nv.interactiveBisect(currentValues, e.pointXValue, chart.x());
  8525. var point = currentValues[pointIndex];
  8526. var pointYValue = chart.y()(point, pointIndex);
  8527. if (pointYValue !== null) {
  8528. highlightPoint(i, pointIndex, true);
  8529. }
  8530. if (point === undefined) return;
  8531. if (singlePoint === undefined) singlePoint = point;
  8532. if (pointXLocation === undefined) pointXLocation = x(chart.x()(point,pointIndex));
  8533. allData.push({
  8534. key: series.key,
  8535. value: pointYValue,
  8536. color: color(series,series.seriesIndex),
  8537. data: point,
  8538. yAxis: series.yAxis == 2 ? yAxis2 : yAxis1
  8539. });
  8540. });
  8541. var defaultValueFormatter = function(d,i) {
  8542. var yAxis = allData[i].yAxis;
  8543. return d == null ? "N/A" : yAxis.tickFormat()(d);
  8544. };
  8545. interactiveLayer.tooltip
  8546. .headerFormatter(function(d, i) {
  8547. return xAxis.tickFormat()(d, i);
  8548. })
  8549. .valueFormatter(interactiveLayer.tooltip.valueFormatter() || defaultValueFormatter)
  8550. .data({
  8551. value: chart.x()( singlePoint,pointIndex ),
  8552. index: pointIndex,
  8553. series: allData
  8554. })();
  8555. interactiveLayer.renderGuideLine(pointXLocation);
  8556. });
  8557. interactiveLayer.dispatch.on("elementMouseout",function(e) {
  8558. clearHighlights();
  8559. });
  8560. } else {
  8561. lines1.dispatch.on('elementMouseover.tooltip', mouseover_line);
  8562. lines2.dispatch.on('elementMouseover.tooltip', mouseover_line);
  8563. lines1.dispatch.on('elementMouseout.tooltip', function(evt) {
  8564. tooltip.hidden(true)
  8565. });
  8566. lines2.dispatch.on('elementMouseout.tooltip', function(evt) {
  8567. tooltip.hidden(true)
  8568. });
  8569. scatters1.dispatch.on('elementMouseover.tooltip', mouseover_scatter);
  8570. scatters2.dispatch.on('elementMouseover.tooltip', mouseover_scatter);
  8571. scatters1.dispatch.on('elementMouseout.tooltip', function(evt) {
  8572. tooltip.hidden(true)
  8573. });
  8574. scatters2.dispatch.on('elementMouseout.tooltip', function(evt) {
  8575. tooltip.hidden(true)
  8576. });
  8577. stack1.dispatch.on('elementMouseover.tooltip', mouseover_stack);
  8578. stack2.dispatch.on('elementMouseover.tooltip', mouseover_stack);
  8579. stack1.dispatch.on('elementMouseout.tooltip', function(evt) {
  8580. tooltip.hidden(true)
  8581. });
  8582. stack2.dispatch.on('elementMouseout.tooltip', function(evt) {
  8583. tooltip.hidden(true)
  8584. });
  8585. bars1.dispatch.on('elementMouseover.tooltip', mouseover_bar);
  8586. bars2.dispatch.on('elementMouseover.tooltip', mouseover_bar);
  8587. bars1.dispatch.on('elementMouseout.tooltip', function(evt) {
  8588. tooltip.hidden(true);
  8589. });
  8590. bars2.dispatch.on('elementMouseout.tooltip', function(evt) {
  8591. tooltip.hidden(true);
  8592. });
  8593. bars1.dispatch.on('elementMousemove.tooltip', function(evt) {
  8594. tooltip();
  8595. });
  8596. bars2.dispatch.on('elementMousemove.tooltip', function(evt) {
  8597. tooltip();
  8598. });
  8599. }
  8600. });
  8601. return chart;
  8602. }
  8603. //============================================================
  8604. // Global getters and setters
  8605. //------------------------------------------------------------
  8606. chart.dispatch = dispatch;
  8607. chart.legend = legend;
  8608. chart.lines1 = lines1;
  8609. chart.lines2 = lines2;
  8610. chart.scatters1 = scatters1;
  8611. chart.scatters2 = scatters2;
  8612. chart.bars1 = bars1;
  8613. chart.bars2 = bars2;
  8614. chart.stack1 = stack1;
  8615. chart.stack2 = stack2;
  8616. chart.xAxis = xAxis;
  8617. chart.yAxis1 = yAxis1;
  8618. chart.yAxis2 = yAxis2;
  8619. chart.tooltip = tooltip;
  8620. chart.interactiveLayer = interactiveLayer;
  8621. chart.options = nv.utils.optionsFunc.bind(chart);
  8622. chart._options = Object.create({}, {
  8623. // simple options, just get/set the necessary values
  8624. width: {get: function(){return width;}, set: function(_){width=_;}},
  8625. height: {get: function(){return height;}, set: function(_){height=_;}},
  8626. showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
  8627. yDomain1: {get: function(){return yDomain1;}, set: function(_){yDomain1=_;}},
  8628. yDomain2: {get: function(){return yDomain2;}, set: function(_){yDomain2=_;}},
  8629. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  8630. interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
  8631. legendRightAxisHint: {get: function(){return legendRightAxisHint;}, set: function(_){legendRightAxisHint=_;}},
  8632. // options that require extra logic in the setter
  8633. margin: {get: function(){return margin;}, set: function(_){
  8634. margin.top = _.top !== undefined ? _.top : margin.top;
  8635. margin.right = _.right !== undefined ? _.right : margin.right;
  8636. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  8637. margin.left = _.left !== undefined ? _.left : margin.left;
  8638. }},
  8639. color: {get: function(){return color;}, set: function(_){
  8640. color = nv.utils.getColor(_);
  8641. }},
  8642. x: {get: function(){return getX;}, set: function(_){
  8643. getX = _;
  8644. lines1.x(_);
  8645. lines2.x(_);
  8646. scatters1.x(_);
  8647. scatters2.x(_);
  8648. bars1.x(_);
  8649. bars2.x(_);
  8650. stack1.x(_);
  8651. stack2.x(_);
  8652. }},
  8653. y: {get: function(){return getY;}, set: function(_){
  8654. getY = _;
  8655. lines1.y(_);
  8656. lines2.y(_);
  8657. scatters1.y(_);
  8658. scatters2.y(_);
  8659. stack1.y(_);
  8660. stack2.y(_);
  8661. bars1.y(_);
  8662. bars2.y(_);
  8663. }},
  8664. useVoronoi: {get: function(){return useVoronoi;}, set: function(_){
  8665. useVoronoi=_;
  8666. lines1.useVoronoi(_);
  8667. lines2.useVoronoi(_);
  8668. stack1.useVoronoi(_);
  8669. stack2.useVoronoi(_);
  8670. }},
  8671. useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
  8672. useInteractiveGuideline = _;
  8673. if (useInteractiveGuideline) {
  8674. lines1.interactive(false);
  8675. lines1.useVoronoi(false);
  8676. lines2.interactive(false);
  8677. lines2.useVoronoi(false);
  8678. stack1.interactive(false);
  8679. stack1.useVoronoi(false);
  8680. stack2.interactive(false);
  8681. stack2.useVoronoi(false);
  8682. scatters1.interactive(false);
  8683. scatters2.interactive(false);
  8684. }
  8685. }}
  8686. });
  8687. nv.utils.initOptions(chart);
  8688. return chart;
  8689. };
  8690. nv.models.ohlcBar = function() {
  8691. "use strict";
  8692. //============================================================
  8693. // Public Variables with Default Settings
  8694. //------------------------------------------------------------
  8695. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  8696. , width = null
  8697. , height = null
  8698. , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
  8699. , container = null
  8700. , x = d3.scale.linear()
  8701. , y = d3.scale.linear()
  8702. , getX = function(d) { return d.x }
  8703. , getY = function(d) { return d.y }
  8704. , getOpen = function(d) { return d.open }
  8705. , getClose = function(d) { return d.close }
  8706. , getHigh = function(d) { return d.high }
  8707. , getLow = function(d) { return d.low }
  8708. , forceX = []
  8709. , forceY = []
  8710. , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
  8711. , clipEdge = true
  8712. , color = nv.utils.defaultColor()
  8713. , interactive = false
  8714. , xDomain
  8715. , yDomain
  8716. , xRange
  8717. , yRange
  8718. , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove')
  8719. ;
  8720. //============================================================
  8721. // Private Variables
  8722. //------------------------------------------------------------
  8723. function chart(selection) {
  8724. selection.each(function(data) {
  8725. container = d3.select(this);
  8726. var availableWidth = nv.utils.availableWidth(width, container, margin),
  8727. availableHeight = nv.utils.availableHeight(height, container, margin);
  8728. nv.utils.initSVG(container);
  8729. // ohlc bar width.
  8730. var w = (availableWidth / data[0].values.length) * .9;
  8731. // Setup Scales
  8732. x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
  8733. if (padData)
  8734. x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
  8735. else
  8736. x.range(xRange || [5 + w/2, availableWidth - w/2 - 5]);
  8737. y.domain(yDomain || [
  8738. d3.min(data[0].values.map(getLow).concat(forceY)),
  8739. d3.max(data[0].values.map(getHigh).concat(forceY))
  8740. ]
  8741. ).range(yRange || [availableHeight, 0]);
  8742. // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
  8743. if (x.domain()[0] === x.domain()[1])
  8744. x.domain()[0] ?
  8745. x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
  8746. : x.domain([-1,1]);
  8747. if (y.domain()[0] === y.domain()[1])
  8748. y.domain()[0] ?
  8749. y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
  8750. : y.domain([-1,1]);
  8751. // Setup containers and skeleton of chart
  8752. var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]);
  8753. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar');
  8754. var defsEnter = wrapEnter.append('defs');
  8755. var gEnter = wrapEnter.append('g');
  8756. var g = wrap.select('g');
  8757. gEnter.append('g').attr('class', 'nv-ticks');
  8758. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  8759. container
  8760. .on('click', function(d,i) {
  8761. dispatch.chartClick({
  8762. data: d,
  8763. index: i,
  8764. pos: d3.event,
  8765. id: id
  8766. });
  8767. });
  8768. defsEnter.append('clipPath')
  8769. .attr('id', 'nv-chart-clip-path-' + id)
  8770. .append('rect');
  8771. wrap.select('#nv-chart-clip-path-' + id + ' rect')
  8772. .attr('width', availableWidth)
  8773. .attr('height', availableHeight);
  8774. g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
  8775. var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
  8776. .data(function(d) { return d });
  8777. ticks.exit().remove();
  8778. ticks.enter().append('path')
  8779. .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
  8780. .attr('d', function(d,i) {
  8781. return 'm0,0l0,'
  8782. + (y(getOpen(d,i))
  8783. - y(getHigh(d,i)))
  8784. + 'l'
  8785. + (-w/2)
  8786. + ',0l'
  8787. + (w/2)
  8788. + ',0l0,'
  8789. + (y(getLow(d,i)) - y(getOpen(d,i)))
  8790. + 'l0,'
  8791. + (y(getClose(d,i))
  8792. - y(getLow(d,i)))
  8793. + 'l'
  8794. + (w/2)
  8795. + ',0l'
  8796. + (-w/2)
  8797. + ',0z';
  8798. })
  8799. .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
  8800. .attr('fill', function(d,i) { return color[0]; })
  8801. .attr('stroke', function(d,i) { return color[0]; })
  8802. .attr('x', 0 )
  8803. .attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) })
  8804. .attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) });
  8805. // the bar colors are controlled by CSS currently
  8806. ticks.attr('class', function(d,i,j) {
  8807. return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i;
  8808. });
  8809. d3.transition(ticks)
  8810. .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
  8811. .attr('d', function(d,i) {
  8812. var w = (availableWidth / data[0].values.length) * .9;
  8813. return 'm0,0l0,'
  8814. + (y(getOpen(d,i))
  8815. - y(getHigh(d,i)))
  8816. + 'l'
  8817. + (-w/2)
  8818. + ',0l'
  8819. + (w/2)
  8820. + ',0l0,'
  8821. + (y(getLow(d,i))
  8822. - y(getOpen(d,i)))
  8823. + 'l0,'
  8824. + (y(getClose(d,i))
  8825. - y(getLow(d,i)))
  8826. + 'l'
  8827. + (w/2)
  8828. + ',0l'
  8829. + (-w/2)
  8830. + ',0z';
  8831. });
  8832. });
  8833. return chart;
  8834. }
  8835. //Create methods to allow outside functions to highlight a specific bar.
  8836. chart.highlightPoint = function(pointIndex, isHoverOver) {
  8837. chart.clearHighlights();
  8838. container.select(".nv-ohlcBar .nv-tick-0-" + pointIndex)
  8839. .classed("hover", isHoverOver)
  8840. ;
  8841. };
  8842. chart.clearHighlights = function() {
  8843. container.select(".nv-ohlcBar .nv-tick.hover")
  8844. .classed("hover", false)
  8845. ;
  8846. };
  8847. //============================================================
  8848. // Expose Public Variables
  8849. //------------------------------------------------------------
  8850. chart.dispatch = dispatch;
  8851. chart.options = nv.utils.optionsFunc.bind(chart);
  8852. chart._options = Object.create({}, {
  8853. // simple options, just get/set the necessary values
  8854. width: {get: function(){return width;}, set: function(_){width=_;}},
  8855. height: {get: function(){return height;}, set: function(_){height=_;}},
  8856. xScale: {get: function(){return x;}, set: function(_){x=_;}},
  8857. yScale: {get: function(){return y;}, set: function(_){y=_;}},
  8858. xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
  8859. yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
  8860. xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
  8861. yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
  8862. forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
  8863. forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
  8864. padData: {get: function(){return padData;}, set: function(_){padData=_;}},
  8865. clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
  8866. id: {get: function(){return id;}, set: function(_){id=_;}},
  8867. interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
  8868. x: {get: function(){return getX;}, set: function(_){getX=_;}},
  8869. y: {get: function(){return getY;}, set: function(_){getY=_;}},
  8870. open: {get: function(){return getOpen();}, set: function(_){getOpen=_;}},
  8871. close: {get: function(){return getClose();}, set: function(_){getClose=_;}},
  8872. high: {get: function(){return getHigh;}, set: function(_){getHigh=_;}},
  8873. low: {get: function(){return getLow;}, set: function(_){getLow=_;}},
  8874. // options that require extra logic in the setter
  8875. margin: {get: function(){return margin;}, set: function(_){
  8876. margin.top = _.top != undefined ? _.top : margin.top;
  8877. margin.right = _.right != undefined ? _.right : margin.right;
  8878. margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
  8879. margin.left = _.left != undefined ? _.left : margin.left;
  8880. }},
  8881. color: {get: function(){return color;}, set: function(_){
  8882. color = nv.utils.getColor(_);
  8883. }}
  8884. });
  8885. nv.utils.initOptions(chart);
  8886. return chart;
  8887. };
  8888. // Code adapted from Jason Davies' "Parallel Coordinates"
  8889. // http://bl.ocks.org/jasondavies/1341281
  8890. nv.models.parallelCoordinates = function() {
  8891. "use strict";
  8892. //============================================================
  8893. // Public Variables with Default Settings
  8894. //------------------------------------------------------------
  8895. var margin = {top: 30, right: 0, bottom: 10, left: 0}
  8896. , width = null
  8897. , height = null
  8898. , availableWidth = null
  8899. , availableHeight = null
  8900. , x = d3.scale.ordinal()
  8901. , y = {}
  8902. , undefinedValuesLabel = "undefined values"
  8903. , dimensionData = []
  8904. , enabledDimensions = []
  8905. , dimensionNames = []
  8906. , displayBrush = true
  8907. , color = nv.utils.defaultColor()
  8908. , filters = []
  8909. , active = []
  8910. , dragging = []
  8911. , axisWithUndefinedValues = []
  8912. , lineTension = 1
  8913. , foreground
  8914. , background
  8915. , dimensions
  8916. , line = d3.svg.line()
  8917. , axis = d3.svg.axis()
  8918. , dispatch = d3.dispatch('brushstart', 'brush', 'brushEnd', 'dimensionsOrder', "stateChange", 'elementClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd', 'activeChanged')
  8919. ;
  8920. //============================================================
  8921. // Private Variables
  8922. //------------------------------------------------------------
  8923. var renderWatch = nv.utils.renderWatch(dispatch);
  8924. function chart(selection) {
  8925. renderWatch.reset();
  8926. selection.each(function(data) {
  8927. var container = d3.select(this);
  8928. availableWidth = nv.utils.availableWidth(width, container, margin);
  8929. availableHeight = nv.utils.availableHeight(height, container, margin);
  8930. nv.utils.initSVG(container);
  8931. //Convert old data to new format (name, values)
  8932. if (data[0].values === undefined) {
  8933. var newData = [];
  8934. data.forEach(function (d) {
  8935. var val = {};
  8936. var key = Object.keys(d);
  8937. key.forEach(function (k) { if (k !== "name") val[k] = d[k] });
  8938. newData.push({ key: d.name, values: val });
  8939. });
  8940. data = newData;
  8941. }
  8942. var dataValues = data.map(function (d) {return d.values});
  8943. if (active.length === 0) {
  8944. active = data;
  8945. }; //set all active before first brush call
  8946. dimensionNames = dimensionData.sort(function (a, b) { return a.currentPosition - b.currentPosition; }).map(function (d) { return d.key });
  8947. enabledDimensions = dimensionData.filter(function (d) { return !d.disabled; });
  8948. // Setup Scales
  8949. x.rangePoints([0, availableWidth], 1).domain(enabledDimensions.map(function (d) { return d.key; }));
  8950. //Set as true if all values on an axis are missing.
  8951. // Extract the list of dimensions and create a scale for each.
  8952. var oldDomainMaxValue = {};
  8953. var displayMissingValuesline = false;
  8954. var currentTicks = [];
  8955. dimensionNames.forEach(function(d) {
  8956. var extent = d3.extent(dataValues, function (p) { return +p[d]; });
  8957. var min = extent[0];
  8958. var max = extent[1];
  8959. var onlyUndefinedValues = false;
  8960. //If there is no values to display on an axis, set the extent to 0
  8961. if (isNaN(min) || isNaN(max)) {
  8962. onlyUndefinedValues = true;
  8963. min = 0;
  8964. max = 0;
  8965. }
  8966. //Scale axis if there is only one value
  8967. if (min === max) {
  8968. min = min - 1;
  8969. max = max + 1;
  8970. }
  8971. var f = filters.filter(function (k) { return k.dimension == d; });
  8972. if (f.length !== 0) {
  8973. //If there is only NaN values, keep the existing domain.
  8974. if (onlyUndefinedValues) {
  8975. min = y[d].domain()[0];
  8976. max = y[d].domain()[1];
  8977. }
  8978. //If the brush extent is > max (< min), keep the extent value.
  8979. else if (!f[0].hasOnlyNaN && displayBrush) {
  8980. min = min > f[0].extent[0] ? f[0].extent[0] : min;
  8981. max = max < f[0].extent[1] ? f[0].extent[1] : max;
  8982. }
  8983. //If there is NaN values brushed be sure the brush extent is on the domain.
  8984. else if (f[0].hasNaN) {
  8985. max = max < f[0].extent[1] ? f[0].extent[1] : max;
  8986. oldDomainMaxValue[d] = y[d].domain()[1];
  8987. displayMissingValuesline = true;
  8988. }
  8989. }
  8990. //Use 90% of (availableHeight - 12) for the axis range, 12 reprensenting the space necessary to display "undefined values" text.
  8991. //The remaining 10% are used to display the missingValue line.
  8992. y[d] = d3.scale.linear()
  8993. .domain([min, max])
  8994. .range([(availableHeight - 12) * 0.9, 0]);
  8995. axisWithUndefinedValues = [];
  8996. y[d].brush = d3.svg.brush().y(y[d]).on('brushstart', brushstart).on('brush', brush).on('brushend', brushend);
  8997. });
  8998. // Setup containers and skeleton of chart
  8999. var wrap = container.selectAll('g.nv-wrap.nv-parallelCoordinates').data([data]);
  9000. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-parallelCoordinates');
  9001. var gEnter = wrapEnter.append('g');
  9002. var g = wrap.select('g');
  9003. gEnter.append('g').attr('class', 'nv-parallelCoordinates background');
  9004. gEnter.append('g').attr('class', 'nv-parallelCoordinates foreground');
  9005. gEnter.append('g').attr('class', 'nv-parallelCoordinates missingValuesline');
  9006. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  9007. line.interpolate('cardinal').tension(lineTension);
  9008. axis.orient('left');
  9009. var axisDrag = d3.behavior.drag()
  9010. .on('dragstart', dragStart)
  9011. .on('drag', dragMove)
  9012. .on('dragend', dragEnd);
  9013. //Add missing value line at the bottom of the chart
  9014. var missingValuesline, missingValueslineText;
  9015. var step = x.range()[1] - x.range()[0];
  9016. if (!isNaN(step)) {
  9017. var lineData = [0 + step / 2, availableHeight - 12, availableWidth - step / 2, availableHeight - 12];
  9018. missingValuesline = wrap.select('.missingValuesline').selectAll('line').data([lineData]);
  9019. missingValuesline.enter().append('line');
  9020. missingValuesline.exit().remove();
  9021. missingValuesline.attr("x1", function(d) { return d[0]; })
  9022. .attr("y1", function(d) { return d[1]; })
  9023. .attr("x2", function(d) { return d[2]; })
  9024. .attr("y2", function(d) { return d[3]; });
  9025. //Add the text "undefined values" under the missing value line
  9026. missingValueslineText = wrap.select('.missingValuesline').selectAll('text').data([undefinedValuesLabel]);
  9027. missingValueslineText.append('text').data([undefinedValuesLabel]);
  9028. missingValueslineText.enter().append('text');
  9029. missingValueslineText.exit().remove();
  9030. missingValueslineText.attr("y", availableHeight)
  9031. //To have the text right align with the missingValues line, substract 92 representing the text size.
  9032. .attr("x", availableWidth - 92 - step / 2)
  9033. .text(function(d) { return d; });
  9034. }
  9035. // Add grey background lines for context.
  9036. background = wrap.select('.background').selectAll('path').data(data);
  9037. background.enter().append('path');
  9038. background.exit().remove();
  9039. background.attr('d', path);
  9040. // Add blue foreground lines for focus.
  9041. foreground = wrap.select('.foreground').selectAll('path').data(data);
  9042. foreground.enter().append('path')
  9043. foreground.exit().remove();
  9044. foreground.attr('d', path)
  9045. .style("stroke-width", function (d, i) {
  9046. if (isNaN(d.strokeWidth)) { d.strokeWidth = 1;} return d.strokeWidth;})
  9047. .attr('stroke', function (d, i) { return d.color || color(d, i); });
  9048. foreground.on("mouseover", function (d, i) {
  9049. d3.select(this).classed('hover', true).style("stroke-width", d.strokeWidth + 2 + "px").style("stroke-opacity", 1);
  9050. dispatch.elementMouseover({
  9051. label: d.name,
  9052. color: d.color || color(d, i),
  9053. values: d.values,
  9054. dimensions: enabledDimensions
  9055. });
  9056. });
  9057. foreground.on("mouseout", function (d, i) {
  9058. d3.select(this).classed('hover', false).style("stroke-width", d.strokeWidth + "px").style("stroke-opacity", 0.7);
  9059. dispatch.elementMouseout({
  9060. label: d.name,
  9061. index: i
  9062. });
  9063. });
  9064. foreground.on('mousemove', function (d, i) {
  9065. dispatch.elementMousemove();
  9066. });
  9067. foreground.on('click', function (d) {
  9068. dispatch.elementClick({
  9069. id: d.id
  9070. });
  9071. });
  9072. // Add a group element for each dimension.
  9073. dimensions = g.selectAll('.dimension').data(enabledDimensions);
  9074. var dimensionsEnter = dimensions.enter().append('g').attr('class', 'nv-parallelCoordinates dimension');
  9075. dimensions.attr('transform', function(d) { return 'translate(' + x(d.key) + ',0)'; });
  9076. dimensionsEnter.append('g').attr('class', 'nv-axis');
  9077. // Add an axis and title.
  9078. dimensionsEnter.append('text')
  9079. .attr('class', 'nv-label')
  9080. .style("cursor", "move")
  9081. .attr('dy', '-1em')
  9082. .attr('text-anchor', 'middle')
  9083. .on("mouseover", function(d, i) {
  9084. dispatch.elementMouseover({
  9085. label: d.tooltip || d.key,
  9086. color: d.color
  9087. });
  9088. })
  9089. .on("mouseout", function(d, i) {
  9090. dispatch.elementMouseout({
  9091. label: d.tooltip
  9092. });
  9093. })
  9094. .on('mousemove', function (d, i) {
  9095. dispatch.elementMousemove();
  9096. })
  9097. .call(axisDrag);
  9098. dimensionsEnter.append('g').attr('class', 'nv-brushBackground');
  9099. dimensions.exit().remove();
  9100. dimensions.select('.nv-label').text(function (d) { return d.key });
  9101. // Add and store a brush for each axis.
  9102. restoreBrush(displayBrush);
  9103. var actives = dimensionNames.filter(function (p) { return !y[p].brush.empty(); }),
  9104. extents = actives.map(function (p) { return y[p].brush.extent(); });
  9105. var formerActive = active.slice(0);
  9106. //Restore active values
  9107. active = [];
  9108. foreground.style("display", function (d) {
  9109. var isActive = actives.every(function (p, i) {
  9110. if ((isNaN(d.values[p]) || isNaN(parseFloat(d.values[p]))) && extents[i][0] == y[p].brush.y().domain()[0]) {
  9111. return true;
  9112. }
  9113. return (extents[i][0] <= d.values[p] && d.values[p] <= extents[i][1]) && !isNaN(parseFloat(d.values[p]));
  9114. });
  9115. if (isActive)
  9116. active.push(d);
  9117. return !isActive ? "none" : null;
  9118. });
  9119. if (filters.length > 0 || !nv.utils.arrayEquals(active, formerActive)) {
  9120. dispatch.activeChanged(active);
  9121. }
  9122. // Returns the path for a given data point.
  9123. function path(d) {
  9124. return line(enabledDimensions.map(function (p) {
  9125. //If value if missing, put the value on the missing value line
  9126. if (isNaN(d.values[p.key]) || isNaN(parseFloat(d.values[p.key])) || displayMissingValuesline) {
  9127. var domain = y[p.key].domain();
  9128. var range = y[p.key].range();
  9129. var min = domain[0] - (domain[1] - domain[0]) / 9;
  9130. //If it's not already the case, allow brush to select undefined values
  9131. if (axisWithUndefinedValues.indexOf(p.key) < 0) {
  9132. var newscale = d3.scale.linear().domain([min, domain[1]]).range([availableHeight - 12, range[1]]);
  9133. y[p.key].brush.y(newscale);
  9134. axisWithUndefinedValues.push(p.key);
  9135. }
  9136. if (isNaN(d.values[p.key]) || isNaN(parseFloat(d.values[p.key]))) {
  9137. return [x(p.key), y[p.key](min)];
  9138. }
  9139. }
  9140. //If parallelCoordinate contain missing values show the missing values line otherwise, hide it.
  9141. if (missingValuesline !== undefined) {
  9142. if (axisWithUndefinedValues.length > 0 || displayMissingValuesline) {
  9143. missingValuesline.style("display", "inline");
  9144. missingValueslineText.style("display", "inline");
  9145. } else {
  9146. missingValuesline.style("display", "none");
  9147. missingValueslineText.style("display", "none");
  9148. }
  9149. }
  9150. return [x(p.key), y[p.key](d.values[p.key])];
  9151. }));
  9152. }
  9153. function restoreBrush(visible) {
  9154. filters.forEach(function (f) {
  9155. //If filter brushed NaN values, keep the brush on the bottom of the axis.
  9156. var brushDomain = y[f.dimension].brush.y().domain();
  9157. if (f.hasOnlyNaN) {
  9158. f.extent[1] = (y[f.dimension].domain()[1] - brushDomain[0]) * (f.extent[1] - f.extent[0]) / (oldDomainMaxValue[f.dimension] - f.extent[0]) + brushDomain[0];
  9159. }
  9160. if (f.hasNaN) {
  9161. f.extent[0] = brushDomain[0];
  9162. }
  9163. if (visible)
  9164. y[f.dimension].brush.extent(f.extent);
  9165. });
  9166. dimensions.select('.nv-brushBackground')
  9167. .each(function (d) {
  9168. d3.select(this).call(y[d.key].brush);
  9169. })
  9170. .selectAll('rect')
  9171. .attr('x', -8)
  9172. .attr('width', 16);
  9173. updateTicks();
  9174. }
  9175. // Handles a brush event, toggling the display of foreground lines.
  9176. function brushstart() {
  9177. //If brush aren't visible, show it before brushing again.
  9178. if (displayBrush === false) {
  9179. displayBrush = true;
  9180. restoreBrush(true);
  9181. }
  9182. }
  9183. // Handles a brush event, toggling the display of foreground lines.
  9184. function brush() {
  9185. actives = dimensionNames.filter(function (p) { return !y[p].brush.empty(); });
  9186. extents = actives.map(function(p) { return y[p].brush.extent(); });
  9187. filters = []; //erase current filters
  9188. actives.forEach(function(d,i) {
  9189. filters[i] = {
  9190. dimension: d,
  9191. extent: extents[i],
  9192. hasNaN: false,
  9193. hasOnlyNaN: false
  9194. }
  9195. });
  9196. active = []; //erase current active list
  9197. foreground.style('display', function(d) {
  9198. var isActive = actives.every(function(p, i) {
  9199. if ((isNaN(d.values[p]) || isNaN(parseFloat(d.values[p]))) && extents[i][0] == y[p].brush.y().domain()[0]) return true;
  9200. return (extents[i][0] <= d.values[p] && d.values[p] <= extents[i][1]) && !isNaN(parseFloat(d.values[p]));
  9201. });
  9202. if (isActive) active.push(d);
  9203. return isActive ? null : 'none';
  9204. });
  9205. updateTicks();
  9206. dispatch.brush({
  9207. filters: filters,
  9208. active: active
  9209. });
  9210. }
  9211. function brushend() {
  9212. var hasActiveBrush = actives.length > 0 ? true : false;
  9213. filters.forEach(function (f) {
  9214. if (f.extent[0] === y[f.dimension].brush.y().domain()[0] && axisWithUndefinedValues.indexOf(f.dimension) >= 0)
  9215. f.hasNaN = true;
  9216. if (f.extent[1] < y[f.dimension].domain()[0])
  9217. f.hasOnlyNaN = true;
  9218. });
  9219. dispatch.brushEnd(active, hasActiveBrush);
  9220. }
  9221. function updateTicks() {
  9222. dimensions.select('.nv-axis')
  9223. .each(function (d, i) {
  9224. var f = filters.filter(function (k) { return k.dimension == d.key; });
  9225. currentTicks[d.key] = y[d.key].domain();
  9226. //If brush are available, display brush extent
  9227. if (f.length != 0 && displayBrush)
  9228. {
  9229. currentTicks[d.key] = [];
  9230. if (f[0].extent[1] > y[d.key].domain()[0])
  9231. currentTicks[d.key] = [f[0].extent[1]];
  9232. if (f[0].extent[0] >= y[d.key].domain()[0])
  9233. currentTicks[d.key].push(f[0].extent[0]);
  9234. }
  9235. d3.select(this).call(axis.scale(y[d.key]).tickFormat(d.format).tickValues(currentTicks[d.key]));
  9236. });
  9237. }
  9238. function dragStart(d) {
  9239. dragging[d.key] = this.parentNode.__origin__ = x(d.key);
  9240. background.attr("visibility", "hidden");
  9241. }
  9242. function dragMove(d) {
  9243. dragging[d.key] = Math.min(availableWidth, Math.max(0, this.parentNode.__origin__ += d3.event.x));
  9244. foreground.attr("d", path);
  9245. enabledDimensions.sort(function (a, b) { return dimensionPosition(a.key) - dimensionPosition(b.key); });
  9246. enabledDimensions.forEach(function (d, i) { return d.currentPosition = i; });
  9247. x.domain(enabledDimensions.map(function (d) { return d.key; }));
  9248. dimensions.attr("transform", function(d) { return "translate(" + dimensionPosition(d.key) + ")"; });
  9249. }
  9250. function dragEnd(d, i) {
  9251. delete this.parentNode.__origin__;
  9252. delete dragging[d.key];
  9253. d3.select(this.parentNode).attr("transform", "translate(" + x(d.key) + ")");
  9254. foreground
  9255. .attr("d", path);
  9256. background
  9257. .attr("d", path)
  9258. .attr("visibility", null);
  9259. dispatch.dimensionsOrder(enabledDimensions);
  9260. }
  9261. function dimensionPosition(d) {
  9262. var v = dragging[d];
  9263. return v == null ? x(d) : v;
  9264. }
  9265. });
  9266. return chart;
  9267. }
  9268. //============================================================
  9269. // Expose Public Variables
  9270. //------------------------------------------------------------
  9271. chart.dispatch = dispatch;
  9272. chart.options = nv.utils.optionsFunc.bind(chart);
  9273. chart._options = Object.create({}, {
  9274. // simple options, just get/set the necessary values
  9275. width: {get: function(){return width;}, set: function(_){width= _;}},
  9276. height: {get: function(){return height;}, set: function(_){height= _;}},
  9277. dimensionData: { get: function () { return dimensionData; }, set: function (_) { dimensionData = _; } },
  9278. displayBrush: { get: function () { return displayBrush; }, set: function (_) { displayBrush = _; } },
  9279. filters: { get: function () { return filters; }, set: function (_) { filters = _; } },
  9280. active: { get: function () { return active; }, set: function (_) { active = _; } },
  9281. lineTension: {get: function(){return lineTension;}, set: function(_){lineTension = _;}},
  9282. undefinedValuesLabel : {get: function(){return undefinedValuesLabel;}, set: function(_){undefinedValuesLabel=_;}},
  9283. // deprecated options
  9284. dimensions: {get: function () { return dimensionData.map(function (d){return d.key}); }, set: function (_) {
  9285. // deprecated after 1.8.1
  9286. nv.deprecated('dimensions', 'use dimensionData instead');
  9287. if (dimensionData.length === 0) {
  9288. _.forEach(function (k) { dimensionData.push({ key: k }) })
  9289. } else {
  9290. _.forEach(function (k, i) { dimensionData[i].key= k })
  9291. }
  9292. }},
  9293. dimensionNames: {get: function () { return dimensionData.map(function (d){return d.key}); }, set: function (_) {
  9294. // deprecated after 1.8.1
  9295. nv.deprecated('dimensionNames', 'use dimensionData instead');
  9296. dimensionNames = [];
  9297. if (dimensionData.length === 0) {
  9298. _.forEach(function (k) { dimensionData.push({ key: k }) })
  9299. } else {
  9300. _.forEach(function (k, i) { dimensionData[i].key = k })
  9301. }
  9302. }},
  9303. dimensionFormats: {get: function () { return dimensionData.map(function (d) { return d.format }); }, set: function (_) {
  9304. // deprecated after 1.8.1
  9305. nv.deprecated('dimensionFormats', 'use dimensionData instead');
  9306. if (dimensionData.length === 0) {
  9307. _.forEach(function (f) { dimensionData.push({ format: f }) })
  9308. } else {
  9309. _.forEach(function (f, i) { dimensionData[i].format = f })
  9310. }
  9311. }},
  9312. // options that require extra logic in the setter
  9313. margin: {get: function(){return margin;}, set: function(_){
  9314. margin.top = _.top !== undefined ? _.top : margin.top;
  9315. margin.right = _.right !== undefined ? _.right : margin.right;
  9316. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  9317. margin.left = _.left !== undefined ? _.left : margin.left;
  9318. }},
  9319. color: {get: function(){return color;}, set: function(_){
  9320. color = nv.utils.getColor(_);
  9321. }}
  9322. });
  9323. nv.utils.initOptions(chart);
  9324. return chart;
  9325. };
  9326. nv.models.parallelCoordinatesChart = function () {
  9327. "use strict";
  9328. //============================================================
  9329. // Public Variables with Default Settings
  9330. //------------------------------------------------------------
  9331. var parallelCoordinates = nv.models.parallelCoordinates()
  9332. var legend = nv.models.legend()
  9333. var tooltip = nv.models.tooltip();
  9334. var dimensionTooltip = nv.models.tooltip();
  9335. var margin = { top: 0, right: 0, bottom: 0, left: 0 }
  9336. , width = null
  9337. , height = null
  9338. , showLegend = true
  9339. , color = nv.utils.defaultColor()
  9340. , state = nv.utils.state()
  9341. , dimensionData = []
  9342. , displayBrush = true
  9343. , defaultState = null
  9344. , noData = null
  9345. , nanValue = "undefined"
  9346. , dispatch = d3.dispatch('dimensionsOrder', 'brushEnd', 'stateChange', 'changeState', 'renderEnd')
  9347. , controlWidth = function () { return showControls ? 180 : 0 }
  9348. ;
  9349. //============================================================
  9350. //============================================================
  9351. // Private Variables
  9352. //------------------------------------------------------------
  9353. var renderWatch = nv.utils.renderWatch(dispatch);
  9354. var stateGetter = function(data) {
  9355. return function() {
  9356. return {
  9357. active: data.map(function(d) { return !d.disabled })
  9358. };
  9359. }
  9360. };
  9361. var stateSetter = function(data) {
  9362. return function(state) {
  9363. if(state.active !== undefined) {
  9364. data.forEach(function(series, i) {
  9365. series.disabled = !state.active[i];
  9366. });
  9367. }
  9368. }
  9369. };
  9370. tooltip.contentGenerator(function(data) {
  9371. 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>';
  9372. if(data.series.length !== 0)
  9373. {
  9374. str = str + '<tbody><tr><td height ="10px"></td></tr>';
  9375. data.series.forEach(function(d){
  9376. 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>';
  9377. });
  9378. str = str + '</tbody>';
  9379. }
  9380. str = str + '</table>';
  9381. return str;
  9382. });
  9383. //============================================================
  9384. // Chart function
  9385. //------------------------------------------------------------
  9386. function chart(selection) {
  9387. renderWatch.reset();
  9388. renderWatch.models(parallelCoordinates);
  9389. selection.each(function(data) {
  9390. var container = d3.select(this);
  9391. nv.utils.initSVG(container);
  9392. var that = this;
  9393. var availableWidth = nv.utils.availableWidth(width, container, margin),
  9394. availableHeight = nv.utils.availableHeight(height, container, margin);
  9395. chart.update = function() { container.call(chart); };
  9396. chart.container = this;
  9397. state.setter(stateSetter(dimensionData), chart.update)
  9398. .getter(stateGetter(dimensionData))
  9399. .update();
  9400. //set state.disabled
  9401. state.disabled = dimensionData.map(function (d) { return !!d.disabled });
  9402. //Keep dimensions position in memory
  9403. dimensionData = dimensionData.map(function (d) {d.disabled = !!d.disabled; return d});
  9404. dimensionData.forEach(function (d, i) {
  9405. d.originalPosition = isNaN(d.originalPosition) ? i : d.originalPosition;
  9406. d.currentPosition = isNaN(d.currentPosition) ? i : d.currentPosition;
  9407. });
  9408. if (!defaultState) {
  9409. var key;
  9410. defaultState = {};
  9411. for(key in state) {
  9412. if(state[key] instanceof Array)
  9413. defaultState[key] = state[key].slice(0);
  9414. else
  9415. defaultState[key] = state[key];
  9416. }
  9417. }
  9418. // Display No Data message if there's nothing to show.
  9419. if(!data || !data.length) {
  9420. nv.utils.noData(chart, container);
  9421. return chart;
  9422. } else {
  9423. container.selectAll('.nv-noData').remove();
  9424. }
  9425. //------------------------------------------------------------
  9426. // Setup containers and skeleton of chart
  9427. var wrap = container.selectAll('g.nv-wrap.nv-parallelCoordinatesChart').data([data]);
  9428. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-parallelCoordinatesChart').append('g');
  9429. var g = wrap.select('g');
  9430. gEnter.append('g').attr('class', 'nv-parallelCoordinatesWrap');
  9431. gEnter.append('g').attr('class', 'nv-legendWrap');
  9432. g.select("rect")
  9433. .attr("width", availableWidth)
  9434. .attr("height", (availableHeight > 0) ? availableHeight : 0);
  9435. // Legend
  9436. if (!showLegend) {
  9437. g.select('.nv-legendWrap').selectAll('*').remove();
  9438. } else {
  9439. legend.width(availableWidth)
  9440. .color(function (d) { return "rgb(188,190,192)"; });
  9441. g.select('.nv-legendWrap')
  9442. .datum(dimensionData.sort(function (a, b) { return a.originalPosition - b.originalPosition; }))
  9443. .call(legend);
  9444. if (legend.height() > margin.top) {
  9445. margin.top = legend.height();
  9446. availableHeight = nv.utils.availableHeight(height, container, margin);
  9447. }
  9448. wrap.select('.nv-legendWrap')
  9449. .attr('transform', 'translate( 0 ,' + (-margin.top) + ')');
  9450. }
  9451. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  9452. // Main Chart Component(s)
  9453. parallelCoordinates
  9454. .width(availableWidth)
  9455. .height(availableHeight)
  9456. .dimensionData(dimensionData)
  9457. .displayBrush(displayBrush);
  9458. var parallelCoordinatesWrap = g.select('.nv-parallelCoordinatesWrap ')
  9459. .datum(data);
  9460. parallelCoordinatesWrap.transition().call(parallelCoordinates);
  9461. //============================================================
  9462. // Event Handling/Dispatching (in chart's scope)
  9463. //------------------------------------------------------------
  9464. //Display reset brush button
  9465. parallelCoordinates.dispatch.on('brushEnd', function (active, hasActiveBrush) {
  9466. if (hasActiveBrush) {
  9467. displayBrush = true;
  9468. dispatch.brushEnd(active);
  9469. } else {
  9470. displayBrush = false;
  9471. }
  9472. });
  9473. legend.dispatch.on('stateChange', function(newState) {
  9474. for(var key in newState) {
  9475. state[key] = newState[key];
  9476. }
  9477. dispatch.stateChange(state);
  9478. chart.update();
  9479. });
  9480. //Update dimensions order and display reset sorting button
  9481. parallelCoordinates.dispatch.on('dimensionsOrder', function (e) {
  9482. dimensionData.sort(function (a, b) { return a.currentPosition - b.currentPosition; });
  9483. var isSorted = false;
  9484. dimensionData.forEach(function (d, i) {
  9485. d.currentPosition = i;
  9486. if (d.currentPosition !== d.originalPosition)
  9487. isSorted = true;
  9488. });
  9489. dispatch.dimensionsOrder(dimensionData, isSorted);
  9490. });
  9491. // Update chart from a state object passed to event handler
  9492. dispatch.on('changeState', function (e) {
  9493. if (typeof e.disabled !== 'undefined') {
  9494. dimensionData.forEach(function (series, i) {
  9495. series.disabled = e.disabled[i];
  9496. });
  9497. state.disabled = e.disabled;
  9498. }
  9499. chart.update();
  9500. });
  9501. });
  9502. renderWatch.renderEnd('parraleleCoordinateChart immediate');
  9503. return chart;
  9504. }
  9505. //============================================================
  9506. // Event Handling/Dispatching (out of chart's scope)
  9507. //------------------------------------------------------------
  9508. parallelCoordinates.dispatch.on('elementMouseover.tooltip', function (evt) {
  9509. var tp = {
  9510. key: evt.label,
  9511. color: evt.color,
  9512. series: []
  9513. }
  9514. if(evt.values){
  9515. Object.keys(evt.values).forEach(function (d) {
  9516. var dim = evt.dimensions.filter(function (dd) {return dd.key === d;})[0];
  9517. if(dim){
  9518. var v;
  9519. if (isNaN(evt.values[d]) || isNaN(parseFloat(evt.values[d]))) {
  9520. v = nanValue;
  9521. } else {
  9522. v = dim.format(evt.values[d]);
  9523. }
  9524. tp.series.push({ idx: dim.currentPosition, key: d, value: v, color: dim.color });
  9525. }
  9526. });
  9527. tp.series.sort(function(a,b) {return a.idx - b.idx});
  9528. }
  9529. tooltip.data(tp).hidden(false);
  9530. });
  9531. parallelCoordinates.dispatch.on('elementMouseout.tooltip', function(evt) {
  9532. tooltip.hidden(true)
  9533. });
  9534. parallelCoordinates.dispatch.on('elementMousemove.tooltip', function () {
  9535. tooltip();
  9536. });
  9537. //============================================================
  9538. // Expose Public Variables
  9539. //------------------------------------------------------------
  9540. // expose chart's sub-components
  9541. chart.dispatch = dispatch;
  9542. chart.parallelCoordinates = parallelCoordinates;
  9543. chart.legend = legend;
  9544. chart.tooltip = tooltip;
  9545. chart.options = nv.utils.optionsFunc.bind(chart);
  9546. chart._options = Object.create({}, {
  9547. // simple options, just get/set the necessary values
  9548. width: { get: function () { return width; }, set: function (_) { width = _; } },
  9549. height: { get: function () { return height; }, set: function (_) { height = _; } },
  9550. showLegend: { get: function () { return showLegend; }, set: function (_) { showLegend = _; } },
  9551. defaultState: { get: function () { return defaultState; }, set: function (_) { defaultState = _; } },
  9552. dimensionData: { get: function () { return dimensionData; }, set: function (_) { dimensionData = _; } },
  9553. displayBrush: { get: function () { return displayBrush; }, set: function (_) { displayBrush = _; } },
  9554. noData: { get: function () { return noData; }, set: function (_) { noData = _; } },
  9555. nanValue: { get: function () { return nanValue; }, set: function (_) { nanValue = _; } },
  9556. // options that require extra logic in the setter
  9557. margin: {
  9558. get: function () { return margin; },
  9559. set: function (_) {
  9560. margin.top = _.top !== undefined ? _.top : margin.top;
  9561. margin.right = _.right !== undefined ? _.right : margin.right;
  9562. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  9563. margin.left = _.left !== undefined ? _.left : margin.left;
  9564. }
  9565. },
  9566. color: {get: function(){return color;}, set: function(_){
  9567. color = nv.utils.getColor(_);
  9568. legend.color(color);
  9569. parallelCoordinates.color(color);
  9570. }}
  9571. });
  9572. nv.utils.inheritOptions(chart, parallelCoordinates);
  9573. nv.utils.initOptions(chart);
  9574. return chart;
  9575. };
  9576. nv.models.pie = function() {
  9577. "use strict";
  9578. //============================================================
  9579. // Public Variables with Default Settings
  9580. //------------------------------------------------------------
  9581. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  9582. , width = 500
  9583. , height = 500
  9584. , getX = function(d) { return d.x }
  9585. , getY = function(d) { return d.y }
  9586. , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
  9587. , container = null
  9588. , color = nv.utils.defaultColor()
  9589. , valueFormat = d3.format(',.2f')
  9590. , showLabels = true
  9591. , labelsOutside = false
  9592. , labelType = "key"
  9593. , labelThreshold = .02 //if slice percentage is under this, don't show label
  9594. , donut = false
  9595. , title = false
  9596. , growOnHover = true
  9597. , titleOffset = 0
  9598. , labelSunbeamLayout = false
  9599. , startAngle = false
  9600. , padAngle = false
  9601. , endAngle = false
  9602. , cornerRadius = 0
  9603. , donutRatio = 0.5
  9604. , duration = 250
  9605. , arcsRadius = []
  9606. , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
  9607. ;
  9608. var arcs = [];
  9609. var arcsOver = [];
  9610. //============================================================
  9611. // chart function
  9612. //------------------------------------------------------------
  9613. var renderWatch = nv.utils.renderWatch(dispatch);
  9614. function chart(selection) {
  9615. renderWatch.reset();
  9616. selection.each(function(data) {
  9617. var availableWidth = width - margin.left - margin.right
  9618. , availableHeight = height - margin.top - margin.bottom
  9619. , radius = Math.min(availableWidth, availableHeight) / 2
  9620. , arcsRadiusOuter = []
  9621. , arcsRadiusInner = []
  9622. ;
  9623. container = d3.select(this)
  9624. if (arcsRadius.length === 0) {
  9625. var outer = radius - radius / 5;
  9626. var inner = donutRatio * radius;
  9627. for (var i = 0; i < data[0].length; i++) {
  9628. arcsRadiusOuter.push(outer);
  9629. arcsRadiusInner.push(inner);
  9630. }
  9631. } else {
  9632. if(growOnHover){
  9633. arcsRadiusOuter = arcsRadius.map(function (d) { return (d.outer - d.outer / 5) * radius; });
  9634. arcsRadiusInner = arcsRadius.map(function (d) { return (d.inner - d.inner / 5) * radius; });
  9635. donutRatio = d3.min(arcsRadius.map(function (d) { return (d.inner - d.inner / 5); }));
  9636. } else {
  9637. arcsRadiusOuter = arcsRadius.map(function (d) { return d.outer * radius; });
  9638. arcsRadiusInner = arcsRadius.map(function (d) { return d.inner * radius; });
  9639. donutRatio = d3.min(arcsRadius.map(function (d) { return d.inner; }));
  9640. }
  9641. }
  9642. nv.utils.initSVG(container);
  9643. // Setup containers and skeleton of chart
  9644. var wrap = container.selectAll('.nv-wrap.nv-pie').data(data);
  9645. var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id);
  9646. var gEnter = wrapEnter.append('g');
  9647. var g = wrap.select('g');
  9648. var g_pie = gEnter.append('g').attr('class', 'nv-pie');
  9649. gEnter.append('g').attr('class', 'nv-pieLabels');
  9650. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  9651. g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
  9652. g.select('.nv-pieLabels').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
  9653. //
  9654. container.on('click', function(d,i) {
  9655. dispatch.chartClick({
  9656. data: d,
  9657. index: i,
  9658. pos: d3.event,
  9659. id: id
  9660. });
  9661. });
  9662. arcs = [];
  9663. arcsOver = [];
  9664. for (var i = 0; i < data[0].length; i++) {
  9665. var arc = d3.svg.arc().outerRadius(arcsRadiusOuter[i]);
  9666. var arcOver = d3.svg.arc().outerRadius(arcsRadiusOuter[i] + 5);
  9667. if (startAngle !== false) {
  9668. arc.startAngle(startAngle);
  9669. arcOver.startAngle(startAngle);
  9670. }
  9671. if (endAngle !== false) {
  9672. arc.endAngle(endAngle);
  9673. arcOver.endAngle(endAngle);
  9674. }
  9675. if (donut) {
  9676. arc.innerRadius(arcsRadiusInner[i]);
  9677. arcOver.innerRadius(arcsRadiusInner[i]);
  9678. }
  9679. if (arc.cornerRadius && cornerRadius) {
  9680. arc.cornerRadius(cornerRadius);
  9681. arcOver.cornerRadius(cornerRadius);
  9682. }
  9683. arcs.push(arc);
  9684. arcsOver.push(arcOver);
  9685. }
  9686. // Setup the Pie chart and choose the data element
  9687. var pie = d3.layout.pie()
  9688. .sort(null)
  9689. .value(function(d) { return d.disabled ? 0 : getY(d) });
  9690. // padAngle added in d3 3.5
  9691. if (pie.padAngle && padAngle) {
  9692. pie.padAngle(padAngle);
  9693. }
  9694. // if title is specified and donut, put it in the middle
  9695. if (donut && title) {
  9696. g_pie.append("text").attr('class', 'nv-pie-title');
  9697. wrap.select('.nv-pie-title')
  9698. .style("text-anchor", "middle")
  9699. .text(function (d) {
  9700. return title;
  9701. })
  9702. .style("font-size", (Math.min(availableWidth, availableHeight)) * donutRatio * 2 / (title.length + 2) + "px")
  9703. .attr("dy", "0.35em") // trick to vertically center text
  9704. .attr('transform', function(d, i) {
  9705. return 'translate(0, '+ titleOffset + ')';
  9706. });
  9707. }
  9708. var slices = wrap.select('.nv-pie').selectAll('.nv-slice').data(pie);
  9709. var pieLabels = wrap.select('.nv-pieLabels').selectAll('.nv-label').data(pie);
  9710. slices.exit().remove();
  9711. pieLabels.exit().remove();
  9712. var ae = slices.enter().append('g');
  9713. ae.attr('class', 'nv-slice');
  9714. ae.on('mouseover', function(d, i) {
  9715. d3.select(this).classed('hover', true);
  9716. if (growOnHover) {
  9717. d3.select(this).select("path").transition()
  9718. .duration(70)
  9719. .attr("d", arcsOver[i]);
  9720. }
  9721. dispatch.elementMouseover({
  9722. data: d.data,
  9723. index: i,
  9724. color: d3.select(this).style("fill"),
  9725. percent: (d.endAngle - d.startAngle) / (2 * Math.PI)
  9726. });
  9727. });
  9728. ae.on('mouseout', function(d, i) {
  9729. d3.select(this).classed('hover', false);
  9730. if (growOnHover) {
  9731. d3.select(this).select("path").transition()
  9732. .duration(50)
  9733. .attr("d", arcs[i]);
  9734. }
  9735. dispatch.elementMouseout({data: d.data, index: i});
  9736. });
  9737. ae.on('mousemove', function(d, i) {
  9738. dispatch.elementMousemove({data: d.data, index: i});
  9739. });
  9740. ae.on('click', function(d, i) {
  9741. var element = this;
  9742. dispatch.elementClick({
  9743. data: d.data,
  9744. index: i,
  9745. color: d3.select(this).style("fill"),
  9746. event: d3.event,
  9747. element: element
  9748. });
  9749. });
  9750. ae.on('dblclick', function(d, i) {
  9751. dispatch.elementDblClick({
  9752. data: d.data,
  9753. index: i,
  9754. color: d3.select(this).style("fill")
  9755. });
  9756. });
  9757. slices.attr('fill', function(d,i) { return color(d.data, i); });
  9758. slices.attr('stroke', function(d,i) { return color(d.data, i); });
  9759. var paths = ae.append('path').each(function(d) {
  9760. this._current = d;
  9761. });
  9762. slices.select('path')
  9763. .transition()
  9764. .duration(duration)
  9765. .attr('d', function (d, i) { return arcs[i](d); })
  9766. .attrTween('d', arcTween);
  9767. if (showLabels) {
  9768. // This does the normal label
  9769. var labelsArc = [];
  9770. for (var i = 0; i < data[0].length; i++) {
  9771. labelsArc.push(arcs[i]);
  9772. if (labelsOutside) {
  9773. if (donut) {
  9774. labelsArc[i] = d3.svg.arc().outerRadius(arcs[i].outerRadius());
  9775. if (startAngle !== false) labelsArc[i].startAngle(startAngle);
  9776. if (endAngle !== false) labelsArc[i].endAngle(endAngle);
  9777. }
  9778. } else if (!donut) {
  9779. labelsArc[i].innerRadius(0);
  9780. }
  9781. }
  9782. pieLabels.enter().append("g").classed("nv-label",true).each(function(d,i) {
  9783. var group = d3.select(this);
  9784. group.attr('transform', function (d, i) {
  9785. if (labelSunbeamLayout) {
  9786. d.outerRadius = arcsRadiusOuter[i] + 10; // Set Outer Coordinate
  9787. d.innerRadius = arcsRadiusOuter[i] + 15; // Set Inner Coordinate
  9788. var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
  9789. if ((d.startAngle + d.endAngle) / 2 < Math.PI) {
  9790. rotateAngle -= 90;
  9791. } else {
  9792. rotateAngle += 90;
  9793. }
  9794. return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')';
  9795. } else {
  9796. d.outerRadius = radius + 10; // Set Outer Coordinate
  9797. d.innerRadius = radius + 15; // Set Inner Coordinate
  9798. return 'translate(' + labelsArc[i].centroid(d) + ')'
  9799. }
  9800. });
  9801. group.append('rect')
  9802. .style('stroke', '#fff')
  9803. .style('fill', '#fff')
  9804. .attr("rx", 3)
  9805. .attr("ry", 3);
  9806. group.append('text')
  9807. .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
  9808. .style('fill', '#000')
  9809. });
  9810. var labelLocationHash = {};
  9811. var avgHeight = 14;
  9812. var avgWidth = 140;
  9813. var createHashKey = function(coordinates) {
  9814. return Math.floor(coordinates[0]/avgWidth) * avgWidth + ',' + Math.floor(coordinates[1]/avgHeight) * avgHeight;
  9815. };
  9816. var getSlicePercentage = function(d) {
  9817. return (d.endAngle - d.startAngle) / (2 * Math.PI);
  9818. };
  9819. pieLabels.watchTransition(renderWatch, 'pie labels').attr('transform', function (d, i) {
  9820. if (labelSunbeamLayout) {
  9821. d.outerRadius = arcsRadiusOuter[i] + 10; // Set Outer Coordinate
  9822. d.innerRadius = arcsRadiusOuter[i] + 15; // Set Inner Coordinate
  9823. var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
  9824. if ((d.startAngle + d.endAngle) / 2 < Math.PI) {
  9825. rotateAngle -= 90;
  9826. } else {
  9827. rotateAngle += 90;
  9828. }
  9829. return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')';
  9830. } else {
  9831. d.outerRadius = radius + 10; // Set Outer Coordinate
  9832. d.innerRadius = radius + 15; // Set Inner Coordinate
  9833. /*
  9834. Overlapping pie labels are not good. What this attempts to do is, prevent overlapping.
  9835. Each label location is hashed, and if a hash collision occurs, we assume an overlap.
  9836. Adjust the label's y-position to remove the overlap.
  9837. */
  9838. var center = labelsArc[i].centroid(d);
  9839. var percent = getSlicePercentage(d);
  9840. if (d.value && percent >= labelThreshold) {
  9841. var hashKey = createHashKey(center);
  9842. if (labelLocationHash[hashKey]) {
  9843. center[1] -= avgHeight;
  9844. }
  9845. labelLocationHash[createHashKey(center)] = true;
  9846. }
  9847. return 'translate(' + center + ')'
  9848. }
  9849. });
  9850. pieLabels.select(".nv-label text")
  9851. .style('text-anchor', function(d,i) {
  9852. //center the text on it's origin or begin/end if orthogonal aligned
  9853. return labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle';
  9854. })
  9855. .text(function(d, i) {
  9856. var percent = getSlicePercentage(d);
  9857. var label = '';
  9858. if (!d.value || percent < labelThreshold) return '';
  9859. if(typeof labelType === 'function') {
  9860. label = labelType(d, i, {
  9861. 'key': getX(d.data),
  9862. 'value': getY(d.data),
  9863. 'percent': valueFormat(percent)
  9864. });
  9865. } else {
  9866. switch (labelType) {
  9867. case 'key':
  9868. label = getX(d.data);
  9869. break;
  9870. case 'value':
  9871. label = valueFormat(getY(d.data));
  9872. break;
  9873. case 'percent':
  9874. label = d3.format('%')(percent);
  9875. break;
  9876. }
  9877. }
  9878. return label;
  9879. })
  9880. ;
  9881. }
  9882. // Computes the angle of an arc, converting from radians to degrees.
  9883. function angle(d) {
  9884. var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
  9885. return a > 90 ? a - 180 : a;
  9886. }
  9887. function arcTween(a, idx) {
  9888. a.endAngle = isNaN(a.endAngle) ? 0 : a.endAngle;
  9889. a.startAngle = isNaN(a.startAngle) ? 0 : a.startAngle;
  9890. if (!donut) a.innerRadius = 0;
  9891. var i = d3.interpolate(this._current, a);
  9892. this._current = i(0);
  9893. return function (t) {
  9894. return arcs[idx](i(t));
  9895. };
  9896. }
  9897. });
  9898. renderWatch.renderEnd('pie immediate');
  9899. return chart;
  9900. }
  9901. //============================================================
  9902. // Expose Public Variables
  9903. //------------------------------------------------------------
  9904. chart.dispatch = dispatch;
  9905. chart.options = nv.utils.optionsFunc.bind(chart);
  9906. chart._options = Object.create({}, {
  9907. // simple options, just get/set the necessary values
  9908. arcsRadius: { get: function () { return arcsRadius; }, set: function (_) { arcsRadius = _; } },
  9909. width: {get: function(){return width;}, set: function(_){width=_;}},
  9910. height: {get: function(){return height;}, set: function(_){height=_;}},
  9911. showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}},
  9912. title: {get: function(){return title;}, set: function(_){title=_;}},
  9913. titleOffset: {get: function(){return titleOffset;}, set: function(_){titleOffset=_;}},
  9914. labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_;}},
  9915. valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
  9916. x: {get: function(){return getX;}, set: function(_){getX=_;}},
  9917. id: {get: function(){return id;}, set: function(_){id=_;}},
  9918. endAngle: {get: function(){return endAngle;}, set: function(_){endAngle=_;}},
  9919. startAngle: {get: function(){return startAngle;}, set: function(_){startAngle=_;}},
  9920. padAngle: {get: function(){return padAngle;}, set: function(_){padAngle=_;}},
  9921. cornerRadius: {get: function(){return cornerRadius;}, set: function(_){cornerRadius=_;}},
  9922. donutRatio: {get: function(){return donutRatio;}, set: function(_){donutRatio=_;}},
  9923. labelsOutside: {get: function(){return labelsOutside;}, set: function(_){labelsOutside=_;}},
  9924. labelSunbeamLayout: {get: function(){return labelSunbeamLayout;}, set: function(_){labelSunbeamLayout=_;}},
  9925. donut: {get: function(){return donut;}, set: function(_){donut=_;}},
  9926. growOnHover: {get: function(){return growOnHover;}, set: function(_){growOnHover=_;}},
  9927. // depreciated after 1.7.1
  9928. pieLabelsOutside: {get: function(){return labelsOutside;}, set: function(_){
  9929. labelsOutside=_;
  9930. nv.deprecated('pieLabelsOutside', 'use labelsOutside instead');
  9931. }},
  9932. // depreciated after 1.7.1
  9933. donutLabelsOutside: {get: function(){return labelsOutside;}, set: function(_){
  9934. labelsOutside=_;
  9935. nv.deprecated('donutLabelsOutside', 'use labelsOutside instead');
  9936. }},
  9937. // deprecated after 1.7.1
  9938. labelFormat: {get: function(){ return valueFormat;}, set: function(_) {
  9939. valueFormat=_;
  9940. nv.deprecated('labelFormat','use valueFormat instead');
  9941. }},
  9942. // options that require extra logic in the setter
  9943. margin: {get: function(){return margin;}, set: function(_){
  9944. margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
  9945. margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
  9946. margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
  9947. margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
  9948. }},
  9949. duration: {get: function(){return duration;}, set: function(_){
  9950. duration = _;
  9951. renderWatch.reset(duration);
  9952. }},
  9953. y: {get: function(){return getY;}, set: function(_){
  9954. getY=d3.functor(_);
  9955. }},
  9956. color: {get: function(){return color;}, set: function(_){
  9957. color=nv.utils.getColor(_);
  9958. }},
  9959. labelType: {get: function(){return labelType;}, set: function(_){
  9960. labelType= _ || 'key';
  9961. }}
  9962. });
  9963. nv.utils.initOptions(chart);
  9964. return chart;
  9965. };
  9966. nv.models.pieChart = function() {
  9967. "use strict";
  9968. //============================================================
  9969. // Public Variables with Default Settings
  9970. //------------------------------------------------------------
  9971. var pie = nv.models.pie();
  9972. var legend = nv.models.legend();
  9973. var tooltip = nv.models.tooltip();
  9974. var margin = {top: 30, right: 20, bottom: 20, left: 20}
  9975. , width = null
  9976. , height = null
  9977. , showTooltipPercent = false
  9978. , showLegend = true
  9979. , legendPosition = "top"
  9980. , color = nv.utils.defaultColor()
  9981. , state = nv.utils.state()
  9982. , defaultState = null
  9983. , noData = null
  9984. , duration = 250
  9985. , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd')
  9986. ;
  9987. tooltip
  9988. .duration(0)
  9989. .headerEnabled(false)
  9990. .valueFormatter(function(d, i) {
  9991. return pie.valueFormat()(d, i);
  9992. });
  9993. //============================================================
  9994. // Private Variables
  9995. //------------------------------------------------------------
  9996. var renderWatch = nv.utils.renderWatch(dispatch);
  9997. var stateGetter = function(data) {
  9998. return function(){
  9999. return {
  10000. active: data.map(function(d) { return !d.disabled })
  10001. };
  10002. }
  10003. };
  10004. var stateSetter = function(data) {
  10005. return function(state) {
  10006. if (state.active !== undefined) {
  10007. data.forEach(function (series, i) {
  10008. series.disabled = !state.active[i];
  10009. });
  10010. }
  10011. }
  10012. };
  10013. //============================================================
  10014. // Chart function
  10015. //------------------------------------------------------------
  10016. function chart(selection) {
  10017. renderWatch.reset();
  10018. renderWatch.models(pie);
  10019. selection.each(function(data) {
  10020. var container = d3.select(this);
  10021. nv.utils.initSVG(container);
  10022. var that = this;
  10023. var availableWidth = nv.utils.availableWidth(width, container, margin),
  10024. availableHeight = nv.utils.availableHeight(height, container, margin);
  10025. chart.update = function() { container.transition().call(chart); };
  10026. chart.container = this;
  10027. state.setter(stateSetter(data), chart.update)
  10028. .getter(stateGetter(data))
  10029. .update();
  10030. //set state.disabled
  10031. state.disabled = data.map(function(d) { return !!d.disabled });
  10032. if (!defaultState) {
  10033. var key;
  10034. defaultState = {};
  10035. for (key in state) {
  10036. if (state[key] instanceof Array)
  10037. defaultState[key] = state[key].slice(0);
  10038. else
  10039. defaultState[key] = state[key];
  10040. }
  10041. }
  10042. // Display No Data message if there's nothing to show.
  10043. if (!data || !data.length) {
  10044. nv.utils.noData(chart, container);
  10045. return chart;
  10046. } else {
  10047. container.selectAll('.nv-noData').remove();
  10048. }
  10049. // Setup containers and skeleton of chart
  10050. var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]);
  10051. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g');
  10052. var g = wrap.select('g');
  10053. gEnter.append('g').attr('class', 'nv-pieWrap');
  10054. gEnter.append('g').attr('class', 'nv-legendWrap');
  10055. // Legend
  10056. if (!showLegend) {
  10057. g.select('.nv-legendWrap').selectAll('*').remove();
  10058. } else {
  10059. if (legendPosition === "top") {
  10060. legend.width( availableWidth ).key(pie.x());
  10061. wrap.select('.nv-legendWrap')
  10062. .datum(data)
  10063. .call(legend);
  10064. if (legend.height() > margin.top) {
  10065. margin.top = legend.height();
  10066. availableHeight = nv.utils.availableHeight(height, container, margin);
  10067. }
  10068. wrap.select('.nv-legendWrap')
  10069. .attr('transform', 'translate(0,' + (-margin.top) +')');
  10070. } else if (legendPosition === "right") {
  10071. var legendWidth = nv.models.legend().width();
  10072. if (availableWidth / 2 < legendWidth) {
  10073. legendWidth = (availableWidth / 2)
  10074. }
  10075. legend.height(availableHeight).key(pie.x());
  10076. legend.width(legendWidth);
  10077. availableWidth -= legend.width();
  10078. wrap.select('.nv-legendWrap')
  10079. .datum(data)
  10080. .call(legend)
  10081. .attr('transform', 'translate(' + (availableWidth) +',0)');
  10082. }
  10083. }
  10084. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  10085. // Main Chart Component(s)
  10086. pie.width(availableWidth).height(availableHeight);
  10087. var pieWrap = g.select('.nv-pieWrap').datum([data]);
  10088. d3.transition(pieWrap).call(pie);
  10089. //============================================================
  10090. // Event Handling/Dispatching (in chart's scope)
  10091. //------------------------------------------------------------
  10092. legend.dispatch.on('stateChange', function(newState) {
  10093. for (var key in newState) {
  10094. state[key] = newState[key];
  10095. }
  10096. dispatch.stateChange(state);
  10097. chart.update();
  10098. });
  10099. // Update chart from a state object passed to event handler
  10100. dispatch.on('changeState', function(e) {
  10101. if (typeof e.disabled !== 'undefined') {
  10102. data.forEach(function(series,i) {
  10103. series.disabled = e.disabled[i];
  10104. });
  10105. state.disabled = e.disabled;
  10106. }
  10107. chart.update();
  10108. });
  10109. });
  10110. renderWatch.renderEnd('pieChart immediate');
  10111. return chart;
  10112. }
  10113. //============================================================
  10114. // Event Handling/Dispatching (out of chart's scope)
  10115. //------------------------------------------------------------
  10116. pie.dispatch.on('elementMouseover.tooltip', function(evt) {
  10117. evt['series'] = {
  10118. key: chart.x()(evt.data),
  10119. value: chart.y()(evt.data),
  10120. color: evt.color,
  10121. percent: evt.percent
  10122. };
  10123. if (!showTooltipPercent) {
  10124. delete evt.percent;
  10125. delete evt.series.percent;
  10126. }
  10127. tooltip.data(evt).hidden(false);
  10128. });
  10129. pie.dispatch.on('elementMouseout.tooltip', function(evt) {
  10130. tooltip.hidden(true);
  10131. });
  10132. pie.dispatch.on('elementMousemove.tooltip', function(evt) {
  10133. tooltip();
  10134. });
  10135. //============================================================
  10136. // Expose Public Variables
  10137. //------------------------------------------------------------
  10138. // expose chart's sub-components
  10139. chart.legend = legend;
  10140. chart.dispatch = dispatch;
  10141. chart.pie = pie;
  10142. chart.tooltip = tooltip;
  10143. chart.options = nv.utils.optionsFunc.bind(chart);
  10144. // use Object get/set functionality to map between vars and chart functions
  10145. chart._options = Object.create({}, {
  10146. // simple options, just get/set the necessary values
  10147. width: {get: function(){return width;}, set: function(_){width=_;}},
  10148. height: {get: function(){return height;}, set: function(_){height=_;}},
  10149. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  10150. showTooltipPercent: {get: function(){return showTooltipPercent;}, set: function(_){showTooltipPercent=_;}},
  10151. showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
  10152. legendPosition: {get: function(){return legendPosition;}, set: function(_){legendPosition=_;}},
  10153. defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
  10154. // options that require extra logic in the setter
  10155. color: {get: function(){return color;}, set: function(_){
  10156. color = _;
  10157. legend.color(color);
  10158. pie.color(color);
  10159. }},
  10160. duration: {get: function(){return duration;}, set: function(_){
  10161. duration = _;
  10162. renderWatch.reset(duration);
  10163. pie.duration(duration);
  10164. }},
  10165. margin: {get: function(){return margin;}, set: function(_){
  10166. margin.top = _.top !== undefined ? _.top : margin.top;
  10167. margin.right = _.right !== undefined ? _.right : margin.right;
  10168. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  10169. margin.left = _.left !== undefined ? _.left : margin.left;
  10170. }}
  10171. });
  10172. nv.utils.inheritOptions(chart, pie);
  10173. nv.utils.initOptions(chart);
  10174. return chart;
  10175. };
  10176. nv.models.scatter = function() {
  10177. "use strict";
  10178. //============================================================
  10179. // Public Variables with Default Settings
  10180. //------------------------------------------------------------
  10181. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  10182. , width = null
  10183. , height = null
  10184. , color = nv.utils.defaultColor() // chooses color
  10185. , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one
  10186. , container = null
  10187. , x = d3.scale.linear()
  10188. , y = d3.scale.linear()
  10189. , z = d3.scale.linear() //linear because d3.svg.shape.size is treated as area
  10190. , getX = function(d) { return d.x } // accessor to get the x value
  10191. , getY = function(d) { return d.y } // accessor to get the y value
  10192. , getSize = function(d) { return d.size || 1} // accessor to get the point size
  10193. , getShape = function(d) { return d.shape || 'circle' } // accessor to get point shape
  10194. , forceX = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
  10195. , forceY = [] // List of numbers to Force into the Y scale
  10196. , forceSize = [] // List of numbers to Force into the Size scale
  10197. , interactive = true // If true, plots a voronoi overlay for advanced point intersection
  10198. , pointActive = function(d) { return !d.notActive } // any points that return false will be filtered out
  10199. , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
  10200. , padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding
  10201. , clipEdge = false // if true, masks points within x and y scale
  10202. , clipVoronoi = true // if true, masks each point with a circle... can turn off to slightly increase performance
  10203. , showVoronoi = false // display the voronoi areas
  10204. , clipRadius = function() { return 25 } // function to get the radius for voronoi point clips
  10205. , xDomain = null // Override x domain (skips the calculation from data)
  10206. , yDomain = null // Override y domain
  10207. , xRange = null // Override x range
  10208. , yRange = null // Override y range
  10209. , sizeDomain = null // Override point size domain
  10210. , sizeRange = null
  10211. , singlePoint = false
  10212. , dispatch = d3.dispatch('elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
  10213. , useVoronoi = true
  10214. , duration = 250
  10215. , interactiveUpdateDelay = 300
  10216. , showLabels = false
  10217. ;
  10218. //============================================================
  10219. // Private Variables
  10220. //------------------------------------------------------------
  10221. var x0, y0, z0 // used to store previous scales
  10222. , timeoutID
  10223. , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips
  10224. , renderWatch = nv.utils.renderWatch(dispatch, duration)
  10225. , _sizeRange_def = [16, 256]
  10226. , _caches
  10227. ;
  10228. function getCache(d) {
  10229. var cache, i;
  10230. cache = _caches = _caches || {};
  10231. i = d[0].series;
  10232. cache = cache[i] = cache[i] || {};
  10233. i = d[1];
  10234. cache = cache[i] = cache[i] || {};
  10235. return cache;
  10236. }
  10237. function getDiffs(d) {
  10238. var i, key,
  10239. point = d[0],
  10240. cache = getCache(d),
  10241. diffs = false;
  10242. for (i = 1; i < arguments.length; i ++) {
  10243. key = arguments[i];
  10244. if (cache[key] !== point[key] || !cache.hasOwnProperty(key)) {
  10245. cache[key] = point[key];
  10246. diffs = true;
  10247. }
  10248. }
  10249. return diffs;
  10250. }
  10251. function chart(selection) {
  10252. renderWatch.reset();
  10253. selection.each(function(data) {
  10254. container = d3.select(this);
  10255. var availableWidth = nv.utils.availableWidth(width, container, margin),
  10256. availableHeight = nv.utils.availableHeight(height, container, margin);
  10257. nv.utils.initSVG(container);
  10258. //add series index to each data point for reference
  10259. data.forEach(function(series, i) {
  10260. series.values.forEach(function(point) {
  10261. point.series = i;
  10262. });
  10263. });
  10264. // Setup Scales
  10265. var logScale = chart.yScale().name === d3.scale.log().name ? true : false;
  10266. // remap and flatten the data for use in calculating the scales' domains
  10267. 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
  10268. d3.merge(
  10269. data.map(function(d) {
  10270. return d.values.map(function(d,i) {
  10271. return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) }
  10272. })
  10273. })
  10274. );
  10275. x .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX)))
  10276. if (padData && data[0])
  10277. x.range(xRange || [(availableWidth * padDataOuter + availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length) ]);
  10278. //x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
  10279. else
  10280. x.range(xRange || [0, availableWidth]);
  10281. if (logScale) {
  10282. var min = d3.min(seriesData.map(function(d) { if (d.y !== 0) return d.y; }));
  10283. y.clamp(true)
  10284. .domain(yDomain || d3.extent(seriesData.map(function(d) {
  10285. if (d.y !== 0) return d.y;
  10286. else return min * 0.1;
  10287. }).concat(forceY)))
  10288. .range(yRange || [availableHeight, 0]);
  10289. } else {
  10290. y.domain(yDomain || d3.extent(seriesData.map(function (d) { return d.y;}).concat(forceY)))
  10291. .range(yRange || [availableHeight, 0]);
  10292. }
  10293. z .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize)))
  10294. .range(sizeRange || _sizeRange_def);
  10295. // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
  10296. singlePoint = x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1];
  10297. if (x.domain()[0] === x.domain()[1])
  10298. x.domain()[0] ?
  10299. x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
  10300. : x.domain([-1,1]);
  10301. if (y.domain()[0] === y.domain()[1])
  10302. y.domain()[0] ?
  10303. y.domain([y.domain()[0] - y.domain()[0] * 0.01, y.domain()[1] + y.domain()[1] * 0.01])
  10304. : y.domain([-1,1]);
  10305. if ( isNaN(x.domain()[0])) {
  10306. x.domain([-1,1]);
  10307. }
  10308. if ( isNaN(y.domain()[0])) {
  10309. y.domain([-1,1]);
  10310. }
  10311. x0 = x0 || x;
  10312. y0 = y0 || y;
  10313. z0 = z0 || z;
  10314. var scaleDiff = x(1) !== x0(1) || y(1) !== y0(1) || z(1) !== z0(1);
  10315. // Setup containers and skeleton of chart
  10316. var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]);
  10317. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id);
  10318. var defsEnter = wrapEnter.append('defs');
  10319. var gEnter = wrapEnter.append('g');
  10320. var g = wrap.select('g');
  10321. wrap.classed('nv-single-point', singlePoint);
  10322. gEnter.append('g').attr('class', 'nv-groups');
  10323. gEnter.append('g').attr('class', 'nv-point-paths');
  10324. wrapEnter.append('g').attr('class', 'nv-point-clips');
  10325. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  10326. defsEnter.append('clipPath')
  10327. .attr('id', 'nv-edge-clip-' + id)
  10328. .append('rect');
  10329. wrap.select('#nv-edge-clip-' + id + ' rect')
  10330. .attr('width', availableWidth)
  10331. .attr('height', (availableHeight > 0) ? availableHeight : 0);
  10332. g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
  10333. function updateInteractiveLayer() {
  10334. // Always clear needs-update flag regardless of whether or not
  10335. // we will actually do anything (avoids needless invocations).
  10336. needsUpdate = false;
  10337. if (!interactive) return false;
  10338. // inject series and point index for reference into voronoi
  10339. if (useVoronoi === true) {
  10340. var vertices = d3.merge(data.map(function(group, groupIndex) {
  10341. return group.values
  10342. .map(function(point, pointIndex) {
  10343. // *Adding noise to make duplicates very unlikely
  10344. // *Injecting series and point index for reference
  10345. /* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi.
  10346. */
  10347. var pX = getX(point,pointIndex);
  10348. var pY = getY(point,pointIndex);
  10349. return [nv.utils.NaNtoZero(x(pX))+ Math.random() * 1e-4,
  10350. nv.utils.NaNtoZero(y(pY))+ Math.random() * 1e-4,
  10351. groupIndex,
  10352. pointIndex, point]; //temp hack to add noise until I think of a better way so there are no duplicates
  10353. })
  10354. .filter(function(pointArray, pointIndex) {
  10355. return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct!
  10356. })
  10357. })
  10358. );
  10359. if (vertices.length == 0) return false; // No active points, we're done
  10360. if (vertices.length < 3) {
  10361. // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work
  10362. vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]);
  10363. vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]);
  10364. vertices.push([x.range()[0] - 20, y.range()[0] + 20, null, null]);
  10365. vertices.push([x.range()[1] + 20, y.range()[1] - 20, null, null]);
  10366. }
  10367. // keep voronoi sections from going more than 10 outside of graph
  10368. // to avoid overlap with other things like legend etc
  10369. var bounds = d3.geom.polygon([
  10370. [-10,-10],
  10371. [-10,height + 10],
  10372. [width + 10,height + 10],
  10373. [width + 10,-10]
  10374. ]);
  10375. var voronoi = d3.geom.voronoi(vertices).map(function(d, i) {
  10376. return {
  10377. 'data': bounds.clip(d),
  10378. 'series': vertices[i][2],
  10379. 'point': vertices[i][3]
  10380. }
  10381. });
  10382. // nuke all voronoi paths on reload and recreate them
  10383. wrap.select('.nv-point-paths').selectAll('path').remove();
  10384. var pointPaths = wrap.select('.nv-point-paths').selectAll('path').data(voronoi);
  10385. var vPointPaths = pointPaths
  10386. .enter().append("svg:path")
  10387. .attr("d", function(d) {
  10388. if (!d || !d.data || d.data.length === 0)
  10389. return 'M 0 0';
  10390. else
  10391. return "M" + d.data.join(",") + "Z";
  10392. })
  10393. .attr("id", function(d,i) {
  10394. return "nv-path-"+i; })
  10395. .attr("clip-path", function(d,i) { return "url(#nv-clip-"+id+"-"+i+")"; })
  10396. ;
  10397. // good for debugging point hover issues
  10398. if (showVoronoi) {
  10399. vPointPaths.style("fill", d3.rgb(230, 230, 230))
  10400. .style('fill-opacity', 0.4)
  10401. .style('stroke-opacity', 1)
  10402. .style("stroke", d3.rgb(200,200,200));
  10403. }
  10404. if (clipVoronoi) {
  10405. // voronoi sections are already set to clip,
  10406. // just create the circles with the IDs they expect
  10407. wrap.select('.nv-point-clips').selectAll('*').remove(); // must do * since it has sub-dom
  10408. var pointClips = wrap.select('.nv-point-clips').selectAll('clipPath').data(vertices);
  10409. var vPointClips = pointClips
  10410. .enter().append("svg:clipPath")
  10411. .attr("id", function(d, i) { return "nv-clip-"+id+"-"+i;})
  10412. .append("svg:circle")
  10413. .attr('cx', function(d) { return d[0]; })
  10414. .attr('cy', function(d) { return d[1]; })
  10415. .attr('r', clipRadius);
  10416. }
  10417. var mouseEventCallback = function(d, mDispatch) {
  10418. if (needsUpdate) return 0;
  10419. var series = data[d.series];
  10420. if (series === undefined) return;
  10421. var point = series.values[d.point];
  10422. point['color'] = color(series, d.series);
  10423. // standardize attributes for tooltip.
  10424. point['x'] = getX(point);
  10425. point['y'] = getY(point);
  10426. // can't just get box of event node since it's actually a voronoi polygon
  10427. var box = container.node().getBoundingClientRect();
  10428. var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
  10429. var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
  10430. var pos = {
  10431. left: x(getX(point, d.point)) + box.left + scrollLeft + margin.left + 10,
  10432. top: y(getY(point, d.point)) + box.top + scrollTop + margin.top + 10
  10433. };
  10434. mDispatch({
  10435. point: point,
  10436. series: series,
  10437. pos: pos,
  10438. relativePos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top],
  10439. seriesIndex: d.series,
  10440. pointIndex: d.point
  10441. });
  10442. };
  10443. pointPaths
  10444. .on('click', function(d) {
  10445. mouseEventCallback(d, dispatch.elementClick);
  10446. })
  10447. .on('dblclick', function(d) {
  10448. mouseEventCallback(d, dispatch.elementDblClick);
  10449. })
  10450. .on('mouseover', function(d) {
  10451. mouseEventCallback(d, dispatch.elementMouseover);
  10452. })
  10453. .on('mouseout', function(d, i) {
  10454. mouseEventCallback(d, dispatch.elementMouseout);
  10455. });
  10456. } else {
  10457. // add event handlers to points instead voronoi paths
  10458. wrap.select('.nv-groups').selectAll('.nv-group')
  10459. .selectAll('.nv-point')
  10460. //.data(dataWithPoints)
  10461. //.style('pointer-events', 'auto') // recativate events, disabled by css
  10462. .on('click', function(d,i) {
  10463. //nv.log('test', d, i);
  10464. if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
  10465. var series = data[d.series],
  10466. point = series.values[i];
  10467. var element = this;
  10468. dispatch.elementClick({
  10469. point: point,
  10470. series: series,
  10471. pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], //TODO: make this pos base on the page
  10472. relativePos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
  10473. seriesIndex: d.series,
  10474. pointIndex: i,
  10475. event: d3.event,
  10476. element: element
  10477. });
  10478. })
  10479. .on('dblclick', function(d,i) {
  10480. if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
  10481. var series = data[d.series],
  10482. point = series.values[i];
  10483. dispatch.elementDblClick({
  10484. point: point,
  10485. series: series,
  10486. pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],//TODO: make this pos base on the page
  10487. relativePos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
  10488. seriesIndex: d.series,
  10489. pointIndex: i
  10490. });
  10491. })
  10492. .on('mouseover', function(d,i) {
  10493. if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
  10494. var series = data[d.series],
  10495. point = series.values[i];
  10496. dispatch.elementMouseover({
  10497. point: point,
  10498. series: series,
  10499. pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],//TODO: make this pos base on the page
  10500. relativePos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
  10501. seriesIndex: d.series,
  10502. pointIndex: i,
  10503. color: color(d, i)
  10504. });
  10505. })
  10506. .on('mouseout', function(d,i) {
  10507. if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
  10508. var series = data[d.series],
  10509. point = series.values[i];
  10510. dispatch.elementMouseout({
  10511. point: point,
  10512. series: series,
  10513. pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],//TODO: make this pos base on the page
  10514. relativePos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
  10515. seriesIndex: d.series,
  10516. pointIndex: i,
  10517. color: color(d, i)
  10518. });
  10519. });
  10520. }
  10521. }
  10522. needsUpdate = true;
  10523. var groups = wrap.select('.nv-groups').selectAll('.nv-group')
  10524. .data(function(d) { return d }, function(d) { return d.key });
  10525. groups.enter().append('g')
  10526. .style('stroke-opacity', 1e-6)
  10527. .style('fill-opacity', 1e-6);
  10528. groups.exit()
  10529. .remove();
  10530. groups
  10531. .attr('class', function(d,i) {
  10532. return (d.classed || '') + ' nv-group nv-series-' + i;
  10533. })
  10534. .classed('nv-noninteractive', !interactive)
  10535. .classed('hover', function(d) { return d.hover });
  10536. groups.watchTransition(renderWatch, 'scatter: groups')
  10537. .style('fill', function(d,i) { return color(d, i) })
  10538. .style('stroke', function(d,i) { return color(d, i) })
  10539. .style('stroke-opacity', 1)
  10540. .style('fill-opacity', .5);
  10541. // create the points, maintaining their IDs from the original data set
  10542. var points = groups.selectAll('path.nv-point')
  10543. .data(function(d) {
  10544. return d.values.map(
  10545. function (point, pointIndex) {
  10546. return [point, pointIndex]
  10547. }).filter(
  10548. function(pointArray, pointIndex) {
  10549. return pointActive(pointArray[0], pointIndex)
  10550. })
  10551. });
  10552. points.enter().append('path')
  10553. .attr('class', function (d) {
  10554. return 'nv-point nv-point-' + d[1];
  10555. })
  10556. .style('fill', function (d) { return d.color })
  10557. .style('stroke', function (d) { return d.color })
  10558. .attr('transform', function(d) {
  10559. return 'translate(' + nv.utils.NaNtoZero(x0(getX(d[0],d[1]))) + ',' + nv.utils.NaNtoZero(y0(getY(d[0],d[1]))) + ')'
  10560. })
  10561. .attr('d',
  10562. nv.utils.symbol()
  10563. .type(function(d) { return getShape(d[0]); })
  10564. .size(function(d) { return z(getSize(d[0],d[1])) })
  10565. );
  10566. points.exit().remove();
  10567. groups.exit().selectAll('path.nv-point')
  10568. .watchTransition(renderWatch, 'scatter exit')
  10569. .attr('transform', function(d) {
  10570. return 'translate(' + nv.utils.NaNtoZero(x(getX(d[0],d[1]))) + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')'
  10571. })
  10572. .remove();
  10573. points.filter(function (d) { return scaleDiff || getDiffs(d, 'x', 'y'); })
  10574. .watchTransition(renderWatch, 'scatter points')
  10575. .attr('transform', function(d) {
  10576. //nv.log(d, getX(d[0],d[1]), x(getX(d[0],d[1])));
  10577. return 'translate(' + nv.utils.NaNtoZero(x(getX(d[0],d[1]))) + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')'
  10578. });
  10579. points.filter(function (d) { return scaleDiff || getDiffs(d, 'shape', 'size'); })
  10580. .watchTransition(renderWatch, 'scatter points')
  10581. .attr('d',
  10582. nv.utils.symbol()
  10583. .type(function(d) { return getShape(d[0]); })
  10584. .size(function(d) { return z(getSize(d[0],d[1])) })
  10585. );
  10586. // add label a label to scatter chart
  10587. if(showLabels)
  10588. {
  10589. var titles = groups.selectAll('.nv-label')
  10590. .data(function(d) {
  10591. return d.values.map(
  10592. function (point, pointIndex) {
  10593. return [point, pointIndex]
  10594. }).filter(
  10595. function(pointArray, pointIndex) {
  10596. return pointActive(pointArray[0], pointIndex)
  10597. })
  10598. });
  10599. titles.enter().append('text')
  10600. .style('fill', function (d,i) {
  10601. return d.color })
  10602. .style('stroke-opacity', 0)
  10603. .style('fill-opacity', 1)
  10604. .attr('transform', function(d) {
  10605. var dx = nv.utils.NaNtoZero(x0(getX(d[0],d[1]))) + Math.sqrt(z(getSize(d[0],d[1]))/Math.PI) + 2;
  10606. return 'translate(' + dx + ',' + nv.utils.NaNtoZero(y0(getY(d[0],d[1]))) + ')';
  10607. })
  10608. .text(function(d,i){
  10609. return d[0].label;});
  10610. titles.exit().remove();
  10611. groups.exit().selectAll('path.nv-label')
  10612. .watchTransition(renderWatch, 'scatter exit')
  10613. .attr('transform', function(d) {
  10614. var dx = nv.utils.NaNtoZero(x(getX(d[0],d[1])))+ Math.sqrt(z(getSize(d[0],d[1]))/Math.PI)+2;
  10615. return 'translate(' + dx + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')';
  10616. })
  10617. .remove();
  10618. titles.each(function(d) {
  10619. d3.select(this)
  10620. .classed('nv-label', true)
  10621. .classed('nv-label-' + d[1], false)
  10622. .classed('hover',false);
  10623. });
  10624. titles.watchTransition(renderWatch, 'scatter labels')
  10625. .attr('transform', function(d) {
  10626. var dx = nv.utils.NaNtoZero(x(getX(d[0],d[1])))+ Math.sqrt(z(getSize(d[0],d[1]))/Math.PI)+2;
  10627. return 'translate(' + dx + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')'
  10628. });
  10629. }
  10630. // Delay updating the invisible interactive layer for smoother animation
  10631. if( interactiveUpdateDelay )
  10632. {
  10633. clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer
  10634. timeoutID = setTimeout(updateInteractiveLayer, interactiveUpdateDelay );
  10635. }
  10636. else
  10637. {
  10638. updateInteractiveLayer();
  10639. }
  10640. //store old scales for use in transitions on update
  10641. x0 = x.copy();
  10642. y0 = y.copy();
  10643. z0 = z.copy();
  10644. });
  10645. renderWatch.renderEnd('scatter immediate');
  10646. return chart;
  10647. }
  10648. //============================================================
  10649. // Expose Public Variables
  10650. //------------------------------------------------------------
  10651. chart.dispatch = dispatch;
  10652. chart.options = nv.utils.optionsFunc.bind(chart);
  10653. // utility function calls provided by this chart
  10654. chart._calls = new function() {
  10655. this.clearHighlights = function () {
  10656. nv.dom.write(function() {
  10657. container.selectAll(".nv-point.hover").classed("hover", false);
  10658. });
  10659. return null;
  10660. };
  10661. this.highlightPoint = function (seriesIndex, pointIndex, isHoverOver) {
  10662. nv.dom.write(function() {
  10663. container.select('.nv-groups')
  10664. .selectAll(".nv-series-" + seriesIndex)
  10665. .selectAll(".nv-point-" + pointIndex)
  10666. .classed("hover", isHoverOver);
  10667. });
  10668. };
  10669. };
  10670. // trigger calls from events too
  10671. dispatch.on('elementMouseover.point', function(d) {
  10672. if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,true);
  10673. });
  10674. dispatch.on('elementMouseout.point', function(d) {
  10675. if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,false);
  10676. });
  10677. chart._options = Object.create({}, {
  10678. // simple options, just get/set the necessary values
  10679. width: {get: function(){return width;}, set: function(_){width=_;}},
  10680. height: {get: function(){return height;}, set: function(_){height=_;}},
  10681. xScale: {get: function(){return x;}, set: function(_){x=_;}},
  10682. yScale: {get: function(){return y;}, set: function(_){y=_;}},
  10683. pointScale: {get: function(){return z;}, set: function(_){z=_;}},
  10684. xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
  10685. yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
  10686. pointDomain: {get: function(){return sizeDomain;}, set: function(_){sizeDomain=_;}},
  10687. xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
  10688. yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
  10689. pointRange: {get: function(){return sizeRange;}, set: function(_){sizeRange=_;}},
  10690. forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
  10691. forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
  10692. forcePoint: {get: function(){return forceSize;}, set: function(_){forceSize=_;}},
  10693. interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
  10694. pointActive: {get: function(){return pointActive;}, set: function(_){pointActive=_;}},
  10695. padDataOuter: {get: function(){return padDataOuter;}, set: function(_){padDataOuter=_;}},
  10696. padData: {get: function(){return padData;}, set: function(_){padData=_;}},
  10697. clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
  10698. clipVoronoi: {get: function(){return clipVoronoi;}, set: function(_){clipVoronoi=_;}},
  10699. clipRadius: {get: function(){return clipRadius;}, set: function(_){clipRadius=_;}},
  10700. showVoronoi: {get: function(){return showVoronoi;}, set: function(_){showVoronoi=_;}},
  10701. id: {get: function(){return id;}, set: function(_){id=_;}},
  10702. interactiveUpdateDelay: {get:function(){return interactiveUpdateDelay;}, set: function(_){interactiveUpdateDelay=_;}},
  10703. showLabels: {get: function(){return showLabels;}, set: function(_){ showLabels = _;}},
  10704. // simple functor options
  10705. x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}},
  10706. y: {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}},
  10707. pointSize: {get: function(){return getSize;}, set: function(_){getSize = d3.functor(_);}},
  10708. pointShape: {get: function(){return getShape;}, set: function(_){getShape = d3.functor(_);}},
  10709. // options that require extra logic in the setter
  10710. margin: {get: function(){return margin;}, set: function(_){
  10711. margin.top = _.top !== undefined ? _.top : margin.top;
  10712. margin.right = _.right !== undefined ? _.right : margin.right;
  10713. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  10714. margin.left = _.left !== undefined ? _.left : margin.left;
  10715. }},
  10716. duration: {get: function(){return duration;}, set: function(_){
  10717. duration = _;
  10718. renderWatch.reset(duration);
  10719. }},
  10720. color: {get: function(){return color;}, set: function(_){
  10721. color = nv.utils.getColor(_);
  10722. }},
  10723. useVoronoi: {get: function(){return useVoronoi;}, set: function(_){
  10724. useVoronoi = _;
  10725. if (useVoronoi === false) {
  10726. clipVoronoi = false;
  10727. }
  10728. }}
  10729. });
  10730. nv.utils.initOptions(chart);
  10731. return chart;
  10732. };
  10733. nv.models.scatterChart = function() {
  10734. "use strict";
  10735. //============================================================
  10736. // Public Variables with Default Settings
  10737. //------------------------------------------------------------
  10738. var scatter = nv.models.scatter()
  10739. , xAxis = nv.models.axis()
  10740. , yAxis = nv.models.axis()
  10741. , legend = nv.models.legend()
  10742. , distX = nv.models.distribution()
  10743. , distY = nv.models.distribution()
  10744. , tooltip = nv.models.tooltip()
  10745. ;
  10746. var margin = {top: 30, right: 20, bottom: 50, left: 75}
  10747. , width = null
  10748. , height = null
  10749. , container = null
  10750. , color = nv.utils.defaultColor()
  10751. , x = scatter.xScale()
  10752. , y = scatter.yScale()
  10753. , showDistX = false
  10754. , showDistY = false
  10755. , showLegend = true
  10756. , showXAxis = true
  10757. , showYAxis = true
  10758. , rightAlignYAxis = false
  10759. , state = nv.utils.state()
  10760. , defaultState = null
  10761. , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
  10762. , noData = null
  10763. , duration = 250
  10764. , showLabels = false
  10765. ;
  10766. scatter.xScale(x).yScale(y);
  10767. xAxis.orient('bottom').tickPadding(10);
  10768. yAxis
  10769. .orient((rightAlignYAxis) ? 'right' : 'left')
  10770. .tickPadding(10)
  10771. ;
  10772. distX.axis('x');
  10773. distY.axis('y');
  10774. tooltip
  10775. .headerFormatter(function(d, i) {
  10776. return xAxis.tickFormat()(d, i);
  10777. })
  10778. .valueFormatter(function(d, i) {
  10779. return yAxis.tickFormat()(d, i);
  10780. });
  10781. //============================================================
  10782. // Private Variables
  10783. //------------------------------------------------------------
  10784. var x0, y0
  10785. , renderWatch = nv.utils.renderWatch(dispatch, duration);
  10786. var stateGetter = function(data) {
  10787. return function(){
  10788. return {
  10789. active: data.map(function(d) { return !d.disabled })
  10790. };
  10791. }
  10792. };
  10793. var stateSetter = function(data) {
  10794. return function(state) {
  10795. if (state.active !== undefined)
  10796. data.forEach(function(series,i) {
  10797. series.disabled = !state.active[i];
  10798. });
  10799. }
  10800. };
  10801. function chart(selection) {
  10802. renderWatch.reset();
  10803. renderWatch.models(scatter);
  10804. if (showXAxis) renderWatch.models(xAxis);
  10805. if (showYAxis) renderWatch.models(yAxis);
  10806. if (showDistX) renderWatch.models(distX);
  10807. if (showDistY) renderWatch.models(distY);
  10808. selection.each(function(data) {
  10809. var that = this;
  10810. container = d3.select(this);
  10811. nv.utils.initSVG(container);
  10812. var availableWidth = nv.utils.availableWidth(width, container, margin),
  10813. availableHeight = nv.utils.availableHeight(height, container, margin);
  10814. chart.update = function() {
  10815. if (duration === 0)
  10816. container.call(chart);
  10817. else
  10818. container.transition().duration(duration).call(chart);
  10819. };
  10820. chart.container = this;
  10821. state
  10822. .setter(stateSetter(data), chart.update)
  10823. .getter(stateGetter(data))
  10824. .update();
  10825. // DEPRECATED set state.disableddisabled
  10826. state.disabled = data.map(function(d) { return !!d.disabled });
  10827. if (!defaultState) {
  10828. var key;
  10829. defaultState = {};
  10830. for (key in state) {
  10831. if (state[key] instanceof Array)
  10832. defaultState[key] = state[key].slice(0);
  10833. else
  10834. defaultState[key] = state[key];
  10835. }
  10836. }
  10837. // Display noData message if there's nothing to show.
  10838. if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
  10839. nv.utils.noData(chart, container);
  10840. renderWatch.renderEnd('scatter immediate');
  10841. return chart;
  10842. } else {
  10843. container.selectAll('.nv-noData').remove();
  10844. }
  10845. // Setup Scales
  10846. x = scatter.xScale();
  10847. y = scatter.yScale();
  10848. // Setup containers and skeleton of chart
  10849. var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
  10850. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
  10851. var gEnter = wrapEnter.append('g');
  10852. var g = wrap.select('g');
  10853. // background for pointer events
  10854. gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none");
  10855. gEnter.append('g').attr('class', 'nv-x nv-axis');
  10856. gEnter.append('g').attr('class', 'nv-y nv-axis');
  10857. gEnter.append('g').attr('class', 'nv-scatterWrap');
  10858. gEnter.append('g').attr('class', 'nv-regressionLinesWrap');
  10859. gEnter.append('g').attr('class', 'nv-distWrap');
  10860. gEnter.append('g').attr('class', 'nv-legendWrap');
  10861. if (rightAlignYAxis) {
  10862. g.select(".nv-y.nv-axis")
  10863. .attr("transform", "translate(" + availableWidth + ",0)");
  10864. }
  10865. // Legend
  10866. if (!showLegend) {
  10867. g.select('.nv-legendWrap').selectAll('*').remove();
  10868. } else {
  10869. var legendWidth = availableWidth;
  10870. legend.width(legendWidth);
  10871. wrap.select('.nv-legendWrap')
  10872. .datum(data)
  10873. .call(legend);
  10874. if (legend.height() > margin.top) {
  10875. margin.top = legend.height();
  10876. availableHeight = nv.utils.availableHeight(height, container, margin);
  10877. }
  10878. wrap.select('.nv-legendWrap')
  10879. .attr('transform', 'translate(0' + ',' + (-margin.top) +')');
  10880. }
  10881. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  10882. // Main Chart Component(s)
  10883. scatter
  10884. .width(availableWidth)
  10885. .height(availableHeight)
  10886. .color(data.map(function(d,i) {
  10887. d.color = d.color || color(d, i);
  10888. return d.color;
  10889. }).filter(function(d,i) { return !data[i].disabled }))
  10890. .showLabels(showLabels);
  10891. wrap.select('.nv-scatterWrap')
  10892. .datum(data.filter(function(d) { return !d.disabled }))
  10893. .call(scatter);
  10894. wrap.select('.nv-regressionLinesWrap')
  10895. .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')');
  10896. var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines')
  10897. .data(function (d) {
  10898. return d;
  10899. });
  10900. regWrap.enter().append('g').attr('class', 'nv-regLines');
  10901. var regLine = regWrap.selectAll('.nv-regLine')
  10902. .data(function (d) {
  10903. return [d]
  10904. });
  10905. regLine.enter()
  10906. .append('line').attr('class', 'nv-regLine')
  10907. .style('stroke-opacity', 0);
  10908. // don't add lines unless we have slope and intercept to use
  10909. regLine.filter(function(d) {
  10910. return d.intercept && d.slope;
  10911. })
  10912. .watchTransition(renderWatch, 'scatterPlusLineChart: regline')
  10913. .attr('x1', x.range()[0])
  10914. .attr('x2', x.range()[1])
  10915. .attr('y1', function (d, i) {
  10916. return y(x.domain()[0] * d.slope + d.intercept)
  10917. })
  10918. .attr('y2', function (d, i) {
  10919. return y(x.domain()[1] * d.slope + d.intercept)
  10920. })
  10921. .style('stroke', function (d, i, j) {
  10922. return color(d, j)
  10923. })
  10924. .style('stroke-opacity', function (d, i) {
  10925. return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1
  10926. });
  10927. // Setup Axes
  10928. if (showXAxis) {
  10929. xAxis
  10930. .scale(x)
  10931. ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
  10932. .tickSize( -availableHeight , 0);
  10933. g.select('.nv-x.nv-axis')
  10934. .attr('transform', 'translate(0,' + y.range()[0] + ')')
  10935. .call(xAxis);
  10936. }
  10937. if (showYAxis) {
  10938. yAxis
  10939. .scale(y)
  10940. ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
  10941. .tickSize( -availableWidth, 0);
  10942. g.select('.nv-y.nv-axis')
  10943. .call(yAxis);
  10944. }
  10945. // Setup Distribution
  10946. if (showDistX) {
  10947. distX
  10948. .getData(scatter.x())
  10949. .scale(x)
  10950. .width(availableWidth)
  10951. .color(data.map(function(d,i) {
  10952. return d.color || color(d, i);
  10953. }).filter(function(d,i) { return !data[i].disabled }));
  10954. gEnter.select('.nv-distWrap').append('g')
  10955. .attr('class', 'nv-distributionX');
  10956. g.select('.nv-distributionX')
  10957. .attr('transform', 'translate(0,' + y.range()[0] + ')')
  10958. .datum(data.filter(function(d) { return !d.disabled }))
  10959. .call(distX);
  10960. }
  10961. if (showDistY) {
  10962. distY
  10963. .getData(scatter.y())
  10964. .scale(y)
  10965. .width(availableHeight)
  10966. .color(data.map(function(d,i) {
  10967. return d.color || color(d, i);
  10968. }).filter(function(d,i) { return !data[i].disabled }));
  10969. gEnter.select('.nv-distWrap').append('g')
  10970. .attr('class', 'nv-distributionY');
  10971. g.select('.nv-distributionY')
  10972. .attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
  10973. .datum(data.filter(function(d) { return !d.disabled }))
  10974. .call(distY);
  10975. }
  10976. //============================================================
  10977. // Event Handling/Dispatching (in chart's scope)
  10978. //------------------------------------------------------------
  10979. legend.dispatch.on('stateChange', function(newState) {
  10980. for (var key in newState)
  10981. state[key] = newState[key];
  10982. dispatch.stateChange(state);
  10983. chart.update();
  10984. });
  10985. // Update chart from a state object passed to event handler
  10986. dispatch.on('changeState', function(e) {
  10987. if (typeof e.disabled !== 'undefined') {
  10988. data.forEach(function(series,i) {
  10989. series.disabled = e.disabled[i];
  10990. });
  10991. state.disabled = e.disabled;
  10992. }
  10993. chart.update();
  10994. });
  10995. // mouseover needs availableHeight so we just keep scatter mouse events inside the chart block
  10996. scatter.dispatch.on('elementMouseout.tooltip', function(evt) {
  10997. tooltip.hidden(true);
  10998. container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex)
  10999. .attr('y1', 0);
  11000. container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
  11001. .attr('x2', distY.size());
  11002. });
  11003. scatter.dispatch.on('elementMouseover.tooltip', function(evt) {
  11004. container.select('.nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex)
  11005. .attr('y1', evt.relativePos[1] - availableHeight);
  11006. container.select('.nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
  11007. .attr('x2', evt.relativePos[0] + distX.size());
  11008. tooltip.data(evt).hidden(false);
  11009. });
  11010. //store old scales for use in transitions on update
  11011. x0 = x.copy();
  11012. y0 = y.copy();
  11013. });
  11014. renderWatch.renderEnd('scatter with line immediate');
  11015. return chart;
  11016. }
  11017. //============================================================
  11018. // Expose Public Variables
  11019. //------------------------------------------------------------
  11020. // expose chart's sub-components
  11021. chart.dispatch = dispatch;
  11022. chart.scatter = scatter;
  11023. chart.legend = legend;
  11024. chart.xAxis = xAxis;
  11025. chart.yAxis = yAxis;
  11026. chart.distX = distX;
  11027. chart.distY = distY;
  11028. chart.tooltip = tooltip;
  11029. chart.options = nv.utils.optionsFunc.bind(chart);
  11030. chart._options = Object.create({}, {
  11031. // simple options, just get/set the necessary values
  11032. width: {get: function(){return width;}, set: function(_){width=_;}},
  11033. height: {get: function(){return height;}, set: function(_){height=_;}},
  11034. container: {get: function(){return container;}, set: function(_){container=_;}},
  11035. showDistX: {get: function(){return showDistX;}, set: function(_){showDistX=_;}},
  11036. showDistY: {get: function(){return showDistY;}, set: function(_){showDistY=_;}},
  11037. showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
  11038. showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
  11039. showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
  11040. defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
  11041. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  11042. duration: {get: function(){return duration;}, set: function(_){duration=_;}},
  11043. showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}},
  11044. // options that require extra logic in the setter
  11045. margin: {get: function(){return margin;}, set: function(_){
  11046. margin.top = _.top !== undefined ? _.top : margin.top;
  11047. margin.right = _.right !== undefined ? _.right : margin.right;
  11048. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  11049. margin.left = _.left !== undefined ? _.left : margin.left;
  11050. }},
  11051. rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
  11052. rightAlignYAxis = _;
  11053. yAxis.orient( (_) ? 'right' : 'left');
  11054. }},
  11055. color: {get: function(){return color;}, set: function(_){
  11056. color = nv.utils.getColor(_);
  11057. legend.color(color);
  11058. distX.color(color);
  11059. distY.color(color);
  11060. }}
  11061. });
  11062. nv.utils.inheritOptions(chart, scatter);
  11063. nv.utils.initOptions(chart);
  11064. return chart;
  11065. };
  11066. nv.models.sparkline = function() {
  11067. "use strict";
  11068. //============================================================
  11069. // Public Variables with Default Settings
  11070. //------------------------------------------------------------
  11071. var margin = {top: 2, right: 0, bottom: 2, left: 0}
  11072. , width = 400
  11073. , height = 32
  11074. , container = null
  11075. , animate = true
  11076. , x = d3.scale.linear()
  11077. , y = d3.scale.linear()
  11078. , getX = function(d) { return d.x }
  11079. , getY = function(d) { return d.y }
  11080. , color = nv.utils.getColor(['#000'])
  11081. , xDomain
  11082. , yDomain
  11083. , xRange
  11084. , yRange
  11085. , showMinMaxPoints = true
  11086. , showCurrentPoint = true
  11087. , dispatch = d3.dispatch('renderEnd')
  11088. ;
  11089. //============================================================
  11090. // Private Variables
  11091. //------------------------------------------------------------
  11092. var renderWatch = nv.utils.renderWatch(dispatch);
  11093. function chart(selection) {
  11094. renderWatch.reset();
  11095. selection.each(function(data) {
  11096. var availableWidth = width - margin.left - margin.right,
  11097. availableHeight = height - margin.top - margin.bottom;
  11098. container = d3.select(this);
  11099. nv.utils.initSVG(container);
  11100. // Setup Scales
  11101. x .domain(xDomain || d3.extent(data, getX ))
  11102. .range(xRange || [0, availableWidth]);
  11103. y .domain(yDomain || d3.extent(data, getY ))
  11104. .range(yRange || [availableHeight, 0]);
  11105. // Setup containers and skeleton of chart
  11106. var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]);
  11107. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline');
  11108. var gEnter = wrapEnter.append('g');
  11109. var g = wrap.select('g');
  11110. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
  11111. var paths = wrap.selectAll('path')
  11112. .data(function(d) { return [d] });
  11113. paths.enter().append('path');
  11114. paths.exit().remove();
  11115. paths
  11116. .style('stroke', function(d,i) { return d.color || color(d, i) })
  11117. .attr('d', d3.svg.line()
  11118. .x(function(d,i) { return x(getX(d,i)) })
  11119. .y(function(d,i) { return y(getY(d,i)) })
  11120. );
  11121. // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent)
  11122. var points = wrap.selectAll('circle.nv-point')
  11123. .data(function(data) {
  11124. var yValues = data.map(function(d, i) { return getY(d,i); });
  11125. function pointIndex(index) {
  11126. if (index != -1) {
  11127. var result = data[index];
  11128. result.pointIndex = index;
  11129. return result;
  11130. } else {
  11131. return null;
  11132. }
  11133. }
  11134. var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])),
  11135. minPoint = pointIndex(yValues.indexOf(y.domain()[0])),
  11136. currentPoint = pointIndex(yValues.length - 1);
  11137. return [(showMinMaxPoints ? minPoint : null), (showMinMaxPoints ? maxPoint : null), (showCurrentPoint ? currentPoint : null)].filter(function (d) {return d != null;});
  11138. });
  11139. points.enter().append('circle');
  11140. points.exit().remove();
  11141. points
  11142. .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) })
  11143. .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) })
  11144. .attr('r', 2)
  11145. .attr('class', function(d,i) {
  11146. return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' :
  11147. getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue'
  11148. });
  11149. });
  11150. renderWatch.renderEnd('sparkline immediate');
  11151. return chart;
  11152. }
  11153. //============================================================
  11154. // Expose Public Variables
  11155. //------------------------------------------------------------
  11156. chart.options = nv.utils.optionsFunc.bind(chart);
  11157. chart._options = Object.create({}, {
  11158. // simple options, just get/set the necessary values
  11159. width: {get: function(){return width;}, set: function(_){width=_;}},
  11160. height: {get: function(){return height;}, set: function(_){height=_;}},
  11161. xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
  11162. yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
  11163. xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
  11164. yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
  11165. xScale: {get: function(){return x;}, set: function(_){x=_;}},
  11166. yScale: {get: function(){return y;}, set: function(_){y=_;}},
  11167. animate: {get: function(){return animate;}, set: function(_){animate=_;}},
  11168. showMinMaxPoints: {get: function(){return showMinMaxPoints;}, set: function(_){showMinMaxPoints=_;}},
  11169. showCurrentPoint: {get: function(){return showCurrentPoint;}, set: function(_){showCurrentPoint=_;}},
  11170. //functor options
  11171. x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}},
  11172. y: {get: function(){return getY;}, set: function(_){getY=d3.functor(_);}},
  11173. // options that require extra logic in the setter
  11174. margin: {get: function(){return margin;}, set: function(_){
  11175. margin.top = _.top !== undefined ? _.top : margin.top;
  11176. margin.right = _.right !== undefined ? _.right : margin.right;
  11177. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  11178. margin.left = _.left !== undefined ? _.left : margin.left;
  11179. }},
  11180. color: {get: function(){return color;}, set: function(_){
  11181. color = nv.utils.getColor(_);
  11182. }}
  11183. });
  11184. chart.dispatch = dispatch;
  11185. nv.utils.initOptions(chart);
  11186. return chart;
  11187. };
  11188. nv.models.sparklinePlus = function() {
  11189. "use strict";
  11190. //============================================================
  11191. // Public Variables with Default Settings
  11192. //------------------------------------------------------------
  11193. var sparkline = nv.models.sparkline();
  11194. var margin = {top: 15, right: 100, bottom: 10, left: 50}
  11195. , width = null
  11196. , height = null
  11197. , x
  11198. , y
  11199. , index = []
  11200. , paused = false
  11201. , xTickFormat = d3.format(',r')
  11202. , yTickFormat = d3.format(',.2f')
  11203. , showLastValue = true
  11204. , alignValue = true
  11205. , rightAlignValue = false
  11206. , noData = null
  11207. , dispatch = d3.dispatch('renderEnd')
  11208. ;
  11209. //============================================================
  11210. // Private Variables
  11211. //------------------------------------------------------------
  11212. var renderWatch = nv.utils.renderWatch(dispatch);
  11213. function chart(selection) {
  11214. renderWatch.reset();
  11215. renderWatch.models(sparkline);
  11216. selection.each(function(data) {
  11217. var container = d3.select(this);
  11218. nv.utils.initSVG(container);
  11219. var availableWidth = nv.utils.availableWidth(width, container, margin),
  11220. availableHeight = nv.utils.availableHeight(height, container, margin);
  11221. chart.update = function() { container.call(chart); };
  11222. chart.container = this;
  11223. // Display No Data message if there's nothing to show.
  11224. if (!data || !data.length) {
  11225. nv.utils.noData(chart, container)
  11226. return chart;
  11227. } else {
  11228. container.selectAll('.nv-noData').remove();
  11229. }
  11230. var currentValue = sparkline.y()(data[data.length-1], data.length-1);
  11231. // Setup Scales
  11232. x = sparkline.xScale();
  11233. y = sparkline.yScale();
  11234. // Setup containers and skeleton of chart
  11235. var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]);
  11236. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus');
  11237. var gEnter = wrapEnter.append('g');
  11238. var g = wrap.select('g');
  11239. gEnter.append('g').attr('class', 'nv-sparklineWrap');
  11240. gEnter.append('g').attr('class', 'nv-valueWrap');
  11241. gEnter.append('g').attr('class', 'nv-hoverArea');
  11242. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  11243. // Main Chart Component(s)
  11244. var sparklineWrap = g.select('.nv-sparklineWrap');
  11245. sparkline.width(availableWidth).height(availableHeight);
  11246. sparklineWrap.call(sparkline);
  11247. if (showLastValue) {
  11248. var valueWrap = g.select('.nv-valueWrap');
  11249. var value = valueWrap.selectAll('.nv-currentValue')
  11250. .data([currentValue]);
  11251. value.enter().append('text').attr('class', 'nv-currentValue')
  11252. .attr('dx', rightAlignValue ? -8 : 8)
  11253. .attr('dy', '.9em')
  11254. .style('text-anchor', rightAlignValue ? 'end' : 'start');
  11255. value
  11256. .attr('x', availableWidth + (rightAlignValue ? margin.right : 0))
  11257. .attr('y', alignValue ? function (d) {
  11258. return y(d)
  11259. } : 0)
  11260. .style('fill', sparkline.color()(data[data.length - 1], data.length - 1))
  11261. .text(yTickFormat(currentValue));
  11262. }
  11263. gEnter.select('.nv-hoverArea').append('rect')
  11264. .on('mousemove', sparklineHover)
  11265. .on('click', function() { paused = !paused })
  11266. .on('mouseout', function() { index = []; updateValueLine(); });
  11267. g.select('.nv-hoverArea rect')
  11268. .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' })
  11269. .attr('width', availableWidth + margin.left + margin.right)
  11270. .attr('height', availableHeight + margin.top);
  11271. //index is currently global (within the chart), may or may not keep it that way
  11272. function updateValueLine() {
  11273. if (paused) return;
  11274. var hoverValue = g.selectAll('.nv-hoverValue').data(index);
  11275. var hoverEnter = hoverValue.enter()
  11276. .append('g').attr('class', 'nv-hoverValue')
  11277. .style('stroke-opacity', 0)
  11278. .style('fill-opacity', 0);
  11279. hoverValue.exit()
  11280. .transition().duration(250)
  11281. .style('stroke-opacity', 0)
  11282. .style('fill-opacity', 0)
  11283. .remove();
  11284. hoverValue
  11285. .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' })
  11286. .transition().duration(250)
  11287. .style('stroke-opacity', 1)
  11288. .style('fill-opacity', 1);
  11289. if (!index.length) return;
  11290. hoverEnter.append('line')
  11291. .attr('x1', 0)
  11292. .attr('y1', -margin.top)
  11293. .attr('x2', 0)
  11294. .attr('y2', availableHeight);
  11295. hoverEnter.append('text').attr('class', 'nv-xValue')
  11296. .attr('x', -6)
  11297. .attr('y', -margin.top)
  11298. .attr('text-anchor', 'end')
  11299. .attr('dy', '.9em');
  11300. g.select('.nv-hoverValue .nv-xValue')
  11301. .text(xTickFormat(sparkline.x()(data[index[0]], index[0])));
  11302. hoverEnter.append('text').attr('class', 'nv-yValue')
  11303. .attr('x', 6)
  11304. .attr('y', -margin.top)
  11305. .attr('text-anchor', 'start')
  11306. .attr('dy', '.9em');
  11307. g.select('.nv-hoverValue .nv-yValue')
  11308. .text(yTickFormat(sparkline.y()(data[index[0]], index[0])));
  11309. }
  11310. function sparklineHover() {
  11311. if (paused) return;
  11312. var pos = d3.mouse(this)[0] - margin.left;
  11313. function getClosestIndex(data, x) {
  11314. var distance = Math.abs(sparkline.x()(data[0], 0) - x);
  11315. var closestIndex = 0;
  11316. for (var i = 0; i < data.length; i++){
  11317. if (Math.abs(sparkline.x()(data[i], i) - x) < distance) {
  11318. distance = Math.abs(sparkline.x()(data[i], i) - x);
  11319. closestIndex = i;
  11320. }
  11321. }
  11322. return closestIndex;
  11323. }
  11324. index = [getClosestIndex(data, Math.round(x.invert(pos)))];
  11325. updateValueLine();
  11326. }
  11327. });
  11328. renderWatch.renderEnd('sparklinePlus immediate');
  11329. return chart;
  11330. }
  11331. //============================================================
  11332. // Expose Public Variables
  11333. //------------------------------------------------------------
  11334. // expose chart's sub-components
  11335. chart.dispatch = dispatch;
  11336. chart.sparkline = sparkline;
  11337. chart.options = nv.utils.optionsFunc.bind(chart);
  11338. chart._options = Object.create({}, {
  11339. // simple options, just get/set the necessary values
  11340. width: {get: function(){return width;}, set: function(_){width=_;}},
  11341. height: {get: function(){return height;}, set: function(_){height=_;}},
  11342. xTickFormat: {get: function(){return xTickFormat;}, set: function(_){xTickFormat=_;}},
  11343. yTickFormat: {get: function(){return yTickFormat;}, set: function(_){yTickFormat=_;}},
  11344. showLastValue: {get: function(){return showLastValue;}, set: function(_){showLastValue=_;}},
  11345. alignValue: {get: function(){return alignValue;}, set: function(_){alignValue=_;}},
  11346. rightAlignValue: {get: function(){return rightAlignValue;}, set: function(_){rightAlignValue=_;}},
  11347. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  11348. // options that require extra logic in the setter
  11349. margin: {get: function(){return margin;}, set: function(_){
  11350. margin.top = _.top !== undefined ? _.top : margin.top;
  11351. margin.right = _.right !== undefined ? _.right : margin.right;
  11352. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  11353. margin.left = _.left !== undefined ? _.left : margin.left;
  11354. }}
  11355. });
  11356. nv.utils.inheritOptions(chart, sparkline);
  11357. nv.utils.initOptions(chart);
  11358. return chart;
  11359. };
  11360. nv.models.stackedArea = function() {
  11361. "use strict";
  11362. //============================================================
  11363. // Public Variables with Default Settings
  11364. //------------------------------------------------------------
  11365. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  11366. , width = 960
  11367. , height = 500
  11368. , color = nv.utils.defaultColor() // a function that computes the color
  11369. , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one
  11370. , container = null
  11371. , getX = function(d) { return d.x } // accessor to get the x value from a data point
  11372. , getY = function(d) { return d.y } // accessor to get the y value from a data point
  11373. , 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
  11374. , style = 'stack'
  11375. , offset = 'zero'
  11376. , order = 'default'
  11377. , interpolate = 'linear' // controls the line interpolation
  11378. , clipEdge = false // if true, masks lines within x and y scale
  11379. , x //can be accessed via chart.xScale()
  11380. , y //can be accessed via chart.yScale()
  11381. , scatter = nv.models.scatter()
  11382. , duration = 250
  11383. , dispatch = d3.dispatch('areaClick', 'areaMouseover', 'areaMouseout','renderEnd', 'elementClick', 'elementMouseover', 'elementMouseout')
  11384. ;
  11385. scatter
  11386. .pointSize(2.2) // default size
  11387. .pointDomain([2.2, 2.2]) // all the same size by default
  11388. ;
  11389. /************************************
  11390. * offset:
  11391. * 'wiggle' (stream)
  11392. * 'zero' (stacked)
  11393. * 'expand' (normalize to 100%)
  11394. * 'silhouette' (simple centered)
  11395. *
  11396. * order:
  11397. * 'inside-out' (stream)
  11398. * 'default' (input order)
  11399. ************************************/
  11400. var renderWatch = nv.utils.renderWatch(dispatch, duration);
  11401. function chart(selection) {
  11402. renderWatch.reset();
  11403. renderWatch.models(scatter);
  11404. selection.each(function(data) {
  11405. var availableWidth = width - margin.left - margin.right,
  11406. availableHeight = height - margin.top - margin.bottom;
  11407. container = d3.select(this);
  11408. nv.utils.initSVG(container);
  11409. // Setup Scales
  11410. x = scatter.xScale();
  11411. y = scatter.yScale();
  11412. var dataRaw = data;
  11413. // Injecting point index into each point because d3.layout.stack().out does not give index
  11414. data.forEach(function(aseries, i) {
  11415. aseries.seriesIndex = i;
  11416. aseries.values = aseries.values.map(function(d, j) {
  11417. d.index = j;
  11418. d.seriesIndex = i;
  11419. return d;
  11420. });
  11421. });
  11422. var dataFiltered = data.filter(function(series) {
  11423. return !series.disabled;
  11424. });
  11425. data = d3.layout.stack()
  11426. .order(order)
  11427. .offset(offset)
  11428. .values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion
  11429. .x(getX)
  11430. .y(getY)
  11431. .out(function(d, y0, y) {
  11432. d.display = {
  11433. y: y,
  11434. y0: y0
  11435. };
  11436. })
  11437. (dataFiltered);
  11438. // Setup containers and skeleton of chart
  11439. var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]);
  11440. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea');
  11441. var defsEnter = wrapEnter.append('defs');
  11442. var gEnter = wrapEnter.append('g');
  11443. var g = wrap.select('g');
  11444. gEnter.append('g').attr('class', 'nv-areaWrap');
  11445. gEnter.append('g').attr('class', 'nv-scatterWrap');
  11446. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  11447. // If the user has not specified forceY, make sure 0 is included in the domain
  11448. // Otherwise, use user-specified values for forceY
  11449. if (scatter.forceY().length == 0) {
  11450. scatter.forceY().push(0);
  11451. }
  11452. scatter
  11453. .width(availableWidth)
  11454. .height(availableHeight)
  11455. .x(getX)
  11456. .y(function(d) {
  11457. if (d.display !== undefined) { return d.display.y + d.display.y0; }
  11458. })
  11459. .color(data.map(function(d,i) {
  11460. d.color = d.color || color(d, d.seriesIndex);
  11461. return d.color;
  11462. }));
  11463. var scatterWrap = g.select('.nv-scatterWrap')
  11464. .datum(data);
  11465. scatterWrap.call(scatter);
  11466. defsEnter.append('clipPath')
  11467. .attr('id', 'nv-edge-clip-' + id)
  11468. .append('rect');
  11469. wrap.select('#nv-edge-clip-' + id + ' rect')
  11470. .attr('width', availableWidth)
  11471. .attr('height', availableHeight);
  11472. g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
  11473. var area = d3.svg.area()
  11474. .defined(defined)
  11475. .x(function(d,i) { return x(getX(d,i)) })
  11476. .y0(function(d) {
  11477. return y(d.display.y0)
  11478. })
  11479. .y1(function(d) {
  11480. return y(d.display.y + d.display.y0)
  11481. })
  11482. .interpolate(interpolate);
  11483. var zeroArea = d3.svg.area()
  11484. .defined(defined)
  11485. .x(function(d,i) { return x(getX(d,i)) })
  11486. .y0(function(d) { return y(d.display.y0) })
  11487. .y1(function(d) { return y(d.display.y0) });
  11488. var path = g.select('.nv-areaWrap').selectAll('path.nv-area')
  11489. .data(function(d) { return d });
  11490. path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i })
  11491. .attr('d', function(d,i){
  11492. return zeroArea(d.values, d.seriesIndex);
  11493. })
  11494. .on('mouseover', function(d,i) {
  11495. d3.select(this).classed('hover', true);
  11496. dispatch.areaMouseover({
  11497. point: d,
  11498. series: d.key,
  11499. pos: [d3.event.pageX, d3.event.pageY],
  11500. seriesIndex: d.seriesIndex
  11501. });
  11502. })
  11503. .on('mouseout', function(d,i) {
  11504. d3.select(this).classed('hover', false);
  11505. dispatch.areaMouseout({
  11506. point: d,
  11507. series: d.key,
  11508. pos: [d3.event.pageX, d3.event.pageY],
  11509. seriesIndex: d.seriesIndex
  11510. });
  11511. })
  11512. .on('click', function(d,i) {
  11513. d3.select(this).classed('hover', false);
  11514. dispatch.areaClick({
  11515. point: d,
  11516. series: d.key,
  11517. pos: [d3.event.pageX, d3.event.pageY],
  11518. seriesIndex: d.seriesIndex
  11519. });
  11520. });
  11521. path.exit().remove();
  11522. path.style('fill', function(d,i){
  11523. return d.color || color(d, d.seriesIndex)
  11524. })
  11525. .style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) });
  11526. path.watchTransition(renderWatch,'stackedArea path')
  11527. .attr('d', function(d,i) {
  11528. return area(d.values,i)
  11529. });
  11530. //============================================================
  11531. // Event Handling/Dispatching (in chart's scope)
  11532. //------------------------------------------------------------
  11533. scatter.dispatch.on('elementMouseover.area', function(e) {
  11534. g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true);
  11535. });
  11536. scatter.dispatch.on('elementMouseout.area', function(e) {
  11537. g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false);
  11538. });
  11539. //Special offset functions
  11540. chart.d3_stackedOffset_stackPercent = function(stackData) {
  11541. var n = stackData.length, //How many series
  11542. m = stackData[0].length, //how many points per series
  11543. i,
  11544. j,
  11545. o,
  11546. y0 = [];
  11547. for (j = 0; j < m; ++j) { //Looping through all points
  11548. for (i = 0, o = 0; i < dataRaw.length; i++) { //looping through all series
  11549. o += getY(dataRaw[i].values[j]); //total y value of all series at a certian point in time.
  11550. }
  11551. if (o) for (i = 0; i < n; i++) { //(total y value of all series at point in time i) != 0
  11552. stackData[i][j][1] /= o;
  11553. } else { //(total y value of all series at point in time i) == 0
  11554. for (i = 0; i < n; i++) {
  11555. stackData[i][j][1] = 0;
  11556. }
  11557. }
  11558. }
  11559. for (j = 0; j < m; ++j) y0[j] = 0;
  11560. return y0;
  11561. };
  11562. });
  11563. renderWatch.renderEnd('stackedArea immediate');
  11564. return chart;
  11565. }
  11566. //============================================================
  11567. // Global getters and setters
  11568. //------------------------------------------------------------
  11569. chart.dispatch = dispatch;
  11570. chart.scatter = scatter;
  11571. scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); });
  11572. scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); });
  11573. scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); });
  11574. chart.interpolate = function(_) {
  11575. if (!arguments.length) return interpolate;
  11576. interpolate = _;
  11577. return chart;
  11578. };
  11579. chart.duration = function(_) {
  11580. if (!arguments.length) return duration;
  11581. duration = _;
  11582. renderWatch.reset(duration);
  11583. scatter.duration(duration);
  11584. return chart;
  11585. };
  11586. chart.dispatch = dispatch;
  11587. chart.scatter = scatter;
  11588. chart.options = nv.utils.optionsFunc.bind(chart);
  11589. chart._options = Object.create({}, {
  11590. // simple options, just get/set the necessary values
  11591. width: {get: function(){return width;}, set: function(_){width=_;}},
  11592. height: {get: function(){return height;}, set: function(_){height=_;}},
  11593. defined: {get: function(){return defined;}, set: function(_){defined=_;}},
  11594. clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
  11595. offset: {get: function(){return offset;}, set: function(_){offset=_;}},
  11596. order: {get: function(){return order;}, set: function(_){order=_;}},
  11597. interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
  11598. // simple functor options
  11599. x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}},
  11600. y: {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}},
  11601. // options that require extra logic in the setter
  11602. margin: {get: function(){return margin;}, set: function(_){
  11603. margin.top = _.top !== undefined ? _.top : margin.top;
  11604. margin.right = _.right !== undefined ? _.right : margin.right;
  11605. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  11606. margin.left = _.left !== undefined ? _.left : margin.left;
  11607. }},
  11608. color: {get: function(){return color;}, set: function(_){
  11609. color = nv.utils.getColor(_);
  11610. }},
  11611. style: {get: function(){return style;}, set: function(_){
  11612. style = _;
  11613. switch (style) {
  11614. case 'stack':
  11615. chart.offset('zero');
  11616. chart.order('default');
  11617. break;
  11618. case 'stream':
  11619. chart.offset('wiggle');
  11620. chart.order('inside-out');
  11621. break;
  11622. case 'stream-center':
  11623. chart.offset('silhouette');
  11624. chart.order('inside-out');
  11625. break;
  11626. case 'expand':
  11627. chart.offset('expand');
  11628. chart.order('default');
  11629. break;
  11630. case 'stack_percent':
  11631. chart.offset(chart.d3_stackedOffset_stackPercent);
  11632. chart.order('default');
  11633. break;
  11634. }
  11635. }},
  11636. duration: {get: function(){return duration;}, set: function(_){
  11637. duration = _;
  11638. renderWatch.reset(duration);
  11639. scatter.duration(duration);
  11640. }}
  11641. });
  11642. nv.utils.inheritOptions(chart, scatter);
  11643. nv.utils.initOptions(chart);
  11644. return chart;
  11645. };
  11646. nv.models.stackedAreaChart = function() {
  11647. "use strict";
  11648. //============================================================
  11649. // Public Variables with Default Settings
  11650. //------------------------------------------------------------
  11651. var stacked = nv.models.stackedArea()
  11652. , xAxis = nv.models.axis()
  11653. , yAxis = nv.models.axis()
  11654. , legend = nv.models.legend()
  11655. , controls = nv.models.legend()
  11656. , interactiveLayer = nv.interactiveGuideline()
  11657. , tooltip = nv.models.tooltip()
  11658. , focus = nv.models.focus(nv.models.stackedArea())
  11659. ;
  11660. var margin = {top: 30, right: 25, bottom: 50, left: 60}
  11661. , width = null
  11662. , height = null
  11663. , color = nv.utils.defaultColor()
  11664. , showControls = true
  11665. , showLegend = true
  11666. , showXAxis = true
  11667. , showYAxis = true
  11668. , rightAlignYAxis = false
  11669. , focusEnable = false
  11670. , useInteractiveGuideline = false
  11671. , showTotalInTooltip = true
  11672. , totalLabel = 'TOTAL'
  11673. , x //can be accessed via chart.xScale()
  11674. , y //can be accessed via chart.yScale()
  11675. , state = nv.utils.state()
  11676. , defaultState = null
  11677. , noData = null
  11678. , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd')
  11679. , controlWidth = 250
  11680. , controlOptions = ['Stacked','Stream','Expanded']
  11681. , controlLabels = {}
  11682. , duration = 250
  11683. ;
  11684. state.style = stacked.style();
  11685. xAxis.orient('bottom').tickPadding(7);
  11686. yAxis.orient((rightAlignYAxis) ? 'right' : 'left');
  11687. tooltip
  11688. .headerFormatter(function(d, i) {
  11689. return xAxis.tickFormat()(d, i);
  11690. })
  11691. .valueFormatter(function(d, i) {
  11692. return yAxis.tickFormat()(d, i);
  11693. });
  11694. interactiveLayer.tooltip
  11695. .headerFormatter(function(d, i) {
  11696. return xAxis.tickFormat()(d, i);
  11697. })
  11698. .valueFormatter(function(d, i) {
  11699. return d == null ? "N/A" : yAxis.tickFormat()(d, i);
  11700. });
  11701. var oldYTickFormat = null,
  11702. oldValueFormatter = null;
  11703. controls.updateState(false);
  11704. //============================================================
  11705. // Private Variables
  11706. //------------------------------------------------------------
  11707. var renderWatch = nv.utils.renderWatch(dispatch);
  11708. var style = stacked.style();
  11709. var stateGetter = function(data) {
  11710. return function(){
  11711. return {
  11712. active: data.map(function(d) { return !d.disabled }),
  11713. style: stacked.style()
  11714. };
  11715. }
  11716. };
  11717. var stateSetter = function(data) {
  11718. return function(state) {
  11719. if (state.style !== undefined)
  11720. style = state.style;
  11721. if (state.active !== undefined)
  11722. data.forEach(function(series,i) {
  11723. series.disabled = !state.active[i];
  11724. });
  11725. }
  11726. };
  11727. var percentFormatter = d3.format('%');
  11728. function chart(selection) {
  11729. renderWatch.reset();
  11730. renderWatch.models(stacked);
  11731. if (showXAxis) renderWatch.models(xAxis);
  11732. if (showYAxis) renderWatch.models(yAxis);
  11733. selection.each(function(data) {
  11734. var container = d3.select(this),
  11735. that = this;
  11736. nv.utils.initSVG(container);
  11737. var availableWidth = nv.utils.availableWidth(width, container, margin),
  11738. availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0);
  11739. chart.update = function() { container.transition().duration(duration).call(chart); };
  11740. chart.container = this;
  11741. state
  11742. .setter(stateSetter(data), chart.update)
  11743. .getter(stateGetter(data))
  11744. .update();
  11745. // DEPRECATED set state.disabled
  11746. state.disabled = data.map(function(d) { return !!d.disabled });
  11747. if (!defaultState) {
  11748. var key;
  11749. defaultState = {};
  11750. for (key in state) {
  11751. if (state[key] instanceof Array)
  11752. defaultState[key] = state[key].slice(0);
  11753. else
  11754. defaultState[key] = state[key];
  11755. }
  11756. }
  11757. // Display No Data message if there's nothing to show.
  11758. if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
  11759. nv.utils.noData(chart, container)
  11760. return chart;
  11761. } else {
  11762. container.selectAll('.nv-noData').remove();
  11763. }
  11764. // Setup Scales
  11765. x = stacked.xScale();
  11766. y = stacked.yScale();
  11767. // Setup containers and skeleton of chart
  11768. var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]);
  11769. var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g');
  11770. var g = wrap.select('g');
  11771. gEnter.append('g').attr('class', 'nv-legendWrap');
  11772. gEnter.append('g').attr('class', 'nv-controlsWrap');
  11773. var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
  11774. focusEnter.append('g').attr('class', 'nv-background').append('rect');
  11775. focusEnter.append('g').attr('class', 'nv-x nv-axis');
  11776. focusEnter.append('g').attr('class', 'nv-y nv-axis');
  11777. focusEnter.append('g').attr('class', 'nv-stackedWrap');
  11778. focusEnter.append('g').attr('class', 'nv-interactive');
  11779. // g.select("rect").attr("width",availableWidth).attr("height",availableHeight);
  11780. var contextEnter = gEnter.append('g').attr('class', 'nv-focusWrap');
  11781. // Legend
  11782. if (!showLegend) {
  11783. g.select('.nv-legendWrap').selectAll('*').remove();
  11784. } else {
  11785. var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth;
  11786. legend.width(legendWidth);
  11787. g.select('.nv-legendWrap').datum(data).call(legend);
  11788. if (legend.height() > margin.top) {
  11789. margin.top = legend.height();
  11790. availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0);
  11791. }
  11792. g.select('.nv-legendWrap')
  11793. .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')');
  11794. }
  11795. // Controls
  11796. if (!showControls) {
  11797. g.select('.nv-controlsWrap').selectAll('*').remove();
  11798. } else {
  11799. var controlsData = [
  11800. {
  11801. key: controlLabels.stacked || 'Stacked',
  11802. metaKey: 'Stacked',
  11803. disabled: stacked.style() != 'stack',
  11804. style: 'stack'
  11805. },
  11806. {
  11807. key: controlLabels.stream || 'Stream',
  11808. metaKey: 'Stream',
  11809. disabled: stacked.style() != 'stream',
  11810. style: 'stream'
  11811. },
  11812. {
  11813. key: controlLabels.expanded || 'Expanded',
  11814. metaKey: 'Expanded',
  11815. disabled: stacked.style() != 'expand',
  11816. style: 'expand'
  11817. },
  11818. {
  11819. key: controlLabels.stack_percent || 'Stack %',
  11820. metaKey: 'Stack_Percent',
  11821. disabled: stacked.style() != 'stack_percent',
  11822. style: 'stack_percent'
  11823. }
  11824. ];
  11825. controlWidth = (controlOptions.length/3) * 260;
  11826. controlsData = controlsData.filter(function(d) {
  11827. return controlOptions.indexOf(d.metaKey) !== -1;
  11828. });
  11829. controls
  11830. .width( controlWidth )
  11831. .color(['#444', '#444', '#444']);
  11832. g.select('.nv-controlsWrap')
  11833. .datum(controlsData)
  11834. .call(controls);
  11835. if (Math.max(controls.height(), legend.height()) > margin.top) {
  11836. margin.top = Math.max(controls.height(), legend.height());
  11837. availableHeight = nv.utils.availableHeight(height, container, margin);
  11838. }
  11839. g.select('.nv-controlsWrap')
  11840. .attr('transform', 'translate(0,' + (-margin.top) +')');
  11841. }
  11842. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  11843. if (rightAlignYAxis) {
  11844. g.select(".nv-y.nv-axis")
  11845. .attr("transform", "translate(" + availableWidth + ",0)");
  11846. }
  11847. //Set up interactive layer
  11848. if (useInteractiveGuideline) {
  11849. interactiveLayer
  11850. .width(availableWidth)
  11851. .height(availableHeight)
  11852. .margin({left: margin.left, top: margin.top})
  11853. .svgContainer(container)
  11854. .xScale(x);
  11855. wrap.select(".nv-interactive").call(interactiveLayer);
  11856. }
  11857. g.select('.nv-focus .nv-background rect')
  11858. .attr('width', availableWidth)
  11859. .attr('height', availableHeight);
  11860. stacked
  11861. .width(availableWidth)
  11862. .height(availableHeight)
  11863. .color(data.map(function(d,i) {
  11864. return d.color || color(d, i);
  11865. }).filter(function(d,i) { return !data[i].disabled; }));
  11866. var stackedWrap = g.select('.nv-focus .nv-stackedWrap')
  11867. .datum(data.filter(function(d) { return !d.disabled; }));
  11868. // Setup Axes
  11869. if (showXAxis) {
  11870. xAxis.scale(x)
  11871. ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
  11872. .tickSize( -availableHeight, 0);
  11873. }
  11874. if (showYAxis) {
  11875. var ticks;
  11876. if (stacked.offset() === 'wiggle') {
  11877. ticks = 0;
  11878. }
  11879. else {
  11880. ticks = nv.utils.calcTicksY(availableHeight/36, data);
  11881. }
  11882. yAxis.scale(y)
  11883. ._ticks(ticks)
  11884. .tickSize(-availableWidth, 0);
  11885. }
  11886. //============================================================
  11887. // Update Axes
  11888. //============================================================
  11889. function updateXAxis() {
  11890. if(showXAxis) {
  11891. g.select('.nv-focus .nv-x.nv-axis')
  11892. .attr('transform', 'translate(0,' + availableHeight + ')')
  11893. .transition()
  11894. .duration(duration)
  11895. .call(xAxis)
  11896. ;
  11897. }
  11898. }
  11899. function updateYAxis() {
  11900. if(showYAxis) {
  11901. if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') {
  11902. var currentFormat = yAxis.tickFormat();
  11903. if ( !oldYTickFormat || currentFormat !== percentFormatter )
  11904. oldYTickFormat = currentFormat;
  11905. //Forces the yAxis to use percentage in 'expand' mode.
  11906. yAxis.tickFormat(percentFormatter);
  11907. }
  11908. else {
  11909. if (oldYTickFormat) {
  11910. yAxis.tickFormat(oldYTickFormat);
  11911. oldYTickFormat = null;
  11912. }
  11913. }
  11914. g.select('.nv-focus .nv-y.nv-axis')
  11915. .transition().duration(0)
  11916. .call(yAxis);
  11917. }
  11918. }
  11919. //============================================================
  11920. // Update Focus
  11921. //============================================================
  11922. if(!focusEnable) {
  11923. stackedWrap.transition().call(stacked);
  11924. updateXAxis();
  11925. updateYAxis();
  11926. } else {
  11927. focus.width(availableWidth);
  11928. g.select('.nv-focusWrap')
  11929. .attr('transform', 'translate(0,' + ( availableHeight + margin.bottom + focus.margin().top) + ')')
  11930. .datum(data.filter(function(d) { return !d.disabled; }))
  11931. .call(focus);
  11932. var extent = focus.brush.empty() ? focus.xDomain() : focus.brush.extent();
  11933. if(extent !== null){
  11934. onBrush(extent);
  11935. }
  11936. }
  11937. //============================================================
  11938. // Event Handling/Dispatching (in chart's scope)
  11939. //------------------------------------------------------------
  11940. stacked.dispatch.on('areaClick.toggle', function(e) {
  11941. if (data.filter(function(d) { return !d.disabled }).length === 1)
  11942. data.forEach(function(d) {
  11943. d.disabled = false;
  11944. });
  11945. else
  11946. data.forEach(function(d,i) {
  11947. d.disabled = (i != e.seriesIndex);
  11948. });
  11949. state.disabled = data.map(function(d) { return !!d.disabled });
  11950. dispatch.stateChange(state);
  11951. chart.update();
  11952. });
  11953. legend.dispatch.on('stateChange', function(newState) {
  11954. for (var key in newState)
  11955. state[key] = newState[key];
  11956. dispatch.stateChange(state);
  11957. chart.update();
  11958. });
  11959. controls.dispatch.on('legendClick', function(d,i) {
  11960. if (!d.disabled) return;
  11961. controlsData = controlsData.map(function(s) {
  11962. s.disabled = true;
  11963. return s;
  11964. });
  11965. d.disabled = false;
  11966. stacked.style(d.style);
  11967. state.style = stacked.style();
  11968. dispatch.stateChange(state);
  11969. chart.update();
  11970. });
  11971. interactiveLayer.dispatch.on('elementMousemove', function(e) {
  11972. stacked.clearHighlights();
  11973. var singlePoint, pointIndex, pointXLocation, allData = [], valueSum = 0, allNullValues = true;
  11974. data
  11975. .filter(function(series, i) {
  11976. series.seriesIndex = i;
  11977. return !series.disabled;
  11978. })
  11979. .forEach(function(series,i) {
  11980. pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
  11981. var point = series.values[pointIndex];
  11982. var pointYValue = chart.y()(point, pointIndex);
  11983. if (pointYValue != null) {
  11984. stacked.highlightPoint(i, pointIndex, true);
  11985. }
  11986. if (typeof point === 'undefined') return;
  11987. if (typeof singlePoint === 'undefined') singlePoint = point;
  11988. if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
  11989. //If we are in 'expand' mode, use the stacked percent value instead of raw value.
  11990. var tooltipValue = (stacked.style() == 'expand') ? point.display.y : chart.y()(point,pointIndex);
  11991. allData.push({
  11992. key: series.key,
  11993. value: tooltipValue,
  11994. color: color(series,series.seriesIndex),
  11995. point: point
  11996. });
  11997. if (showTotalInTooltip && stacked.style() != 'expand' && tooltipValue != null) {
  11998. valueSum += tooltipValue;
  11999. allNullValues = false;
  12000. };
  12001. });
  12002. allData.reverse();
  12003. //Highlight the tooltip entry based on which stack the mouse is closest to.
  12004. if (allData.length > 2) {
  12005. var yValue = chart.yScale().invert(e.mouseY);
  12006. var yDistMax = Infinity, indexToHighlight = null;
  12007. allData.forEach(function(series,i) {
  12008. //To handle situation where the stacked area chart is negative, we need to use absolute values
  12009. //when checking if the mouse Y value is within the stack area.
  12010. yValue = Math.abs(yValue);
  12011. var stackedY0 = Math.abs(series.point.display.y0);
  12012. var stackedY = Math.abs(series.point.display.y);
  12013. if ( yValue >= stackedY0 && yValue <= (stackedY + stackedY0))
  12014. {
  12015. indexToHighlight = i;
  12016. return;
  12017. }
  12018. });
  12019. if (indexToHighlight != null)
  12020. allData[indexToHighlight].highlight = true;
  12021. }
  12022. //If we are not in 'expand' mode, add a 'Total' row to the tooltip.
  12023. if (showTotalInTooltip && stacked.style() != 'expand' && allData.length >= 2 && !allNullValues) {
  12024. allData.push({
  12025. key: totalLabel,
  12026. value: valueSum,
  12027. total: true
  12028. });
  12029. }
  12030. var xValue = chart.x()(singlePoint,pointIndex);
  12031. var valueFormatter = interactiveLayer.tooltip.valueFormatter();
  12032. // Keeps track of the tooltip valueFormatter if the chart changes to expanded view
  12033. if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') {
  12034. if ( !oldValueFormatter ) {
  12035. oldValueFormatter = valueFormatter;
  12036. }
  12037. //Forces the tooltip to use percentage in 'expand' mode.
  12038. valueFormatter = d3.format(".1%");
  12039. }
  12040. else {
  12041. if (oldValueFormatter) {
  12042. valueFormatter = oldValueFormatter;
  12043. oldValueFormatter = null;
  12044. }
  12045. }
  12046. interactiveLayer.tooltip
  12047. .valueFormatter(valueFormatter)
  12048. .data(
  12049. {
  12050. value: xValue,
  12051. series: allData
  12052. }
  12053. )();
  12054. interactiveLayer.renderGuideLine(pointXLocation);
  12055. });
  12056. interactiveLayer.dispatch.on("elementMouseout",function(e) {
  12057. stacked.clearHighlights();
  12058. });
  12059. /* Update `main' graph on brush update. */
  12060. focus.dispatch.on("onBrush", function(extent) {
  12061. onBrush(extent);
  12062. });
  12063. // Update chart from a state object passed to event handler
  12064. dispatch.on('changeState', function(e) {
  12065. if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
  12066. data.forEach(function(series,i) {
  12067. series.disabled = e.disabled[i];
  12068. });
  12069. state.disabled = e.disabled;
  12070. }
  12071. if (typeof e.style !== 'undefined') {
  12072. stacked.style(e.style);
  12073. style = e.style;
  12074. }
  12075. chart.update();
  12076. });
  12077. //============================================================
  12078. // Functions
  12079. //------------------------------------------------------------
  12080. function onBrush(extent) {
  12081. // Update Main (Focus)
  12082. var stackedWrap = g.select('.nv-focus .nv-stackedWrap')
  12083. .datum(
  12084. data.filter(function(d) { return !d.disabled; })
  12085. .map(function(d,i) {
  12086. return {
  12087. key: d.key,
  12088. area: d.area,
  12089. classed: d.classed,
  12090. values: d.values.filter(function(d,i) {
  12091. return stacked.x()(d,i) >= extent[0] && stacked.x()(d,i) <= extent[1];
  12092. }),
  12093. disableTooltip: d.disableTooltip
  12094. };
  12095. })
  12096. );
  12097. stackedWrap.transition().duration(duration).call(stacked);
  12098. // Update Main (Focus) Axes
  12099. updateXAxis();
  12100. updateYAxis();
  12101. }
  12102. });
  12103. renderWatch.renderEnd('stacked Area chart immediate');
  12104. return chart;
  12105. }
  12106. //============================================================
  12107. // Event Handling/Dispatching (out of chart's scope)
  12108. //------------------------------------------------------------
  12109. stacked.dispatch.on('elementMouseover.tooltip', function(evt) {
  12110. evt.point['x'] = stacked.x()(evt.point);
  12111. evt.point['y'] = stacked.y()(evt.point);
  12112. tooltip.data(evt).hidden(false);
  12113. });
  12114. stacked.dispatch.on('elementMouseout.tooltip', function(evt) {
  12115. tooltip.hidden(true)
  12116. });
  12117. //============================================================
  12118. // Expose Public Variables
  12119. //------------------------------------------------------------
  12120. // expose chart's sub-components
  12121. chart.dispatch = dispatch;
  12122. chart.stacked = stacked;
  12123. chart.legend = legend;
  12124. chart.controls = controls;
  12125. chart.xAxis = xAxis;
  12126. chart.x2Axis = focus.xAxis;
  12127. chart.yAxis = yAxis;
  12128. chart.y2Axis = focus.yAxis;
  12129. chart.interactiveLayer = interactiveLayer;
  12130. chart.tooltip = tooltip;
  12131. chart.focus = focus;
  12132. chart.dispatch = dispatch;
  12133. chart.options = nv.utils.optionsFunc.bind(chart);
  12134. chart._options = Object.create({}, {
  12135. // simple options, just get/set the necessary values
  12136. width: {get: function(){return width;}, set: function(_){width=_;}},
  12137. height: {get: function(){return height;}, set: function(_){height=_;}},
  12138. showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
  12139. showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
  12140. showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
  12141. defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
  12142. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  12143. showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
  12144. controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
  12145. controlOptions: {get: function(){return controlOptions;}, set: function(_){controlOptions=_;}},
  12146. showTotalInTooltip: {get: function(){return showTotalInTooltip;}, set: function(_){showTotalInTooltip=_;}},
  12147. totalLabel: {get: function(){return totalLabel;}, set: function(_){totalLabel=_;}},
  12148. focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}},
  12149. focusHeight: {get: function(){return focus.height();}, set: function(_){focus.height(_);}},
  12150. brushExtent: {get: function(){return focus.brushExtent();}, set: function(_){focus.brushExtent(_);}},
  12151. // options that require extra logic in the setter
  12152. margin: {get: function(){return margin;}, set: function(_){
  12153. margin.top = _.top !== undefined ? _.top : margin.top;
  12154. margin.right = _.right !== undefined ? _.right : margin.right;
  12155. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  12156. margin.left = _.left !== undefined ? _.left : margin.left;
  12157. }},
  12158. focusMargin: {get: function(){return focus.margin}, set: function(_){
  12159. focus.margin.top = _.top !== undefined ? _.top : focus.margin.top;
  12160. focus.margin.right = _.right !== undefined ? _.right : focus.margin.right;
  12161. focus.margin.bottom = _.bottom !== undefined ? _.bottom : focus.margin.bottom;
  12162. focus.margin.left = _.left !== undefined ? _.left : focus.margin.left;
  12163. }},
  12164. duration: {get: function(){return duration;}, set: function(_){
  12165. duration = _;
  12166. renderWatch.reset(duration);
  12167. stacked.duration(duration);
  12168. xAxis.duration(duration);
  12169. yAxis.duration(duration);
  12170. }},
  12171. color: {get: function(){return color;}, set: function(_){
  12172. color = nv.utils.getColor(_);
  12173. legend.color(color);
  12174. stacked.color(color);
  12175. focus.color(color);
  12176. }},
  12177. x: {get: function(){return stacked.x();}, set: function(_){
  12178. stacked.x(_);
  12179. focus.x(_);
  12180. }},
  12181. y: {get: function(){return stacked.y();}, set: function(_){
  12182. stacked.y(_);
  12183. focus.y(_);
  12184. }},
  12185. rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
  12186. rightAlignYAxis = _;
  12187. yAxis.orient( rightAlignYAxis ? 'right' : 'left');
  12188. }},
  12189. useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
  12190. useInteractiveGuideline = !!_;
  12191. chart.interactive(!_);
  12192. chart.useVoronoi(!_);
  12193. stacked.scatter.interactive(!_);
  12194. }}
  12195. });
  12196. nv.utils.inheritOptions(chart, stacked);
  12197. nv.utils.initOptions(chart);
  12198. return chart;
  12199. };
  12200. nv.models.stackedAreaWithFocusChart = function() {
  12201. return nv.models.stackedAreaChart()
  12202. .margin({ bottom: 30 })
  12203. .focusEnable( true );
  12204. };
  12205. // based on http://bl.ocks.org/kerryrodden/477c1bfb081b783f80ad
  12206. nv.models.sunburst = function() {
  12207. "use strict";
  12208. //============================================================
  12209. // Public Variables with Default Settings
  12210. //------------------------------------------------------------
  12211. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  12212. , width = 600
  12213. , height = 600
  12214. , mode = "count"
  12215. , modes = {count: function(d) { return 1; }, value: function(d) { return d.value || d.size }, size: function(d) { return d.value || d.size }}
  12216. , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
  12217. , container = null
  12218. , color = nv.utils.defaultColor()
  12219. , showLabels = false
  12220. , labelFormat = function(d){if(mode === 'count'){return d.name + ' #' + d.value}else{return d.name + ' ' + (d.value || d.size)}}
  12221. , labelThreshold = 0.02
  12222. , sort = function(d1, d2){return d1.name > d2.name;}
  12223. , key = function(d,i){return d.name;}
  12224. , groupColorByParent = true
  12225. , duration = 500
  12226. , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMousemove', 'elementMouseover', 'elementMouseout', 'renderEnd');
  12227. //============================================================
  12228. // aux functions and setup
  12229. //------------------------------------------------------------
  12230. var x = d3.scale.linear().range([0, 2 * Math.PI]);
  12231. var y = d3.scale.sqrt();
  12232. var partition = d3.layout.partition().sort(sort);
  12233. var node, availableWidth, availableHeight, radius;
  12234. var prevPositions = {};
  12235. var arc = d3.svg.arc()
  12236. .startAngle(function(d) {return Math.max(0, Math.min(2 * Math.PI, x(d.x))) })
  12237. .endAngle(function(d) {return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))) })
  12238. .innerRadius(function(d) {return Math.max(0, y(d.y)) })
  12239. .outerRadius(function(d) {return Math.max(0, y(d.y + d.dy)) });
  12240. function rotationToAvoidUpsideDown(d) {
  12241. var centerAngle = computeCenterAngle(d);
  12242. if(centerAngle > 90){
  12243. return 180;
  12244. }
  12245. else {
  12246. return 0;
  12247. }
  12248. }
  12249. function computeCenterAngle(d) {
  12250. var startAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x)));
  12251. var endAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
  12252. var centerAngle = (((startAngle + endAngle) / 2) * (180 / Math.PI)) - 90;
  12253. return centerAngle;
  12254. }
  12255. function computeNodePercentage(d) {
  12256. var startAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x)));
  12257. var endAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
  12258. return (endAngle - startAngle) / (2 * Math.PI);
  12259. }
  12260. function labelThresholdMatched(d) {
  12261. var startAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x)));
  12262. var endAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
  12263. var size = endAngle - startAngle;
  12264. return size > labelThreshold;
  12265. }
  12266. // When zooming: interpolate the scales.
  12267. function arcTweenZoom(e,i) {
  12268. var xd = d3.interpolate(x.domain(), [node.x, node.x + node.dx]),
  12269. yd = d3.interpolate(y.domain(), [node.y, 1]),
  12270. yr = d3.interpolate(y.range(), [node.y ? 20 : 0, radius]);
  12271. if (i === 0) {
  12272. return function() {return arc(e);}
  12273. }
  12274. else {
  12275. return function (t) {
  12276. x.domain(xd(t));
  12277. y.domain(yd(t)).range(yr(t));
  12278. return arc(e);
  12279. }
  12280. };
  12281. }
  12282. function arcTweenUpdate(d) {
  12283. var ipo = d3.interpolate({x: d.x0, dx: d.dx0, y: d.y0, dy: d.dy0}, d);
  12284. return function (t) {
  12285. var b = ipo(t);
  12286. d.x0 = b.x;
  12287. d.dx0 = b.dx;
  12288. d.y0 = b.y;
  12289. d.dy0 = b.dy;
  12290. return arc(b);
  12291. };
  12292. }
  12293. function updatePrevPosition(node) {
  12294. var k = key(node);
  12295. if(! prevPositions[k]) prevPositions[k] = {};
  12296. var pP = prevPositions[k];
  12297. pP.dx = node.dx;
  12298. pP.x = node.x;
  12299. pP.dy = node.dy;
  12300. pP.y = node.y;
  12301. }
  12302. function storeRetrievePrevPositions(nodes) {
  12303. nodes.forEach(function(n){
  12304. var k = key(n);
  12305. var pP = prevPositions[k];
  12306. //console.log(k,n,pP);
  12307. if( pP ){
  12308. n.dx0 = pP.dx;
  12309. n.x0 = pP.x;
  12310. n.dy0 = pP.dy;
  12311. n.y0 = pP.y;
  12312. }
  12313. else {
  12314. n.dx0 = n.dx;
  12315. n.x0 = n.x;
  12316. n.dy0 = n.dy;
  12317. n.y0 = n.y;
  12318. }
  12319. updatePrevPosition(n);
  12320. });
  12321. }
  12322. function zoomClick(d) {
  12323. var labels = container.selectAll('text')
  12324. var path = container.selectAll('path')
  12325. // fade out all text elements
  12326. labels.transition().attr("opacity",0);
  12327. // to allow reference to the new center node
  12328. node = d;
  12329. path.transition()
  12330. .duration(duration)
  12331. .attrTween("d", arcTweenZoom)
  12332. .each('end', function(e) {
  12333. // partially taken from here: http://bl.ocks.org/metmajer/5480307
  12334. // check if the animated element's data e lies within the visible angle span given in d
  12335. if(e.x >= d.x && e.x < (d.x + d.dx) ){
  12336. if(e.depth >= d.depth){
  12337. // get a selection of the associated text element
  12338. var parentNode = d3.select(this.parentNode);
  12339. var arcText = parentNode.select('text');
  12340. // fade in the text element and recalculate positions
  12341. arcText.transition().duration(duration)
  12342. .text( function(e){return labelFormat(e) })
  12343. .attr("opacity", function(d){
  12344. if(labelThresholdMatched(d)) {
  12345. return 1;
  12346. }
  12347. else {
  12348. return 0;
  12349. }
  12350. })
  12351. .attr("transform", function() {
  12352. var width = this.getBBox().width;
  12353. if(e.depth === 0)
  12354. return "translate(" + (width / 2 * - 1) + ",0)";
  12355. else if(e.depth === d.depth){
  12356. return "translate(" + (y(e.y) + 5) + ",0)";
  12357. }
  12358. else {
  12359. var centerAngle = computeCenterAngle(e);
  12360. var rotation = rotationToAvoidUpsideDown(e);
  12361. if (rotation === 0) {
  12362. return 'rotate('+ centerAngle +')translate(' + (y(e.y) + 5) + ',0)';
  12363. }
  12364. else {
  12365. return 'rotate('+ centerAngle +')translate(' + (y(e.y) + width + 5) + ',0)rotate(' + rotation + ')';
  12366. }
  12367. }
  12368. });
  12369. }
  12370. }
  12371. })
  12372. }
  12373. //============================================================
  12374. // chart function
  12375. //------------------------------------------------------------
  12376. var renderWatch = nv.utils.renderWatch(dispatch);
  12377. function chart(selection) {
  12378. renderWatch.reset();
  12379. selection.each(function(data) {
  12380. container = d3.select(this);
  12381. availableWidth = nv.utils.availableWidth(width, container, margin);
  12382. availableHeight = nv.utils.availableHeight(height, container, margin);
  12383. radius = Math.min(availableWidth, availableHeight) / 2;
  12384. y.range([0, radius]);
  12385. // Setup containers and skeleton of chart
  12386. var wrap = container.select('g.nvd3.nv-wrap.nv-sunburst');
  12387. if( !wrap[0][0] ) {
  12388. wrap = container.append('g')
  12389. .attr('class', 'nvd3 nv-wrap nv-sunburst nv-chart-' + id)
  12390. .attr('transform', 'translate(' + ((availableWidth / 2) + margin.left + margin.right) + ',' + ((availableHeight / 2) + margin.top + margin.bottom) + ')');
  12391. } else {
  12392. wrap.attr('transform', 'translate(' + ((availableWidth / 2) + margin.left + margin.right) + ',' + ((availableHeight / 2) + margin.top + margin.bottom) + ')');
  12393. }
  12394. container.on('click', function (d, i) {
  12395. dispatch.chartClick({
  12396. data: d,
  12397. index: i,
  12398. pos: d3.event,
  12399. id: id
  12400. });
  12401. });
  12402. partition.value(modes[mode] || modes["count"]);
  12403. //reverse the drawing order so that the labels of inner
  12404. //arcs are drawn on top of the outer arcs.
  12405. var nodes = partition.nodes(data[0]).reverse()
  12406. storeRetrievePrevPositions(nodes);
  12407. var cG = wrap.selectAll('.arc-container').data(nodes, key)
  12408. //handle new datapoints
  12409. var cGE = cG.enter()
  12410. .append("g")
  12411. .attr("class",'arc-container')
  12412. cGE.append("path")
  12413. .attr("d", arc)
  12414. .style("fill", function (d) {
  12415. if (d.color) {
  12416. return d.color;
  12417. }
  12418. else if (groupColorByParent) {
  12419. return color((d.children ? d : d.parent).name);
  12420. }
  12421. else {
  12422. return color(d.name);
  12423. }
  12424. })
  12425. .style("stroke", "#FFF")
  12426. .on("click", zoomClick)
  12427. .on('mouseover', function(d,i){
  12428. d3.select(this).classed('hover', true).style('opacity', 0.8);
  12429. dispatch.elementMouseover({
  12430. data: d,
  12431. color: d3.select(this).style("fill"),
  12432. percent: computeNodePercentage(d)
  12433. });
  12434. })
  12435. .on('mouseout', function(d,i){
  12436. d3.select(this).classed('hover', false).style('opacity', 1);
  12437. dispatch.elementMouseout({
  12438. data: d
  12439. });
  12440. })
  12441. .on('mousemove', function(d,i){
  12442. dispatch.elementMousemove({
  12443. data: d
  12444. });
  12445. });
  12446. ///Iterating via each and selecting based on the this
  12447. ///makes it work ... a cG.selectAll('path') doesn't.
  12448. ///Without iteration the data (in the element) didn't update.
  12449. cG.each(function(d){
  12450. d3.select(this).select('path')
  12451. .transition()
  12452. .duration(duration)
  12453. .attrTween('d', arcTweenUpdate);
  12454. });
  12455. if(showLabels){
  12456. //remove labels first and add them back
  12457. cG.selectAll('text').remove();
  12458. //this way labels are on top of newly added arcs
  12459. cG.append('text')
  12460. .text( function(e){ return labelFormat(e)})
  12461. .transition()
  12462. .duration(duration)
  12463. .attr("opacity", function(d){
  12464. if(labelThresholdMatched(d)) {
  12465. return 1;
  12466. }
  12467. else {
  12468. return 0;
  12469. }
  12470. })
  12471. .attr("transform", function(d) {
  12472. var width = this.getBBox().width;
  12473. if(d.depth === 0){
  12474. return "rotate(0)translate(" + (width / 2 * -1) + ",0)";
  12475. }
  12476. else {
  12477. var centerAngle = computeCenterAngle(d);
  12478. var rotation = rotationToAvoidUpsideDown(d);
  12479. if (rotation === 0) {
  12480. return 'rotate('+ centerAngle +')translate(' + (y(d.y) + 5) + ',0)';
  12481. }
  12482. else {
  12483. return 'rotate('+ centerAngle +')translate(' + (y(d.y) + width + 5) + ',0)rotate(' + rotation + ')';
  12484. }
  12485. }
  12486. });
  12487. }
  12488. //zoom out to the center when the data is updated.
  12489. zoomClick(nodes[nodes.length - 1])
  12490. //remove unmatched elements ...
  12491. cG.exit()
  12492. .transition()
  12493. .duration(duration)
  12494. .attr('opacity',0)
  12495. .each('end',function(d){
  12496. var k = key(d);
  12497. prevPositions[k] = undefined;
  12498. })
  12499. .remove();
  12500. });
  12501. renderWatch.renderEnd('sunburst immediate');
  12502. return chart;
  12503. }
  12504. //============================================================
  12505. // Expose Public Variables
  12506. //------------------------------------------------------------
  12507. chart.dispatch = dispatch;
  12508. chart.options = nv.utils.optionsFunc.bind(chart);
  12509. chart._options = Object.create({}, {
  12510. // simple options, just get/set the necessary values
  12511. width: {get: function(){return width;}, set: function(_){width=_;}},
  12512. height: {get: function(){return height;}, set: function(_){height=_;}},
  12513. mode: {get: function(){return mode;}, set: function(_){mode=_;}},
  12514. id: {get: function(){return id;}, set: function(_){id=_;}},
  12515. duration: {get: function(){return duration;}, set: function(_){duration=_;}},
  12516. groupColorByParent: {get: function(){return groupColorByParent;}, set: function(_){groupColorByParent=!!_;}},
  12517. showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=!!_}},
  12518. labelFormat: {get: function(){return labelFormat;}, set: function(_){labelFormat=_}},
  12519. labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_}},
  12520. sort: {get: function(){return sort;}, set: function(_){sort=_}},
  12521. key: {get: function(){return key;}, set: function(_){key=_}},
  12522. // options that require extra logic in the setter
  12523. margin: {get: function(){return margin;}, set: function(_){
  12524. margin.top = _.top != undefined ? _.top : margin.top;
  12525. margin.right = _.right != undefined ? _.right : margin.right;
  12526. margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
  12527. margin.left = _.left != undefined ? _.left : margin.left;
  12528. }},
  12529. color: {get: function(){return color;}, set: function(_){
  12530. color=nv.utils.getColor(_);
  12531. }}
  12532. });
  12533. nv.utils.initOptions(chart);
  12534. return chart;
  12535. };
  12536. nv.models.sunburstChart = function() {
  12537. "use strict";
  12538. //============================================================
  12539. // Public Variables with Default Settings
  12540. //------------------------------------------------------------
  12541. var sunburst = nv.models.sunburst();
  12542. var tooltip = nv.models.tooltip();
  12543. var margin = {top: 30, right: 20, bottom: 20, left: 20}
  12544. , width = null
  12545. , height = null
  12546. , color = nv.utils.defaultColor()
  12547. , showTooltipPercent = false
  12548. , id = Math.round(Math.random() * 100000)
  12549. , defaultState = null
  12550. , noData = null
  12551. , duration = 250
  12552. , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd');
  12553. //============================================================
  12554. // Private Variables
  12555. //------------------------------------------------------------
  12556. var renderWatch = nv.utils.renderWatch(dispatch);
  12557. tooltip
  12558. .duration(0)
  12559. .headerEnabled(false)
  12560. .valueFormatter(function(d){return d;});
  12561. //============================================================
  12562. // Chart function
  12563. //------------------------------------------------------------
  12564. function chart(selection) {
  12565. renderWatch.reset();
  12566. renderWatch.models(sunburst);
  12567. selection.each(function(data) {
  12568. var container = d3.select(this);
  12569. nv.utils.initSVG(container);
  12570. var availableWidth = nv.utils.availableWidth(width, container, margin);
  12571. var availableHeight = nv.utils.availableHeight(height, container, margin);
  12572. chart.update = function() {
  12573. if (duration === 0) {
  12574. container.call(chart);
  12575. } else {
  12576. container.transition().duration(duration).call(chart);
  12577. }
  12578. };
  12579. chart.container = container;
  12580. // Display No Data message if there's nothing to show.
  12581. if (!data || !data.length) {
  12582. nv.utils.noData(chart, container);
  12583. return chart;
  12584. } else {
  12585. container.selectAll('.nv-noData').remove();
  12586. }
  12587. sunburst.width(availableWidth).height(availableHeight).margin(margin);
  12588. container.call(sunburst);
  12589. });
  12590. renderWatch.renderEnd('sunburstChart immediate');
  12591. return chart;
  12592. }
  12593. //============================================================
  12594. // Event Handling/Dispatching (out of chart's scope)
  12595. //------------------------------------------------------------
  12596. sunburst.dispatch.on('elementMouseover.tooltip', function(evt) {
  12597. evt.series = {
  12598. key: evt.data.name,
  12599. value: (evt.data.value || evt.data.size),
  12600. color: evt.color,
  12601. percent: evt.percent
  12602. };
  12603. if (!showTooltipPercent) {
  12604. delete evt.percent;
  12605. delete evt.series.percent;
  12606. }
  12607. tooltip.data(evt).hidden(false);
  12608. });
  12609. sunburst.dispatch.on('elementMouseout.tooltip', function(evt) {
  12610. tooltip.hidden(true);
  12611. });
  12612. sunburst.dispatch.on('elementMousemove.tooltip', function(evt) {
  12613. tooltip();
  12614. });
  12615. //============================================================
  12616. // Expose Public Variables
  12617. //------------------------------------------------------------
  12618. // expose chart's sub-components
  12619. chart.dispatch = dispatch;
  12620. chart.sunburst = sunburst;
  12621. chart.tooltip = tooltip;
  12622. chart.options = nv.utils.optionsFunc.bind(chart);
  12623. // use Object get/set functionality to map between vars and chart functions
  12624. chart._options = Object.create({}, {
  12625. // simple options, just get/set the necessary values
  12626. noData: {get: function(){return noData;}, set: function(_){noData=_;}},
  12627. defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
  12628. showTooltipPercent: {get: function(){return showTooltipPercent;}, set: function(_){showTooltipPercent=_;}},
  12629. // options that require extra logic in the setter
  12630. color: {get: function(){return color;}, set: function(_){
  12631. color = _;
  12632. sunburst.color(color);
  12633. }},
  12634. duration: {get: function(){return duration;}, set: function(_){
  12635. duration = _;
  12636. renderWatch.reset(duration);
  12637. sunburst.duration(duration);
  12638. }},
  12639. margin: {get: function(){return margin;}, set: function(_){
  12640. margin.top = _.top !== undefined ? _.top : margin.top;
  12641. margin.right = _.right !== undefined ? _.right : margin.right;
  12642. margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
  12643. margin.left = _.left !== undefined ? _.left : margin.left;
  12644. sunburst.margin(margin);
  12645. }}
  12646. });
  12647. nv.utils.inheritOptions(chart, sunburst);
  12648. nv.utils.initOptions(chart);
  12649. return chart;
  12650. };
  12651. nv.version = "1.8.4";
  12652. })();