Gruntfile.js000064400000004342150145030150007036 0ustar00module.exports = function (grunt) { require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), coffee: { lib: { options: { bare: false }, files: { 'morris.js': ['build/morris.coffee'] } }, spec: { options: { bare: true }, files: { 'build/spec.js': ['build/spec.coffee'] } }, }, concat: { 'build/morris.coffee': { options: { banner: "### @license\n"+ "<%= pkg.name %> v<%= pkg.version %>\n"+ "Copyright <%= (new Date()).getFullYear() %> <%= pkg.author.name %> All rights reserved.\n" + "Licensed under the <%= pkg.license %> License.\n" + "###\n", }, src: [ 'lib/morris.coffee', 'lib/morris.grid.coffee', 'lib/morris.hover.coffee', 'lib/morris.line.coffee', 'lib/morris.area.coffee', 'lib/morris.bar.coffee', 'lib/morris.donut.coffee' ], dest: 'build/morris.coffee' }, 'build/spec.coffee': ['spec/support/**/*.coffee', 'spec/lib/**/*.coffee'] }, less: { all: { src: 'less/*.less', dest: 'morris.css', options: { compress: true } } }, uglify: { build: { options: { preserveComments: 'some' }, files: { 'morris.min.js': 'morris.js' } } }, mocha: { index: ['spec/specs.html'], options: {run: true} }, watch: { all: { files: ['lib/**/*.coffee', 'spec/lib/**/*.coffee', 'spec/support/**/*.coffee', 'less/**/*.less'], tasks: 'default' }, dev: { files: 'lib/*.coffee' , tasks: ['concat:build/morris.coffee', 'coffee:lib'] } }, shell: { visual_spec: { command: './run.sh', options: { stdout: true, failOnError: true, execOptions: { cwd: 'spec/viz' } } } } }); grunt.registerTask('default', ['concat', 'coffee', 'less', 'uglify', 'mocha', 'shell:visual_spec']); }; spec/specs.html000064400000002645150145030150007502 0ustar00 morris.js tests
spec/viz/exemplary/stacked_bar0.png000064400000013343150145030150013342 0ustar00PNG  IHDR,5AsBIT|d pHYs  IDATx]pSlٖ-K`c00RB,mt'4;bozh/:tl'mH[$m&pHb1Ȳcz?{x4 tsa&oO~9,˲{n_s.BO$J&ڻw<ݭx<.g zUUqeYa>}Z---tQ\2 /w}WK,eYv/$ϧ'xBhTʕ+[%ǘ7k7[^vƔ0IJO2404ENsUGGǼ?F^A֙3go>IR0Tss&&&~N1ov5oȕӷl}:W6OWlٖxX[v&yrW%I joou8V^W۶m3<`0(Iڹsm&s;v͛5==ం.fU ^`: `: `: `: `: `: `: `Y,R2T"P:V6-xeeezRee< :\)H(fJ&J&*++WUUp)qISEE ZQ[l6T*xHTJ555vp1x<|m񨼼\|s~y(sQ5l14>Ouuux]vJw@$P'UVVΙt+~yY3iLƑ/ASV%: spc(ieٳd2 B B NUt%mrrRtZw8!J^~eЦM]`ɊCv;XL:z~9!FGG暌8=¼D"J' c ӸS*m*ՏE,>7?ϲ,Iw}rѫ;vӮ>աco(pʮMu͊3{Jӂ>?(%pܜ:߾)}>Ir,{nWrMss̙3w[k+~-M{]yJ… P_?b4TQQaJ}r>W yC?~x~R)RM~ܻc̫~S"O?=}krǡщ#A}kS̫РzYL?|]͊zV_:wCޏNuT:ڪ#UW_e)۾/5}ٯV5H*e~ϟӦZUWmNt@IػIL2TFo^PXX/V.uxBg `%u>YR1y$ihqXA% M:~mhrxw;d]ߤW/ :fXXJFsZKժ,sO^QsZsAJ=yk7}N<{'r( i֥)}c?sLgHeǷ[n:$5֨1\:xz]E; :XWa^ݭ|8 /ϹmjomKUcg0*;CkG"z>nS//mh$] \AB%aJ |UJ$S7{|#MkV9<(PR/Դ(w-wzW!wY)(C%ظޯnQnl엥 %ﲒ~sm^\YPέw{wl>=v:r ٬u뿬w\a:$99j\aUC+[.ѫ :._$y#ǽ4ToK008LRRV+Sf$+. mBdu_d3E[{n_2Yg\޽4$GR6=+lcXzg`dWo,p^$o[&Yy,KYy)+²Bn۲n3g`H\(hntd-K:ӧFڹ]ꪛ_65o_(mJuղXmK hReeg=4O*<>KCa])sDfu ]zQ\0At @0At @0At @0At @0@A[oe, O|866/ԩSZv3/(ĄUWWg< yM{/_n< \>n;i)-4704E; D҆iŋTq\q[FpXVaИ#lN\GGǼ⊠r^*4Uw c1I>K_vX> UWW]4m˶fs9k;r=!&rUAb ` >g;`: `: `: `: `: `: `: `: `: `: `: `: `:{L&SNippP`P]]]s6W'N u!eY;g9+eٳz衇ҢN% ]v@ *++fo BD" r9 UUU͹VT*!FGGot^.X,D6L^HD 邷lƝR-/Is` 0x,˲蝮__W?{Ça^ anTBJ&|n(&zMM,YsI4==P(dp 7yr1=z:pZ[[ K7_D TVƛ܁e5 `: `pSe2:uJ RmmcѣGyf566:=+;wNPGGxGORwwǵtRutthѢENZz|G^/OdK/͹m˖-ZtC.s K`PR6uz,/% qϫ[W֭[~ÇeYvޭ2NZeȑ#qxϿF566?@鱊bYΞ=zH---T"еkלuFGGUQQJGq+WS6lвe˴qF9=$ %IݻWMMMڲeӣҟg-_\x}Ѩ,Y?_#errRs~ BD"ZbsWIRÓ׮]TUU5А-[DSUU\lVpXOVKK|>ӣȈ._~X_RLFׯז-[-s%VTʡPjjj$ITJǏ׵grd2^xAhT۷oeY@?&N륗^҃>sDQeYܹSHD˵qFG+~̜RCԍjjj=V'P4rJ:=ktwwAtZpXifUhT_s@ d29Ą j||\og߿9W%A577kbb'NkttTGѱc$IǎؘÓ˵k<թRSS%K^קiB!'C)z7fZJTJTJt\^tp8v's{W>IzG`.קW^yE433{OWvx۷O>Μ9x<a>P$ћo9{ŋgq4׶m3̞ڹsnU{l۶M=~)EwucsXH$P(D́"4o([LNNjNRtt @0At @0At @09*IENDB`spec/viz/exemplary/bar0.png000064400000015430150145030150011643 0ustar00PNG  IHDR,5AsBIT|d pHYs  IDATx{plْec'% Ŕ6'aI2K鰻3ۙݙvXff2l1 &B$rIcBb'|d;Hg05+k&3ңG_$}=9,˲rZG`@: 0t @`@: 0t @`8jxbbBV8VuuZZZt-U9Ͷ/,˲i&i޽v@γeFpDV$$rò,ˮh||\?*f>tIbzW5::_ep,z;iQQQ6l_3::g}V?l g:'?$(,Wm綠sioc7]@e ե[JV\Feee*{.XSɌ_B|r{yyΜ9~IR8ࠚ(gtjݺu@ IzUT1 6]SSS\PdQEEEX\@: 0t @`@: 0t @`@: 0t @`@: 0t @`@: 0t @`@: 0t @`g*O{ァ`0*rQ{۶mH$F诿qFUVVֻpnZZZsRR?^[nUMMZ[[UUU3g,jp8F544$,:-OS|>IR,Аjkkzzzk.K@w8 P(ݻwk͚5*//_zzڲe:;;u՛}=,K)'I:t6lؠk.z;w&oԨOjoo_t-NZsLpYy\vac_|ĉO~(|1EQA'(r2Q箴7ԤR7,WII|~9c}=y<hffFDBt%t:w^I4885k,`X}ddDhT?Z~r~:::~:tHW\͛UXXxsejс^QQ7 >}֦Va~9)`ٳ5E,#p-w @`@: 0t @`@:Gȗ~X?acNWSN{;]en*6W?6?K{^y |`0t @`@: 0t @`Od=˲FǕH$ny;ۯ]HX4קT|ƶt@VFyyy]WhMuhk{i: kMLL(HNn 'Ͽ4ZKI,9>xh޲mݷS/~e84ڕUP{sC&N;SVUW;wš+w*K)?1oj_H,+W9y@_q|E[u㷴bB2mYyWi61ϛr3}VmUZnZwmz_#9ݮS׎YvT[}Ec3,1t@Nl`XHίg>7?{Qvrۂ@d-׫MNN*+krr򆟟zSQ'992@dkX,X,x<ٛf"ai*`x\}wq(zC.+yJۍ{w*>t ,'qᥴغ8s>mܱFѧor,޹#WK{;MOOUa7,5&]^L`@: 0t @`@:Hfggu1+h| zッN]]n{c۶mH$ P0 /zJyy_ n=jhh$]pAn[k֬I>NEeY:~~i~ׅ jժzEEE;O<իWEUWW2}*,,O.+--U(Z9W^ջᆱ7&GvK/Ŗ>@嚷ifff=zTmmm*..N.Ֆ-[٩W.ܝN-Q cN<'|r;w&oԨOjoo_lI:uu<[6SMu,g[Y}0fa–s\ݜNt߯X,H$"-in^^^Ǫ7EF U__\VRRŖ#;32)飔rJ{{MMM*{oj蛅7 oFߤעܽ^VX'NHN>)J.]`0\#ۯq:ڻw$Iw;q)uVuvvKHD;vH%˥*IRyWGG߯Cʕ+ڼy o</~P(yo߾}޺UpX~_NgJE`n喛jp즶;F 0t @`@: 0t @`@: 0t @`@: 0t @`@: 0t @`@: 0t @`@:p쬎;~_^>Ϯri8^_^@@/]l t˲tq=#ڵkFu; *,,O.+--U(r-r-|Ii{}<ݥcB!ţ7 fm7 oYxX}SQQ:eYioK.__|Q^~ey睺Lt[F~_XLHDn[ܨHΜ9cs%eʕڱcG~UTTdcUjzzZ:z貙6CCCOwޱ111<ٳGPH`r}>$iffƮ\.<$ԩSڷo_9٣I]NVӟ~*IZV/9otffF`2;;^{MzT]]mwIYY_W_}U/.vVEE%\YQQwkWU~qq?hzzzY|1B_%%%KSYYJ$ڳg0+GUOOO~KK""Uex}Ν;Qm۶MDBDBTPP`se :rGiժUf_|;I޶,KxلLNN^s>իgw())ѦMsϩ@GOutt]\]]VZ]vɲ,Yv1sL,Բ2ؘz.9wqIIɲ:p@t @`@: 0t @`G"IENDB`spec/viz/exemplary/line0.png000064400000054434150145030150012035 0ustar00PNG  IHDR,5AsBIT|d pHYs   IDATxw\g bw4T5&͞={{MO6ՖS]Q" 3b s=]K!I  ͚ DBA   8A. @$tAAp"  ]AH DBA   8A. @$tAAp" P:Ah&˵k)QRRO@H@ބ`Bo4a0I&IB( JN *'\pS;qE3|{hr!˅`WB|Ш 4%*1p. 9sJ2((:<\w]t K7]tuuxp$IB;.hzǮs2SY%dW:NPz?Ÿ{:,AhDB;"Ip9gsIONޞδuAޖiO|vS⌧ W g'4*%*'<`6K% ɌhFk0Q3R3R5PVJOq )(pwwbx "@Qzee%理6mн{w0L>|L|||:t( -Ğyl;ŮS\-k5*K7Cʓ@Oze'ez _AZ~r9SƹJDl価J =4jB_bۗK.Ƴ> @ll,3\RRRXhJVh+*PA+?o\#Lg4QV'%<ҧ |qS;oْuFЯ|iRh¿I1K,Xz1GqJZ_̔~ܖqC9Y–,{з>jEHeum˓ ?o<)O5ڦ8N^g„ ( ٿ?ZFCJJ /^^^LFFڵ{Wh9{%uqugEk6" v,IdV1[|,"Yh8~py:3k0cbD I<( y>ȓ0%Net!$ͭsS5VS_&r2:>@Ou`*KosjbMy:>ו'vn/zW/cR A PcFK蹹;vI&C֭)++}zZ-.._в2}1/؄$41>{f0pZ-_% W3^6*1%ě%<3ԗ^~,ݱn7ވF}&ލm. . @II yyyt BBB8qΝ__zV~>̎v̨d͚TՈ)wieW3x^ Z[{ UTv Ej|Pޮμ;o"댻kT|1(IbwAh*U5|v1n-ωNW|FDbb<޿ @aa!WFRj9s&m۶֒_R&p9+})gb(|<|7/ A}xTXt'ӽ7'2J;>M#IZϙklN$gkzr.W?rk5*ޛ7gFvB+.dXkGA6ATUkQ(˓ ?/]$VrZ.(s#I(..ϟ͑3i? fDHWCPVe؜%) ^}>7.Nwyдm>Ň[Oc4[?ڦCqS0:|CUaJY׵}p_Oeˎ}̖snLu#Mb"BnV|pt B8Qos>z ߝFhX|1OE&:@Zf+7`ШyfJ$82H,{?:"x1+Zۃ-cNJ}|#;)xA\eKQ;xzJ$CC&t۸ WL&k66LhΪtF^\Ǽ|h8-{%5Z/' _C9s);1jg]4̟>6A.17M%,GeeǻR`Ntngf)$p*z o[?~(eUW8|P/%͆ ѳX,pc(mC't w+c3(!ܿ y=[C>7 65:Va鞋X ?4!E=O=ģcoÖ˶+Y!b};]j 鼸rJgM{Uh8?ī??UJ>=HN'3;pӨQ*( TN QT*QTh4j5":@VwʳѯGI]-$'xo)y,˅_ SY;xy!ߧ[t:0֍oT*qwwZLB ܲY؀n1n(JjO)<K4/!O+rY%fu;{ISǠv-YFQ3>B|kto$fq,γXo־I1Rt:***0̘Q"),L]56))n0(+{L`غ(I䱰:Wqe,aG-nA<7#7xsa"{q ߶΂^`4&s˝ ^;xN]T{tMp$V>̟ܞ<^**.Nxa$S5mF6dh &<]屌"L$%ejP`VZfm=pk*xۻЯK>qM{ˏ=X0=?oQ,%Xs*$aWƊ-Kd7%Ur/dZ0G&4 .gdk3W8鎻w7h4xzhЛ(k/*fJJ["''0?w2A$lHb2^ 3yDwm> %vj˒7 V.<9eӽo$3k=C} tv[.!4K/.OgJϷkwRUMy{⪱=U[L[k`Yvm?}35:=r)3Ƒ IkRR䱡B| ;_rw֤"*k,NoCT*Q9)qdܻXCkŬGu''%MI6LxXfI⍕Y`3U/۪^X/י(/ ׉ k?f%x W211C{ zMw&ȏgOòb2i["Gψ5`2ۤ:|p~~yHkTX^/KhC١Se>9xœ Vŋ+qjf.J+X> yl͆Q 3yD6X+===;(#YtY10eGV{D9X7 Gv;{Ƽy}nvyw;9ޝۑ#TY2>4X4ui&j&^&ݕ$ MP0_(E:^4y:]ƉC$zs ;cX Gmj̦Mtܹ .ミC?¢Yiںp!&7_Oy2[{{j/}|XR'ضטiU[ ,IK&y}h+[M3LOǎ9}4}Aύ}P99ѻs[ )*L_/CEbSZ?I | 3oX#ZGv䤞zޔfN*j)'&_6zv 驑8,U5ZE{RxXwJ揋d.S#BmrV.KFkV;xv8E8!z™Qrw qOvW ef\?Oo7 %L۸REâY1meȚH (<8ųbt4Lܒ TA'3E2Ӽa|}qH[#H9wU[bioȚH ,8s,ˎ}`?o0/ qdOGn>~F<(M?P1 O!>ட3]$s^=9=_*?>zHTkǂ|y*{5^ QVm؃,Y}Izw3)V)]ɩlwT$z10xӕB_.&v v!× nХ$Ib#`m: ]BPpM+?MɻϽn++?JOQŃ| i<;-䱣g.z{C*Y$p0@цQ ƒ{nL'>zjx|l5zYp? F%eygp2InNn(P =Лɉy7kղӗ2aS|B&tC״Ȣl μ񞳹F7x]ɒcjiwFP_:^ᾴ_wWJxP/!.zQ*̊҃i9,[+AL30 RLW1z5TOhaTFurY0z@f.MVAlK,f sJ~QXEV^ N$&P(4j C2s vL`7yDbOY[cn?^iẐ^{;5Y5I$UV횝\˷Ӟ06bpvflf &ط  H+Zo䃭ڱ~MaDBob rP/͚U.0;b4Kzێ[>HwF% )=y{j/c,^Il𪠶1n(m`@;tm]g]lvcn?Fmkcnz$ڧP+ + GƑَ,ܷUy)=#$5)|aTJϫEPĂ"-yQhyRo}~-ūs\xͅӣ +NʝszwޱA:F#IT>qV7,-hBˤP('y圽VXvMm@Ț$yFӝEld7-LJ+TkuT{w_XZN8~ƒPN\@7ϙ'`W&I6䃭3a" -REÙ:;6=q4{fr? ’r~ݙDցD͙˙$8` HD̰g#)^Gܡ^q)+jˆHDDhM]6okˏ' ߍ5)A'HI/r~ gsu0}Nׇ݇NcmxnF dNu~U~ەS?3>$9EAI9m33sp85^&Υgѯk{Υg]P̓+;Rտz3¢1m$>|{([;j % Ҩzit?te{n%$׋hӊf檱ad֮MS bA֞2|-KNJ%N7UyӹSFZʵeʔnhiU*<_wE ̛8gU6$h?'C:۫cqS7]$AhN5*ֽɔ8^$ Ḱ!n+zU[p1z4_̊miͮ$‚ҫ}}}Nm٩-RϱzG"h.NuƍF#J'%MԜe˖-3j(s7ŅݻD2G'dpOkfsh/b֏E\ՙ ogYX>P9Y>N2֧+_2OwnCQ^^NRR/^OO8@`*jUTpJ"BqobL8cA_Ec+/M#]bZp6fRZdX4ᝂx)ڦRR^ɒu(m0e@Fa'w}rEv>'t a۾VT)ܺ˝Wؑ±3!zh|>Gywww |]Fnn.*͛7s%rrrˣ}{GCX83J^? ,t|=UʴdaD2%ěMoFSR㸐S~W63nc\4,,9RiIf;q=&۬?zAA˖-e|̘1t :M6h?~̺r+ fDۤJ}hЩjD̦ܲS%I///T*qaU|}%啀eWԱAC_EnՙoF1 ¿b`Z>Jg+sg17زZۓ3j 騨@RqVJKK1xzz-+;jo#L֗1{6ѐU<.2-5*61NJAWO75Y7fٮkjjBT{(@m}Ah8dW=B}h=.~؜ky:vpNGUU͎)Jow3$IlwsXXpNK*kXf[69)QL 1 p~9΢o;ܗ-׽~5:VlZ~<6f`Ob˺ E$z=zшdjJBVVk6@$@$6<߇ӣMMỹ]2_fٗYZ}}\^VYdտ{C+ԓH-HlsX~8#zs2W*,]MfQgb4I/g]\=\X<+F$s;&[rET2-& 8_^֔L>u/d.㗃,^0W(Wn{ܿK gx X83oٞ.PZQEi!ۍ) _7 E TAp4W(1ݷ<9ě5/]G6xrtG|dL M] 9e8dL.t1ϝ*AoܪH ؆`tfOXЄC$twiu:0/y:3포 r3"Y}ny]=ؼ\F`2٘p?O%P?#5 :A5C2<_g"Tӳ/_.,-F1#P* &w .eS㼴}fNbήނh &)iYڲdpԷs# غI7$y90̬ںΝA=:oX<6c`8Kf:6DBo!*l{Y H,ӑ|W5Iu0J 3mo5MXٽ!zi4Un[ޘ؝`-(Sc;NfVoKe؁=v:$񗟏]֋!XZ$>V͉KAoL^ݝz.̎Ra2yhuZ3Izq{>d ݁]ί`qdUP ay:w(DR3%~ݵ;fGAlz3v B}Ne28˵8;)Yx85\/s ظ N]Ȍ!" %H9g]ѷG hd~<4 ْF]8i"/f} 5vU;lK$k!:3uwrh.L3Q~'_&5X8Yz$4wF$t{*yCMmw4/Wg־y+K$ƽ$`S! ىiCwq||@Xz@"7wn&Y{*}d ˅ oWoo$anc' v'Pt&גzST@R⅑L[ A,ߛ+f;ތC+&lX=Ύ}ԫbHvHeϑSX.i'rʙaY( X0FoZ2Q| /gM]BMJMIvm$;ӤqBKpdd޳c8si/ve<[y-;Pܺxz3&I5)|͚0g{hldb.fcSFdX6IU3lL~5"'&iIr-?DF7oO,6ThvN JprRH:82Ah>Χ_c̵V[hV n.{$ 6cЛUL ? ]lՃ+5;).=UN<;mmZ82A2s~SF dfƑ=?~MwbQ0* ;]ƴ.jpR*j0 kJ+UZ>jg fDxiKϊqdYhIp4^Φ?ϯ #;z隸Fߏbr\TVkvRQUڙf:Ƒ -dX#n6?o˥\8z7~jG%{.&PkTXFwu2]K{W7 f_] Y.9ݕųݴU K}jsՙF2,*raooVNa KՅg'WL B~q)߮E2Cay8uR!>]ߏfBoDB3.ɘjwucڱ~+bXtzKE/7#),-5;`h,CHcNG*eq[*%G0gW DB#_ƞO?dӛQ hker55BOw>J+vNyFфr]ɯdqVPGO fN6$n͑+EE-[ ݆f'cey,gkV8 w4XhJg/gjfY KꫪF˒/.I)cܶen x9+-ݑ"ۈ`b؜%Ԗ%V앐SiWY}_U-Uձt]l^OLMa6̶ ϦLylp.?#I3gfXff&unʔ)(ZI`z]v,]KvA1 v=C-vn2KIII222pqq{򟖐̋+uL~?N2cbw>}fH7%QǙ|i^;d̍. TJ_.ʋ]S9L sU&2 uII ;vCG]Rwwqӏ=ng@L,?" @o0x2s 屙ӥyhhLJw-TȄwwW&NwyϏSRRl:Dyy9III\x???<=\ɯd?wq)ϲUO‹ͬcZSk䏫WˑBzujR ͏hMIΗǦGaP6ʾ)0K+<4}E+4d \n%,=JAJz1O:0K'1m?FI]hV&3%3XЍ>mU>%?zJK)Ⱦkv6~}z} 22PƏ iii Zc_o'е#t_-bSb &نQ 3lJvPOE!5r-?D_ߦn1u>DBo&+tEylTV<]Et;u2L-ΝHMZϜO8pzwQ]xo- F3&UylrPH\lY!R#`cFU[r><&yӪyD a|`(y CΗ :ʞ#nxąn#"jh̛XK ʪ$䴖9c$ @* 7$.Ĥ" `4.fc&jHﻼJhL-q)T$z/2xNfZ7_ej^I@lr* ICXOO` \βvP҇qIDAT4[$To!EB@EULy?P`[ "ڴ♩ERNoͻIfm3/caTn>N9ċMoF9qb2~OvI5NJ_,ʓ#s;ʏCyv8\Ԏ9&4-NϊZ>2?waT؛+\(:r."ߧcEh7ŕ&+~7)Bmp7Sϲ58?GX>kɣ2gdjKEzqwr"߇y$*.FůetV6LO^`c!qH/ Gbè檢+*@Am?lp_N04K${ؒ3_&3Z<4{-6LxGΤ>.?^,c eb)*Q(̎Fb٭99xY怜Zډ_^m .~:p-6Yi7ѵhš]I; gD8khB),-gٺX*-{hJINmJ*eqkP),Y<9HwUy}'ތm ֙˙uڭz`z&4oɉ'&K66Lx+AW P(ࣧhl\> &$zgG+oWF&4Kܒ`YCsѨ?ma6LGW~nzjgL$CKayz0Nͤ7H`bٔ)#P;OKWTVqWcG`dn6Jcgؾۓ3aTBc,zK@ rAOZ~vU&<On6~ݘ8?4&<4IضScxv8ƿvݰCk^ǎS؄~ӏ7&:-dbMN\HڇѸRFgք:MVzuj˜₮$xo){qyG퉧`;=" GF'Oaᘎ6L5I{pSW~ޒ-ɒ_mCp֐Jݒvw;-IML;dng l7K$`aM`~Jd-aV|3} {|Z[S_b5cۦ*$%.cqFV.)e|By+=IRM})@iotҨx;+lyv7u1xaC% g}Rw|SʱPhVv|ӑi |Q<˺>P~u18= " Z~valց_AV)x[XZR se4.\iG w ԨGP%se4Dd_kVӐ ϛzߏs64*T̋v(zzm5p ߎ] JގIᰄ_lT)2VFݩVQEaN_RNJdl?ӑ=pSzDZ(5b~\GgX-LJN72z fG@My(G}Zw(6/ΎΉVs͟2;)s8@0>?3aIS1[\1{`~q̵7q{cupܗ}UJ%|t)*J R #7+ʰjw{1~Wd썭K'kdI@$4̓W"cKrUH2)ݟ=Zآ9x2^{pgŠWb~673Ec.iŲ4'.Ca }52hF=liz8Fu:"cVϭYΥmD{v>=OFj">=i8=qV@ۧA7e= hzBa|z~liX^ZuˠϢ B8r'(-GĶNƞ,[/U@M]b:< @T`Znݺ<ojnBQt{4'wznrM$Ip{h0u|n{$`  =s섮oqz@ חSa| @VCV8m~я@wxO]µ#Xχ Kcuz oj1ǣ&HM+ʠժײ?8|Kt !-ŊffQoȎA;bo(9HRBmsSg[g]f:b6ЉD$t ZGZ;?7Vd1!)kf#LH0!8 $95Ǩ6(lQ 0p"tk FP +-1?{.2$؟(1Љf!?!@G ( t7BBUC@QCRARBTBTDÒ),!# # !cŘwO:-2S lKtD1@$ ;dG߭A6h,}S*&bM@ZV''"bzqi@'aP[mQ]ppr{0  H-8=L-و?Ffp  H`??py`G8$!,$(B(J(U U*U*h*4ZtZMN$3:xH t"""0Љ@'""NK8u(--0ى/N۸q#;B݃i t^OAAA^G~~~daNDDto5 "__t yyy())v ,@^^t@DD@Z'˅'ObASSjjj+W"===Z -jpBMz|ddFk׮bAuu5\.W!""ZT~?.]_~yر#yff&ڊ>ߕ+WN""0٣@z*RSSa6'=ׇXBB =o&~PDDDYTnO:}}}Pؿ?0㝈\wvvbɒ%wCaݺuÇqq  Z6 O$InlZͭO%""{ NDD$:DDD` NDD$:DDD` NDD$:DDD` NDD$:DDD` NDD$:DDD` NDD$:DDD` NDD$:DDD` NDD$:DDD` NDD$:DDD` NDD$:DDD` NDD$:DDD` NDD$:DDD` @-׉CΟ?NX,TTTd2UQL رchmmEEE, vڅp8,W9DDD1M@$ ؼy3P^^χ9!""yVlYVl69!""yz{{z tN7ad2!TF#4 >3`r%vݻcccHLL q_WhddFk׮bAuu5\.W3j5<τ@ "SEDSݻ^z z^P\\b\pë*wI1)))#%% Î;"gff (++b l6FeNv|30̧A]]"_Nx? 188СCCahhHb͛7'%$$`0D1yn4FIENDB`spec/viz/exemplary/area0.png000064400000043742150145030150012016 0ustar00PNG  IHDR,5AsBIT|d pHYs   IDATxw &o (#4lB:HcDbcFbDIxCbhx.|'~j*ꀗj!/@Yt)> QWC\~ϮPL67fSQ@0^h/T;i{JmG}M5?6}S!755q)Ν;bHҴvrju~7vpKm\j7h*UӰ`QU4MES *h((KM n|6nRoD7tt$膁 |^'q˯h补evC-S_GuЇ2Y"](ݡ(3Pt%0@̙3̙3g#%},jSsj$ kY4łjj Z4EPVPNS(uauny29l.O&8iKh7?(.c= Qц躮۷f 79ol/of#7<( N͊ja[P=* 68lt6G*#͓oXd9￾8~ +O#!Ν;Yp!#DWW׈bqJ}M.Æna23HSUn4MR O&1/󺜜kSi:yuQ!&Qٶ믳pB/_>}bB%R|t}ްkC{wڿCϮVj򃷊ܿn5~mSn4/_uII]dsyvhfǁfr|cNڀH\ SWvpb ݽ2V7صw6˶G%SE6wjѨ◅VJ$j(F6Wp7f<!& t1e$ڝv+ &$39z"7̯Y2/ܻ^ 1$E3/HUUezK% ަXXѡ5n'>$EEē}/'/=.꫽hr1n!H1'&&1 tQLd3 lPbƇ)ìSY,'B.f{ײvŤ$.*N8൭{8rM}nUy~q1qIG(FO4Qhnz^w&pHaMΦaXeŔdi I.&7 tQoKm}m PPHU.n4M:C1:#s7$ .&58ro}tG.k̬K#&n&_0gZxYxn{& tQvl7?ç.*/U>,| FFy|遻ɺ1$EYvvmfPL>L]!2g`{&*( s$:n=Vy\WPUe&mQB,]+dϺb%^{7'/iJc\S)*H8;Rg}|塍rɋub\]j 2rqڭ̪ Hٜn}>fN)cD@4M>n:OU+5~7Ӄ>dkdir5'FVUٸ{W/Ĩ@c.Jߪi 3jr:4W#֍,37rsASZ;[[9 -SP6JWd:c1=icl{bur|IG(NW$צ* 7K!E]x2/~sW4Mef[.@T2hv_|.%@Bk?o'!v5=bb;CEC>ge^H]6.UB܂ .FfCjxdP!%\ /3o!xq[bDd]5C!.4M>!v!Da>FW$&Cv$E4ز2.ĘlD#nF]bk'?߲b6fc!׹&)%Cb t1$7=(F؅s2/B]֠UrP)Ls+>,x9F|F]bˮU.Y2.DY 6x{PWƞr@2L僽GDV 11l +e2?EID)~e'Z;,ʌ^bˆ%3\  /U3rg$ES[yunL_acf]P;butHdr}m^>rgN/cx@od}m UA/2'eMݿn9ݽUgT .tY5uA[{&(E#hff_ލngUycOw_CU, łբw,8311tnuN._v/;?㧻Ρ;?OfLq+|Xt>?y>ֱn|6~lF4ƚfy79v25A(΃̓>YYy4sF?y~h¦Lz(fvR05/~ÎfRAa!~MS,v5/5U{8֢Ơus s QQ WsϪ?~vЏy#@bxFp8wC8_dܹ̞]Gb'Ӄ>э.O$=}ӏy kְ\v&\OT4M1 ur<ց+7=gslR9U~K(**=>' XL'Ffҥc=W^<D"xND q9 jPwbl;kv}ЏYp9VբY6,e^u7,,*4Usb <.>/KL&zpi\)BtttpQ:!1}$Z2m\ ƷsX8m1N9'3yqiսŧ{e}\H×_ު)<8ܸmRδjf5LO+!l{͟œޅ);&rOR$I9PLL鋼ѧ+~mV13d(B[~ ~.ۧKrł]\{2֍iϭg=YYO,lg>Lx*M6W<*|a绰i2t+jY0gaٸΡitwl2V܎:I%RiS\^uQ_y:;y]gs9nGStCFcV?L6O,$+{vj$hGN_,j:˝%J ^oN|hᏪx\l Dc p<'_^mRA[OPI|Rjј!UyL\Ic1b/itțI4M d͚׮>6t0| m]r< ,XƞHOpLw>'=.rJIcy~t(cC6+> <+DI56t ×BMz/zµ_oLOvRoC4XTk*/IހߜΩ85Uu QKg | O,E,>$Di WȂe&>e9Ⳗ' U~4يS ޤq9rna&Dx*]4 5>U\p)tݠ'B$Q|"]{`x@`]i絭{ X*3jrC)2y7Nz6YnZxX,N8(yra.I$j=8=z$'l.{vIcM)ũ,4E\Q̈́H3D⩢un ^caT_֣UW.摍Z'^ۺP4צ*5~R$~Vv$zs|)Lg*WU%{e-2Y±dqxamU`9ݠ;L,AlZVaX@/muX_4\ʥ.n8[N[ xr0Įց/,r%^23d&'ib 8&͛9|`,y!v:Q>>ƚ,*ѕp-6`JU.g> f]F9>$NKwxj@(ܷf)ݽB* qtjpMScX&UyI9x 1L Œſf:ݕ~V ,E6Wպ;r2& 1˫念'צ U^^+ٞ/LO;i.9Bl.O(@p&|K߬mMp7VT4VMLg( cEIn5lVK8op;NlGd ]w/ Jd=j}ƴjyxUun@!0q>{hKneFmPaߚ#YzݲML(n%[05>4ȱͥd\hoX=ʨ-I@WoWMUTeTgGb츘,jzx(&X2M,*jb1&tEtEgM ׹ N} dl0ynefmT%k}acP ܲML ٜN8'?`\w RyZ¤khH[M@Cd&9ŖT6+3j8lTh۫( ~ .&d:K$( 3<ʏC,)̭u ݳ\$5Cڶ\lkS xi޾0=7GVM#5":@[; ʞR&tctΛ1@$mpr[Q{môG9y/ˁ@rO91&DIccˣ0LPH5fF|s#:9x|hZU|XEh*v9xGJgsn煵2?\i7V\/_0/=w1[~ZtZU|n:.7 1$nKb[G.&7}Ųس7e=JևrjU>+&ÄwO'x9v)CBfC ~dtä7J(V|EswᬐbnJsys$ROEƏi/c&X_.CB X*Ckw>Sm9e@O3>X^uR_嗹8ݝBD#CBY?6 à7J(V|%xu8&P'/{%ҬR!v!FMyyt D,;JN7hvC{6|LwvEAj/XwihV\wA[wp"]Ծnw-IvWEKm'D FCT#RXq]b\A( U-H!?`%|+m`24lww4lQ{:p&TO v 1LLbTYO-Y?"yݠ'J$Q<~ED_}F͘Vƞޤ L6{E~jYi="͝Y^&UBLDa->&PA+جsФ uhMSUk*)݄Nyt]gU %~gٙ;|&hUU~/<~2 |cå*ݎfuݓ*г]7d {l*ϯbBfќzli|ìib\t:MNÁי!v!=|h.:3Əբwi ~y4ƀv6~KnI8 5gWxw y*l\q6fj6M\^/zӤ9*?*uUU vr9vիW>}:˖-nXWMgoMU*/(4L'C,cʁL(3g$RdQ5s NqLifxeU_䎹(v47=R|aaoA&{!{!}~<'L(V'yHgro>.ZvؙY+UyvVі;Nui,r{x;vV&W넢 ."}31m%X>ϾGdҷjS Z+\SoۛX-ZI SDݎ;o^x'?aҥ^O9y׷}RԃwתrU^p7yN-\½=1s YGŅB4M"k_WB}jG9}dJB`;,XA: x݅mmXύfrN(B4-?vXkM[V_gY:;;9s>7|C}/jmcac0͘9羻(.>Ɵ|y%~~K\)BQU~U׏9@:a֜9aeaY o40EEWgs芢?1ͣfH?:+̒Uä( +s-2:bv 5mhp:U]]]K7lذaH_#L&Nd*ً-vgJhvL`q#Ʈ<*7 !EF&v1yu^wss w?_r&U݅IE7L9q{6- !Fl4UKwylm l6K4%`X_uע5Ч[ 0T> SnBMS x)L qgx~zDʹEp|>i?p$8oL/hZѤ*BӁj#B(ǟ͓K<Zt*ū9]44ѣ,YkL@by^np!BrY.cJuG"&Laq-kLkY,Z|Var9~?O<Ę~OC ~{6לڬ7TBb rcR-pWܱUÝCL.# pܒWF'x"P+upBLFMKs ~u7V{by|E4wAsz9~Hmi~t(B,䚪[Gw5BLvpm t⟶8՝囫WD7̨riE/~|ƀeq(Hd ~r8Ƨ-v\:愄b"$LᠭHZ? igWxqY'rW(jsQE[/4M%qcɏW!BQ>7p,qmn}͝[ionrO-9 w֗/8Ôˑ/W. %+؅b6+uUj=uZ vwf;ڝ>։drw`dx@c~p0J(_Jar! UQ826"QXR&Һ=eLy @V$3(;TN*B"J]Џڛ2wCQɳ~0olK{\2Ntey)BOQU%ȅbBC:%OU^Hr#÷Y\31:yZw,++~Ǣ|xxaPeB9FJ$ Н|~z[V;SԶv̝ޣq:X8=aV Q.%otwt%T埝I\bSUσ#%Kb^sI]Z? wtƴn'W||8œh}[@+ Ad&j\a^eBnf%Hg zg"_a|7O-`ƧZw;vhu˩*$nقqg$Яs7K#t&}mn BQ~PLg$io&8r5÷j]hb='5Yb=Ea^0B Qu5/>1ry%ƕcnE*t|o\Uۉ1N&BOT^ x 41ߜNp=kj=GNjբ )9>7 |gʵ2#b;p,Iz[,;XFe}3dsy~,kq9nb~L~m=/r-V oe6+ MV !ĤrرYc Bv%n;<<=薲lJHi^WR !D%h*5/Th"in['}s덾[Ǡi U t0ytwNf~CBvڱ,bIrתKe{7_Zхnnm=Y11Q@YMn譑ltl*p CStgTdG=G>W !f4^g0z΀_;zp&IgoL.iT|>S/ Mts!kY4j~<.G{'bPAMЋ??}' sYLC \.G2YOv aA^̡wt ʹl_ra)BjѨ z'2ē)L GHmDx4UfXmg*",&t:ۇ6E<Kq[-A+BL] ^J8 YnYP7UzEP50l?-mib\t:M>_w:L:/p{@Ux\ND!(U.&svMk+<0ʹ?5?>UCM~@4 łff iz.л:r+Su98G!D2x_RJdYGo-.ϳO~a/,z\!$`&dZ?߾#As8Uѿ»,3G.? Jr'w/qI !40 8io /K{m=Ƣz8F!=y|`˘|߲+o#w6r+>B!F$.~ǿz|߲YU7Ϭr3ǧ+!b><ɢzMsnٌ|߲ieן>C8iؼ_I9$B ӄg6ߐsc]1em+eKr*ci9(BLpN>pgyZlIDATǖOm> q}n B }9B!F]!B!D@B!*BQ$ЅB .BT t!H !@]!B!D@B!*BQ$ЅB .BT t!H !@]!B!D@B!*BQ$ЅB .BT t!H !@]!B!D@B!*BQ$ЅB .BT t!H !@]!B!D@B!*BQ$ЅB{i84YMDKfX`]HT !]AE7AK-,$ ,AC#$p ?^t}7ޟޟvsx^“3: X`: X`: X`O'E(qEQJnT䤖F8T** RYAw]WsssPKK"٬bXC(+[[[z ǂdņ++tZ~y<88NY#'Ry`OYArd2w Py`OYo|>555i~~^S㺮[ΉQ&Ν;W}kdR`PGs3p[ : X`: X`9K%E(qEQ~Ssojpp묮jffF~R{{.]zӳẮtaӳݾ}[,X'ǏݼyS555U;I---)q T*c\.9|R?~0=:lVODLORssx\PHǏ7.jkkS*‚Lϲښ}˗/bT*O>zDUg$tZ~ys?V[['NH>q]r*;}A:}4w+P(׺qwUJt59amooWzFx~{k2qsExBSSSu:;;MOR>|(׫)s{544P(huuU=sexz{{uY]~]uuuZZZ=()K B&T*iddDwΜ9czu={7s1utt(N^eBuibbB411 fɓ'XH}>4??/IZ\\Ύ9RzzzT*ydTa^W*Z\\ŋMϲF__޽;w0Kx<ׯD"U?z׬O5<= examples.length) { phantom.exit(0); return; } var example = examples[example_index]; var snapshot_index = 0; var page = webpage.create(); page.viewportSize = { width: 500, height: 300 }; page.clipRect = { width: 500, height: 300 }; page.onAlert = function (msg) { var e = JSON.parse(msg); if (e.fn == "snapshot") { page.render("output/" + example.name + snapshot_index + ".png"); snapshot_index += 1; } else if (e.fn == "mousemove") { page.sendEvent("mousemove", e.x, e.y); } }; page.open(html_path, function (status) { if (status == "fail") { console.log("Failed to load test page: " + example.name); phantom.exit(1); } else { page.evaluate(example.runner); } page.close(); run_example(example_index + 1); }); } exports.def = function (name, runner) { examples.push({ name: name, runner: runner }); }; exports.run = function () { if (fs.isDirectory("output")) { fs.list("output").forEach(function (path) { if (path != "." && path != "..") { fs.remove("output/" + path); } }); } else { fs.makeDirectory("output"); } run_example(0); }; spec/viz/test.html000064400000001570150145030150010150 0ustar00
spec/viz/visual_specs.js000064400000002676150145030150011351 0ustar00var examples = require('./examples'); examples.def('line', function () { Morris.Line({ element: 'chart', data: [ { x: 0, y: 10, z: 30 }, { x: 1, y: 20, z: 20 }, { x: 2, y: 30, z: 10 }, { x: 3, y: 30, z: 10 }, { x: 4, y: 20, z: 20 }, { x: 5, y: 10, z: 30 } ], xkey: 'x', ykeys: ['y', 'z'], labels: ['y', 'z'], parseTime: false }); window.snapshot(); }); examples.def('area', function () { Morris.Area({ element: 'chart', data: [ { x: 0, y: 1, z: 1 }, { x: 1, y: 2, z: 1 }, { x: 2, y: 3, z: 1 }, { x: 3, y: 3, z: 1 }, { x: 4, y: 2, z: 1 }, { x: 5, y: 1, z: 1 } ], xkey: 'x', ykeys: ['y', 'z'], labels: ['y', 'z'], parseTime: false }); window.snapshot(); }); examples.def('bar', function () { Morris.Bar({ element: 'chart', data: [ { x: 0, y: 1, z: 3 }, { x: 1, y: 2, z: 2 }, { x: 2, y: 3, z: 1 }, { x: 3, y: 3, z: 1 }, { x: 4, y: 2, z: 2 }, { x: 5, y: 1, z: 3 } ], xkey: 'x', ykeys: ['y', 'z'], labels: ['y', 'z'] }); window.snapshot(); }); examples.def('stacked_bar', function () { Morris.Bar({ element: 'chart', data: [ { x: 0, y: 1, z: 1 }, { x: 1, y: 2, z: 1 }, { x: 2, y: 3, z: 1 }, { x: 3, y: 3, z: 1 }, { x: 4, y: 2, z: 1 }, { x: 5, y: 1, z: 1 } ], xkey: 'x', ykeys: ['y', 'z'], labels: ['y', 'z'], stacked: true }); window.snapshot(); }); examples.run(); spec/viz/run.sh000064400000000674150145030150007447 0ustar00#!/bin/sh # visual_specs.js creates output in output/XXX.png phantomjs visual_specs.js # clear out old diffs mkdir -p diff rm -f diff/* # generate diffs PASS=1 for i in exemplary/*.png do FN=`basename $i` perceptualdiff $i output/$FN -output diff/$FN if [ $? -eq 0 ] then echo "OK: $FN" else echo "FAIL: $FN" PASS=0 fi done # pass / fail if [ $PASS -eq 1 ] then echo "Success." else echo "Failed." exit 1 fi spec/support/placeholder.coffee000064400000000242150145030150012635 0ustar00beforeEach -> placeholder = $('
') $('#test').append(placeholder) afterEach -> $('#test').empty() spec/lib/line/line_spec.coffee000064400000017624150145030150012311 0ustar00describe 'Morris.Line', -> it 'should raise an error when the placeholder element is not found', -> my_data = [{x: 1, y: 1}, {x: 2, y: 2}] fn = -> Morris.Line( element: "thisplacedoesnotexist" data: my_data xkey: 'x' ykeys: ['y'] labels: ['dontcare'] ) fn.should.throw(/Graph container element not found/) it 'should make point styles customizable', -> my_data = [{x: 1, y: 1}, {x: 2, y: 2}] red = '#ff0000' blue = '#0000ff' chart = Morris.Line element: 'graph' data: my_data xkey: 'x' ykeys: ['y'] labels: ['dontcare'] pointStrokeColors: [red, blue] pointStrokeWidths: [1, 2] pointFillColors: [null, red] chart.pointStrokeWidthForSeries(0).should.equal 1 chart.pointStrokeColorForSeries(0).should.equal red chart.pointStrokeWidthForSeries(1).should.equal 2 chart.pointStrokeColorForSeries(1).should.equal blue chart.colorFor(chart.data[0], 0, 'point').should.equal chart.colorFor(chart.data[0], 0, 'line') chart.colorFor(chart.data[1], 1, 'point').should.equal red describe 'generating column labels', -> it 'should use user-supplied x value strings by default', -> chart = Morris.Line element: 'graph' data: [{x: '2012 Q1', y: 1}, {x: '2012 Q2', y: 1}] xkey: 'x' ykeys: ['y'] labels: ['dontcare'] chart.data.map((x) -> x.label).should == ['2012 Q1', '2012 Q2'] it 'should use a default format for timestamp x-values', -> d1 = new Date(2012, 0, 1) d2 = new Date(2012, 0, 2) chart = Morris.Line element: 'graph' data: [{x: d1.getTime(), y: 1}, {x: d2.getTime(), y: 1}] xkey: 'x' ykeys: ['y'] labels: ['dontcare'] chart.data.map((x) -> x.label).should == [d2.toString(), d1.toString()] it 'should use user-defined formatters', -> d = new Date(2012, 0, 1) chart = Morris.Line element: 'graph' data: [{x: d.getTime(), y: 1}, {x: '2012-01-02', y: 1}] xkey: 'x' ykeys: ['y'] labels: ['dontcare'] dateFormat: (d) -> x = new Date(d) "#{x.getYear()}/#{x.getMonth()+1}/#{x.getDay()}" chart.data.map((x) -> x.label).should == ['2012/1/1', '2012/1/2'] describe 'rendering lines', -> beforeEach -> @defaults = element: 'graph' data: [{x:0, y:1, z:0}, {x:1, y:0, z:1}, {x:2, y:1, z:0}, {x:3, y:0, z:1}, {x:4, y:1, z:0}] xkey: 'x' ykeys: ['y', 'z'] labels: ['y', 'z'] lineColors: ['#abcdef', '#fedcba'] smooth: true shouldHavePath = (regex, color = '#abcdef') -> # Matches an SVG path element within the rendered chart. # # Sneakily uses line colors to differentiate between paths within # the chart. $('#graph').find("path[stroke='#{color}']").attr('d').should.match regex it 'should generate smooth lines when options.smooth is true', -> Morris.Line @defaults shouldHavePath /M[\d\.]+,[\d\.]+(C[\d\.]+(,[\d\.]+){5}){4}/ it 'should generate jagged lines when options.smooth is false', -> Morris.Line $.extend(@defaults, smooth: false) shouldHavePath /M[\d\.]+,[\d\.]+(L[\d\.]+,[\d\.]+){4}/ it 'should generate smooth/jagged lines according to the value for each series when options.smooth is an array', -> Morris.Line $.extend(@defaults, smooth: ['y']) shouldHavePath /M[\d\.]+,[\d\.]+(C[\d\.]+(,[\d\.]+){5}){4}/, '#abcdef' shouldHavePath /M[\d\.]+,[\d\.]+(L[\d\.]+,[\d\.]+){4}/, '#fedcba' it 'should ignore undefined values', -> @defaults.data[2].y = undefined Morris.Line @defaults shouldHavePath /M[\d\.]+,[\d\.]+(C[\d\.]+(,[\d\.]+){5}){3}/ it 'should break the line at null values', -> @defaults.data[2].y = null Morris.Line @defaults shouldHavePath /(M[\d\.]+,[\d\.]+C[\d\.]+(,[\d\.]+){5}){2}/ it 'should make line width customizable', -> chart = Morris.Line $.extend(@defaults, lineWidth: [1, 2]) chart.lineWidthForSeries(0).should.equal 1 chart.lineWidthForSeries(1).should.equal 2 describe '#createPath', -> it 'should generate a smooth line', -> testData = [{x: 0, y: 10}, {x: 10, y: 0}, {x: 20, y: 10}] path = Morris.Line.createPath(testData, true, 20) path.should.equal 'M0,10C2.5,7.5,7.5,0,10,0C12.5,0,17.5,7.5,20,10' it 'should generate a jagged line', -> testData = [{x: 0, y: 10}, {x: 10, y: 0}, {x: 20, y: 10}] path = Morris.Line.createPath(testData, false, 20) path.should.equal 'M0,10L10,0L20,10' it 'should prevent paths from descending below the bottom of the chart', -> testData = [{x: 0, y: 20}, {x: 10, y: 30}, {x: 20, y: 10}] path = Morris.Line.createPath(testData, true, 30) path.should.equal 'M0,20C2.5,22.5,7.5,30,10,30C12.5,28.75,17.5,15,20,10' it 'should break the line at null values', -> testData = [{x: 0, y: 10}, {x: 10, y: 0}, {x: 20, y: null}, {x: 30, y: 10}, {x: 40, y: 0}] path = Morris.Line.createPath(testData, true, 20) path.should.equal 'M0,10C2.5,7.5,7.5,2.5,10,0M30,10C32.5,7.5,37.5,2.5,40,0' it 'should ignore leading and trailing null values', -> testData = [{x: 0, y: null}, {x: 10, y: 10}, {x: 20, y: 0}, {x: 30, y: 10}, {x: 40, y: null}] path = Morris.Line.createPath(testData, true, 20) path.should.equal 'M10,10C12.5,7.5,17.5,0,20,0C22.5,0,27.5,7.5,30,10' describe 'svg structure', -> defaults = element: 'graph' data: [{x: '2012 Q1', y: 1}, {x: '2012 Q2', y: 1}] lineColors: [ '#0b62a4', '#7a92a3'] xkey: 'x' ykeys: ['y'] labels: ['dontcare'] it 'should contain a path that represents the line', -> chart = Morris.Line $.extend {}, defaults $('#graph').find("path[stroke='#0b62a4']").size().should.equal 1 it 'should contain a circle for each data point', -> chart = Morris.Line $.extend {}, defaults $('#graph').find("circle").size().should.equal 2 it 'should contain 5 grid lines', -> chart = Morris.Line $.extend {}, defaults $('#graph').find("path[stroke='#aaaaaa']").size().should.equal 5 it 'should contain 9 text elements', -> chart = Morris.Line $.extend {}, defaults $('#graph').find("text").size().should.equal 9 describe 'svg attributes', -> defaults = element: 'graph' data: [{x: '2012 Q1', y: 1}, {x: '2012 Q2', y: 1}] xkey: 'x' ykeys: ['y', 'z'] labels: ['Y', 'Z'] lineColors: [ '#0b62a4', '#7a92a3'] lineWidth: 3 pointStrokeWidths: [5] pointStrokeColors: ['#ffffff'] gridLineColor: '#aaa' gridStrokeWidth: 0.5 gridTextColor: '#888' gridTextSize: 12 pointSize: [5] it 'should have circles with configured fill color', -> chart = Morris.Line $.extend {}, defaults $('#graph').find("circle[fill='#0b62a4']").size().should.equal 2 it 'should have circles with configured stroke width', -> chart = Morris.Line $.extend {}, defaults $('#graph').find("circle[stroke-width='5']").size().should.equal 2 it 'should have circles with configured stroke color', -> chart = Morris.Line $.extend {}, defaults $('#graph').find("circle[stroke='#ffffff']").size().should.equal 2 it 'should have line with configured line width', -> chart = Morris.Line $.extend {}, defaults $('#graph').find("path[stroke-width='3']").size().should.equal 1 it 'should have text with configured font size', -> chart = Morris.Line $.extend {}, defaults $('#graph').find("text[font-size='12px']").size().should.equal 9 it 'should have text with configured font size', -> chart = Morris.Line $.extend {}, defaults $('#graph').find("text[fill='#888888']").size().should.equal 9 it 'should have circle with configured size', -> chart = Morris.Line $.extend {}, defaults $('#graph').find("circle[r='5']").size().should.equal 2 spec/lib/hover_spec.coffee000064400000004254150145030150011551 0ustar00describe "Morris.Hover", -> describe "with dummy content", -> beforeEach -> parent = $('
') .appendTo($('#test')) @hover = new Morris.Hover(parent: parent) @element = $('#test .morris-hover') it "should initialise a hidden, empty popup", -> @element.should.exist @element.should.be.hidden @element.should.be.empty describe "#show", -> it "should show the popup", -> @hover.show() @element.should.be.visible describe "#hide", -> it "should hide the popup", -> @hover.show() @hover.hide() @element.should.be.hidden describe "#html", -> it "should replace the contents of the element", -> @hover.html('
Foobarbaz
') @element.should.have.html('
Foobarbaz
') describe "#moveTo", -> beforeEach -> @hover.html('
') it "should place the popup directly above the given point", -> @hover.moveTo(100, 150) @element.should.have.css('left', '50px') @element.should.have.css('top', '40px') it "should place the popup below the given point if it does not fit above", -> @hover.moveTo(100, 50) @element.should.have.css('left', '50px') @element.should.have.css('top', '60px') it "should center the popup vertically if it will not fit above or below", -> @hover.moveTo(100, 100) @element.should.have.css('left', '50px') @element.should.have.css('top', '40px') it "should center the popup vertically if no y value is supplied", -> @hover.moveTo(100) @element.should.have.css('left', '50px') @element.should.have.css('top', '40px') describe "#update", -> it "should update content, show and reposition the popup", -> hover = new Morris.Hover(parent: $('#test')) html = "
Hello, Everyone!
" hover.update(html, 150, 200) el = $('#test .morris-hover') el.should.have.css('left', '100px') el.should.have.css('top', '90px') el.should.have.text('Hello, Everyone!') spec/lib/bar/bar_spec.coffee000064400000010525150145030150011734 0ustar00describe 'Morris.Bar', -> describe 'when using vertical grid', -> defaults = element: 'graph' data: [{x: 'foo', y: 2, z: 3}, {x: 'bar', y: 4, z: 6}] xkey: 'x' ykeys: ['y', 'z'] labels: ['Y', 'Z'] barColors: [ '#0b62a4', '#7a92a3'] gridLineColor: '#aaa' gridStrokeWidth: 0.5 gridTextColor: '#888' gridTextSize: 12 verticalGridCondition: (index) -> index % 2 verticalGridColor: '#888888' verticalGridOpacity: '0.2' describe 'svg structure', -> it 'should contain extra rectangles for vertical grid', -> $('#graph').css('height', '250px').css('width', '800px') chart = Morris.Bar $.extend {}, defaults $('#graph').find("rect").size().should.equal 6 describe 'svg attributes', -> it 'should have to bars with verticalGrid.color', -> chart = Morris.Bar $.extend {}, defaults $('#graph').find("rect[fill='#{defaults.verticalGridColor}']").size().should.equal 2 it 'should have to bars with verticalGrid.color', -> chart = Morris.Bar $.extend {}, defaults $('#graph').find("rect[fill-opacity='#{defaults.verticalGridOpacity}']").size().should.equal 2 describe 'svg structure', -> defaults = element: 'graph' data: [{x: 'foo', y: 2, z: 3}, {x: 'bar', y: 4, z: 6}] xkey: 'x' ykeys: ['y', 'z'] labels: ['Y', 'Z'] it 'should contain a rect for each bar', -> chart = Morris.Bar $.extend {}, defaults $('#graph').find("rect").size().should.equal 4 it 'should contain 5 grid lines', -> chart = Morris.Bar $.extend {}, defaults $('#graph').find("path").size().should.equal 5 it 'should contain 7 text elements', -> chart = Morris.Bar $.extend {}, defaults $('#graph').find("text").size().should.equal 7 describe 'svg attributes', -> defaults = element: 'graph' data: [{x: 'foo', y: 2, z: 3}, {x: 'bar', y: 4, z: 6}] xkey: 'x' ykeys: ['y', 'z'] labels: ['Y', 'Z'] barColors: [ '#0b62a4', '#7a92a3'] gridLineColor: '#aaa' gridStrokeWidth: 0.5 gridTextColor: '#888' gridTextSize: 12 it 'should have a bar with the first default color', -> chart = Morris.Bar $.extend {}, defaults $('#graph').find("rect[fill='#0b62a4']").size().should.equal 2 it 'should have a bar with no stroke', -> chart = Morris.Bar $.extend {}, defaults $('#graph').find("rect[stroke='none']").size().should.equal 4 it 'should have text with configured fill color', -> chart = Morris.Bar $.extend {}, defaults $('#graph').find("text[fill='#888888']").size().should.equal 7 it 'should have text with configured font size', -> chart = Morris.Bar $.extend {}, defaults $('#graph').find("text[font-size='12px']").size().should.equal 7 describe 'when setting bar radius', -> describe 'svg structure', -> defaults = element: 'graph' data: [{x: 'foo', y: 2, z: 3}, {x: 'bar', y: 4, z: 6}] xkey: 'x' ykeys: ['y', 'z'] labels: ['Y', 'Z'] barRadius: [5, 5, 0, 0] it 'should contain a path for each bar', -> chart = Morris.Bar $.extend {}, defaults $('#graph').find("path").size().should.equal 9 it 'should use rects if radius is too big', -> delete defaults.barStyle chart = Morris.Bar $.extend {}, defaults, barRadius: [300, 300, 0, 0] $('#graph').find("rect").size().should.equal 4 describe 'barSize option', -> describe 'svg attributes', -> defaults = element: 'graph' barSize: 20 data: [ {x: '2011 Q1', y: 3, z: 2, a: 3} {x: '2011 Q2', y: 2, z: null, a: 1} {x: '2011 Q3', y: 0, z: 2, a: 4} {x: '2011 Q4', y: 2, z: 4, a: 3} ], xkey: 'x' ykeys: ['y', 'z', 'a'] labels: ['Y', 'Z', 'A'] it 'should calc the width if too narrow for barSize', -> $('#graph').width('200px') chart = Morris.Bar $.extend {}, defaults $('#graph').find("rect").filter((i) -> parseFloat($(@).attr('width'), 10) < 10 ).size().should.equal 11 it 'should set width to @options.barSize if possible', -> chart = Morris.Bar $.extend {}, defaults $('#graph').find("rect[width='#{defaults.barSize}']").size().should.equal 11 spec/lib/bar/colours.coffee000064400000002456150145030150011650 0ustar00describe 'Morris.Bar#colorFor', -> defaults = element: 'graph' data: [{x: 'foo', y: 2, z: 3}, {x: 'bar', y: 4, z: 6}] xkey: 'x' ykeys: ['y', 'z'] labels: ['Y', 'Z'] it 'should fetch colours from an array', -> chart = Morris.Bar $.extend {}, defaults, barColors: ['#f00', '#0f0', '#00f'] chart.colorFor(chart.data[0], 0, 'bar').should.equal '#f00' chart.colorFor(chart.data[0], 0, 'hover').should.equal '#f00' chart.colorFor(chart.data[0], 1, 'bar').should.equal '#0f0' chart.colorFor(chart.data[0], 1, 'hover').should.equal '#0f0' chart.colorFor(chart.data[0], 2, 'bar').should.equal '#00f' chart.colorFor(chart.data[0], 2, 'hover').should.equal '#00f' chart.colorFor(chart.data[0], 3, 'bar').should.equal '#f00' chart.colorFor(chart.data[0], 4, 'hover').should.equal '#0f0' it 'should defer to a callback', -> stub = sinon.stub().returns '#f00' chart = Morris.Bar $.extend {}, defaults, barColors: stub stub.reset() chart.colorFor(chart.data[0], 0, 'bar') stub.should.have.been.calledWith( {x:0, y:2, label:'foo'}, {index:0, key:'y', label:'Y'}, 'bar') chart.colorFor(chart.data[0], 1, 'hover') stub.should.have.been.calledWith( {x:0, y:3, label:'foo'}, {index:1, key:'z', label:'Z'}, 'hover') spec/lib/area/area_spec.coffee000064400000004206150145030150012243 0ustar00describe 'Morris.Area', -> describe 'svg structure', -> defaults = element: 'graph' data: [{x: '2012 Q1', y: 1}, {x: '2012 Q2', y: 1}] lineColors: [ '#0b62a4', '#7a92a3'] gridLineColor: '#aaa' xkey: 'x' ykeys: ['y'] labels: ['Y'] it 'should contain a line path for each line', -> chart = Morris.Area $.extend {}, defaults $('#graph').find("path[stroke='#0b62a4']").size().should.equal 1 it 'should contain a path with stroke-width 0 for each line', -> chart = Morris.Area $.extend {}, defaults $('#graph').find("path[stroke='#0b62a4']").size().should.equal 1 it 'should contain 5 grid lines', -> chart = Morris.Area $.extend {}, defaults $('#graph').find("path[stroke='#aaaaaa']").size().should.equal 5 it 'should contain 9 text elements', -> chart = Morris.Area $.extend {}, defaults $('#graph').find("text").size().should.equal 9 describe 'svg attributes', -> defaults = element: 'graph' data: [{x: '2012 Q1', y: 1}, {x: '2012 Q2', y: 1}] xkey: 'x' ykeys: ['y'] labels: ['Y'] lineColors: [ '#0b62a4', '#7a92a3'] lineWidth: 3 pointWidths: [5] pointStrokeColors: ['#ffffff'] gridLineColor: '#aaa' gridStrokeWidth: 0.5 gridTextColor: '#888' gridTextSize: 12 it 'should not be cumulative if behaveLikeLine', -> chart = Morris.Area $.extend {}, defaults, behaveLikeLine: true chart.cumulative.should.equal false it 'should have a line with transparent fill if behaveLikeLine', -> chart = Morris.Area $.extend {}, defaults, behaveLikeLine: true $('#graph').find("path[fill-opacity='0.8']").size().should.equal 1 it 'should not have a line with transparent fill', -> chart = Morris.Area $.extend {}, defaults $('#graph').find("path[fill-opacity='0.8']").size().should.equal 0 it 'should have a line with the fill of a modified line color', -> chart = Morris.Area $.extend {}, defaults $('#graph').find("path[fill='#0b62a4']").size().should.equal 0 $('#graph').find("path[fill='#7a92a3']").size().should.equal 0 spec/lib/grid/auto_grid_lines_spec.coffee000064400000002204150145030150014513 0ustar00describe 'Morris.Grid#autoGridLines', -> beforeEach -> @subject = Morris.Grid.prototype.autoGridLines it 'should draw at fixed intervals', -> @subject(0, 4, 5).should.deep.equal [0, 1, 2, 3, 4] @subject(0, 400, 5).should.deep.equal [0, 100, 200, 300, 400] it 'should pick intervals that show significant numbers', -> @subject(102, 499, 5).should.deep.equal [100, 200, 300, 400, 500] it 'should draw zero when it falls within [ymin..ymax]', -> @subject(-100, 300, 5).should.deep.equal [-100, 0, 100, 200, 300] @subject(-50, 350, 5).should.deep.equal [-125, 0, 125, 250, 375] @subject(-400, 400, 5).should.deep.equal [-400, -200, 0, 200, 400] @subject(100, 500, 5).should.deep.equal [100, 200, 300, 400, 500] @subject(-500, -100, 5).should.deep.equal [-500, -400, -300, -200, -100] it 'should generate decimal labels to 2 significant figures', -> @subject(0, 1, 5).should.deep.equal [0, 0.25, 0.5, 0.75, 1] @subject(0.1, 0.5, 5).should.deep.equal [0.1, 0.2, 0.3, 0.4, 0.5] it 'should use integer intervals for intervals larger than 1', -> @subject(0, 9, 5).should.deep.equal [0, 3, 6, 9, 12] spec/lib/grid/set_data_spec.coffee000064400000015030150145030150013131 0ustar00describe 'Morris.Grid#setData', -> it 'should not alter user-supplied data', -> my_data = [{x: 1, y: 1}, {x: 2, y: 2}] expected_data = [{x: 1, y: 1}, {x: 2, y: 2}] Morris.Line element: 'graph' data: my_data xkey: 'x' ykeys: ['y'] labels: ['dontcare'] my_data.should.deep.equal expected_data describe 'ymin/ymax', -> beforeEach -> @defaults = element: 'graph' xkey: 'x' ykeys: ['y', 'z'] labels: ['y', 'z'] it 'should use a user-specified minimum and maximum value', -> line = Morris.Line $.extend @defaults, data: [{x: 1, y: 1}] ymin: 10 ymax: 20 line.ymin.should.equal 10 line.ymax.should.equal 20 describe 'auto', -> it 'should automatically calculate the minimum and maximum value', -> line = Morris.Line $.extend @defaults, data: [{x: 1, y: 10}, {x: 2, y: 15}, {x: 3, y: null}, {x: 4}] ymin: 'auto' ymax: 'auto' line.ymin.should.equal 10 line.ymax.should.equal 15 it 'should automatically calculate the minimum and maximum value given no y data', -> line = Morris.Line $.extend @defaults, data: [{x: 1}, {x: 2}, {x: 3}, {x: 4}] ymin: 'auto' ymax: 'auto' line.ymin.should.equal 0 line.ymax.should.equal 1 describe 'auto [n]', -> it 'should automatically calculate the minimum and maximum value', -> line = Morris.Line $.extend @defaults, data: [{x: 1, y: 10}, {x: 2, y: 15}, {x: 3, y: null}, {x: 4}] ymin: 'auto 11' ymax: 'auto 13' line.ymin.should.equal 10 line.ymax.should.equal 15 it 'should automatically calculate the minimum and maximum value given no data', -> line = Morris.Line $.extend @defaults, data: [{x: 1}, {x: 2}, {x: 3}, {x: 4}] ymin: 'auto 11' ymax: 'auto 13' line.ymin.should.equal 11 line.ymax.should.equal 13 it 'should use a user-specified minimum and maximum value', -> line = Morris.Line $.extend @defaults, data: [{x: 1, y: 10}, {x: 2, y: 15}, {x: 3, y: null}, {x: 4}] ymin: 'auto 5' ymax: 'auto 20' line.ymin.should.equal 5 line.ymax.should.equal 20 it 'should use a user-specified minimum and maximum value given no data', -> line = Morris.Line $.extend @defaults, data: [{x: 1}, {x: 2}, {x: 3}, {x: 4}] ymin: 'auto 5' ymax: 'auto 20' line.ymin.should.equal 5 line.ymax.should.equal 20 describe 'xmin/xmax', -> it 'should calculate the horizontal range', -> line = Morris.Line element: 'graph' data: [{x: 2, y: 2}, {x: 1, y: 1}, {x: 4, y: 4}, {x: 3, y: 3}] xkey: 'x' ykeys: ['y'] labels: ['y'] line.xmin.should == 1 line.xmax.should == 4 it "should pad the range if there's only one data point", -> line = Morris.Line element: 'graph' data: [{x: 2, y: 2}] xkey: 'x' ykeys: ['y'] labels: ['y'] line.xmin.should == 1 line.xmax.should == 3 describe 'sorting', -> it 'should sort data when parseTime is true', -> line = Morris.Line element: 'graph' data: [ {x: '2012 Q1', y: 2}, {x: '2012 Q3', y: 1}, {x: '2012 Q4', y: 4}, {x: '2012 Q2', y: 3}] xkey: 'x' ykeys: ['y'] labels: ['y'] line.data.map((row) -> row.label).should.deep.equal ['2012 Q1', '2012 Q2', '2012 Q3', '2012 Q4'] it 'should not sort data when parseTime is false', -> line = Morris.Line element: 'graph' data: [{x: 1, y: 2}, {x: 4, y: 1}, {x: 3, y: 4}, {x: 2, y: 3}] xkey: 'x' ykeys: ['y'] labels: ['y'] parseTime: false line.data.map((row) -> row.label).should.deep.equal [1, 4, 3, 2] describe 'timestamp data', -> it 'should generate default labels for timestamp x-values', -> d = [ new Date 2012, 0, 1 new Date 2012, 0, 2 new Date 2012, 0, 3 new Date 2012, 0, 4 ] line = Morris.Line element: 'graph' data: [ {x: d[0].getTime(), y: 2}, {x: d[1].getTime(), y: 1}, {x: d[2].getTime(), y: 4}, {x: d[3].getTime(), y: 3}] xkey: 'x' ykeys: ['y'] labels: ['y'] line.data.map((row) -> row.label).should.deep.equal d.map((t) -> t.toString()) it 'should use a user-supplied formatter for labels', -> line = Morris.Line element: 'graph' data: [ {x: new Date(2012, 0, 1).getTime(), y: 2}, {x: new Date(2012, 0, 2).getTime(), y: 1}, {x: new Date(2012, 0, 3).getTime(), y: 4}, {x: new Date(2012, 0, 4).getTime(), y: 3}] xkey: 'x' ykeys: ['y'] labels: ['y'] dateFormat: (ts) -> date = new Date(ts) "#{date.getFullYear()}-#{date.getMonth()+1}-#{date.getDate()}" line.data.map((row) -> row.label).should.deep.equal ['2012-1-1', '2012-1-2', '2012-1-3', '2012-1-4'] it 'should parse y-values in strings', -> line = Morris.Line element: 'graph' data: [{x: 2, y: '12'}, {x: 1, y: '13.5'}, {x: 4, y: '14'}, {x: 3, y: '16'}] xkey: 'x' ykeys: ['y'] labels: ['y'] line.ymin.should == 12 line.ymax.should == 16 line.data.map((row) -> row.y).should.deep.equal [[13.5], [12], [16], [14]] it 'should clear the chart when empty data is supplied', -> line = Morris.Line element: 'graph', data: [{x: 2, y: '12'}, {x: 1, y: '13.5'}, {x: 4, y: '14'}, {x: 3, y: '16'}] xkey: 'x' ykeys: ['y'] labels: ['y'] line.data.length.should.equal 4 line.setData([]) line.data.length.should.equal 0 line.setData([{x: 2, y: '12'}, {x: 1, y: '13.5'}, {x: 4, y: '14'}, {x: 3, y: '16'}]) line.data.length.should.equal 4 it 'should be able to add data if the chart is initialised with empty data', -> line = Morris.Line element: 'graph', data: [] xkey: 'x' ykeys: ['y'] labels: ['y'] line.data.length.should.equal 0 line.setData([{x: 2, y: '12'}, {x: 1, y: '13.5'}, {x: 4, y: '14'}, {x: 3, y: '16'}]) line.data.length.should.equal 4 it 'should automatically choose significant numbers for y-labels', -> line = Morris.Line element: 'graph', data: [{x: 1, y: 0}, {x: 2, y: 3600}] xkey: 'x' ykeys: ['y'] labels: ['y'] line.grid.should == [0, 1000, 2000, 3000, 4000] spec/lib/grid/y_label_format_spec.coffee000064400000000673150145030150014333 0ustar00describe 'Morris.Grid#yLabelFormat', -> it 'should use custom formatter for y labels', -> formatter = (label) -> flabel = parseFloat(label) / 1000 "#{flabel.toFixed(1)}k" line = Morris.Line element: 'graph' data: [{x: 1, y: 1500}, {x: 2, y: 2500}] xkey: 'x' ykeys: ['y'] labels: ['dontcare'] preUnits: "$" yLabelFormat: formatter line.yLabelFormat(1500).should.equal "1.5k" spec/lib/pad_spec.coffee000064400000001114150145030150011162 0ustar00describe '#pad', -> it 'should pad numbers', -> Morris.pad2(0).should.equal("00") Morris.pad2(1).should.equal("01") Morris.pad2(2).should.equal("02") Morris.pad2(3).should.equal("03") Morris.pad2(4).should.equal("04") Morris.pad2(5).should.equal("05") Morris.pad2(6).should.equal("06") Morris.pad2(7).should.equal("07") Morris.pad2(8).should.equal("08") Morris.pad2(9).should.equal("09") Morris.pad2(10).should.equal("10") Morris.pad2(12).should.equal("12") Morris.pad2(34).should.equal("34") Morris.pad2(123).should.equal("123")spec/lib/label_series_spec.coffee000064400000015106150145030150013055 0ustar00describe '#labelSeries', -> it 'should generate decade intervals', -> Morris.labelSeries( new Date(1952, 0, 1).getTime(), new Date(2012, 0, 1).getTime(), 1000 ).should.deep.equal([ ["1960", new Date(1960, 0, 1).getTime()], ["1970", new Date(1970, 0, 1).getTime()], ["1980", new Date(1980, 0, 1).getTime()], ["1990", new Date(1990, 0, 1).getTime()], ["2000", new Date(2000, 0, 1).getTime()], ["2010", new Date(2010, 0, 1).getTime()] ]) Morris.labelSeries( new Date(1952, 3, 1).getTime(), new Date(2012, 3, 1).getTime(), 1000 ).should.deep.equal([ ["1960", new Date(1960, 0, 1).getTime()], ["1970", new Date(1970, 0, 1).getTime()], ["1980", new Date(1980, 0, 1).getTime()], ["1990", new Date(1990, 0, 1).getTime()], ["2000", new Date(2000, 0, 1).getTime()], ["2010", new Date(2010, 0, 1).getTime()] ]) it 'should generate year intervals', -> Morris.labelSeries( new Date(2007, 0, 1).getTime(), new Date(2012, 0, 1).getTime(), 1000 ).should.deep.equal([ ["2007", new Date(2007, 0, 1).getTime()], ["2008", new Date(2008, 0, 1).getTime()], ["2009", new Date(2009, 0, 1).getTime()], ["2010", new Date(2010, 0, 1).getTime()], ["2011", new Date(2011, 0, 1).getTime()], ["2012", new Date(2012, 0, 1).getTime()] ]) Morris.labelSeries( new Date(2007, 3, 1).getTime(), new Date(2012, 3, 1).getTime(), 1000 ).should.deep.equal([ ["2008", new Date(2008, 0, 1).getTime()], ["2009", new Date(2009, 0, 1).getTime()], ["2010", new Date(2010, 0, 1).getTime()], ["2011", new Date(2011, 0, 1).getTime()], ["2012", new Date(2012, 0, 1).getTime()] ]) it 'should generate month intervals', -> Morris.labelSeries( new Date(2012, 0, 1).getTime(), new Date(2012, 5, 1).getTime(), 1000 ).should.deep.equal([ ["2012-01", new Date(2012, 0, 1).getTime()], ["2012-02", new Date(2012, 1, 1).getTime()], ["2012-03", new Date(2012, 2, 1).getTime()], ["2012-04", new Date(2012, 3, 1).getTime()], ["2012-05", new Date(2012, 4, 1).getTime()], ["2012-06", new Date(2012, 5, 1).getTime()] ]) it 'should generate week intervals', -> Morris.labelSeries( new Date(2012, 0, 1).getTime(), new Date(2012, 1, 10).getTime(), 1000 ).should.deep.equal([ ["2012-01-01", new Date(2012, 0, 1).getTime()], ["2012-01-08", new Date(2012, 0, 8).getTime()], ["2012-01-15", new Date(2012, 0, 15).getTime()], ["2012-01-22", new Date(2012, 0, 22).getTime()], ["2012-01-29", new Date(2012, 0, 29).getTime()], ["2012-02-05", new Date(2012, 1, 5).getTime()] ]) it 'should generate day intervals', -> Morris.labelSeries( new Date(2012, 0, 1).getTime(), new Date(2012, 0, 6).getTime(), 1000 ).should.deep.equal([ ["2012-01-01", new Date(2012, 0, 1).getTime()], ["2012-01-02", new Date(2012, 0, 2).getTime()], ["2012-01-03", new Date(2012, 0, 3).getTime()], ["2012-01-04", new Date(2012, 0, 4).getTime()], ["2012-01-05", new Date(2012, 0, 5).getTime()], ["2012-01-06", new Date(2012, 0, 6).getTime()] ]) it 'should generate hour intervals', -> Morris.labelSeries( new Date(2012, 0, 1, 0).getTime(), new Date(2012, 0, 1, 5).getTime(), 1000 ).should.deep.equal([ ["00:00", new Date(2012, 0, 1, 0).getTime()], ["01:00", new Date(2012, 0, 1, 1).getTime()], ["02:00", new Date(2012, 0, 1, 2).getTime()], ["03:00", new Date(2012, 0, 1, 3).getTime()], ["04:00", new Date(2012, 0, 1, 4).getTime()], ["05:00", new Date(2012, 0, 1, 5).getTime()] ]) it 'should generate half-hour intervals', -> Morris.labelSeries( new Date(2012, 0, 1, 0, 0).getTime(), new Date(2012, 0, 1, 2, 30).getTime(), 1000 ).should.deep.equal([ ["00:00", new Date(2012, 0, 1, 0, 0).getTime()], ["00:30", new Date(2012, 0, 1, 0, 30).getTime()], ["01:00", new Date(2012, 0, 1, 1, 0).getTime()], ["01:30", new Date(2012, 0, 1, 1, 30).getTime()], ["02:00", new Date(2012, 0, 1, 2, 0).getTime()], ["02:30", new Date(2012, 0, 1, 2, 30).getTime()] ]) Morris.labelSeries( new Date(2012, 4, 12, 0, 0).getTime(), new Date(2012, 4, 12, 2, 30).getTime(), 1000 ).should.deep.equal([ ["00:00", new Date(2012, 4, 12, 0, 0).getTime()], ["00:30", new Date(2012, 4, 12, 0, 30).getTime()], ["01:00", new Date(2012, 4, 12, 1, 0).getTime()], ["01:30", new Date(2012, 4, 12, 1, 30).getTime()], ["02:00", new Date(2012, 4, 12, 2, 0).getTime()], ["02:30", new Date(2012, 4, 12, 2, 30).getTime()] ]) it 'should generate fifteen-minute intervals', -> Morris.labelSeries( new Date(2012, 0, 1, 0, 0).getTime(), new Date(2012, 0, 1, 1, 15).getTime(), 1000 ).should.deep.equal([ ["00:00", new Date(2012, 0, 1, 0, 0).getTime()], ["00:15", new Date(2012, 0, 1, 0, 15).getTime()], ["00:30", new Date(2012, 0, 1, 0, 30).getTime()], ["00:45", new Date(2012, 0, 1, 0, 45).getTime()], ["01:00", new Date(2012, 0, 1, 1, 0).getTime()], ["01:15", new Date(2012, 0, 1, 1, 15).getTime()] ]) Morris.labelSeries( new Date(2012, 4, 12, 0, 0).getTime(), new Date(2012, 4, 12, 1, 15).getTime(), 1000 ).should.deep.equal([ ["00:00", new Date(2012, 4, 12, 0, 0).getTime()], ["00:15", new Date(2012, 4, 12, 0, 15).getTime()], ["00:30", new Date(2012, 4, 12, 0, 30).getTime()], ["00:45", new Date(2012, 4, 12, 0, 45).getTime()], ["01:00", new Date(2012, 4, 12, 1, 0).getTime()], ["01:15", new Date(2012, 4, 12, 1, 15).getTime()] ]) it 'should override automatic intervals', -> Morris.labelSeries( new Date(2011, 11, 12).getTime(), new Date(2012, 0, 12).getTime(), 1000, "year" ).should.deep.equal([ ["2012", new Date(2012, 0, 1).getTime()] ]) it 'should apply custom formatters', -> Morris.labelSeries( new Date(2012, 0, 1).getTime(), new Date(2012, 0, 6).getTime(), 1000, "day", (d) -> "#{d.getMonth()+1}/#{d.getDate()}/#{d.getFullYear()}" ).should.deep.equal([ ["1/1/2012", new Date(2012, 0, 1).getTime()], ["1/2/2012", new Date(2012, 0, 2).getTime()], ["1/3/2012", new Date(2012, 0, 3).getTime()], ["1/4/2012", new Date(2012, 0, 4).getTime()], ["1/5/2012", new Date(2012, 0, 5).getTime()], ["1/6/2012", new Date(2012, 0, 6).getTime()] ]) spec/lib/donut/donut_spec.coffee000064400000005476150145030150012717 0ustar00describe 'Morris.Donut', -> describe 'svg structure', -> defaults = element: 'graph' data: [ {label: 'Jam', value: 25 }, {label: 'Frosted', value: 40 }, {label: 'Custard', value: 25 }, {label: 'Sugar', value: 10 } ] formatter: (y) -> "#{y}%" it 'should contain 2 paths for each segment', -> chart = Morris.Donut $.extend {}, defaults $('#graph').find("path").size().should.equal 8 it 'should contain 2 text elements for the label', -> chart = Morris.Donut $.extend {}, defaults $('#graph').find("text").size().should.equal 2 describe 'svg attributes', -> defaults = element: 'graph' data: [ {label: 'Jam', value: 25 }, {label: 'Frosted', value: 40 }, {label: 'Custard', value: 25 }, {label: 'Sugar', value: 10 } ] formatter: (y) -> "#{y}%" colors: [ '#0B62A4', '#3980B5', '#679DC6', '#95BBD7'] it 'should have a label with font size 15', -> chart = Morris.Donut $.extend {}, defaults $('#graph').find("text[font-size='15px']").size().should.equal 1 it 'should have a label with font size 14', -> chart = Morris.Donut $.extend {}, defaults $('#graph').find("text[font-size='14px']").size().should.equal 1 it 'should have a label with font-weight 800', -> chart = Morris.Donut $.extend {}, defaults $('#graph').find("text[font-weight='800']").size().should.equal 1 it 'should have 1 paths with fill of first color', -> chart = Morris.Donut $.extend {}, defaults $('#graph').find("path[fill='#0b62a4']").size().should.equal 1 it 'should have 1 paths with stroke of first color', -> chart = Morris.Donut $.extend {}, defaults $('#graph').find("path[stroke='#0b62a4']").size().should.equal 1 it 'should have a path with white stroke', -> chart = Morris.Donut $.extend {}, defaults $('#graph').find("path[stroke='#ffffff']").size().should.equal 4 it 'should have a path with stroke-width 3', -> chart = Morris.Donut $.extend {}, defaults $('#graph').find("path[stroke-width='3']").size().should.equal 4 it 'should have a path with stroke-width 2', -> chart = Morris.Donut $.extend {}, defaults $('#graph').find("path[stroke-width='2']").size().should.equal 4 describe 'setData', -> defaults = element: 'graph' data: [ {label: 'One', value: 25 }, {label: "Two", value: 30} ] colors: ['#ff0000', '#00ff00', '#0000ff'] it 'should update the chart', -> chart = Morris.Donut $.extend {}, defaults $('#graph').find("path[stroke='#0000ff']").size().should.equal 0 chart.setData [ { label: 'One', value: 25 } { label: 'Two', value: 30 } { label: 'Three', value: 35 } ] $('#graph').find("path[stroke='#0000ff']").size().should.equal 1 spec/lib/commas_spec.coffee000064400000002456150145030150011707 0ustar00describe '#commas', -> it 'should insert commas into long numbers', -> # zero Morris.commas(0).should.equal("0") # positive integers Morris.commas(1).should.equal("1") Morris.commas(12).should.equal("12") Morris.commas(123).should.equal("123") Morris.commas(1234).should.equal("1,234") Morris.commas(12345).should.equal("12,345") Morris.commas(123456).should.equal("123,456") Morris.commas(1234567).should.equal("1,234,567") # negative integers Morris.commas(-1).should.equal("-1") Morris.commas(-12).should.equal("-12") Morris.commas(-123).should.equal("-123") Morris.commas(-1234).should.equal("-1,234") Morris.commas(-12345).should.equal("-12,345") Morris.commas(-123456).should.equal("-123,456") Morris.commas(-1234567).should.equal("-1,234,567") # positive decimals Morris.commas(1.2).should.equal("1.2") Morris.commas(12.34).should.equal("12.34") Morris.commas(123.456).should.equal("123.456") Morris.commas(1234.56).should.equal("1,234.56") # negative decimals Morris.commas(-1.2).should.equal("-1.2") Morris.commas(-12.34).should.equal("-12.34") Morris.commas(-123.456).should.equal("-123.456") Morris.commas(-1234.56).should.equal("-1,234.56") # null Morris.commas(null).should.equal('-') spec/lib/parse_time_spec.coffee000064400000003744150145030150012561 0ustar00describe '#parseTime', -> it 'should parse years', -> Morris.parseDate('2012').should.equal(new Date(2012, 0, 1).getTime()) it 'should parse quarters', -> Morris.parseDate('2012 Q1').should.equal(new Date(2012, 2, 1).getTime()) it 'should parse months', -> Morris.parseDate('2012-09').should.equal(new Date(2012, 8, 1).getTime()) Morris.parseDate('2012-10').should.equal(new Date(2012, 9, 1).getTime()) it 'should parse dates', -> Morris.parseDate('2012-09-15').should.equal(new Date(2012, 8, 15).getTime()) Morris.parseDate('2012-10-15').should.equal(new Date(2012, 9, 15).getTime()) it 'should parse times', -> Morris.parseDate("2012-10-15 12:34").should.equal(new Date(2012, 9, 15, 12, 34).getTime()) Morris.parseDate("2012-10-15T12:34").should.equal(new Date(2012, 9, 15, 12, 34).getTime()) Morris.parseDate("2012-10-15 12:34:55").should.equal(new Date(2012, 9, 15, 12, 34, 55).getTime()) Morris.parseDate("2012-10-15T12:34:55").should.equal(new Date(2012, 9, 15, 12, 34, 55).getTime()) it 'should parse times with timezones', -> Morris.parseDate("2012-10-15T12:34+0100").should.equal(Date.UTC(2012, 9, 15, 11, 34)) Morris.parseDate("2012-10-15T12:34+02:00").should.equal(Date.UTC(2012, 9, 15, 10, 34)) Morris.parseDate("2012-10-15T12:34-0100").should.equal(Date.UTC(2012, 9, 15, 13, 34)) Morris.parseDate("2012-10-15T12:34-02:00").should.equal(Date.UTC(2012, 9, 15, 14, 34)) Morris.parseDate("2012-10-15T12:34:55Z").should.equal(Date.UTC(2012, 9, 15, 12, 34, 55)) Morris.parseDate("2012-10-15T12:34:55+0600").should.equal(Date.UTC(2012, 9, 15, 6, 34, 55)) Morris.parseDate("2012-10-15T12:34:55+04:00").should.equal(Date.UTC(2012, 9, 15, 8, 34, 55)) Morris.parseDate("2012-10-15T12:34:55-0600").should.equal(Date.UTC(2012, 9, 15, 18, 34, 55)) it 'should pass-through timestamps', -> Morris.parseDate(new Date(2012, 9, 15, 12, 34, 55, 123).getTime()) .should.equal(new Date(2012, 9, 15, 12, 34, 55, 123).getTime())bower.json000064400000000607150145030150006552 0ustar00{ "name": "morris.js", "version": "0.5.0", "main": [ "./morris.js", "./morris.css" ], "dependencies": { "jquery": ">= 2.1.0", "raphael": ">= 2.0", "mocha": "~1.17.1" }, "devDependencies": { "mocha": "~1.17.1", "chai": "~1.9.0", "chai-jquery": "~1.2.1", "sinon": "http://sinonjs.org/releases/sinon-1.8.1.js", "sinon-chai": "~2.5.0" } } morris.js000064400000200777150145030150006424 0ustar00/* @license morris.js v0.5.0 Copyright 2014 Olly Smith All rights reserved. Licensed under the BSD-2-Clause License. */ (function() { var $, Morris, minutesSpecHelper, secondsSpecHelper, __slice = [].slice, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; Morris = window.Morris = {}; $ = jQuery; Morris.EventEmitter = (function() { function EventEmitter() {} EventEmitter.prototype.on = function(name, handler) { if (this.handlers == null) { this.handlers = {}; } if (this.handlers[name] == null) { this.handlers[name] = []; } this.handlers[name].push(handler); return this; }; EventEmitter.prototype.fire = function() { var args, handler, name, _i, _len, _ref, _results; name = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; if ((this.handlers != null) && (this.handlers[name] != null)) { _ref = this.handlers[name]; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { handler = _ref[_i]; _results.push(handler.apply(null, args)); } return _results; } }; return EventEmitter; })(); Morris.commas = function(num) { var absnum, intnum, ret, strabsnum; if (num != null) { ret = num < 0 ? "-" : ""; absnum = Math.abs(num); intnum = Math.floor(absnum).toFixed(0); ret += intnum.replace(/(?=(?:\d{3})+$)(?!^)/g, ','); strabsnum = absnum.toString(); if (strabsnum.length > intnum.length) { ret += strabsnum.slice(intnum.length); } return ret; } else { return '-'; } }; Morris.pad2 = function(number) { return (number < 10 ? '0' : '') + number; }; Morris.Grid = (function(_super) { __extends(Grid, _super); function Grid(options) { this.resizeHandler = __bind(this.resizeHandler, this); var _this = this; if (typeof options.element === 'string') { this.el = $(document.getElementById(options.element)); } else { this.el = $(options.element); } if ((this.el == null) || this.el.length === 0) { throw new Error("Graph container element not found"); } if (this.el.css('position') === 'static') { this.el.css('position', 'relative'); } this.options = $.extend({}, this.gridDefaults, this.defaults || {}, options); if (typeof this.options.units === 'string') { this.options.postUnits = options.units; } this.raphael = new Raphael(this.el[0]); this.elementWidth = null; this.elementHeight = null; this.dirty = false; this.selectFrom = null; if (this.init) { this.init(); } this.setData(this.options.data); this.el.bind('mousemove', function(evt) { var left, offset, right, width, x; offset = _this.el.offset(); x = evt.pageX - offset.left; if (_this.selectFrom) { left = _this.data[_this.hitTest(Math.min(x, _this.selectFrom))]._x; right = _this.data[_this.hitTest(Math.max(x, _this.selectFrom))]._x; width = right - left; return _this.selectionRect.attr({ x: left, width: width }); } else { return _this.fire('hovermove', x, evt.pageY - offset.top); } }); this.el.bind('mouseleave', function(evt) { if (_this.selectFrom) { _this.selectionRect.hide(); _this.selectFrom = null; } return _this.fire('hoverout'); }); this.el.bind('touchstart touchmove touchend', function(evt) { var offset, touch; touch = evt.originalEvent.touches[0] || evt.originalEvent.changedTouches[0]; offset = _this.el.offset(); return _this.fire('hovermove', touch.pageX - offset.left, touch.pageY - offset.top); }); this.el.bind('click', function(evt) { var offset; offset = _this.el.offset(); return _this.fire('gridclick', evt.pageX - offset.left, evt.pageY - offset.top); }); if (this.options.rangeSelect) { this.selectionRect = this.raphael.rect(0, 0, 0, this.el.innerHeight()).attr({ fill: this.options.rangeSelectColor, stroke: false }).toBack().hide(); this.el.bind('mousedown', function(evt) { var offset; offset = _this.el.offset(); return _this.startRange(evt.pageX - offset.left); }); this.el.bind('mouseup', function(evt) { var offset; offset = _this.el.offset(); _this.endRange(evt.pageX - offset.left); return _this.fire('hovermove', evt.pageX - offset.left, evt.pageY - offset.top); }); } if (this.options.resize) { $(window).bind('resize', function(evt) { if (_this.timeoutId != null) { window.clearTimeout(_this.timeoutId); } return _this.timeoutId = window.setTimeout(_this.resizeHandler, 100); }); } this.el.css('-webkit-tap-highlight-color', 'rgba(0,0,0,0)'); if (this.postInit) { this.postInit(); } } Grid.prototype.gridDefaults = { dateFormat: null, axes: true, grid: true, gridLineColor: '#aaa', gridStrokeWidth: 0.5, gridTextColor: '#888', gridTextSize: 12, gridTextFamily: 'sans-serif', gridTextWeight: 'normal', hideHover: false, yLabelFormat: null, xLabelAngle: 0, numLines: 5, padding: 25, parseTime: true, postUnits: '', preUnits: '', ymax: 'auto', ymin: 'auto 0', goals: [], goalStrokeWidth: 1.0, goalLineColors: ['#666633', '#999966', '#cc6666', '#663333'], events: [], eventStrokeWidth: 1.0, eventLineColors: ['#005a04', '#ccffbb', '#3a5f0b', '#005502'], rangeSelect: null, rangeSelectColor: '#eef', resize: false }; Grid.prototype.setData = function(data, redraw) { var e, idx, index, maxGoal, minGoal, ret, row, step, total, y, ykey, ymax, ymin, yval, _ref; if (redraw == null) { redraw = true; } this.options.data = data; if ((data == null) || data.length === 0) { this.data = []; this.raphael.clear(); if (this.hover != null) { this.hover.hide(); } return; } ymax = this.cumulative ? 0 : null; ymin = this.cumulative ? 0 : null; if (this.options.goals.length > 0) { minGoal = Math.min.apply(Math, this.options.goals); maxGoal = Math.max.apply(Math, this.options.goals); ymin = ymin != null ? Math.min(ymin, minGoal) : minGoal; ymax = ymax != null ? Math.max(ymax, maxGoal) : maxGoal; } this.data = (function() { var _i, _len, _results; _results = []; for (index = _i = 0, _len = data.length; _i < _len; index = ++_i) { row = data[index]; ret = { src: row }; ret.label = row[this.options.xkey]; if (this.options.parseTime) { ret.x = Morris.parseDate(ret.label); if (this.options.dateFormat) { ret.label = this.options.dateFormat(ret.x); } else if (typeof ret.label === 'number') { ret.label = new Date(ret.label).toString(); } } else { ret.x = index; if (this.options.xLabelFormat) { ret.label = this.options.xLabelFormat(ret); } } total = 0; ret.y = (function() { var _j, _len1, _ref, _results1; _ref = this.options.ykeys; _results1 = []; for (idx = _j = 0, _len1 = _ref.length; _j < _len1; idx = ++_j) { ykey = _ref[idx]; yval = row[ykey]; if (typeof yval === 'string') { yval = parseFloat(yval); } if ((yval != null) && typeof yval !== 'number') { yval = null; } if (yval != null) { if (this.cumulative) { total += yval; } else { if (ymax != null) { ymax = Math.max(yval, ymax); ymin = Math.min(yval, ymin); } else { ymax = ymin = yval; } } } if (this.cumulative && (total != null)) { ymax = Math.max(total, ymax); ymin = Math.min(total, ymin); } _results1.push(yval); } return _results1; }).call(this); _results.push(ret); } return _results; }).call(this); if (this.options.parseTime) { this.data = this.data.sort(function(a, b) { return (a.x > b.x) - (b.x > a.x); }); } this.xmin = this.data[0].x; this.xmax = this.data[this.data.length - 1].x; this.events = []; if (this.options.events.length > 0) { if (this.options.parseTime) { this.events = (function() { var _i, _len, _ref, _results; _ref = this.options.events; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { e = _ref[_i]; _results.push(Morris.parseDate(e)); } return _results; }).call(this); } else { this.events = this.options.events; } this.xmax = Math.max(this.xmax, Math.max.apply(Math, this.events)); this.xmin = Math.min(this.xmin, Math.min.apply(Math, this.events)); } if (this.xmin === this.xmax) { this.xmin -= 1; this.xmax += 1; } this.ymin = this.yboundary('min', ymin); this.ymax = this.yboundary('max', ymax); if (this.ymin === this.ymax) { if (ymin) { this.ymin -= 1; } this.ymax += 1; } if (((_ref = this.options.axes) === true || _ref === 'both' || _ref === 'y') || this.options.grid === true) { if (this.options.ymax === this.gridDefaults.ymax && this.options.ymin === this.gridDefaults.ymin) { this.grid = this.autoGridLines(this.ymin, this.ymax, this.options.numLines); this.ymin = Math.min(this.ymin, this.grid[0]); this.ymax = Math.max(this.ymax, this.grid[this.grid.length - 1]); } else { step = (this.ymax - this.ymin) / (this.options.numLines - 1); this.grid = (function() { var _i, _ref1, _ref2, _results; _results = []; for (y = _i = _ref1 = this.ymin, _ref2 = this.ymax; step > 0 ? _i <= _ref2 : _i >= _ref2; y = _i += step) { _results.push(y); } return _results; }).call(this); } } this.dirty = true; if (redraw) { return this.redraw(); } }; Grid.prototype.yboundary = function(boundaryType, currentValue) { var boundaryOption, suggestedValue; boundaryOption = this.options["y" + boundaryType]; if (typeof boundaryOption === 'string') { if (boundaryOption.slice(0, 4) === 'auto') { if (boundaryOption.length > 5) { suggestedValue = parseInt(boundaryOption.slice(5), 10); if (currentValue == null) { return suggestedValue; } return Math[boundaryType](currentValue, suggestedValue); } else { if (currentValue != null) { return currentValue; } else { return 0; } } } else { return parseInt(boundaryOption, 10); } } else { return boundaryOption; } }; Grid.prototype.autoGridLines = function(ymin, ymax, nlines) { var gmax, gmin, grid, smag, span, step, unit, y, ymag; span = ymax - ymin; ymag = Math.floor(Math.log(span) / Math.log(10)); unit = Math.pow(10, ymag); gmin = Math.floor(ymin / unit) * unit; gmax = Math.ceil(ymax / unit) * unit; step = (gmax - gmin) / (nlines - 1); if (unit === 1 && step > 1 && Math.ceil(step) !== step) { step = Math.ceil(step); gmax = gmin + step * (nlines - 1); } if (gmin < 0 && gmax > 0) { gmin = Math.floor(ymin / step) * step; gmax = Math.ceil(ymax / step) * step; } if (step < 1) { smag = Math.floor(Math.log(step) / Math.log(10)); grid = (function() { var _i, _results; _results = []; for (y = _i = gmin; step > 0 ? _i <= gmax : _i >= gmax; y = _i += step) { _results.push(parseFloat(y.toFixed(1 - smag))); } return _results; })(); } else { grid = (function() { var _i, _results; _results = []; for (y = _i = gmin; step > 0 ? _i <= gmax : _i >= gmax; y = _i += step) { _results.push(y); } return _results; })(); } return grid; }; Grid.prototype._calc = function() { var bottomOffsets, gridLine, h, i, w, yLabelWidths, _ref, _ref1; w = this.el.width(); h = this.el.height(); if (this.elementWidth !== w || this.elementHeight !== h || this.dirty) { this.elementWidth = w; this.elementHeight = h; this.dirty = false; this.left = this.options.padding; this.right = this.elementWidth - this.options.padding; this.top = this.options.padding; this.bottom = this.elementHeight - this.options.padding; if ((_ref = this.options.axes) === true || _ref === 'both' || _ref === 'y') { yLabelWidths = (function() { var _i, _len, _ref1, _results; _ref1 = this.grid; _results = []; for (_i = 0, _len = _ref1.length; _i < _len; _i++) { gridLine = _ref1[_i]; _results.push(this.measureText(this.yAxisFormat(gridLine)).width); } return _results; }).call(this); this.left += Math.max.apply(Math, yLabelWidths); } if ((_ref1 = this.options.axes) === true || _ref1 === 'both' || _ref1 === 'x') { bottomOffsets = (function() { var _i, _ref2, _results; _results = []; for (i = _i = 0, _ref2 = this.data.length; 0 <= _ref2 ? _i < _ref2 : _i > _ref2; i = 0 <= _ref2 ? ++_i : --_i) { _results.push(this.measureText(this.data[i].text, -this.options.xLabelAngle).height); } return _results; }).call(this); this.bottom -= Math.max.apply(Math, bottomOffsets); } this.width = Math.max(1, this.right - this.left); this.height = Math.max(1, this.bottom - this.top); this.dx = this.width / (this.xmax - this.xmin); this.dy = this.height / (this.ymax - this.ymin); if (this.calc) { return this.calc(); } } }; Grid.prototype.transY = function(y) { return this.bottom - (y - this.ymin) * this.dy; }; Grid.prototype.transX = function(x) { if (this.data.length === 1) { return (this.left + this.right) / 2; } else { return this.left + (x - this.xmin) * this.dx; } }; Grid.prototype.redraw = function() { this.raphael.clear(); this._calc(); this.drawGrid(); this.drawGoals(); this.drawEvents(); if (this.draw) { return this.draw(); } }; Grid.prototype.measureText = function(text, angle) { var ret, tt; if (angle == null) { angle = 0; } tt = this.raphael.text(100, 100, text).attr('font-size', this.options.gridTextSize).attr('font-family', this.options.gridTextFamily).attr('font-weight', this.options.gridTextWeight).rotate(angle); ret = tt.getBBox(); tt.remove(); return ret; }; Grid.prototype.yAxisFormat = function(label) { return this.yLabelFormat(label); }; Grid.prototype.yLabelFormat = function(label) { if (typeof this.options.yLabelFormat === 'function') { return this.options.yLabelFormat(label); } else { return "" + this.options.preUnits + (Morris.commas(label)) + this.options.postUnits; } }; Grid.prototype.drawGrid = function() { var lineY, y, _i, _len, _ref, _ref1, _ref2, _results; if (this.options.grid === false && ((_ref = this.options.axes) !== true && _ref !== 'both' && _ref !== 'y')) { return; } _ref1 = this.grid; _results = []; for (_i = 0, _len = _ref1.length; _i < _len; _i++) { lineY = _ref1[_i]; y = this.transY(lineY); if ((_ref2 = this.options.axes) === true || _ref2 === 'both' || _ref2 === 'y') { this.drawYAxisLabel(this.left - this.options.padding / 2, y, this.yAxisFormat(lineY)); } if (this.options.grid) { _results.push(this.drawGridLine("M" + this.left + "," + y + "H" + (this.left + this.width))); } else { _results.push(void 0); } } return _results; }; Grid.prototype.drawGoals = function() { var color, goal, i, _i, _len, _ref, _results; _ref = this.options.goals; _results = []; for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { goal = _ref[i]; color = this.options.goalLineColors[i % this.options.goalLineColors.length]; _results.push(this.drawGoal(goal, color)); } return _results; }; Grid.prototype.drawEvents = function() { var color, event, i, _i, _len, _ref, _results; _ref = this.events; _results = []; for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { event = _ref[i]; color = this.options.eventLineColors[i % this.options.eventLineColors.length]; _results.push(this.drawEvent(event, color)); } return _results; }; Grid.prototype.drawGoal = function(goal, color) { return this.raphael.path("M" + this.left + "," + (this.transY(goal)) + "H" + this.right).attr('stroke', color).attr('stroke-width', this.options.goalStrokeWidth); }; Grid.prototype.drawEvent = function(event, color) { return this.raphael.path("M" + (this.transX(event)) + "," + this.bottom + "V" + this.top).attr('stroke', color).attr('stroke-width', this.options.eventStrokeWidth); }; Grid.prototype.drawYAxisLabel = function(xPos, yPos, text) { return this.raphael.text(xPos, yPos, text).attr('font-size', this.options.gridTextSize).attr('font-family', this.options.gridTextFamily).attr('font-weight', this.options.gridTextWeight).attr('fill', this.options.gridTextColor).attr('text-anchor', 'end'); }; Grid.prototype.drawGridLine = function(path) { return this.raphael.path(path).attr('stroke', this.options.gridLineColor).attr('stroke-width', this.options.gridStrokeWidth); }; Grid.prototype.startRange = function(x) { this.hover.hide(); this.selectFrom = x; return this.selectionRect.attr({ x: x, width: 0 }).show(); }; Grid.prototype.endRange = function(x) { var end, start; if (this.selectFrom) { start = Math.min(this.selectFrom, x); end = Math.max(this.selectFrom, x); this.options.rangeSelect.call(this.el, { start: this.data[this.hitTest(start)].x, end: this.data[this.hitTest(end)].x }); return this.selectFrom = null; } }; Grid.prototype.resizeHandler = function() { this.timeoutId = null; this.raphael.setSize(this.el.width(), this.el.height()); return this.redraw(); }; return Grid; })(Morris.EventEmitter); Morris.parseDate = function(date) { var isecs, m, msecs, n, o, offsetmins, p, q, r, ret, secs; if (typeof date === 'number') { return date; } m = date.match(/^(\d+) Q(\d)$/); n = date.match(/^(\d+)-(\d+)$/); o = date.match(/^(\d+)-(\d+)-(\d+)$/); p = date.match(/^(\d+) W(\d+)$/); q = date.match(/^(\d+)-(\d+)-(\d+)[ T](\d+):(\d+)(Z|([+-])(\d\d):?(\d\d))?$/); r = date.match(/^(\d+)-(\d+)-(\d+)[ T](\d+):(\d+):(\d+(\.\d+)?)(Z|([+-])(\d\d):?(\d\d))?$/); if (m) { return new Date(parseInt(m[1], 10), parseInt(m[2], 10) * 3 - 1, 1).getTime(); } else if (n) { return new Date(parseInt(n[1], 10), parseInt(n[2], 10) - 1, 1).getTime(); } else if (o) { return new Date(parseInt(o[1], 10), parseInt(o[2], 10) - 1, parseInt(o[3], 10)).getTime(); } else if (p) { ret = new Date(parseInt(p[1], 10), 0, 1); if (ret.getDay() !== 4) { ret.setMonth(0, 1 + ((4 - ret.getDay()) + 7) % 7); } return ret.getTime() + parseInt(p[2], 10) * 604800000; } else if (q) { if (!q[6]) { return new Date(parseInt(q[1], 10), parseInt(q[2], 10) - 1, parseInt(q[3], 10), parseInt(q[4], 10), parseInt(q[5], 10)).getTime(); } else { offsetmins = 0; if (q[6] !== 'Z') { offsetmins = parseInt(q[8], 10) * 60 + parseInt(q[9], 10); if (q[7] === '+') { offsetmins = 0 - offsetmins; } } return Date.UTC(parseInt(q[1], 10), parseInt(q[2], 10) - 1, parseInt(q[3], 10), parseInt(q[4], 10), parseInt(q[5], 10) + offsetmins); } } else if (r) { secs = parseFloat(r[6]); isecs = Math.floor(secs); msecs = Math.round((secs - isecs) * 1000); if (!r[8]) { return new Date(parseInt(r[1], 10), parseInt(r[2], 10) - 1, parseInt(r[3], 10), parseInt(r[4], 10), parseInt(r[5], 10), isecs, msecs).getTime(); } else { offsetmins = 0; if (r[8] !== 'Z') { offsetmins = parseInt(r[10], 10) * 60 + parseInt(r[11], 10); if (r[9] === '+') { offsetmins = 0 - offsetmins; } } return Date.UTC(parseInt(r[1], 10), parseInt(r[2], 10) - 1, parseInt(r[3], 10), parseInt(r[4], 10), parseInt(r[5], 10) + offsetmins, isecs, msecs); } } else { return new Date(parseInt(date, 10), 0, 1).getTime(); } }; Morris.Hover = (function() { Hover.defaults = { "class": 'morris-hover morris-default-style' }; function Hover(options) { if (options == null) { options = {}; } this.options = $.extend({}, Morris.Hover.defaults, options); this.el = $("
"); this.el.hide(); this.options.parent.append(this.el); } Hover.prototype.update = function(html, x, y) { if (!html) { return this.hide(); } else { this.html(html); this.show(); return this.moveTo(x, y); } }; Hover.prototype.html = function(content) { return this.el.html(content); }; Hover.prototype.moveTo = function(x, y) { var hoverHeight, hoverWidth, left, parentHeight, parentWidth, top; parentWidth = this.options.parent.innerWidth(); parentHeight = this.options.parent.innerHeight(); hoverWidth = this.el.outerWidth(); hoverHeight = this.el.outerHeight(); left = Math.min(Math.max(0, x - hoverWidth / 2), parentWidth - hoverWidth); if (y != null) { top = y - hoverHeight - 10; if (top < 0) { top = y + 10; if (top + hoverHeight > parentHeight) { top = parentHeight / 2 - hoverHeight / 2; } } } else { top = parentHeight / 2 - hoverHeight / 2; } return this.el.css({ left: left + "px", top: parseInt(top) + "px" }); }; Hover.prototype.show = function() { return this.el.show(); }; Hover.prototype.hide = function() { return this.el.hide(); }; return Hover; })(); Morris.Line = (function(_super) { __extends(Line, _super); function Line(options) { this.hilight = __bind(this.hilight, this); this.onHoverOut = __bind(this.onHoverOut, this); this.onHoverMove = __bind(this.onHoverMove, this); this.onGridClick = __bind(this.onGridClick, this); if (!(this instanceof Morris.Line)) { return new Morris.Line(options); } Line.__super__.constructor.call(this, options); } Line.prototype.init = function() { if (this.options.hideHover !== 'always') { this.hover = new Morris.Hover({ parent: this.el }); this.on('hovermove', this.onHoverMove); this.on('hoverout', this.onHoverOut); return this.on('gridclick', this.onGridClick); } }; Line.prototype.defaults = { lineWidth: 3, pointSize: 4, lineColors: ['#0b62a4', '#7A92A3', '#4da74d', '#afd8f8', '#edc240', '#cb4b4b', '#9440ed'], pointStrokeWidths: [1], pointStrokeColors: ['#ffffff'], pointFillColors: [], smooth: true, xLabels: 'auto', xLabelFormat: null, xLabelMargin: 24, hideHover: false }; Line.prototype.calc = function() { this.calcPoints(); return this.generatePaths(); }; Line.prototype.calcPoints = function() { var row, y, _i, _len, _ref, _results; _ref = this.data; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { row = _ref[_i]; row._x = this.transX(row.x); row._y = (function() { var _j, _len1, _ref1, _results1; _ref1 = row.y; _results1 = []; for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { y = _ref1[_j]; if (y != null) { _results1.push(this.transY(y)); } else { _results1.push(y); } } return _results1; }).call(this); _results.push(row._ymax = Math.min.apply(Math, [this.bottom].concat((function() { var _j, _len1, _ref1, _results1; _ref1 = row._y; _results1 = []; for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { y = _ref1[_j]; if (y != null) { _results1.push(y); } } return _results1; })()))); } return _results; }; Line.prototype.hitTest = function(x) { var index, r, _i, _len, _ref; if (this.data.length === 0) { return null; } _ref = this.data.slice(1); for (index = _i = 0, _len = _ref.length; _i < _len; index = ++_i) { r = _ref[index]; if (x < (r._x + this.data[index]._x) / 2) { break; } } return index; }; Line.prototype.onGridClick = function(x, y) { var index; index = this.hitTest(x); return this.fire('click', index, this.data[index].src, x, y); }; Line.prototype.onHoverMove = function(x, y) { var index; index = this.hitTest(x); return this.displayHoverForRow(index); }; Line.prototype.onHoverOut = function() { if (this.options.hideHover !== false) { return this.displayHoverForRow(null); } }; Line.prototype.displayHoverForRow = function(index) { var _ref; if (index != null) { (_ref = this.hover).update.apply(_ref, this.hoverContentForRow(index)); return this.hilight(index); } else { this.hover.hide(); return this.hilight(); } }; Line.prototype.hoverContentForRow = function(index) { var content, j, row, y, _i, _len, _ref; row = this.data[index]; content = "
" + row.label + "
"; _ref = row.y; for (j = _i = 0, _len = _ref.length; _i < _len; j = ++_i) { y = _ref[j]; content += "
\n " + this.options.labels[j] + ":\n " + (this.yLabelFormat(y)) + "\n
"; } if (typeof this.options.hoverCallback === 'function') { content = this.options.hoverCallback(index, this.options, content, row.src); } return [content, row._x, row._ymax]; }; Line.prototype.generatePaths = function() { var coords, i, r, smooth; return this.paths = (function() { var _i, _ref, _ref1, _results; _results = []; for (i = _i = 0, _ref = this.options.ykeys.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { smooth = typeof this.options.smooth === "boolean" ? this.options.smooth : (_ref1 = this.options.ykeys[i], __indexOf.call(this.options.smooth, _ref1) >= 0); coords = (function() { var _j, _len, _ref2, _results1; _ref2 = this.data; _results1 = []; for (_j = 0, _len = _ref2.length; _j < _len; _j++) { r = _ref2[_j]; if (r._y[i] !== void 0) { _results1.push({ x: r._x, y: r._y[i] }); } } return _results1; }).call(this); if (coords.length > 1) { _results.push(Morris.Line.createPath(coords, smooth, this.bottom)); } else { _results.push(null); } } return _results; }).call(this); }; Line.prototype.draw = function() { var _ref; if ((_ref = this.options.axes) === true || _ref === 'both' || _ref === 'x') { this.drawXAxis(); } this.drawSeries(); if (this.options.hideHover === false) { return this.displayHoverForRow(this.data.length - 1); } }; Line.prototype.drawXAxis = function() { var drawLabel, l, labels, prevAngleMargin, prevLabelMargin, row, ypos, _i, _len, _results, _this = this; ypos = this.bottom + this.options.padding / 2; prevLabelMargin = null; prevAngleMargin = null; drawLabel = function(labelText, xpos) { var label, labelBox, margin, offset, textBox; label = _this.drawXAxisLabel(_this.transX(xpos), ypos, labelText); textBox = label.getBBox(); label.transform("r" + (-_this.options.xLabelAngle)); labelBox = label.getBBox(); label.transform("t0," + (labelBox.height / 2) + "..."); if (_this.options.xLabelAngle !== 0) { offset = -0.5 * textBox.width * Math.cos(_this.options.xLabelAngle * Math.PI / 180.0); label.transform("t" + offset + ",0..."); } labelBox = label.getBBox(); if (((prevLabelMargin == null) || prevLabelMargin >= labelBox.x + labelBox.width || (prevAngleMargin != null) && prevAngleMargin >= labelBox.x) && labelBox.x >= 0 && (labelBox.x + labelBox.width) < _this.el.width()) { if (_this.options.xLabelAngle !== 0) { margin = 1.25 * _this.options.gridTextSize / Math.sin(_this.options.xLabelAngle * Math.PI / 180.0); prevAngleMargin = labelBox.x - margin; } return prevLabelMargin = labelBox.x - _this.options.xLabelMargin; } else { return label.remove(); } }; if (this.options.parseTime) { if (this.data.length === 1 && this.options.xLabels === 'auto') { labels = [[this.data[0].label, this.data[0].x]]; } else { labels = Morris.labelSeries(this.xmin, this.xmax, this.width, this.options.xLabels, this.options.xLabelFormat); } } else { labels = (function() { var _i, _len, _ref, _results; _ref = this.data; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { row = _ref[_i]; _results.push([row.label, row.x]); } return _results; }).call(this); } labels.reverse(); _results = []; for (_i = 0, _len = labels.length; _i < _len; _i++) { l = labels[_i]; _results.push(drawLabel(l[0], l[1])); } return _results; }; Line.prototype.drawSeries = function() { var i, _i, _j, _ref, _ref1, _results; this.seriesPoints = []; for (i = _i = _ref = this.options.ykeys.length - 1; _ref <= 0 ? _i <= 0 : _i >= 0; i = _ref <= 0 ? ++_i : --_i) { this._drawLineFor(i); } _results = []; for (i = _j = _ref1 = this.options.ykeys.length - 1; _ref1 <= 0 ? _j <= 0 : _j >= 0; i = _ref1 <= 0 ? ++_j : --_j) { _results.push(this._drawPointFor(i)); } return _results; }; Line.prototype._drawPointFor = function(index) { var circle, row, _i, _len, _ref, _results; this.seriesPoints[index] = []; _ref = this.data; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { row = _ref[_i]; circle = null; if (row._y[index] != null) { circle = this.drawLinePoint(row._x, row._y[index], this.colorFor(row, index, 'point'), index); } _results.push(this.seriesPoints[index].push(circle)); } return _results; }; Line.prototype._drawLineFor = function(index) { var path; path = this.paths[index]; if (path !== null) { return this.drawLinePath(path, this.colorFor(null, index, 'line'), index); } }; Line.createPath = function(coords, smooth, bottom) { var coord, g, grads, i, ix, lg, path, prevCoord, x1, x2, y1, y2, _i, _len; path = ""; if (smooth) { grads = Morris.Line.gradients(coords); } prevCoord = { y: null }; for (i = _i = 0, _len = coords.length; _i < _len; i = ++_i) { coord = coords[i]; if (coord.y != null) { if (prevCoord.y != null) { if (smooth) { g = grads[i]; lg = grads[i - 1]; ix = (coord.x - prevCoord.x) / 4; x1 = prevCoord.x + ix; y1 = Math.min(bottom, prevCoord.y + ix * lg); x2 = coord.x - ix; y2 = Math.min(bottom, coord.y - ix * g); path += "C" + x1 + "," + y1 + "," + x2 + "," + y2 + "," + coord.x + "," + coord.y; } else { path += "L" + coord.x + "," + coord.y; } } else { if (!smooth || (grads[i] != null)) { path += "M" + coord.x + "," + coord.y; } } } prevCoord = coord; } return path; }; Line.gradients = function(coords) { var coord, grad, i, nextCoord, prevCoord, _i, _len, _results; grad = function(a, b) { return (a.y - b.y) / (a.x - b.x); }; _results = []; for (i = _i = 0, _len = coords.length; _i < _len; i = ++_i) { coord = coords[i]; if (coord.y != null) { nextCoord = coords[i + 1] || { y: null }; prevCoord = coords[i - 1] || { y: null }; if ((prevCoord.y != null) && (nextCoord.y != null)) { _results.push(grad(prevCoord, nextCoord)); } else if (prevCoord.y != null) { _results.push(grad(prevCoord, coord)); } else if (nextCoord.y != null) { _results.push(grad(coord, nextCoord)); } else { _results.push(null); } } else { _results.push(null); } } return _results; }; Line.prototype.hilight = function(index) { var i, _i, _j, _ref, _ref1; if (this.prevHilight !== null && this.prevHilight !== index) { for (i = _i = 0, _ref = this.seriesPoints.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) { if (this.seriesPoints[i][this.prevHilight]) { this.seriesPoints[i][this.prevHilight].animate(this.pointShrinkSeries(i)); } } } if (index !== null && this.prevHilight !== index) { for (i = _j = 0, _ref1 = this.seriesPoints.length - 1; 0 <= _ref1 ? _j <= _ref1 : _j >= _ref1; i = 0 <= _ref1 ? ++_j : --_j) { if (this.seriesPoints[i][index]) { this.seriesPoints[i][index].animate(this.pointGrowSeries(i)); } } } return this.prevHilight = index; }; Line.prototype.colorFor = function(row, sidx, type) { if (typeof this.options.lineColors === 'function') { return this.options.lineColors.call(this, row, sidx, type); } else if (type === 'point') { return this.options.pointFillColors[sidx % this.options.pointFillColors.length] || this.options.lineColors[sidx % this.options.lineColors.length]; } else { return this.options.lineColors[sidx % this.options.lineColors.length]; } }; Line.prototype.drawXAxisLabel = function(xPos, yPos, text) { return this.raphael.text(xPos, yPos, text).attr('font-size', this.options.gridTextSize).attr('font-family', this.options.gridTextFamily).attr('font-weight', this.options.gridTextWeight).attr('fill', this.options.gridTextColor); }; Line.prototype.drawLinePath = function(path, lineColor, lineIndex) { return this.raphael.path(path).attr('stroke', lineColor).attr('stroke-width', this.lineWidthForSeries(lineIndex)); }; Line.prototype.drawLinePoint = function(xPos, yPos, pointColor, lineIndex) { return this.raphael.circle(xPos, yPos, this.pointSizeForSeries(lineIndex)).attr('fill', pointColor).attr('stroke-width', this.pointStrokeWidthForSeries(lineIndex)).attr('stroke', this.pointStrokeColorForSeries(lineIndex)); }; Line.prototype.pointStrokeWidthForSeries = function(index) { return this.options.pointStrokeWidths[index % this.options.pointStrokeWidths.length]; }; Line.prototype.pointStrokeColorForSeries = function(index) { return this.options.pointStrokeColors[index % this.options.pointStrokeColors.length]; }; Line.prototype.lineWidthForSeries = function(index) { if (this.options.lineWidth instanceof Array) { return this.options.lineWidth[index % this.options.lineWidth.length]; } else { return this.options.lineWidth; } }; Line.prototype.pointSizeForSeries = function(index) { if (this.options.pointSize instanceof Array) { return this.options.pointSize[index % this.options.pointSize.length]; } else { return this.options.pointSize; } }; Line.prototype.pointGrowSeries = function(index) { return Raphael.animation({ r: this.pointSizeForSeries(index) + 3 }, 25, 'linear'); }; Line.prototype.pointShrinkSeries = function(index) { return Raphael.animation({ r: this.pointSizeForSeries(index) }, 25, 'linear'); }; return Line; })(Morris.Grid); Morris.labelSeries = function(dmin, dmax, pxwidth, specName, xLabelFormat) { var d, d0, ddensity, name, ret, s, spec, t, _i, _len, _ref; ddensity = 200 * (dmax - dmin) / pxwidth; d0 = new Date(dmin); spec = Morris.LABEL_SPECS[specName]; if (spec === void 0) { _ref = Morris.AUTO_LABEL_ORDER; for (_i = 0, _len = _ref.length; _i < _len; _i++) { name = _ref[_i]; s = Morris.LABEL_SPECS[name]; if (ddensity >= s.span) { spec = s; break; } } } if (spec === void 0) { spec = Morris.LABEL_SPECS["second"]; } if (xLabelFormat) { spec = $.extend({}, spec, { fmt: xLabelFormat }); } d = spec.start(d0); ret = []; while ((t = d.getTime()) <= dmax) { if (t >= dmin) { ret.push([spec.fmt(d), t]); } spec.incr(d); } return ret; }; minutesSpecHelper = function(interval) { return { span: interval * 60 * 1000, start: function(d) { return new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours()); }, fmt: function(d) { return "" + (Morris.pad2(d.getHours())) + ":" + (Morris.pad2(d.getMinutes())); }, incr: function(d) { return d.setUTCMinutes(d.getUTCMinutes() + interval); } }; }; secondsSpecHelper = function(interval) { return { span: interval * 1000, start: function(d) { return new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes()); }, fmt: function(d) { return "" + (Morris.pad2(d.getHours())) + ":" + (Morris.pad2(d.getMinutes())) + ":" + (Morris.pad2(d.getSeconds())); }, incr: function(d) { return d.setUTCSeconds(d.getUTCSeconds() + interval); } }; }; Morris.LABEL_SPECS = { "decade": { span: 172800000000, start: function(d) { return new Date(d.getFullYear() - d.getFullYear() % 10, 0, 1); }, fmt: function(d) { return "" + (d.getFullYear()); }, incr: function(d) { return d.setFullYear(d.getFullYear() + 10); } }, "year": { span: 17280000000, start: function(d) { return new Date(d.getFullYear(), 0, 1); }, fmt: function(d) { return "" + (d.getFullYear()); }, incr: function(d) { return d.setFullYear(d.getFullYear() + 1); } }, "month": { span: 2419200000, start: function(d) { return new Date(d.getFullYear(), d.getMonth(), 1); }, fmt: function(d) { return "" + (d.getFullYear()) + "-" + (Morris.pad2(d.getMonth() + 1)); }, incr: function(d) { return d.setMonth(d.getMonth() + 1); } }, "week": { span: 604800000, start: function(d) { return new Date(d.getFullYear(), d.getMonth(), d.getDate()); }, fmt: function(d) { return "" + (d.getFullYear()) + "-" + (Morris.pad2(d.getMonth() + 1)) + "-" + (Morris.pad2(d.getDate())); }, incr: function(d) { return d.setDate(d.getDate() + 7); } }, "day": { span: 86400000, start: function(d) { return new Date(d.getFullYear(), d.getMonth(), d.getDate()); }, fmt: function(d) { return "" + (d.getFullYear()) + "-" + (Morris.pad2(d.getMonth() + 1)) + "-" + (Morris.pad2(d.getDate())); }, incr: function(d) { return d.setDate(d.getDate() + 1); } }, "hour": minutesSpecHelper(60), "30min": minutesSpecHelper(30), "15min": minutesSpecHelper(15), "10min": minutesSpecHelper(10), "5min": minutesSpecHelper(5), "minute": minutesSpecHelper(1), "30sec": secondsSpecHelper(30), "15sec": secondsSpecHelper(15), "10sec": secondsSpecHelper(10), "5sec": secondsSpecHelper(5), "second": secondsSpecHelper(1) }; Morris.AUTO_LABEL_ORDER = ["decade", "year", "month", "week", "day", "hour", "30min", "15min", "10min", "5min", "minute", "30sec", "15sec", "10sec", "5sec", "second"]; Morris.Area = (function(_super) { var areaDefaults; __extends(Area, _super); areaDefaults = { fillOpacity: 'auto', behaveLikeLine: false }; function Area(options) { var areaOptions; if (!(this instanceof Morris.Area)) { return new Morris.Area(options); } areaOptions = $.extend({}, areaDefaults, options); this.cumulative = !areaOptions.behaveLikeLine; if (areaOptions.fillOpacity === 'auto') { areaOptions.fillOpacity = areaOptions.behaveLikeLine ? .8 : 1; } Area.__super__.constructor.call(this, areaOptions); } Area.prototype.calcPoints = function() { var row, total, y, _i, _len, _ref, _results; _ref = this.data; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { row = _ref[_i]; row._x = this.transX(row.x); total = 0; row._y = (function() { var _j, _len1, _ref1, _results1; _ref1 = row.y; _results1 = []; for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { y = _ref1[_j]; if (this.options.behaveLikeLine) { _results1.push(this.transY(y)); } else { total += y || 0; _results1.push(this.transY(total)); } } return _results1; }).call(this); _results.push(row._ymax = Math.max.apply(Math, row._y)); } return _results; }; Area.prototype.drawSeries = function() { var i, range, _i, _j, _k, _len, _ref, _ref1, _results, _results1, _results2; this.seriesPoints = []; if (this.options.behaveLikeLine) { range = (function() { _results = []; for (var _i = 0, _ref = this.options.ykeys.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; 0 <= _ref ? _i++ : _i--){ _results.push(_i); } return _results; }).apply(this); } else { range = (function() { _results1 = []; for (var _j = _ref1 = this.options.ykeys.length - 1; _ref1 <= 0 ? _j <= 0 : _j >= 0; _ref1 <= 0 ? _j++ : _j--){ _results1.push(_j); } return _results1; }).apply(this); } _results2 = []; for (_k = 0, _len = range.length; _k < _len; _k++) { i = range[_k]; this._drawFillFor(i); this._drawLineFor(i); _results2.push(this._drawPointFor(i)); } return _results2; }; Area.prototype._drawFillFor = function(index) { var path; path = this.paths[index]; if (path !== null) { path = path + ("L" + (this.transX(this.xmax)) + "," + this.bottom + "L" + (this.transX(this.xmin)) + "," + this.bottom + "Z"); return this.drawFilledPath(path, this.fillForSeries(index)); } }; Area.prototype.fillForSeries = function(i) { var color; color = Raphael.rgb2hsl(this.colorFor(this.data[i], i, 'line')); return Raphael.hsl(color.h, this.options.behaveLikeLine ? color.s * 0.9 : color.s * 0.75, Math.min(0.98, this.options.behaveLikeLine ? color.l * 1.2 : color.l * 1.25)); }; Area.prototype.drawFilledPath = function(path, fill) { return this.raphael.path(path).attr('fill', fill).attr('fill-opacity', this.options.fillOpacity).attr('stroke', 'none'); }; return Area; })(Morris.Line); Morris.Bar = (function(_super) { __extends(Bar, _super); function Bar(options) { this.onHoverOut = __bind(this.onHoverOut, this); this.onHoverMove = __bind(this.onHoverMove, this); this.onGridClick = __bind(this.onGridClick, this); if (!(this instanceof Morris.Bar)) { return new Morris.Bar(options); } Bar.__super__.constructor.call(this, $.extend({}, options, { parseTime: false })); } Bar.prototype.init = function() { this.cumulative = this.options.stacked; if (this.options.hideHover !== 'always') { this.hover = new Morris.Hover({ parent: this.el }); this.on('hovermove', this.onHoverMove); this.on('hoverout', this.onHoverOut); return this.on('gridclick', this.onGridClick); } }; Bar.prototype.defaults = { barSizeRatio: 0.75, barGap: 3, barColors: ['#0b62a4', '#7a92a3', '#4da74d', '#afd8f8', '#edc240', '#cb4b4b', '#9440ed'], barOpacity: 1.0, barRadius: [0, 0, 0, 0], xLabelMargin: 50 }; Bar.prototype.calc = function() { var _ref; this.calcBars(); if (this.options.hideHover === false) { return (_ref = this.hover).update.apply(_ref, this.hoverContentForRow(this.data.length - 1)); } }; Bar.prototype.calcBars = function() { var idx, row, y, _i, _len, _ref, _results; _ref = this.data; _results = []; for (idx = _i = 0, _len = _ref.length; _i < _len; idx = ++_i) { row = _ref[idx]; row._x = this.left + this.width * (idx + 0.5) / this.data.length; _results.push(row._y = (function() { var _j, _len1, _ref1, _results1; _ref1 = row.y; _results1 = []; for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { y = _ref1[_j]; if (y != null) { _results1.push(this.transY(y)); } else { _results1.push(null); } } return _results1; }).call(this)); } return _results; }; Bar.prototype.draw = function() { var _ref; if ((_ref = this.options.axes) === true || _ref === 'both' || _ref === 'x') { this.drawXAxis(); } return this.drawSeries(); }; Bar.prototype.drawXAxis = function() { var i, label, labelBox, margin, offset, prevAngleMargin, prevLabelMargin, row, textBox, ypos, _i, _ref, _results; ypos = this.bottom + (this.options.xAxisLabelTopPadding || this.options.padding / 2); prevLabelMargin = null; prevAngleMargin = null; _results = []; for (i = _i = 0, _ref = this.data.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { row = this.data[this.data.length - 1 - i]; label = this.drawXAxisLabel(row._x, ypos, row.label); textBox = label.getBBox(); label.transform("r" + (-this.options.xLabelAngle)); labelBox = label.getBBox(); label.transform("t0," + (labelBox.height / 2) + "..."); if (this.options.xLabelAngle !== 0) { offset = -0.5 * textBox.width * Math.cos(this.options.xLabelAngle * Math.PI / 180.0); label.transform("t" + offset + ",0..."); } if (((prevLabelMargin == null) || prevLabelMargin >= labelBox.x + labelBox.width || (prevAngleMargin != null) && prevAngleMargin >= labelBox.x) && labelBox.x >= 0 && (labelBox.x + labelBox.width) < this.el.width()) { if (this.options.xLabelAngle !== 0) { margin = 1.25 * this.options.gridTextSize / Math.sin(this.options.xLabelAngle * Math.PI / 180.0); prevAngleMargin = labelBox.x - margin; } _results.push(prevLabelMargin = labelBox.x - this.options.xLabelMargin); } else { _results.push(label.remove()); } } return _results; }; Bar.prototype.drawSeries = function() { var barWidth, bottom, groupWidth, idx, lastTop, left, leftPadding, numBars, row, sidx, size, spaceLeft, top, ypos, zeroPos; groupWidth = this.width / this.options.data.length; numBars = this.options.stacked ? 1 : this.options.ykeys.length; barWidth = (groupWidth * this.options.barSizeRatio - this.options.barGap * (numBars - 1)) / numBars; if (this.options.barSize) { barWidth = Math.min(barWidth, this.options.barSize); } spaceLeft = groupWidth - barWidth * numBars - this.options.barGap * (numBars - 1); leftPadding = spaceLeft / 2; zeroPos = this.ymin <= 0 && this.ymax >= 0 ? this.transY(0) : null; return this.bars = (function() { var _i, _len, _ref, _results; _ref = this.data; _results = []; for (idx = _i = 0, _len = _ref.length; _i < _len; idx = ++_i) { row = _ref[idx]; lastTop = 0; _results.push((function() { var _j, _len1, _ref1, _results1; _ref1 = row._y; _results1 = []; for (sidx = _j = 0, _len1 = _ref1.length; _j < _len1; sidx = ++_j) { ypos = _ref1[sidx]; if (ypos !== null) { if (zeroPos) { top = Math.min(ypos, zeroPos); bottom = Math.max(ypos, zeroPos); } else { top = ypos; bottom = this.bottom; } left = this.left + idx * groupWidth + leftPadding; if (!this.options.stacked) { left += sidx * (barWidth + this.options.barGap); } size = bottom - top; if (this.options.verticalGridCondition && this.options.verticalGridCondition(row.x)) { this.drawBar(this.left + idx * groupWidth, this.top, groupWidth, Math.abs(this.top - this.bottom), this.options.verticalGridColor, this.options.verticalGridOpacity, this.options.barRadius); } if (this.options.stacked) { top -= lastTop; } this.drawBar(left, top, barWidth, size, this.colorFor(row, sidx, 'bar'), this.options.barOpacity, this.options.barRadius); _results1.push(lastTop += size); } else { _results1.push(null); } } return _results1; }).call(this)); } return _results; }).call(this); }; Bar.prototype.colorFor = function(row, sidx, type) { var r, s; if (typeof this.options.barColors === 'function') { r = { x: row.x, y: row.y[sidx], label: row.label }; s = { index: sidx, key: this.options.ykeys[sidx], label: this.options.labels[sidx] }; return this.options.barColors.call(this, r, s, type); } else { return this.options.barColors[sidx % this.options.barColors.length]; } }; Bar.prototype.hitTest = function(x) { if (this.data.length === 0) { return null; } x = Math.max(Math.min(x, this.right), this.left); return Math.min(this.data.length - 1, Math.floor((x - this.left) / (this.width / this.data.length))); }; Bar.prototype.onGridClick = function(x, y) { var index; index = this.hitTest(x); return this.fire('click', index, this.data[index].src, x, y); }; Bar.prototype.onHoverMove = function(x, y) { var index, _ref; index = this.hitTest(x); return (_ref = this.hover).update.apply(_ref, this.hoverContentForRow(index)); }; Bar.prototype.onHoverOut = function() { if (this.options.hideHover !== false) { return this.hover.hide(); } }; Bar.prototype.hoverContentForRow = function(index) { var content, j, row, x, y, _i, _len, _ref; row = this.data[index]; content = "
" + row.label + "
"; _ref = row.y; for (j = _i = 0, _len = _ref.length; _i < _len; j = ++_i) { y = _ref[j]; content += "
\n " + this.options.labels[j] + ":\n " + (this.yLabelFormat(y)) + "\n
"; } if (typeof this.options.hoverCallback === 'function') { content = this.options.hoverCallback(index, this.options, content, row.src); } x = this.left + (index + 0.5) * this.width / this.data.length; return [content, x]; }; Bar.prototype.drawXAxisLabel = function(xPos, yPos, text) { var label; return label = this.raphael.text(xPos, yPos, text).attr('font-size', this.options.gridTextSize).attr('font-family', this.options.gridTextFamily).attr('font-weight', this.options.gridTextWeight).attr('fill', this.options.gridTextColor); }; Bar.prototype.drawBar = function(xPos, yPos, width, height, barColor, opacity, radiusArray) { var maxRadius, path; maxRadius = Math.max.apply(Math, radiusArray); if (maxRadius === 0 || maxRadius > height) { path = this.raphael.rect(xPos, yPos, width, height); } else { path = this.raphael.path(this.roundedRect(xPos, yPos, width, height, radiusArray)); } return path.attr('fill', barColor).attr('fill-opacity', opacity).attr('stroke', 'none'); }; Bar.prototype.roundedRect = function(x, y, w, h, r) { if (r == null) { r = [0, 0, 0, 0]; } return ["M", x, r[0] + y, "Q", x, y, x + r[0], y, "L", x + w - r[1], y, "Q", x + w, y, x + w, y + r[1], "L", x + w, y + h - r[2], "Q", x + w, y + h, x + w - r[2], y + h, "L", x + r[3], y + h, "Q", x, y + h, x, y + h - r[3], "Z"]; }; return Bar; })(Morris.Grid); Morris.Donut = (function(_super) { __extends(Donut, _super); Donut.prototype.defaults = { colors: ['#0B62A4', '#3980B5', '#679DC6', '#95BBD7', '#B0CCE1', '#095791', '#095085', '#083E67', '#052C48', '#042135'], backgroundColor: '#FFFFFF', labelColor: '#000000', formatter: Morris.commas, resize: false }; function Donut(options) { this.resizeHandler = __bind(this.resizeHandler, this); this.select = __bind(this.select, this); this.click = __bind(this.click, this); var _this = this; if (!(this instanceof Morris.Donut)) { return new Morris.Donut(options); } this.options = $.extend({}, this.defaults, options); if (typeof options.element === 'string') { this.el = $(document.getElementById(options.element)); } else { this.el = $(options.element); } if (this.el === null || this.el.length === 0) { throw new Error("Graph placeholder not found."); } if (options.data === void 0 || options.data.length === 0) { return; } this.raphael = new Raphael(this.el[0]); if (this.options.resize) { $(window).bind('resize', function(evt) { if (_this.timeoutId != null) { window.clearTimeout(_this.timeoutId); } return _this.timeoutId = window.setTimeout(_this.resizeHandler, 100); }); } this.setData(options.data); } Donut.prototype.redraw = function() { var C, cx, cy, i, idx, last, max_value, min, next, seg, total, value, w, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _results; this.raphael.clear(); cx = this.el.width() / 2; cy = this.el.height() / 2; w = (Math.min(cx, cy) - 10) / 3; total = 0; _ref = this.values; for (_i = 0, _len = _ref.length; _i < _len; _i++) { value = _ref[_i]; total += value; } min = 5 / (2 * w); C = 1.9999 * Math.PI - min * this.data.length; last = 0; idx = 0; this.segments = []; _ref1 = this.values; for (i = _j = 0, _len1 = _ref1.length; _j < _len1; i = ++_j) { value = _ref1[i]; next = last + min + C * (value / total); seg = new Morris.DonutSegment(cx, cy, w * 2, w, last, next, this.data[i].color || this.options.colors[idx % this.options.colors.length], this.options.backgroundColor, idx, this.raphael); seg.render(); this.segments.push(seg); seg.on('hover', this.select); seg.on('click', this.click); last = next; idx += 1; } this.text1 = this.drawEmptyDonutLabel(cx, cy - 10, this.options.labelColor, 15, 800); this.text2 = this.drawEmptyDonutLabel(cx, cy + 10, this.options.labelColor, 14); max_value = Math.max.apply(Math, this.values); idx = 0; _ref2 = this.values; _results = []; for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { value = _ref2[_k]; if (value === max_value) { this.select(idx); break; } _results.push(idx += 1); } return _results; }; Donut.prototype.setData = function(data) { var row; this.data = data; this.values = (function() { var _i, _len, _ref, _results; _ref = this.data; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { row = _ref[_i]; _results.push(parseFloat(row.value)); } return _results; }).call(this); return this.redraw(); }; Donut.prototype.click = function(idx) { return this.fire('click', idx, this.data[idx]); }; Donut.prototype.select = function(idx) { var row, s, segment, _i, _len, _ref; _ref = this.segments; for (_i = 0, _len = _ref.length; _i < _len; _i++) { s = _ref[_i]; s.deselect(); } segment = this.segments[idx]; segment.select(); row = this.data[idx]; return this.setLabels(row.label, this.options.formatter(row.value, row)); }; Donut.prototype.setLabels = function(label1, label2) { var inner, maxHeightBottom, maxHeightTop, maxWidth, text1bbox, text1scale, text2bbox, text2scale; inner = (Math.min(this.el.width() / 2, this.el.height() / 2) - 10) * 2 / 3; maxWidth = 1.8 * inner; maxHeightTop = inner / 2; maxHeightBottom = inner / 3; this.text1.attr({ text: label1, transform: '' }); text1bbox = this.text1.getBBox(); text1scale = Math.min(maxWidth / text1bbox.width, maxHeightTop / text1bbox.height); this.text1.attr({ transform: "S" + text1scale + "," + text1scale + "," + (text1bbox.x + text1bbox.width / 2) + "," + (text1bbox.y + text1bbox.height) }); this.text2.attr({ text: label2, transform: '' }); text2bbox = this.text2.getBBox(); text2scale = Math.min(maxWidth / text2bbox.width, maxHeightBottom / text2bbox.height); return this.text2.attr({ transform: "S" + text2scale + "," + text2scale + "," + (text2bbox.x + text2bbox.width / 2) + "," + text2bbox.y }); }; Donut.prototype.drawEmptyDonutLabel = function(xPos, yPos, color, fontSize, fontWeight) { var text; text = this.raphael.text(xPos, yPos, '').attr('font-size', fontSize).attr('fill', color); if (fontWeight != null) { text.attr('font-weight', fontWeight); } return text; }; Donut.prototype.resizeHandler = function() { this.timeoutId = null; this.raphael.setSize(this.el.width(), this.el.height()); return this.redraw(); }; return Donut; })(Morris.EventEmitter); Morris.DonutSegment = (function(_super) { __extends(DonutSegment, _super); function DonutSegment(cx, cy, inner, outer, p0, p1, color, backgroundColor, index, raphael) { this.cx = cx; this.cy = cy; this.inner = inner; this.outer = outer; this.color = color; this.backgroundColor = backgroundColor; this.index = index; this.raphael = raphael; this.deselect = __bind(this.deselect, this); this.select = __bind(this.select, this); this.sin_p0 = Math.sin(p0); this.cos_p0 = Math.cos(p0); this.sin_p1 = Math.sin(p1); this.cos_p1 = Math.cos(p1); this.is_long = (p1 - p0) > Math.PI ? 1 : 0; this.path = this.calcSegment(this.inner + 3, this.inner + this.outer - 5); this.selectedPath = this.calcSegment(this.inner + 3, this.inner + this.outer); this.hilight = this.calcArc(this.inner); } DonutSegment.prototype.calcArcPoints = function(r) { return [this.cx + r * this.sin_p0, this.cy + r * this.cos_p0, this.cx + r * this.sin_p1, this.cy + r * this.cos_p1]; }; DonutSegment.prototype.calcSegment = function(r1, r2) { var ix0, ix1, iy0, iy1, ox0, ox1, oy0, oy1, _ref, _ref1; _ref = this.calcArcPoints(r1), ix0 = _ref[0], iy0 = _ref[1], ix1 = _ref[2], iy1 = _ref[3]; _ref1 = this.calcArcPoints(r2), ox0 = _ref1[0], oy0 = _ref1[1], ox1 = _ref1[2], oy1 = _ref1[3]; return ("M" + ix0 + "," + iy0) + ("A" + r1 + "," + r1 + ",0," + this.is_long + ",0," + ix1 + "," + iy1) + ("L" + ox1 + "," + oy1) + ("A" + r2 + "," + r2 + ",0," + this.is_long + ",1," + ox0 + "," + oy0) + "Z"; }; DonutSegment.prototype.calcArc = function(r) { var ix0, ix1, iy0, iy1, _ref; _ref = this.calcArcPoints(r), ix0 = _ref[0], iy0 = _ref[1], ix1 = _ref[2], iy1 = _ref[3]; return ("M" + ix0 + "," + iy0) + ("A" + r + "," + r + ",0," + this.is_long + ",0," + ix1 + "," + iy1); }; DonutSegment.prototype.render = function() { var _this = this; this.arc = this.drawDonutArc(this.hilight, this.color); return this.seg = this.drawDonutSegment(this.path, this.color, this.backgroundColor, function() { return _this.fire('hover', _this.index); }, function() { return _this.fire('click', _this.index); }); }; DonutSegment.prototype.drawDonutArc = function(path, color) { return this.raphael.path(path).attr({ stroke: color, 'stroke-width': 2, opacity: 0 }); }; DonutSegment.prototype.drawDonutSegment = function(path, fillColor, strokeColor, hoverFunction, clickFunction) { return this.raphael.path(path).attr({ fill: fillColor, stroke: strokeColor, 'stroke-width': 3 }).hover(hoverFunction).click(clickFunction); }; DonutSegment.prototype.select = function() { if (!this.selected) { this.seg.animate({ path: this.selectedPath }, 150, '<>'); this.arc.animate({ opacity: 1 }, 150, '<>'); return this.selected = true; } }; DonutSegment.prototype.deselect = function() { if (this.selected) { this.seg.animate({ path: this.path }, 150, '<>'); this.arc.animate({ opacity: 0 }, 150, '<>'); return this.selected = false; } }; return DonutSegment; })(Morris.EventEmitter); }).call(this); morris.css000064400000000661150145030150006566 0ustar00.morris-hover{position:absolute;z-index:1000}.morris-hover.morris-default-style{border-radius:10px;padding:6px;color:#666;background:rgba(255,255,255,0.8);border:solid 2px rgba(230,230,230,0.8);font-family:sans-serif;font-size:12px;text-align:center}.morris-hover.morris-default-style .morris-hover-row-label{font-weight:bold;margin:0.25em 0} .morris-hover.morris-default-style .morris-hover-point{white-space:nowrap;margin:0.1em 0} package.json000064400000001547150145030150007033 0ustar00{ "name": "morris.js", "version": "0.5.0", "homepage": "http://morrisjs.github.com/morris.js", "license": "BSD-2-Clause", "description": "Easy, pretty charts", "author": { "name": "Olly Smith", "email": "olly@oesmith.co.uk" }, "repository": { "type": "git", "url": "git://github.com/morrisjs/morris.js.git" }, "bugs": { "url": "https://github.com/morrisjs/morris.js/issues" }, "devDependencies": { "matchdep": "~0.1.2", "grunt": "~0.4.1", "grunt-mocha": "~0.4.10", "grunt-contrib-concat": "~0.3.0", "grunt-contrib-coffee": "~0.7.0", "grunt-contrib-uglify": "~0.2.4", "grunt-contrib-less": "~0.7.0", "grunt-contrib-watch": "~0.5.3", "grunt-shell": "~0.5.0", "bower": "~1.2.8" }, "scripts": { "test": "grunt concat coffee mocha" }, "engines": { "node": ">=0.8 <0.11" } } bower.travis.json000064400000000606150145030150010060 0ustar00{ "name": "morris.js", "version": "0.5.0", "main": [ "./morris.js", "./morris.css" ], "dependencies": { "jquery": "JQUERY", "raphael": "RAPHAEL", "mocha": "~1.17.1" }, "devDependencies": { "mocha": "~1.17.1", "chai": "~1.9.0", "chai-jquery": "~1.2.1", "sinon": "http://sinonjs.org/releases/sinon-1.8.1.js", "sinon-chai": "~2.5.0" } } morris.min.js000064400000105504150145030150007176 0ustar00/* @license morris.js v0.5.0 Copyright 2014 Olly Smith All rights reserved. Licensed under the BSD-2-Clause License. */ (function(){var a,b,c,d,e=[].slice,f=function(a,b){return function(){return a.apply(b,arguments)}},g={}.hasOwnProperty,h=function(a,b){function c(){this.constructor=a}for(var d in b)g.call(b,d)&&(a[d]=b[d]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a},i=[].indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(b in this&&this[b]===a)return b;return-1};b=window.Morris={},a=jQuery,b.EventEmitter=function(){function a(){}return a.prototype.on=function(a,b){return null==this.handlers&&(this.handlers={}),null==this.handlers[a]&&(this.handlers[a]=[]),this.handlers[a].push(b),this},a.prototype.fire=function(){var a,b,c,d,f,g,h;if(c=arguments[0],a=2<=arguments.length?e.call(arguments,1):[],null!=this.handlers&&null!=this.handlers[c]){for(g=this.handlers[c],h=[],d=0,f=g.length;f>d;d++)b=g[d],h.push(b.apply(null,a));return h}},a}(),b.commas=function(a){var b,c,d,e;return null!=a?(d=0>a?"-":"",b=Math.abs(a),c=Math.floor(b).toFixed(0),d+=c.replace(/(?=(?:\d{3})+$)(?!^)/g,","),e=b.toString(),e.length>c.length&&(d+=e.slice(c.length)),d):"-"},b.pad2=function(a){return(10>a?"0":"")+a},b.Grid=function(c){function d(b){this.resizeHandler=f(this.resizeHandler,this);var c=this;if(this.el="string"==typeof b.element?a(document.getElementById(b.element)):a(b.element),null==this.el||0===this.el.length)throw new Error("Graph container element not found");"static"===this.el.css("position")&&this.el.css("position","relative"),this.options=a.extend({},this.gridDefaults,this.defaults||{},b),"string"==typeof this.options.units&&(this.options.postUnits=b.units),this.raphael=new Raphael(this.el[0]),this.elementWidth=null,this.elementHeight=null,this.dirty=!1,this.selectFrom=null,this.init&&this.init(),this.setData(this.options.data),this.el.bind("mousemove",function(a){var b,d,e,f,g;return d=c.el.offset(),g=a.pageX-d.left,c.selectFrom?(b=c.data[c.hitTest(Math.min(g,c.selectFrom))]._x,e=c.data[c.hitTest(Math.max(g,c.selectFrom))]._x,f=e-b,c.selectionRect.attr({x:b,width:f})):c.fire("hovermove",g,a.pageY-d.top)}),this.el.bind("mouseleave",function(){return c.selectFrom&&(c.selectionRect.hide(),c.selectFrom=null),c.fire("hoverout")}),this.el.bind("touchstart touchmove touchend",function(a){var b,d;return d=a.originalEvent.touches[0]||a.originalEvent.changedTouches[0],b=c.el.offset(),c.fire("hovermove",d.pageX-b.left,d.pageY-b.top)}),this.el.bind("click",function(a){var b;return b=c.el.offset(),c.fire("gridclick",a.pageX-b.left,a.pageY-b.top)}),this.options.rangeSelect&&(this.selectionRect=this.raphael.rect(0,0,0,this.el.innerHeight()).attr({fill:this.options.rangeSelectColor,stroke:!1}).toBack().hide(),this.el.bind("mousedown",function(a){var b;return b=c.el.offset(),c.startRange(a.pageX-b.left)}),this.el.bind("mouseup",function(a){var b;return b=c.el.offset(),c.endRange(a.pageX-b.left),c.fire("hovermove",a.pageX-b.left,a.pageY-b.top)})),this.options.resize&&a(window).bind("resize",function(){return null!=c.timeoutId&&window.clearTimeout(c.timeoutId),c.timeoutId=window.setTimeout(c.resizeHandler,100)}),this.el.css("-webkit-tap-highlight-color","rgba(0,0,0,0)"),this.postInit&&this.postInit()}return h(d,c),d.prototype.gridDefaults={dateFormat:null,axes:!0,grid:!0,gridLineColor:"#aaa",gridStrokeWidth:.5,gridTextColor:"#888",gridTextSize:12,gridTextFamily:"sans-serif",gridTextWeight:"normal",hideHover:!1,yLabelFormat:null,xLabelAngle:0,numLines:5,padding:25,parseTime:!0,postUnits:"",preUnits:"",ymax:"auto",ymin:"auto 0",goals:[],goalStrokeWidth:1,goalLineColors:["#666633","#999966","#cc6666","#663333"],events:[],eventStrokeWidth:1,eventLineColors:["#005a04","#ccffbb","#3a5f0b","#005502"],rangeSelect:null,rangeSelectColor:"#eef",resize:!1},d.prototype.setData=function(a,c){var d,e,f,g,h,i,j,k,l,m,n,o,p,q,r;return null==c&&(c=!0),this.options.data=a,null==a||0===a.length?(this.data=[],this.raphael.clear(),null!=this.hover&&this.hover.hide(),void 0):(o=this.cumulative?0:null,p=this.cumulative?0:null,this.options.goals.length>0&&(h=Math.min.apply(Math,this.options.goals),g=Math.max.apply(Math,this.options.goals),p=null!=p?Math.min(p,h):h,o=null!=o?Math.max(o,g):g),this.data=function(){var c,d,g;for(g=[],f=c=0,d=a.length;d>c;f=++c)j=a[f],i={src:j},i.label=j[this.options.xkey],this.options.parseTime?(i.x=b.parseDate(i.label),this.options.dateFormat?i.label=this.options.dateFormat(i.x):"number"==typeof i.label&&(i.label=new Date(i.label).toString())):(i.x=f,this.options.xLabelFormat&&(i.label=this.options.xLabelFormat(i))),l=0,i.y=function(){var a,b,c,d;for(c=this.options.ykeys,d=[],e=a=0,b=c.length;b>a;e=++a)n=c[e],q=j[n],"string"==typeof q&&(q=parseFloat(q)),null!=q&&"number"!=typeof q&&(q=null),null!=q&&(this.cumulative?l+=q:null!=o?(o=Math.max(q,o),p=Math.min(q,p)):o=p=q),this.cumulative&&null!=l&&(o=Math.max(l,o),p=Math.min(l,p)),d.push(q);return d}.call(this),g.push(i);return g}.call(this),this.options.parseTime&&(this.data=this.data.sort(function(a,b){return(a.x>b.x)-(b.x>a.x)})),this.xmin=this.data[0].x,this.xmax=this.data[this.data.length-1].x,this.events=[],this.options.events.length>0&&(this.events=this.options.parseTime?function(){var a,c,e,f;for(e=this.options.events,f=[],a=0,c=e.length;c>a;a++)d=e[a],f.push(b.parseDate(d));return f}.call(this):this.options.events,this.xmax=Math.max(this.xmax,Math.max.apply(Math,this.events)),this.xmin=Math.min(this.xmin,Math.min.apply(Math,this.events))),this.xmin===this.xmax&&(this.xmin-=1,this.xmax+=1),this.ymin=this.yboundary("min",p),this.ymax=this.yboundary("max",o),this.ymin===this.ymax&&(p&&(this.ymin-=1),this.ymax+=1),((r=this.options.axes)===!0||"both"===r||"y"===r||this.options.grid===!0)&&(this.options.ymax===this.gridDefaults.ymax&&this.options.ymin===this.gridDefaults.ymin?(this.grid=this.autoGridLines(this.ymin,this.ymax,this.options.numLines),this.ymin=Math.min(this.ymin,this.grid[0]),this.ymax=Math.max(this.ymax,this.grid[this.grid.length-1])):(k=(this.ymax-this.ymin)/(this.options.numLines-1),this.grid=function(){var a,b,c,d;for(d=[],m=a=b=this.ymin,c=this.ymax;k>0?c>=a:a>=c;m=a+=k)d.push(m);return d}.call(this))),this.dirty=!0,c?this.redraw():void 0)},d.prototype.yboundary=function(a,b){var c,d;return c=this.options["y"+a],"string"==typeof c?"auto"===c.slice(0,4)?c.length>5?(d=parseInt(c.slice(5),10),null==b?d:Math[a](b,d)):null!=b?b:0:parseInt(c,10):c},d.prototype.autoGridLines=function(a,b,c){var d,e,f,g,h,i,j,k,l;return h=b-a,l=Math.floor(Math.log(h)/Math.log(10)),j=Math.pow(10,l),e=Math.floor(a/j)*j,d=Math.ceil(b/j)*j,i=(d-e)/(c-1),1===j&&i>1&&Math.ceil(i)!==i&&(i=Math.ceil(i),d=e+i*(c-1)),0>e&&d>0&&(e=Math.floor(a/i)*i,d=Math.ceil(b/i)*i),1>i?(g=Math.floor(Math.log(i)/Math.log(10)),f=function(){var a,b;for(b=[],k=a=e;i>0?d>=a:a>=d;k=a+=i)b.push(parseFloat(k.toFixed(1-g)));return b}()):f=function(){var a,b;for(b=[],k=a=e;i>0?d>=a:a>=d;k=a+=i)b.push(k);return b}(),f},d.prototype._calc=function(){var a,b,c,d,e,f,g,h;return e=this.el.width(),c=this.el.height(),(this.elementWidth!==e||this.elementHeight!==c||this.dirty)&&(this.elementWidth=e,this.elementHeight=c,this.dirty=!1,this.left=this.options.padding,this.right=this.elementWidth-this.options.padding,this.top=this.options.padding,this.bottom=this.elementHeight-this.options.padding,((g=this.options.axes)===!0||"both"===g||"y"===g)&&(f=function(){var a,c,d,e;for(d=this.grid,e=[],a=0,c=d.length;c>a;a++)b=d[a],e.push(this.measureText(this.yAxisFormat(b)).width);return e}.call(this),this.left+=Math.max.apply(Math,f)),((h=this.options.axes)===!0||"both"===h||"x"===h)&&(a=function(){var a,b,c;for(c=[],d=a=0,b=this.data.length;b>=0?b>a:a>b;d=b>=0?++a:--a)c.push(this.measureText(this.data[d].text,-this.options.xLabelAngle).height);return c}.call(this),this.bottom-=Math.max.apply(Math,a)),this.width=Math.max(1,this.right-this.left),this.height=Math.max(1,this.bottom-this.top),this.dx=this.width/(this.xmax-this.xmin),this.dy=this.height/(this.ymax-this.ymin),this.calc)?this.calc():void 0},d.prototype.transY=function(a){return this.bottom-(a-this.ymin)*this.dy},d.prototype.transX=function(a){return 1===this.data.length?(this.left+this.right)/2:this.left+(a-this.xmin)*this.dx},d.prototype.redraw=function(){return this.raphael.clear(),this._calc(),this.drawGrid(),this.drawGoals(),this.drawEvents(),this.draw?this.draw():void 0},d.prototype.measureText=function(a,b){var c,d;return null==b&&(b=0),d=this.raphael.text(100,100,a).attr("font-size",this.options.gridTextSize).attr("font-family",this.options.gridTextFamily).attr("font-weight",this.options.gridTextWeight).rotate(b),c=d.getBBox(),d.remove(),c},d.prototype.yAxisFormat=function(a){return this.yLabelFormat(a)},d.prototype.yLabelFormat=function(a){return"function"==typeof this.options.yLabelFormat?this.options.yLabelFormat(a):""+this.options.preUnits+b.commas(a)+this.options.postUnits},d.prototype.drawGrid=function(){var a,b,c,d,e,f,g,h;if(this.options.grid!==!1||(e=this.options.axes)===!0||"both"===e||"y"===e){for(f=this.grid,h=[],c=0,d=f.length;d>c;c++)a=f[c],b=this.transY(a),((g=this.options.axes)===!0||"both"===g||"y"===g)&&this.drawYAxisLabel(this.left-this.options.padding/2,b,this.yAxisFormat(a)),this.options.grid?h.push(this.drawGridLine("M"+this.left+","+b+"H"+(this.left+this.width))):h.push(void 0);return h}},d.prototype.drawGoals=function(){var a,b,c,d,e,f,g;for(f=this.options.goals,g=[],c=d=0,e=f.length;e>d;c=++d)b=f[c],a=this.options.goalLineColors[c%this.options.goalLineColors.length],g.push(this.drawGoal(b,a));return g},d.prototype.drawEvents=function(){var a,b,c,d,e,f,g;for(f=this.events,g=[],c=d=0,e=f.length;e>d;c=++d)b=f[c],a=this.options.eventLineColors[c%this.options.eventLineColors.length],g.push(this.drawEvent(b,a));return g},d.prototype.drawGoal=function(a,b){return this.raphael.path("M"+this.left+","+this.transY(a)+"H"+this.right).attr("stroke",b).attr("stroke-width",this.options.goalStrokeWidth)},d.prototype.drawEvent=function(a,b){return this.raphael.path("M"+this.transX(a)+","+this.bottom+"V"+this.top).attr("stroke",b).attr("stroke-width",this.options.eventStrokeWidth)},d.prototype.drawYAxisLabel=function(a,b,c){return this.raphael.text(a,b,c).attr("font-size",this.options.gridTextSize).attr("font-family",this.options.gridTextFamily).attr("font-weight",this.options.gridTextWeight).attr("fill",this.options.gridTextColor).attr("text-anchor","end")},d.prototype.drawGridLine=function(a){return this.raphael.path(a).attr("stroke",this.options.gridLineColor).attr("stroke-width",this.options.gridStrokeWidth)},d.prototype.startRange=function(a){return this.hover.hide(),this.selectFrom=a,this.selectionRect.attr({x:a,width:0}).show()},d.prototype.endRange=function(a){var b,c;return this.selectFrom?(c=Math.min(this.selectFrom,a),b=Math.max(this.selectFrom,a),this.options.rangeSelect.call(this.el,{start:this.data[this.hitTest(c)].x,end:this.data[this.hitTest(b)].x}),this.selectFrom=null):void 0},d.prototype.resizeHandler=function(){return this.timeoutId=null,this.raphael.setSize(this.el.width(),this.el.height()),this.redraw()},d}(b.EventEmitter),b.parseDate=function(a){var b,c,d,e,f,g,h,i,j,k,l;return"number"==typeof a?a:(c=a.match(/^(\d+) Q(\d)$/),e=a.match(/^(\d+)-(\d+)$/),f=a.match(/^(\d+)-(\d+)-(\d+)$/),h=a.match(/^(\d+) W(\d+)$/),i=a.match(/^(\d+)-(\d+)-(\d+)[ T](\d+):(\d+)(Z|([+-])(\d\d):?(\d\d))?$/),j=a.match(/^(\d+)-(\d+)-(\d+)[ T](\d+):(\d+):(\d+(\.\d+)?)(Z|([+-])(\d\d):?(\d\d))?$/),c?new Date(parseInt(c[1],10),3*parseInt(c[2],10)-1,1).getTime():e?new Date(parseInt(e[1],10),parseInt(e[2],10)-1,1).getTime():f?new Date(parseInt(f[1],10),parseInt(f[2],10)-1,parseInt(f[3],10)).getTime():h?(k=new Date(parseInt(h[1],10),0,1),4!==k.getDay()&&k.setMonth(0,1+(4-k.getDay()+7)%7),k.getTime()+6048e5*parseInt(h[2],10)):i?i[6]?(g=0,"Z"!==i[6]&&(g=60*parseInt(i[8],10)+parseInt(i[9],10),"+"===i[7]&&(g=0-g)),Date.UTC(parseInt(i[1],10),parseInt(i[2],10)-1,parseInt(i[3],10),parseInt(i[4],10),parseInt(i[5],10)+g)):new Date(parseInt(i[1],10),parseInt(i[2],10)-1,parseInt(i[3],10),parseInt(i[4],10),parseInt(i[5],10)).getTime():j?(l=parseFloat(j[6]),b=Math.floor(l),d=Math.round(1e3*(l-b)),j[8]?(g=0,"Z"!==j[8]&&(g=60*parseInt(j[10],10)+parseInt(j[11],10),"+"===j[9]&&(g=0-g)),Date.UTC(parseInt(j[1],10),parseInt(j[2],10)-1,parseInt(j[3],10),parseInt(j[4],10),parseInt(j[5],10)+g,b,d)):new Date(parseInt(j[1],10),parseInt(j[2],10)-1,parseInt(j[3],10),parseInt(j[4],10),parseInt(j[5],10),b,d).getTime()):new Date(parseInt(a,10),0,1).getTime())},b.Hover=function(){function c(c){null==c&&(c={}),this.options=a.extend({},b.Hover.defaults,c),this.el=a("
"),this.el.hide(),this.options.parent.append(this.el)}return c.defaults={"class":"morris-hover morris-default-style"},c.prototype.update=function(a,b,c){return a?(this.html(a),this.show(),this.moveTo(b,c)):this.hide()},c.prototype.html=function(a){return this.el.html(a)},c.prototype.moveTo=function(a,b){var c,d,e,f,g,h;return g=this.options.parent.innerWidth(),f=this.options.parent.innerHeight(),d=this.el.outerWidth(),c=this.el.outerHeight(),e=Math.min(Math.max(0,a-d/2),g-d),null!=b?(h=b-c-10,0>h&&(h=b+10,h+c>f&&(h=f/2-c/2))):h=f/2-c/2,this.el.css({left:e+"px",top:parseInt(h)+"px"})},c.prototype.show=function(){return this.el.show()},c.prototype.hide=function(){return this.el.hide()},c}(),b.Line=function(a){function c(a){return this.hilight=f(this.hilight,this),this.onHoverOut=f(this.onHoverOut,this),this.onHoverMove=f(this.onHoverMove,this),this.onGridClick=f(this.onGridClick,this),this instanceof b.Line?(c.__super__.constructor.call(this,a),void 0):new b.Line(a)}return h(c,a),c.prototype.init=function(){return"always"!==this.options.hideHover?(this.hover=new b.Hover({parent:this.el}),this.on("hovermove",this.onHoverMove),this.on("hoverout",this.onHoverOut),this.on("gridclick",this.onGridClick)):void 0},c.prototype.defaults={lineWidth:3,pointSize:4,lineColors:["#0b62a4","#7A92A3","#4da74d","#afd8f8","#edc240","#cb4b4b","#9440ed"],pointStrokeWidths:[1],pointStrokeColors:["#ffffff"],pointFillColors:[],smooth:!0,xLabels:"auto",xLabelFormat:null,xLabelMargin:24,hideHover:!1},c.prototype.calc=function(){return this.calcPoints(),this.generatePaths()},c.prototype.calcPoints=function(){var a,b,c,d,e,f;for(e=this.data,f=[],c=0,d=e.length;d>c;c++)a=e[c],a._x=this.transX(a.x),a._y=function(){var c,d,e,f;for(e=a.y,f=[],c=0,d=e.length;d>c;c++)b=e[c],null!=b?f.push(this.transY(b)):f.push(b);return f}.call(this),f.push(a._ymax=Math.min.apply(Math,[this.bottom].concat(function(){var c,d,e,f;for(e=a._y,f=[],c=0,d=e.length;d>c;c++)b=e[c],null!=b&&f.push(b);return f}())));return f},c.prototype.hitTest=function(a){var b,c,d,e,f;if(0===this.data.length)return null;for(f=this.data.slice(1),b=d=0,e=f.length;e>d&&(c=f[b],!(a<(c._x+this.data[b]._x)/2));b=++d);return b},c.prototype.onGridClick=function(a,b){var c;return c=this.hitTest(a),this.fire("click",c,this.data[c].src,a,b)},c.prototype.onHoverMove=function(a){var b;return b=this.hitTest(a),this.displayHoverForRow(b)},c.prototype.onHoverOut=function(){return this.options.hideHover!==!1?this.displayHoverForRow(null):void 0},c.prototype.displayHoverForRow=function(a){var b;return null!=a?((b=this.hover).update.apply(b,this.hoverContentForRow(a)),this.hilight(a)):(this.hover.hide(),this.hilight())},c.prototype.hoverContentForRow=function(a){var b,c,d,e,f,g,h;for(d=this.data[a],b="
"+d.label+"
",h=d.y,c=f=0,g=h.length;g>f;c=++f)e=h[c],b+="
\n "+this.options.labels[c]+":\n "+this.yLabelFormat(e)+"\n
";return"function"==typeof this.options.hoverCallback&&(b=this.options.hoverCallback(a,this.options,b,d.src)),[b,d._x,d._ymax]},c.prototype.generatePaths=function(){var a,c,d,e;return this.paths=function(){var f,g,h,j;for(j=[],c=f=0,g=this.options.ykeys.length;g>=0?g>f:f>g;c=g>=0?++f:--f)e="boolean"==typeof this.options.smooth?this.options.smooth:(h=this.options.ykeys[c],i.call(this.options.smooth,h)>=0),a=function(){var a,b,e,f;for(e=this.data,f=[],a=0,b=e.length;b>a;a++)d=e[a],void 0!==d._y[c]&&f.push({x:d._x,y:d._y[c]});return f}.call(this),a.length>1?j.push(b.Line.createPath(a,e,this.bottom)):j.push(null);return j}.call(this)},c.prototype.draw=function(){var a;return((a=this.options.axes)===!0||"both"===a||"x"===a)&&this.drawXAxis(),this.drawSeries(),this.options.hideHover===!1?this.displayHoverForRow(this.data.length-1):void 0},c.prototype.drawXAxis=function(){var a,c,d,e,f,g,h,i,j,k,l=this;for(h=this.bottom+this.options.padding/2,f=null,e=null,a=function(a,b){var c,d,g,i,j;return c=l.drawXAxisLabel(l.transX(b),h,a),j=c.getBBox(),c.transform("r"+-l.options.xLabelAngle),d=c.getBBox(),c.transform("t0,"+d.height/2+"..."),0!==l.options.xLabelAngle&&(i=-.5*j.width*Math.cos(l.options.xLabelAngle*Math.PI/180),c.transform("t"+i+",0...")),d=c.getBBox(),(null==f||f>=d.x+d.width||null!=e&&e>=d.x)&&d.x>=0&&d.x+d.widtha;a++)g=c[a],d.push([g.label,g.x]);return d}.call(this),d.reverse(),k=[],i=0,j=d.length;j>i;i++)c=d[i],k.push(a(c[0],c[1]));return k},c.prototype.drawSeries=function(){var a,b,c,d,e,f;for(this.seriesPoints=[],a=b=d=this.options.ykeys.length-1;0>=d?0>=b:b>=0;a=0>=d?++b:--b)this._drawLineFor(a);for(f=[],a=c=e=this.options.ykeys.length-1;0>=e?0>=c:c>=0;a=0>=e?++c:--c)f.push(this._drawPointFor(a));return f},c.prototype._drawPointFor=function(a){var b,c,d,e,f,g;for(this.seriesPoints[a]=[],f=this.data,g=[],d=0,e=f.length;e>d;d++)c=f[d],b=null,null!=c._y[a]&&(b=this.drawLinePoint(c._x,c._y[a],this.colorFor(c,a,"point"),a)),g.push(this.seriesPoints[a].push(b));return g},c.prototype._drawLineFor=function(a){var b;return b=this.paths[a],null!==b?this.drawLinePath(b,this.colorFor(null,a,"line"),a):void 0},c.createPath=function(a,c,d){var e,f,g,h,i,j,k,l,m,n,o,p,q,r;for(k="",c&&(g=b.Line.gradients(a)),l={y:null},h=q=0,r=a.length;r>q;h=++q)e=a[h],null!=e.y&&(null!=l.y?c?(f=g[h],j=g[h-1],i=(e.x-l.x)/4,m=l.x+i,o=Math.min(d,l.y+i*j),n=e.x-i,p=Math.min(d,e.y-i*f),k+="C"+m+","+o+","+n+","+p+","+e.x+","+e.y):k+="L"+e.x+","+e.y:c&&null==g[h]||(k+="M"+e.x+","+e.y)),l=e;return k},c.gradients=function(a){var b,c,d,e,f,g,h,i;for(c=function(a,b){return(a.y-b.y)/(a.x-b.x)},i=[],d=g=0,h=a.length;h>g;d=++g)b=a[d],null!=b.y?(e=a[d+1]||{y:null},f=a[d-1]||{y:null},null!=f.y&&null!=e.y?i.push(c(f,e)):null!=f.y?i.push(c(f,b)):null!=e.y?i.push(c(b,e)):i.push(null)):i.push(null);return i},c.prototype.hilight=function(a){var b,c,d,e,f;if(null!==this.prevHilight&&this.prevHilight!==a)for(b=c=0,e=this.seriesPoints.length-1;e>=0?e>=c:c>=e;b=e>=0?++c:--c)this.seriesPoints[b][this.prevHilight]&&this.seriesPoints[b][this.prevHilight].animate(this.pointShrinkSeries(b));if(null!==a&&this.prevHilight!==a)for(b=d=0,f=this.seriesPoints.length-1;f>=0?f>=d:d>=f;b=f>=0?++d:--d)this.seriesPoints[b][a]&&this.seriesPoints[b][a].animate(this.pointGrowSeries(b));return this.prevHilight=a},c.prototype.colorFor=function(a,b,c){return"function"==typeof this.options.lineColors?this.options.lineColors.call(this,a,b,c):"point"===c?this.options.pointFillColors[b%this.options.pointFillColors.length]||this.options.lineColors[b%this.options.lineColors.length]:this.options.lineColors[b%this.options.lineColors.length]},c.prototype.drawXAxisLabel=function(a,b,c){return this.raphael.text(a,b,c).attr("font-size",this.options.gridTextSize).attr("font-family",this.options.gridTextFamily).attr("font-weight",this.options.gridTextWeight).attr("fill",this.options.gridTextColor)},c.prototype.drawLinePath=function(a,b,c){return this.raphael.path(a).attr("stroke",b).attr("stroke-width",this.lineWidthForSeries(c))},c.prototype.drawLinePoint=function(a,b,c,d){return this.raphael.circle(a,b,this.pointSizeForSeries(d)).attr("fill",c).attr("stroke-width",this.pointStrokeWidthForSeries(d)).attr("stroke",this.pointStrokeColorForSeries(d))},c.prototype.pointStrokeWidthForSeries=function(a){return this.options.pointStrokeWidths[a%this.options.pointStrokeWidths.length]},c.prototype.pointStrokeColorForSeries=function(a){return this.options.pointStrokeColors[a%this.options.pointStrokeColors.length]},c.prototype.lineWidthForSeries=function(a){return this.options.lineWidth instanceof Array?this.options.lineWidth[a%this.options.lineWidth.length]:this.options.lineWidth},c.prototype.pointSizeForSeries=function(a){return this.options.pointSize instanceof Array?this.options.pointSize[a%this.options.pointSize.length]:this.options.pointSize},c.prototype.pointGrowSeries=function(a){return Raphael.animation({r:this.pointSizeForSeries(a)+3},25,"linear")},c.prototype.pointShrinkSeries=function(a){return Raphael.animation({r:this.pointSizeForSeries(a)},25,"linear")},c}(b.Grid),b.labelSeries=function(c,d,e,f,g){var h,i,j,k,l,m,n,o,p,q,r;if(j=200*(d-c)/e,i=new Date(c),n=b.LABEL_SPECS[f],void 0===n)for(r=b.AUTO_LABEL_ORDER,p=0,q=r.length;q>p;p++)if(k=r[p],m=b.LABEL_SPECS[k],j>=m.span){n=m;break}for(void 0===n&&(n=b.LABEL_SPECS.second),g&&(n=a.extend({},n,{fmt:g})),h=n.start(i),l=[];(o=h.getTime())<=d;)o>=c&&l.push([n.fmt(h),o]),n.incr(h);return l},c=function(a){return{span:60*a*1e3,start:function(a){return new Date(a.getFullYear(),a.getMonth(),a.getDate(),a.getHours())},fmt:function(a){return""+b.pad2(a.getHours())+":"+b.pad2(a.getMinutes())},incr:function(b){return b.setUTCMinutes(b.getUTCMinutes()+a)}}},d=function(a){return{span:1e3*a,start:function(a){return new Date(a.getFullYear(),a.getMonth(),a.getDate(),a.getHours(),a.getMinutes())},fmt:function(a){return""+b.pad2(a.getHours())+":"+b.pad2(a.getMinutes())+":"+b.pad2(a.getSeconds())},incr:function(b){return b.setUTCSeconds(b.getUTCSeconds()+a)}}},b.LABEL_SPECS={decade:{span:1728e8,start:function(a){return new Date(a.getFullYear()-a.getFullYear()%10,0,1)},fmt:function(a){return""+a.getFullYear()},incr:function(a){return a.setFullYear(a.getFullYear()+10)}},year:{span:1728e7,start:function(a){return new Date(a.getFullYear(),0,1)},fmt:function(a){return""+a.getFullYear()},incr:function(a){return a.setFullYear(a.getFullYear()+1)}},month:{span:24192e5,start:function(a){return new Date(a.getFullYear(),a.getMonth(),1)},fmt:function(a){return""+a.getFullYear()+"-"+b.pad2(a.getMonth()+1)},incr:function(a){return a.setMonth(a.getMonth()+1)}},week:{span:6048e5,start:function(a){return new Date(a.getFullYear(),a.getMonth(),a.getDate())},fmt:function(a){return""+a.getFullYear()+"-"+b.pad2(a.getMonth()+1)+"-"+b.pad2(a.getDate())},incr:function(a){return a.setDate(a.getDate()+7)}},day:{span:864e5,start:function(a){return new Date(a.getFullYear(),a.getMonth(),a.getDate())},fmt:function(a){return""+a.getFullYear()+"-"+b.pad2(a.getMonth()+1)+"-"+b.pad2(a.getDate())},incr:function(a){return a.setDate(a.getDate()+1)}},hour:c(60),"30min":c(30),"15min":c(15),"10min":c(10),"5min":c(5),minute:c(1),"30sec":d(30),"15sec":d(15),"10sec":d(10),"5sec":d(5),second:d(1)},b.AUTO_LABEL_ORDER=["decade","year","month","week","day","hour","30min","15min","10min","5min","minute","30sec","15sec","10sec","5sec","second"],b.Area=function(c){function d(c){var f;return this instanceof b.Area?(f=a.extend({},e,c),this.cumulative=!f.behaveLikeLine,"auto"===f.fillOpacity&&(f.fillOpacity=f.behaveLikeLine?.8:1),d.__super__.constructor.call(this,f),void 0):new b.Area(c)}var e;return h(d,c),e={fillOpacity:"auto",behaveLikeLine:!1},d.prototype.calcPoints=function(){var a,b,c,d,e,f,g;for(f=this.data,g=[],d=0,e=f.length;e>d;d++)a=f[d],a._x=this.transX(a.x),b=0,a._y=function(){var d,e,f,g;for(f=a.y,g=[],d=0,e=f.length;e>d;d++)c=f[d],this.options.behaveLikeLine?g.push(this.transY(c)):(b+=c||0,g.push(this.transY(b)));return g}.call(this),g.push(a._ymax=Math.max.apply(Math,a._y));return g},d.prototype.drawSeries=function(){var a,b,c,d,e,f,g,h;for(this.seriesPoints=[],b=this.options.behaveLikeLine?function(){f=[];for(var a=0,b=this.options.ykeys.length-1;b>=0?b>=a:a>=b;b>=0?a++:a--)f.push(a);return f}.apply(this):function(){g=[];for(var a=e=this.options.ykeys.length-1;0>=e?0>=a:a>=0;0>=e?a++:a--)g.push(a);return g}.apply(this),h=[],c=0,d=b.length;d>c;c++)a=b[c],this._drawFillFor(a),this._drawLineFor(a),h.push(this._drawPointFor(a));return h},d.prototype._drawFillFor=function(a){var b;return b=this.paths[a],null!==b?(b+="L"+this.transX(this.xmax)+","+this.bottom+"L"+this.transX(this.xmin)+","+this.bottom+"Z",this.drawFilledPath(b,this.fillForSeries(a))):void 0},d.prototype.fillForSeries=function(a){var b;return b=Raphael.rgb2hsl(this.colorFor(this.data[a],a,"line")),Raphael.hsl(b.h,this.options.behaveLikeLine?.9*b.s:.75*b.s,Math.min(.98,this.options.behaveLikeLine?1.2*b.l:1.25*b.l))},d.prototype.drawFilledPath=function(a,b){return this.raphael.path(a).attr("fill",b).attr("fill-opacity",this.options.fillOpacity).attr("stroke","none")},d}(b.Line),b.Bar=function(c){function d(c){return this.onHoverOut=f(this.onHoverOut,this),this.onHoverMove=f(this.onHoverMove,this),this.onGridClick=f(this.onGridClick,this),this instanceof b.Bar?(d.__super__.constructor.call(this,a.extend({},c,{parseTime:!1})),void 0):new b.Bar(c)}return h(d,c),d.prototype.init=function(){return this.cumulative=this.options.stacked,"always"!==this.options.hideHover?(this.hover=new b.Hover({parent:this.el}),this.on("hovermove",this.onHoverMove),this.on("hoverout",this.onHoverOut),this.on("gridclick",this.onGridClick)):void 0},d.prototype.defaults={barSizeRatio:.75,barGap:3,barColors:["#0b62a4","#7a92a3","#4da74d","#afd8f8","#edc240","#cb4b4b","#9440ed"],barOpacity:1,barRadius:[0,0,0,0],xLabelMargin:50},d.prototype.calc=function(){var a;return this.calcBars(),this.options.hideHover===!1?(a=this.hover).update.apply(a,this.hoverContentForRow(this.data.length-1)):void 0},d.prototype.calcBars=function(){var a,b,c,d,e,f,g;for(f=this.data,g=[],a=d=0,e=f.length;e>d;a=++d)b=f[a],b._x=this.left+this.width*(a+.5)/this.data.length,g.push(b._y=function(){var a,d,e,f;for(e=b.y,f=[],a=0,d=e.length;d>a;a++)c=e[a],null!=c?f.push(this.transY(c)):f.push(null);return f}.call(this));return g},d.prototype.draw=function(){var a;return((a=this.options.axes)===!0||"both"===a||"x"===a)&&this.drawXAxis(),this.drawSeries()},d.prototype.drawXAxis=function(){var a,b,c,d,e,f,g,h,i,j,k,l,m;for(j=this.bottom+(this.options.xAxisLabelTopPadding||this.options.padding/2),g=null,f=null,m=[],a=k=0,l=this.data.length;l>=0?l>k:k>l;a=l>=0?++k:--k)h=this.data[this.data.length-1-a],b=this.drawXAxisLabel(h._x,j,h.label),i=b.getBBox(),b.transform("r"+-this.options.xLabelAngle),c=b.getBBox(),b.transform("t0,"+c.height/2+"..."),0!==this.options.xLabelAngle&&(e=-.5*i.width*Math.cos(this.options.xLabelAngle*Math.PI/180),b.transform("t"+e+",0...")),(null==g||g>=c.x+c.width||null!=f&&f>=c.x)&&c.x>=0&&c.x+c.width=0?this.transY(0):null,this.bars=function(){var h,l,p,q;for(p=this.data,q=[],d=h=0,l=p.length;l>h;d=++h)i=p[d],e=0,q.push(function(){var h,l,p,q;for(p=i._y,q=[],j=h=0,l=p.length;l>h;j=++h)n=p[j],null!==n?(o?(m=Math.min(n,o),b=Math.max(n,o)):(m=n,b=this.bottom),f=this.left+d*c+g,this.options.stacked||(f+=j*(a+this.options.barGap)),k=b-m,this.options.verticalGridCondition&&this.options.verticalGridCondition(i.x)&&this.drawBar(this.left+d*c,this.top,c,Math.abs(this.top-this.bottom),this.options.verticalGridColor,this.options.verticalGridOpacity,this.options.barRadius),this.options.stacked&&(m-=e),this.drawBar(f,m,a,k,this.colorFor(i,j,"bar"),this.options.barOpacity,this.options.barRadius),q.push(e+=k)):q.push(null);return q}.call(this));return q}.call(this)},d.prototype.colorFor=function(a,b,c){var d,e;return"function"==typeof this.options.barColors?(d={x:a.x,y:a.y[b],label:a.label},e={index:b,key:this.options.ykeys[b],label:this.options.labels[b]},this.options.barColors.call(this,d,e,c)):this.options.barColors[b%this.options.barColors.length]},d.prototype.hitTest=function(a){return 0===this.data.length?null:(a=Math.max(Math.min(a,this.right),this.left),Math.min(this.data.length-1,Math.floor((a-this.left)/(this.width/this.data.length))))},d.prototype.onGridClick=function(a,b){var c;return c=this.hitTest(a),this.fire("click",c,this.data[c].src,a,b)},d.prototype.onHoverMove=function(a){var b,c;return b=this.hitTest(a),(c=this.hover).update.apply(c,this.hoverContentForRow(b))},d.prototype.onHoverOut=function(){return this.options.hideHover!==!1?this.hover.hide():void 0},d.prototype.hoverContentForRow=function(a){var b,c,d,e,f,g,h,i;for(d=this.data[a],b="
"+d.label+"
",i=d.y,c=g=0,h=i.length;h>g;c=++g)f=i[c],b+="
\n "+this.options.labels[c]+":\n "+this.yLabelFormat(f)+"\n
";return"function"==typeof this.options.hoverCallback&&(b=this.options.hoverCallback(a,this.options,b,d.src)),e=this.left+(a+.5)*this.width/this.data.length,[b,e]},d.prototype.drawXAxisLabel=function(a,b,c){var d;return d=this.raphael.text(a,b,c).attr("font-size",this.options.gridTextSize).attr("font-family",this.options.gridTextFamily).attr("font-weight",this.options.gridTextWeight).attr("fill",this.options.gridTextColor)},d.prototype.drawBar=function(a,b,c,d,e,f,g){var h,i;return h=Math.max.apply(Math,g),i=0===h||h>d?this.raphael.rect(a,b,c,d):this.raphael.path(this.roundedRect(a,b,c,d,g)),i.attr("fill",e).attr("fill-opacity",f).attr("stroke","none")},d.prototype.roundedRect=function(a,b,c,d,e){return null==e&&(e=[0,0,0,0]),["M",a,e[0]+b,"Q",a,b,a+e[0],b,"L",a+c-e[1],b,"Q",a+c,b,a+c,b+e[1],"L",a+c,b+d-e[2],"Q",a+c,b+d,a+c-e[2],b+d,"L",a+e[3],b+d,"Q",a,b+d,a,b+d-e[3],"Z"]},d}(b.Grid),b.Donut=function(c){function d(c){this.resizeHandler=f(this.resizeHandler,this),this.select=f(this.select,this),this.click=f(this.click,this);var d=this;if(!(this instanceof b.Donut))return new b.Donut(c);if(this.options=a.extend({},this.defaults,c),this.el="string"==typeof c.element?a(document.getElementById(c.element)):a(c.element),null===this.el||0===this.el.length)throw new Error("Graph placeholder not found.");void 0!==c.data&&0!==c.data.length&&(this.raphael=new Raphael(this.el[0]),this.options.resize&&a(window).bind("resize",function(){return null!=d.timeoutId&&window.clearTimeout(d.timeoutId),d.timeoutId=window.setTimeout(d.resizeHandler,100)}),this.setData(c.data))}return h(d,c),d.prototype.defaults={colors:["#0B62A4","#3980B5","#679DC6","#95BBD7","#B0CCE1","#095791","#095085","#083E67","#052C48","#042135"],backgroundColor:"#FFFFFF",labelColor:"#000000",formatter:b.commas,resize:!1},d.prototype.redraw=function(){var a,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x;for(this.raphael.clear(),c=this.el.width()/2,d=this.el.height()/2,n=(Math.min(c,d)-10)/3,l=0,u=this.values,o=0,r=u.length;r>o;o++)m=u[o],l+=m;for(i=5/(2*n),a=1.9999*Math.PI-i*this.data.length,g=0,f=0,this.segments=[],v=this.values,e=p=0,s=v.length;s>p;e=++p)m=v[e],j=g+i+a*(m/l),k=new b.DonutSegment(c,d,2*n,n,g,j,this.data[e].color||this.options.colors[f%this.options.colors.length],this.options.backgroundColor,f,this.raphael),k.render(),this.segments.push(k),k.on("hover",this.select),k.on("click",this.click),g=j,f+=1;for(this.text1=this.drawEmptyDonutLabel(c,d-10,this.options.labelColor,15,800),this.text2=this.drawEmptyDonutLabel(c,d+10,this.options.labelColor,14),h=Math.max.apply(Math,this.values),f=0,w=this.values,x=[],q=0,t=w.length;t>q;q++){if(m=w[q],m===h){this.select(f); break}x.push(f+=1)}return x},d.prototype.setData=function(a){var b;return this.data=a,this.values=function(){var a,c,d,e;for(d=this.data,e=[],a=0,c=d.length;c>a;a++)b=d[a],e.push(parseFloat(b.value));return e}.call(this),this.redraw()},d.prototype.click=function(a){return this.fire("click",a,this.data[a])},d.prototype.select=function(a){var b,c,d,e,f,g;for(g=this.segments,e=0,f=g.length;f>e;e++)c=g[e],c.deselect();return d=this.segments[a],d.select(),b=this.data[a],this.setLabels(b.label,this.options.formatter(b.value,b))},d.prototype.setLabels=function(a,b){var c,d,e,f,g,h,i,j;return c=2*(Math.min(this.el.width()/2,this.el.height()/2)-10)/3,f=1.8*c,e=c/2,d=c/3,this.text1.attr({text:a,transform:""}),g=this.text1.getBBox(),h=Math.min(f/g.width,e/g.height),this.text1.attr({transform:"S"+h+","+h+","+(g.x+g.width/2)+","+(g.y+g.height)}),this.text2.attr({text:b,transform:""}),i=this.text2.getBBox(),j=Math.min(f/i.width,d/i.height),this.text2.attr({transform:"S"+j+","+j+","+(i.x+i.width/2)+","+i.y})},d.prototype.drawEmptyDonutLabel=function(a,b,c,d,e){var f;return f=this.raphael.text(a,b,"").attr("font-size",d).attr("fill",c),null!=e&&f.attr("font-weight",e),f},d.prototype.resizeHandler=function(){return this.timeoutId=null,this.raphael.setSize(this.el.width(),this.el.height()),this.redraw()},d}(b.EventEmitter),b.DonutSegment=function(a){function b(a,b,c,d,e,g,h,i,j,k){this.cx=a,this.cy=b,this.inner=c,this.outer=d,this.color=h,this.backgroundColor=i,this.index=j,this.raphael=k,this.deselect=f(this.deselect,this),this.select=f(this.select,this),this.sin_p0=Math.sin(e),this.cos_p0=Math.cos(e),this.sin_p1=Math.sin(g),this.cos_p1=Math.cos(g),this.is_long=g-e>Math.PI?1:0,this.path=this.calcSegment(this.inner+3,this.inner+this.outer-5),this.selectedPath=this.calcSegment(this.inner+3,this.inner+this.outer),this.hilight=this.calcArc(this.inner)}return h(b,a),b.prototype.calcArcPoints=function(a){return[this.cx+a*this.sin_p0,this.cy+a*this.cos_p0,this.cx+a*this.sin_p1,this.cy+a*this.cos_p1]},b.prototype.calcSegment=function(a,b){var c,d,e,f,g,h,i,j,k,l;return k=this.calcArcPoints(a),c=k[0],e=k[1],d=k[2],f=k[3],l=this.calcArcPoints(b),g=l[0],i=l[1],h=l[2],j=l[3],"M"+c+","+e+("A"+a+","+a+",0,"+this.is_long+",0,"+d+","+f)+("L"+h+","+j)+("A"+b+","+b+",0,"+this.is_long+",1,"+g+","+i)+"Z"},b.prototype.calcArc=function(a){var b,c,d,e,f;return f=this.calcArcPoints(a),b=f[0],d=f[1],c=f[2],e=f[3],"M"+b+","+d+("A"+a+","+a+",0,"+this.is_long+",0,"+c+","+e)},b.prototype.render=function(){var a=this;return this.arc=this.drawDonutArc(this.hilight,this.color),this.seg=this.drawDonutSegment(this.path,this.color,this.backgroundColor,function(){return a.fire("hover",a.index)},function(){return a.fire("click",a.index)})},b.prototype.drawDonutArc=function(a,b){return this.raphael.path(a).attr({stroke:b,"stroke-width":2,opacity:0})},b.prototype.drawDonutSegment=function(a,b,c,d,e){return this.raphael.path(a).attr({fill:b,stroke:c,"stroke-width":3}).hover(d).click(e)},b.prototype.select=function(){return this.selected?void 0:(this.seg.animate({path:this.selectedPath},150,"<>"),this.arc.animate({opacity:1},150,"<>"),this.selected=!0)},b.prototype.deselect=function(){return this.selected?(this.seg.animate({path:this.path},150,"<>"),this.arc.animate({opacity:0},150,"<>"),this.selected=!1):void 0},b}(b.EventEmitter)}).call(this);.travis.yml000064400000001116150145030150006646 0ustar00language: node_js node_js: - 0.10 before_script: - "npm install -g grunt-cli" - "npm install" - "cp -f bower.travis.json bower.json" - 'sed -i -e "s/JQUERY/$JQUERY/" bower.json' - 'sed -i -e "s/RAPHAEL/$RAPHAEL/" bower.json' - "bower install" env: - JQUERY="~> 1.8.0" RAPHAEL="~> 2.0.0" - JQUERY="~> 1.9.0" RAPHAEL="~> 2.0.0" - JQUERY="~> 2.0.0" RAPHAEL="~> 2.0.0" - JQUERY="~> 2.1.0" RAPHAEL="~> 2.0.0" - JQUERY="~> 1.8.0" RAPHAEL="~> 2.1.0" - JQUERY="~> 1.9.0" RAPHAEL="~> 2.1.0" - JQUERY="~> 2.0.0" RAPHAEL="~> 2.1.0" - JQUERY="~> 2.1.0" RAPHAEL="~> 2.1.0" lib/morris.line.coffee000064400000030643150145030150010724 0ustar00class Morris.Line extends Morris.Grid # Initialise the graph. # constructor: (options) -> return new Morris.Line(options) unless (@ instanceof Morris.Line) super(options) init: -> # Some instance variables for later if @options.hideHover isnt 'always' @hover = new Morris.Hover(parent: @el) @on('hovermove', @onHoverMove) @on('hoverout', @onHoverOut) @on('gridclick', @onGridClick) # Default configuration # defaults: lineWidth: 3 pointSize: 4 lineColors: [ '#0b62a4' '#7A92A3' '#4da74d' '#afd8f8' '#edc240' '#cb4b4b' '#9440ed' ] pointStrokeWidths: [1] pointStrokeColors: ['#ffffff'] pointFillColors: [] smooth: true xLabels: 'auto' xLabelFormat: null xLabelMargin: 24 hideHover: false # Do any size-related calculations # # @private calc: -> @calcPoints() @generatePaths() # calculate series data point coordinates # # @private calcPoints: -> for row in @data row._x = @transX(row.x) row._y = for y in row.y if y? then @transY(y) else y row._ymax = Math.min [@bottom].concat(y for y in row._y when y?)... # hit test - returns the index of the row at the given x-coordinate # hitTest: (x) -> return null if @data.length == 0 # TODO better search algo for r, index in @data.slice(1) break if x < (r._x + @data[index]._x) / 2 index # click on grid event handler # # @private onGridClick: (x, y) => index = @hitTest(x) @fire 'click', index, @data[index].src, x, y # hover movement event handler # # @private onHoverMove: (x, y) => index = @hitTest(x) @displayHoverForRow(index) # hover out event handler # # @private onHoverOut: => if @options.hideHover isnt false @displayHoverForRow(null) # display a hover popup over the given row # # @private displayHoverForRow: (index) -> if index? @hover.update(@hoverContentForRow(index)...) @hilight(index) else @hover.hide() @hilight() # hover content for a point # # @private hoverContentForRow: (index) -> row = @data[index] content = "
#{row.label}
" for y, j in row.y content += """
#{@options.labels[j]}: #{@yLabelFormat(y)}
""" if typeof @options.hoverCallback is 'function' content = @options.hoverCallback(index, @options, content, row.src) [content, row._x, row._ymax] # generate paths for series lines # # @private generatePaths: -> @paths = for i in [0...@options.ykeys.length] smooth = if typeof @options.smooth is "boolean" then @options.smooth else @options.ykeys[i] in @options.smooth coords = ({x: r._x, y: r._y[i]} for r in @data when r._y[i] isnt undefined) if coords.length > 1 Morris.Line.createPath coords, smooth, @bottom else null # Draws the line chart. # draw: -> @drawXAxis() if @options.axes in [true, 'both', 'x'] @drawSeries() if @options.hideHover is false @displayHoverForRow(@data.length - 1) # draw the x-axis labels # # @private drawXAxis: -> # draw x axis labels ypos = @bottom + @options.padding / 2 prevLabelMargin = null prevAngleMargin = null drawLabel = (labelText, xpos) => label = @drawXAxisLabel(@transX(xpos), ypos, labelText) textBox = label.getBBox() label.transform("r#{-@options.xLabelAngle}") labelBox = label.getBBox() label.transform("t0,#{labelBox.height / 2}...") if @options.xLabelAngle != 0 offset = -0.5 * textBox.width * Math.cos(@options.xLabelAngle * Math.PI / 180.0) label.transform("t#{offset},0...") # try to avoid overlaps labelBox = label.getBBox() if (not prevLabelMargin? or prevLabelMargin >= labelBox.x + labelBox.width or prevAngleMargin? and prevAngleMargin >= labelBox.x) and labelBox.x >= 0 and (labelBox.x + labelBox.width) < @el.width() if @options.xLabelAngle != 0 margin = 1.25 * @options.gridTextSize / Math.sin(@options.xLabelAngle * Math.PI / 180.0) prevAngleMargin = labelBox.x - margin prevLabelMargin = labelBox.x - @options.xLabelMargin else label.remove() if @options.parseTime if @data.length == 1 and @options.xLabels == 'auto' # where there's only one value in the series, we can't make a # sensible guess for an x labelling scheme, so just use the original # column label labels = [[@data[0].label, @data[0].x]] else labels = Morris.labelSeries(@xmin, @xmax, @width, @options.xLabels, @options.xLabelFormat) else labels = ([row.label, row.x] for row in @data) labels.reverse() for l in labels drawLabel(l[0], l[1]) # draw the data series # # @private drawSeries: -> @seriesPoints = [] for i in [@options.ykeys.length-1..0] @_drawLineFor i for i in [@options.ykeys.length-1..0] @_drawPointFor i _drawPointFor: (index) -> @seriesPoints[index] = [] for row in @data circle = null if row._y[index]? circle = @drawLinePoint(row._x, row._y[index], @colorFor(row, index, 'point'), index) @seriesPoints[index].push(circle) _drawLineFor: (index) -> path = @paths[index] if path isnt null @drawLinePath path, @colorFor(null, index, 'line'), index # create a path for a data series # # @private @createPath: (coords, smooth, bottom) -> path = "" grads = Morris.Line.gradients(coords) if smooth prevCoord = {y: null} for coord, i in coords if coord.y? if prevCoord.y? if smooth g = grads[i] lg = grads[i - 1] ix = (coord.x - prevCoord.x) / 4 x1 = prevCoord.x + ix y1 = Math.min(bottom, prevCoord.y + ix * lg) x2 = coord.x - ix y2 = Math.min(bottom, coord.y - ix * g) path += "C#{x1},#{y1},#{x2},#{y2},#{coord.x},#{coord.y}" else path += "L#{coord.x},#{coord.y}" else if not smooth or grads[i]? path += "M#{coord.x},#{coord.y}" prevCoord = coord return path # calculate a gradient at each point for a series of points # # @private @gradients: (coords) -> grad = (a, b) -> (a.y - b.y) / (a.x - b.x) for coord, i in coords if coord.y? nextCoord = coords[i + 1] or {y: null} prevCoord = coords[i - 1] or {y: null} if prevCoord.y? and nextCoord.y? grad(prevCoord, nextCoord) else if prevCoord.y? grad(prevCoord, coord) else if nextCoord.y? grad(coord, nextCoord) else null else null # @private hilight: (index) => if @prevHilight isnt null and @prevHilight isnt index for i in [0..@seriesPoints.length-1] if @seriesPoints[i][@prevHilight] @seriesPoints[i][@prevHilight].animate @pointShrinkSeries(i) if index isnt null and @prevHilight isnt index for i in [0..@seriesPoints.length-1] if @seriesPoints[i][index] @seriesPoints[i][index].animate @pointGrowSeries(i) @prevHilight = index colorFor: (row, sidx, type) -> if typeof @options.lineColors is 'function' @options.lineColors.call(@, row, sidx, type) else if type is 'point' @options.pointFillColors[sidx % @options.pointFillColors.length] || @options.lineColors[sidx % @options.lineColors.length] else @options.lineColors[sidx % @options.lineColors.length] drawXAxisLabel: (xPos, yPos, text) -> @raphael.text(xPos, yPos, text) .attr('font-size', @options.gridTextSize) .attr('font-family', @options.gridTextFamily) .attr('font-weight', @options.gridTextWeight) .attr('fill', @options.gridTextColor) drawLinePath: (path, lineColor, lineIndex) -> @raphael.path(path) .attr('stroke', lineColor) .attr('stroke-width', @lineWidthForSeries(lineIndex)) drawLinePoint: (xPos, yPos, pointColor, lineIndex) -> @raphael.circle(xPos, yPos, @pointSizeForSeries(lineIndex)) .attr('fill', pointColor) .attr('stroke-width', @pointStrokeWidthForSeries(lineIndex)) .attr('stroke', @pointStrokeColorForSeries(lineIndex)) # @private pointStrokeWidthForSeries: (index) -> @options.pointStrokeWidths[index % @options.pointStrokeWidths.length] # @private pointStrokeColorForSeries: (index) -> @options.pointStrokeColors[index % @options.pointStrokeColors.length] # @private lineWidthForSeries: (index) -> if (@options.lineWidth instanceof Array) @options.lineWidth[index % @options.lineWidth.length] else @options.lineWidth # @private pointSizeForSeries: (index) -> if (@options.pointSize instanceof Array) @options.pointSize[index % @options.pointSize.length] else @options.pointSize # @private pointGrowSeries: (index) -> Raphael.animation r: @pointSizeForSeries(index) + 3, 25, 'linear' # @private pointShrinkSeries: (index) -> Raphael.animation r: @pointSizeForSeries(index), 25, 'linear' # generate a series of label, timestamp pairs for x-axis labels # # @private Morris.labelSeries = (dmin, dmax, pxwidth, specName, xLabelFormat) -> ddensity = 200 * (dmax - dmin) / pxwidth # seconds per `margin` pixels d0 = new Date(dmin) spec = Morris.LABEL_SPECS[specName] # if the spec doesn't exist, search for the closest one in the list if spec is undefined for name in Morris.AUTO_LABEL_ORDER s = Morris.LABEL_SPECS[name] if ddensity >= s.span spec = s break # if we run out of options, use second-intervals if spec is undefined spec = Morris.LABEL_SPECS["second"] # check if there's a user-defined formatting function if xLabelFormat spec = $.extend({}, spec, {fmt: xLabelFormat}) # calculate labels d = spec.start(d0) ret = [] while (t = d.getTime()) <= dmax if t >= dmin ret.push [spec.fmt(d), t] spec.incr(d) return ret # @private minutesSpecHelper = (interval) -> span: interval * 60 * 1000 start: (d) -> new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours()) fmt: (d) -> "#{Morris.pad2(d.getHours())}:#{Morris.pad2(d.getMinutes())}" incr: (d) -> d.setUTCMinutes(d.getUTCMinutes() + interval) # @private secondsSpecHelper = (interval) -> span: interval * 1000 start: (d) -> new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes()) fmt: (d) -> "#{Morris.pad2(d.getHours())}:#{Morris.pad2(d.getMinutes())}:#{Morris.pad2(d.getSeconds())}" incr: (d) -> d.setUTCSeconds(d.getUTCSeconds() + interval) Morris.LABEL_SPECS = "decade": span: 172800000000 # 10 * 365 * 24 * 60 * 60 * 1000 start: (d) -> new Date(d.getFullYear() - d.getFullYear() % 10, 0, 1) fmt: (d) -> "#{d.getFullYear()}" incr: (d) -> d.setFullYear(d.getFullYear() + 10) "year": span: 17280000000 # 365 * 24 * 60 * 60 * 1000 start: (d) -> new Date(d.getFullYear(), 0, 1) fmt: (d) -> "#{d.getFullYear()}" incr: (d) -> d.setFullYear(d.getFullYear() + 1) "month": span: 2419200000 # 28 * 24 * 60 * 60 * 1000 start: (d) -> new Date(d.getFullYear(), d.getMonth(), 1) fmt: (d) -> "#{d.getFullYear()}-#{Morris.pad2(d.getMonth() + 1)}" incr: (d) -> d.setMonth(d.getMonth() + 1) "week": span: 604800000 # 7 * 24 * 60 * 60 * 1000 start: (d) -> new Date(d.getFullYear(), d.getMonth(), d.getDate()) fmt: (d) -> "#{d.getFullYear()}-#{Morris.pad2(d.getMonth() + 1)}-#{Morris.pad2(d.getDate())}" incr: (d) -> d.setDate(d.getDate() + 7) "day": span: 86400000 # 24 * 60 * 60 * 1000 start: (d) -> new Date(d.getFullYear(), d.getMonth(), d.getDate()) fmt: (d) -> "#{d.getFullYear()}-#{Morris.pad2(d.getMonth() + 1)}-#{Morris.pad2(d.getDate())}" incr: (d) -> d.setDate(d.getDate() + 1) "hour": minutesSpecHelper(60) "30min": minutesSpecHelper(30) "15min": minutesSpecHelper(15) "10min": minutesSpecHelper(10) "5min": minutesSpecHelper(5) "minute": minutesSpecHelper(1) "30sec": secondsSpecHelper(30) "15sec": secondsSpecHelper(15) "10sec": secondsSpecHelper(10) "5sec": secondsSpecHelper(5) "second": secondsSpecHelper(1) Morris.AUTO_LABEL_ORDER = [ "decade", "year", "month", "week", "day", "hour", "30min", "15min", "10min", "5min", "minute", "30sec", "15sec", "10sec", "5sec", "second" ] lib/morris.bar.coffee000064400000015025150145030150010536 0ustar00class Morris.Bar extends Morris.Grid constructor: (options) -> return new Morris.Bar(options) unless (@ instanceof Morris.Bar) super($.extend {}, options, parseTime: false) init: -> @cumulative = @options.stacked if @options.hideHover isnt 'always' @hover = new Morris.Hover(parent: @el) @on('hovermove', @onHoverMove) @on('hoverout', @onHoverOut) @on('gridclick', @onGridClick) # Default configuration # defaults: barSizeRatio: 0.75 barGap: 3 barColors: [ '#0b62a4' '#7a92a3' '#4da74d' '#afd8f8' '#edc240' '#cb4b4b' '#9440ed' ], barOpacity: 1.0 barRadius: [0, 0, 0, 0] xLabelMargin: 50 # Do any size-related calculations # # @private calc: -> @calcBars() if @options.hideHover is false @hover.update(@hoverContentForRow(@data.length - 1)...) # calculate series data bars coordinates and sizes # # @private calcBars: -> for row, idx in @data row._x = @left + @width * (idx + 0.5) / @data.length row._y = for y in row.y if y? then @transY(y) else null # Draws the bar chart. # draw: -> @drawXAxis() if @options.axes in [true, 'both', 'x'] @drawSeries() # draw the x-axis labels # # @private drawXAxis: -> # draw x axis labels ypos = @bottom + (@options.xAxisLabelTopPadding || @options.padding / 2) prevLabelMargin = null prevAngleMargin = null for i in [0...@data.length] row = @data[@data.length - 1 - i] label = @drawXAxisLabel(row._x, ypos, row.label) textBox = label.getBBox() label.transform("r#{-@options.xLabelAngle}") labelBox = label.getBBox() label.transform("t0,#{labelBox.height / 2}...") if @options.xLabelAngle != 0 offset = -0.5 * textBox.width * Math.cos(@options.xLabelAngle * Math.PI / 180.0) label.transform("t#{offset},0...") # try to avoid overlaps if (not prevLabelMargin? or prevLabelMargin >= labelBox.x + labelBox.width or prevAngleMargin? and prevAngleMargin >= labelBox.x) and labelBox.x >= 0 and (labelBox.x + labelBox.width) < @el.width() if @options.xLabelAngle != 0 margin = 1.25 * @options.gridTextSize / Math.sin(@options.xLabelAngle * Math.PI / 180.0) prevAngleMargin = labelBox.x - margin prevLabelMargin = labelBox.x - @options.xLabelMargin else label.remove() # draw the data series # # @private drawSeries: -> groupWidth = @width / @options.data.length numBars = if @options.stacked then 1 else @options.ykeys.length barWidth = (groupWidth * @options.barSizeRatio - @options.barGap * (numBars - 1)) / numBars barWidth = Math.min(barWidth, @options.barSize) if @options.barSize spaceLeft = groupWidth - barWidth * numBars - @options.barGap * (numBars - 1) leftPadding = spaceLeft / 2 zeroPos = if @ymin <= 0 and @ymax >= 0 then @transY(0) else null @bars = for row, idx in @data lastTop = 0 for ypos, sidx in row._y if ypos != null if zeroPos top = Math.min(ypos, zeroPos) bottom = Math.max(ypos, zeroPos) else top = ypos bottom = @bottom left = @left + idx * groupWidth + leftPadding left += sidx * (barWidth + @options.barGap) unless @options.stacked size = bottom - top if @options.verticalGridCondition and @options.verticalGridCondition(row.x) @drawBar(@left + idx * groupWidth, @top, groupWidth, Math.abs(@top - @bottom), @options.verticalGridColor, @options.verticalGridOpacity, @options.barRadius) top -= lastTop if @options.stacked @drawBar(left, top, barWidth, size, @colorFor(row, sidx, 'bar'), @options.barOpacity, @options.barRadius) lastTop += size else null # @private # # @param row [Object] row data # @param sidx [Number] series index # @param type [String] "bar", "hover" or "label" colorFor: (row, sidx, type) -> if typeof @options.barColors is 'function' r = { x: row.x, y: row.y[sidx], label: row.label } s = { index: sidx, key: @options.ykeys[sidx], label: @options.labels[sidx] } @options.barColors.call(@, r, s, type) else @options.barColors[sidx % @options.barColors.length] # hit test - returns the index of the row at the given x-coordinate # hitTest: (x) -> return null if @data.length == 0 x = Math.max(Math.min(x, @right), @left) Math.min(@data.length - 1, Math.floor((x - @left) / (@width / @data.length))) # click on grid event handler # # @private onGridClick: (x, y) => index = @hitTest(x) @fire 'click', index, @data[index].src, x, y # hover movement event handler # # @private onHoverMove: (x, y) => index = @hitTest(x) @hover.update(@hoverContentForRow(index)...) # hover out event handler # # @private onHoverOut: => if @options.hideHover isnt false @hover.hide() # hover content for a point # # @private hoverContentForRow: (index) -> row = @data[index] content = "
#{row.label}
" for y, j in row.y content += """
#{@options.labels[j]}: #{@yLabelFormat(y)}
""" if typeof @options.hoverCallback is 'function' content = @options.hoverCallback(index, @options, content, row.src) x = @left + (index + 0.5) * @width / @data.length [content, x] drawXAxisLabel: (xPos, yPos, text) -> label = @raphael.text(xPos, yPos, text) .attr('font-size', @options.gridTextSize) .attr('font-family', @options.gridTextFamily) .attr('font-weight', @options.gridTextWeight) .attr('fill', @options.gridTextColor) drawBar: (xPos, yPos, width, height, barColor, opacity, radiusArray) -> maxRadius = Math.max(radiusArray...) if maxRadius == 0 or maxRadius > height path = @raphael.rect(xPos, yPos, width, height) else path = @raphael.path @roundedRect(xPos, yPos, width, height, radiusArray) path .attr('fill', barColor) .attr('fill-opacity', opacity) .attr('stroke', 'none') roundedRect: (x, y, w, h, r = [0,0,0,0]) -> [ "M", x, r[0] + y, "Q", x, y, x + r[0], y, "L", x + w - r[1], y, "Q", x + w, y, x + w, y + r[1], "L", x + w, y + h - r[2], "Q", x + w, y + h, x + w - r[2], y + h, "L", x + r[3], y + h, "Q", x, y + h, x, y + h - r[3], "Z" ] lib/morris.donut.coffee000064400000013307150145030150011124 0ustar00# Donut charts. # # @example # Morris.Donut({ # el: $('#donut-container'), # data: [ # { label: 'yin', value: 50 }, # { label: 'yang', value: 50 } # ] # }); class Morris.Donut extends Morris.EventEmitter defaults: colors: [ '#0B62A4' '#3980B5' '#679DC6' '#95BBD7' '#B0CCE1' '#095791' '#095085' '#083E67' '#052C48' '#042135' ], backgroundColor: '#FFFFFF', labelColor: '#000000', formatter: Morris.commas resize: false # Create and render a donut chart. # constructor: (options) -> return new Morris.Donut(options) unless (@ instanceof Morris.Donut) @options = $.extend {}, @defaults, options if typeof options.element is 'string' @el = $ document.getElementById(options.element) else @el = $ options.element if @el == null || @el.length == 0 throw new Error("Graph placeholder not found.") # bail if there's no data if options.data is undefined or options.data.length is 0 return @raphael = new Raphael(@el[0]) if @options.resize $(window).bind 'resize', (evt) => if @timeoutId? window.clearTimeout @timeoutId @timeoutId = window.setTimeout @resizeHandler, 100 @setData options.data # Clear and redraw the chart. redraw: -> @raphael.clear() cx = @el.width() / 2 cy = @el.height() / 2 w = (Math.min(cx, cy) - 10) / 3 total = 0 total += value for value in @values min = 5 / (2 * w) C = 1.9999 * Math.PI - min * @data.length last = 0 idx = 0 @segments = [] for value, i in @values next = last + min + C * (value / total) seg = new Morris.DonutSegment( cx, cy, w*2, w, last, next, @data[i].color || @options.colors[idx % @options.colors.length], @options.backgroundColor, idx, @raphael) seg.render() @segments.push seg seg.on 'hover', @select seg.on 'click', @click last = next idx += 1 @text1 = @drawEmptyDonutLabel(cx, cy - 10, @options.labelColor, 15, 800) @text2 = @drawEmptyDonutLabel(cx, cy + 10, @options.labelColor, 14) max_value = Math.max @values... idx = 0 for value in @values if value == max_value @select idx break idx += 1 setData: (data) -> @data = data @values = (parseFloat(row.value) for row in @data) @redraw() # @private click: (idx) => @fire 'click', idx, @data[idx] # Select the segment at the given index. select: (idx) => s.deselect() for s in @segments segment = @segments[idx] segment.select() row = @data[idx] @setLabels(row.label, @options.formatter(row.value, row)) # @private setLabels: (label1, label2) -> inner = (Math.min(@el.width() / 2, @el.height() / 2) - 10) * 2 / 3 maxWidth = 1.8 * inner maxHeightTop = inner / 2 maxHeightBottom = inner / 3 @text1.attr(text: label1, transform: '') text1bbox = @text1.getBBox() text1scale = Math.min(maxWidth / text1bbox.width, maxHeightTop / text1bbox.height) @text1.attr(transform: "S#{text1scale},#{text1scale},#{text1bbox.x + text1bbox.width / 2},#{text1bbox.y + text1bbox.height}") @text2.attr(text: label2, transform: '') text2bbox = @text2.getBBox() text2scale = Math.min(maxWidth / text2bbox.width, maxHeightBottom / text2bbox.height) @text2.attr(transform: "S#{text2scale},#{text2scale},#{text2bbox.x + text2bbox.width / 2},#{text2bbox.y}") drawEmptyDonutLabel: (xPos, yPos, color, fontSize, fontWeight) -> text = @raphael.text(xPos, yPos, '') .attr('font-size', fontSize) .attr('fill', color) text.attr('font-weight', fontWeight) if fontWeight? return text resizeHandler: => @timeoutId = null @raphael.setSize @el.width(), @el.height() @redraw() # A segment within a donut chart. # # @private class Morris.DonutSegment extends Morris.EventEmitter constructor: (@cx, @cy, @inner, @outer, p0, p1, @color, @backgroundColor, @index, @raphael) -> @sin_p0 = Math.sin(p0) @cos_p0 = Math.cos(p0) @sin_p1 = Math.sin(p1) @cos_p1 = Math.cos(p1) @is_long = if (p1 - p0) > Math.PI then 1 else 0 @path = @calcSegment(@inner + 3, @inner + @outer - 5) @selectedPath = @calcSegment(@inner + 3, @inner + @outer) @hilight = @calcArc(@inner) calcArcPoints: (r) -> return [ @cx + r * @sin_p0, @cy + r * @cos_p0, @cx + r * @sin_p1, @cy + r * @cos_p1] calcSegment: (r1, r2) -> [ix0, iy0, ix1, iy1] = @calcArcPoints(r1) [ox0, oy0, ox1, oy1] = @calcArcPoints(r2) return ( "M#{ix0},#{iy0}" + "A#{r1},#{r1},0,#{@is_long},0,#{ix1},#{iy1}" + "L#{ox1},#{oy1}" + "A#{r2},#{r2},0,#{@is_long},1,#{ox0},#{oy0}" + "Z") calcArc: (r) -> [ix0, iy0, ix1, iy1] = @calcArcPoints(r) return ( "M#{ix0},#{iy0}" + "A#{r},#{r},0,#{@is_long},0,#{ix1},#{iy1}") render: -> @arc = @drawDonutArc(@hilight, @color) @seg = @drawDonutSegment( @path, @color, @backgroundColor, => @fire('hover', @index), => @fire('click', @index) ) drawDonutArc: (path, color) -> @raphael.path(path) .attr(stroke: color, 'stroke-width': 2, opacity: 0) drawDonutSegment: (path, fillColor, strokeColor, hoverFunction, clickFunction) -> @raphael.path(path) .attr(fill: fillColor, stroke: strokeColor, 'stroke-width': 3) .hover(hoverFunction) .click(clickFunction) select: => unless @selected @seg.animate(path: @selectedPath, 150, '<>') @arc.animate(opacity: 1, 150, '<>') @selected = true deselect: => if @selected @seg.animate(path: @path, 150, '<>') @arc.animate(opacity: 0, 150, '<>') @selected = false lib/morris.grid.coffee000064400000033763150145030150010730 0ustar00class Morris.Grid extends Morris.EventEmitter # A generic pair of axes for line/area/bar charts. # # Draws grid lines and axis labels. # constructor: (options) -> # find the container to draw the graph in if typeof options.element is 'string' @el = $ document.getElementById(options.element) else @el = $ options.element if not @el? or @el.length == 0 throw new Error("Graph container element not found") if @el.css('position') == 'static' @el.css('position', 'relative') @options = $.extend {}, @gridDefaults, (@defaults || {}), options # backwards compatibility for units -> postUnits if typeof @options.units is 'string' @options.postUnits = options.units # the raphael drawing instance @raphael = new Raphael(@el[0]) # some redraw stuff @elementWidth = null @elementHeight = null @dirty = false # range selection @selectFrom = null # more stuff @init() if @init # load data @setData @options.data # hover @el.bind 'mousemove', (evt) => offset = @el.offset() x = evt.pageX - offset.left if @selectFrom left = @data[@hitTest(Math.min(x, @selectFrom))]._x right = @data[@hitTest(Math.max(x, @selectFrom))]._x width = right - left @selectionRect.attr({ x: left, width: width }) else @fire 'hovermove', x, evt.pageY - offset.top @el.bind 'mouseleave', (evt) => if @selectFrom @selectionRect.hide() @selectFrom = null @fire 'hoverout' @el.bind 'touchstart touchmove touchend', (evt) => touch = evt.originalEvent.touches[0] or evt.originalEvent.changedTouches[0] offset = @el.offset() @fire 'hovermove', touch.pageX - offset.left, touch.pageY - offset.top @el.bind 'click', (evt) => offset = @el.offset() @fire 'gridclick', evt.pageX - offset.left, evt.pageY - offset.top if @options.rangeSelect @selectionRect = @raphael.rect(0, 0, 0, @el.innerHeight()) .attr({ fill: @options.rangeSelectColor, stroke: false }) .toBack() .hide() @el.bind 'mousedown', (evt) => offset = @el.offset() @startRange evt.pageX - offset.left @el.bind 'mouseup', (evt) => offset = @el.offset() @endRange evt.pageX - offset.left @fire 'hovermove', evt.pageX - offset.left, evt.pageY - offset.top if @options.resize $(window).bind 'resize', (evt) => if @timeoutId? window.clearTimeout @timeoutId @timeoutId = window.setTimeout @resizeHandler, 100 # Disable tap highlight on iOS. @el.css('-webkit-tap-highlight-color', 'rgba(0,0,0,0)') @postInit() if @postInit # Default options # gridDefaults: dateFormat: null axes: true grid: true gridLineColor: '#aaa' gridStrokeWidth: 0.5 gridTextColor: '#888' gridTextSize: 12 gridTextFamily: 'sans-serif' gridTextWeight: 'normal' hideHover: false yLabelFormat: null xLabelAngle: 0 numLines: 5 padding: 25 parseTime: true postUnits: '' preUnits: '' ymax: 'auto' ymin: 'auto 0' goals: [] goalStrokeWidth: 1.0 goalLineColors: [ '#666633' '#999966' '#cc6666' '#663333' ] events: [] eventStrokeWidth: 1.0 eventLineColors: [ '#005a04' '#ccffbb' '#3a5f0b' '#005502' ] rangeSelect: null rangeSelectColor: '#eef' resize: false # Update the data series and redraw the chart. # setData: (data, redraw = true) -> @options.data = data if !data? or data.length == 0 @data = [] @raphael.clear() @hover.hide() if @hover? return ymax = if @cumulative then 0 else null ymin = if @cumulative then 0 else null if @options.goals.length > 0 minGoal = Math.min @options.goals... maxGoal = Math.max @options.goals... ymin = if ymin? then Math.min(ymin, minGoal) else minGoal ymax = if ymax? then Math.max(ymax, maxGoal) else maxGoal @data = for row, index in data ret = {src: row} ret.label = row[@options.xkey] if @options.parseTime ret.x = Morris.parseDate(ret.label) if @options.dateFormat ret.label = @options.dateFormat ret.x else if typeof ret.label is 'number' ret.label = new Date(ret.label).toString() else ret.x = index if @options.xLabelFormat ret.label = @options.xLabelFormat ret total = 0 ret.y = for ykey, idx in @options.ykeys yval = row[ykey] yval = parseFloat(yval) if typeof yval is 'string' yval = null if yval? and typeof yval isnt 'number' if yval? if @cumulative total += yval else if ymax? ymax = Math.max(yval, ymax) ymin = Math.min(yval, ymin) else ymax = ymin = yval if @cumulative and total? ymax = Math.max(total, ymax) ymin = Math.min(total, ymin) yval ret if @options.parseTime @data = @data.sort (a, b) -> (a.x > b.x) - (b.x > a.x) # calculate horizontal range of the graph @xmin = @data[0].x @xmax = @data[@data.length - 1].x @events = [] if @options.events.length > 0 if @options.parseTime @events = (Morris.parseDate(e) for e in @options.events) else @events = @options.events @xmax = Math.max(@xmax, Math.max(@events...)) @xmin = Math.min(@xmin, Math.min(@events...)) if @xmin is @xmax @xmin -= 1 @xmax += 1 @ymin = @yboundary('min', ymin) @ymax = @yboundary('max', ymax) if @ymin is @ymax @ymin -= 1 if ymin @ymax += 1 if @options.axes in [true, 'both', 'y'] or @options.grid is true if (@options.ymax == @gridDefaults.ymax and @options.ymin == @gridDefaults.ymin) # calculate 'magic' grid placement @grid = @autoGridLines(@ymin, @ymax, @options.numLines) @ymin = Math.min(@ymin, @grid[0]) @ymax = Math.max(@ymax, @grid[@grid.length - 1]) else step = (@ymax - @ymin) / (@options.numLines - 1) @grid = (y for y in [@ymin..@ymax] by step) @dirty = true @redraw() if redraw yboundary: (boundaryType, currentValue) -> boundaryOption = @options["y#{boundaryType}"] if typeof boundaryOption is 'string' if boundaryOption[0..3] is 'auto' if boundaryOption.length > 5 suggestedValue = parseInt(boundaryOption[5..], 10) return suggestedValue unless currentValue? Math[boundaryType](currentValue, suggestedValue) else if currentValue? then currentValue else 0 else parseInt(boundaryOption, 10) else boundaryOption autoGridLines: (ymin, ymax, nlines) -> span = ymax - ymin ymag = Math.floor(Math.log(span) / Math.log(10)) unit = Math.pow(10, ymag) # calculate initial grid min and max values gmin = Math.floor(ymin / unit) * unit gmax = Math.ceil(ymax / unit) * unit step = (gmax - gmin) / (nlines - 1) if unit == 1 and step > 1 and Math.ceil(step) != step step = Math.ceil(step) gmax = gmin + step * (nlines - 1) # ensure zero is plotted where the range includes zero if gmin < 0 and gmax > 0 gmin = Math.floor(ymin / step) * step gmax = Math.ceil(ymax / step) * step # special case for decimal numbers if step < 1 smag = Math.floor(Math.log(step) / Math.log(10)) grid = for y in [gmin..gmax] by step parseFloat(y.toFixed(1 - smag)) else grid = (y for y in [gmin..gmax] by step) grid _calc: -> w = @el.width() h = @el.height() if @elementWidth != w or @elementHeight != h or @dirty @elementWidth = w @elementHeight = h @dirty = false # recalculate grid dimensions @left = @options.padding @right = @elementWidth - @options.padding @top = @options.padding @bottom = @elementHeight - @options.padding if @options.axes in [true, 'both', 'y'] yLabelWidths = for gridLine in @grid @measureText(@yAxisFormat(gridLine)).width @left += Math.max(yLabelWidths...) if @options.axes in [true, 'both', 'x'] bottomOffsets = for i in [0...@data.length] @measureText(@data[i].text, -@options.xLabelAngle).height @bottom -= Math.max(bottomOffsets...) @width = Math.max(1, @right - @left) @height = Math.max(1, @bottom - @top) @dx = @width / (@xmax - @xmin) @dy = @height / (@ymax - @ymin) @calc() if @calc # Quick translation helpers # transY: (y) -> @bottom - (y - @ymin) * @dy transX: (x) -> if @data.length == 1 (@left + @right) / 2 else @left + (x - @xmin) * @dx # Draw it! # # If you need to re-size your charts, call this method after changing the # size of the container element. redraw: -> @raphael.clear() @_calc() @drawGrid() @drawGoals() @drawEvents() @draw() if @draw # @private # measureText: (text, angle = 0) -> tt = @raphael.text(100, 100, text) .attr('font-size', @options.gridTextSize) .attr('font-family', @options.gridTextFamily) .attr('font-weight', @options.gridTextWeight) .rotate(angle) ret = tt.getBBox() tt.remove() ret # @private # yAxisFormat: (label) -> @yLabelFormat(label) # @private # yLabelFormat: (label) -> if typeof @options.yLabelFormat is 'function' @options.yLabelFormat(label) else "#{@options.preUnits}#{Morris.commas(label)}#{@options.postUnits}" # draw y axis labels, horizontal lines # drawGrid: -> return if @options.grid is false and @options.axes not in [true, 'both', 'y'] for lineY in @grid y = @transY(lineY) if @options.axes in [true, 'both', 'y'] @drawYAxisLabel(@left - @options.padding / 2, y, @yAxisFormat(lineY)) if @options.grid @drawGridLine("M#{@left},#{y}H#{@left + @width}") # draw goals horizontal lines # drawGoals: -> for goal, i in @options.goals color = @options.goalLineColors[i % @options.goalLineColors.length] @drawGoal(goal, color) # draw events vertical lines drawEvents: -> for event, i in @events color = @options.eventLineColors[i % @options.eventLineColors.length] @drawEvent(event, color) drawGoal: (goal, color) -> @raphael.path("M#{@left},#{@transY(goal)}H#{@right}") .attr('stroke', color) .attr('stroke-width', @options.goalStrokeWidth) drawEvent: (event, color) -> @raphael.path("M#{@transX(event)},#{@bottom}V#{@top}") .attr('stroke', color) .attr('stroke-width', @options.eventStrokeWidth) drawYAxisLabel: (xPos, yPos, text) -> @raphael.text(xPos, yPos, text) .attr('font-size', @options.gridTextSize) .attr('font-family', @options.gridTextFamily) .attr('font-weight', @options.gridTextWeight) .attr('fill', @options.gridTextColor) .attr('text-anchor', 'end') drawGridLine: (path) -> @raphael.path(path) .attr('stroke', @options.gridLineColor) .attr('stroke-width', @options.gridStrokeWidth) # Range selection # startRange: (x) -> @hover.hide() @selectFrom = x @selectionRect.attr({ x: x, width: 0 }).show() endRange: (x) -> if @selectFrom start = Math.min(@selectFrom, x) end = Math.max(@selectFrom, x) @options.rangeSelect.call @el, start: @data[@hitTest(start)].x end: @data[@hitTest(end)].x @selectFrom = null resizeHandler: => @timeoutId = null @raphael.setSize @el.width(), @el.height() @redraw() # Parse a date into a javascript timestamp # # Morris.parseDate = (date) -> if typeof date is 'number' return date m = date.match /^(\d+) Q(\d)$/ n = date.match /^(\d+)-(\d+)$/ o = date.match /^(\d+)-(\d+)-(\d+)$/ p = date.match /^(\d+) W(\d+)$/ q = date.match /^(\d+)-(\d+)-(\d+)[ T](\d+):(\d+)(Z|([+-])(\d\d):?(\d\d))?$/ r = date.match /^(\d+)-(\d+)-(\d+)[ T](\d+):(\d+):(\d+(\.\d+)?)(Z|([+-])(\d\d):?(\d\d))?$/ if m new Date( parseInt(m[1], 10), parseInt(m[2], 10) * 3 - 1, 1).getTime() else if n new Date( parseInt(n[1], 10), parseInt(n[2], 10) - 1, 1).getTime() else if o new Date( parseInt(o[1], 10), parseInt(o[2], 10) - 1, parseInt(o[3], 10)).getTime() else if p # calculate number of weeks in year given ret = new Date(parseInt(p[1], 10), 0, 1); # first thursday in year (ISO 8601 standard) if ret.getDay() isnt 4 ret.setMonth(0, 1 + ((4 - ret.getDay()) + 7) % 7); # add weeks ret.getTime() + parseInt(p[2], 10) * 604800000 else if q if not q[6] # no timezone info, use local new Date( parseInt(q[1], 10), parseInt(q[2], 10) - 1, parseInt(q[3], 10), parseInt(q[4], 10), parseInt(q[5], 10)).getTime() else # timezone info supplied, use UTC offsetmins = 0 if q[6] != 'Z' offsetmins = parseInt(q[8], 10) * 60 + parseInt(q[9], 10) offsetmins = 0 - offsetmins if q[7] == '+' Date.UTC( parseInt(q[1], 10), parseInt(q[2], 10) - 1, parseInt(q[3], 10), parseInt(q[4], 10), parseInt(q[5], 10) + offsetmins) else if r secs = parseFloat(r[6]) isecs = Math.floor(secs) msecs = Math.round((secs - isecs) * 1000) if not r[8] # no timezone info, use local new Date( parseInt(r[1], 10), parseInt(r[2], 10) - 1, parseInt(r[3], 10), parseInt(r[4], 10), parseInt(r[5], 10), isecs, msecs).getTime() else # timezone info supplied, use UTC offsetmins = 0 if r[8] != 'Z' offsetmins = parseInt(r[10], 10) * 60 + parseInt(r[11], 10) offsetmins = 0 - offsetmins if r[9] == '+' Date.UTC( parseInt(r[1], 10), parseInt(r[2], 10) - 1, parseInt(r[3], 10), parseInt(r[4], 10), parseInt(r[5], 10) + offsetmins, isecs, msecs) else new Date(parseInt(date, 10), 0, 1).getTime() lib/morris.area.coffee000064400000003327150145030150010704 0ustar00class Morris.Area extends Morris.Line # Initialise # areaDefaults = fillOpacity: 'auto' behaveLikeLine: false constructor: (options) -> return new Morris.Area(options) unless (@ instanceof Morris.Area) areaOptions = $.extend {}, areaDefaults, options @cumulative = not areaOptions.behaveLikeLine if areaOptions.fillOpacity is 'auto' areaOptions.fillOpacity = if areaOptions.behaveLikeLine then .8 else 1 super(areaOptions) # calculate series data point coordinates # # @private calcPoints: -> for row in @data row._x = @transX(row.x) total = 0 row._y = for y in row.y if @options.behaveLikeLine @transY(y) else total += (y || 0) @transY(total) row._ymax = Math.max row._y... # draw the data series # # @private drawSeries: -> @seriesPoints = [] if @options.behaveLikeLine range = [0..@options.ykeys.length-1] else range = [@options.ykeys.length-1..0] for i in range @_drawFillFor i @_drawLineFor i @_drawPointFor i _drawFillFor: (index) -> path = @paths[index] if path isnt null path = path + "L#{@transX(@xmax)},#{@bottom}L#{@transX(@xmin)},#{@bottom}Z" @drawFilledPath path, @fillForSeries(index) fillForSeries: (i) -> color = Raphael.rgb2hsl @colorFor(@data[i], i, 'line') Raphael.hsl( color.h, if @options.behaveLikeLine then color.s * 0.9 else color.s * 0.75, Math.min(0.98, if @options.behaveLikeLine then color.l * 1.2 else color.l * 1.25)) drawFilledPath: (path, fill) -> @raphael.path(path) .attr('fill', fill) .attr('fill-opacity', @options.fillOpacity) .attr('stroke', 'none') lib/morris.hover.coffee000064400000002124150145030150011111 0ustar00class Morris.Hover # Displays contextual information in a floating HTML div. @defaults: class: 'morris-hover morris-default-style' constructor: (options = {}) -> @options = $.extend {}, Morris.Hover.defaults, options @el = $ "
" @el.hide() @options.parent.append(@el) update: (html, x, y) -> if not html @hide() else @html(html) @show() @moveTo(x, y) html: (content) -> @el.html(content) moveTo: (x, y) -> parentWidth = @options.parent.innerWidth() parentHeight = @options.parent.innerHeight() hoverWidth = @el.outerWidth() hoverHeight = @el.outerHeight() left = Math.min(Math.max(0, x - hoverWidth / 2), parentWidth - hoverWidth) if y? top = y - hoverHeight - 10 if top < 0 top = y + 10 if top + hoverHeight > parentHeight top = parentHeight / 2 - hoverHeight / 2 else top = parentHeight / 2 - hoverHeight / 2 @el.css(left: left + "px", top: parseInt(top) + "px") show: -> @el.show() hide: -> @el.hide() lib/morris.coffee000064400000001756150145030150010001 0ustar00Morris = window.Morris = {} $ = jQuery # Very simple event-emitter class. # # @private class Morris.EventEmitter on: (name, handler) -> unless @handlers? @handlers = {} unless @handlers[name]? @handlers[name] = [] @handlers[name].push(handler) @ fire: (name, args...) -> if @handlers? and @handlers[name]? for handler in @handlers[name] handler(args...) # Make long numbers prettier by inserting commas. # # @example # Morris.commas(1234567) -> '1,234,567' Morris.commas = (num) -> if num? ret = if num < 0 then "-" else "" absnum = Math.abs(num) intnum = Math.floor(absnum).toFixed(0) ret += intnum.replace(/(?=(?:\d{3})+$)(?!^)/g, ',') strabsnum = absnum.toString() if strabsnum.length > intnum.length ret += strabsnum.slice(intnum.length) ret else '-' # Zero-pad numbers to two characters wide. # # @example # Morris.pad2(1) -> '01' Morris.pad2 = (number) -> (if number < 10 then '0' else '') + number README.md000064400000023607150145030150006025 0ustar00# Morris.js - pretty time-series line graphs [![Build Status](https://secure.travis-ci.org/morrisjs/morris.js.png?branch=master)](http://travis-ci.org/morrisjs/morris.js) Morris.js is the library that powers the graphs on http://howmanyleft.co.uk/. It's a very simple API for drawing line, bar, area and donut charts. Cheers! \- Olly (olly@oesmith.co.uk) ## Contributors wanted I'm unfortunately not able to actively support Morris.js any more. I keep an eye on the issues, but I rarely have the time to fix bugs or review pull requests. If you're interested in actively contributing to Morris.js, please contact me on the email address above. ## Requirements - [jQuery](http://jquery.com/) (>= 1.7 recommended, but it'll probably work with older versions) - [Raphael.js](http://raphaeljs.com/) (>= 2.0) ## Usage See [the website](http://morrisjs.github.com/morris.js/). ## Development Very daring. Fork, hack, possibly even add some tests, then send a pull request :) Remember that Morris.js is a coffeescript project. Please make your changes in the `.coffee` files, not in the compiled javascript files in the root directory of the project. ### Developer quick-start You'll need [node.js](https://nodejs.org). I recommend using [nvm](https://github.com/creationix/nvm) for installing node in development environments. With node installed, install [grunt](https://github.com/cowboy/grunt) using `npm install -g grunt-cli`, and then the rest of the test/build dependencies with `npm install` in the morris.js project folder. Once you're all set up, you can compile, minify and run the tests using `grunt`. Note: I'm experimenting with using perceptual diffs to catch rendering regressions. Due to font rendering differences between platforms, the pdiff tests currently *only* pass on OS X. ## Changelog ### 0.5.1 - 15th June 2014 - Fix touch event handling. - Fix stacked=false in bar chart [#275](https://github.com/morrisjs/morris.js/issues/275) - Configurable vertical segments [#297](https://github.com/morrisjs/morris.js/issues/297) - Deprecate continuousLine option. ### 0.5.0 - 19th March 2014 - Update grunt dependency [#288](https://github.com/morrisjs/morris.js/issues/228) - Donut segment color config in data objects [#281](https://github.com/morrisjs/morris.js/issues/281) - Customisable line widths and point drawing [#272](https://github.com/morrisjs/morris.js/issues/272) - Bugfix for @options.smooth [#266](https://github.com/morrisjs/morris.js/issues/266) - Option to disable axes individually [#253](https://github.com/morrisjs/morris.js/issues/253) - Range selection [#252](https://github.com/morrisjs/morris.js/issues/252) - Week format for x-labels [#250](https://github.com/morrisjs/morris.js/issues/250) - Update developer quickstart instructions [#243](https://github.com/morrisjs/morris.js/issues/243) - Experimenting with perceptual diffs. - Add original data row to hover callback [#264](https://github.com/morrisjs/morris.js/issues/264) - setData method for donut charts [#211](https://github.com/morrisjs/morris.js/issues/211) - Automatic resizing [#111](https://github.com/morrisjs/morris.js/issues/111) - Fix travis builds [#298](https://github.com/morrisjs/morris.js/issues/298) - Option for rounded corners on bar charts [#305](https://github.com/morrisjs/morris.js/issues/305) - Option to set padding for X axis labels [#306](https://github.com/morrisjs/morris.js/issues/306) - Use local javascript for examples. - Events on non-time series [#314](https://github.com/morrisjs/morris.js/issues/314) ### 0.4.3 - 12th May 2013 - Fix flickering hover box [#186](https://github.com/morrisjs/morris.js/issues/186) - xLabelAngle option (diagonal labels!!) [#239](https://github.com/morrisjs/morris.js/issues/239) - Fix area chart fill bug [#190](https://github.com/morrisjs/morris.js/issues/190) - Make event handlers chainable - gridTextFamily and gridTextWeight options - Fix hovers with setData [#213](https://github.com/morrisjs/morris.js/issues/213) - Fix hideHover behaviour [#236](https://github.com/morrisjs/morris.js/issues/236) ### 0.4.2 - 14th April 2013 - Fix DST handling [#191](https://github.com/morrisjs/morris.js/issues/191) - Parse data values from strings in Morris.Donut [#189](https://github.com/morrisjs/morris.js/issues/189) - Non-cumulative area charts [#199](https://github.com/morrisjs/morris.js/issues/199) - Round Y-axis labels to significant numbers [#162](https://github.com/morrisjs/morris.js/162) - Customising default hover content [#179](https://github.com/morrisjs/morris.js/179) ### 0.4.1 - 8th February 2013 - Fix goal and event rendering. [#181](https://github.com/morrisjs/morris.js/issues/181) - Don't break when empty data is passed to setData [#142](https://github.com/morrisjs/morris.js/issues/142) - labelColor option for donuts [#159](https://github.com/morrisjs/morris.js/issues/159) ### 0.4.0 - 26th January 2013 - Goals and events [#103](https://github.com/morrisjs/morris.js/issues/103). - Bower package manager metadata. - More flexible formatters [#107](https://github.com/morrisjs/morris.js/issues/107). - Color callbacks. - Decade intervals for time-axis labels. - Non-continous line tweaks [#116](https://github.com/morrisjs/morris.js/issues/116). - Stacked bars [#120](https://github.com/morrisjs/morris.js/issues/120). - HTML hover [#134](https://github.com/morrisjs/morris.js/issues/134). - yLabelFormat [#139](https://github.com/morrisjs/morris.js/issues/139). - Disable axes [#114](https://github.com/morrisjs/morris.js/issues/114). ### 0.3.3 - 1st November 2012 - **Bar charts!** [#101](https://github.com/morrisjs/morris.js/issues/101). ### 0.3.2 - 28th October 2012 - **Area charts!** [#47](https://github.com/morrisjs/morris.js/issues/47). - Some major refactoring and test suite improvements. - Set smooth parameter per series [#91](https://github.com/morrisjs/morris.js/issues/91). - Custom dateFormat for string x-values [#90](https://github.com/morrisjs/morris.js/issues/90). ### 0.3.1 - 13th October 2012 - Add `formatter` option for customising value labels in donuts [#75](https://github.com/morrisjs/morris.js/issues/75). - Cycle `lineColors` on line charts to avoid running out of colours [#78](https://github.com/morrisjs/morris.js/issues/78). - Add method to select donut segments. [#79](https://github.com/morrisjs/morris.js/issues/79). - Don't go negative on yMin when all y values are zero. [#80](https://github.com/morrisjs/morris.js/issues/80). - Don't sort data when parseTime is false [#83](https://github.com/morrisjs/morris.js/issues/83). - Customise styling for points. [#87](https://github.com/morrisjs/morris.js/issues/87). ### 0.3.0 - 15th September 2012 - Donut charts! - Bugfix: ymin/ymax bug [#71](https://github.com/morrisjs/morris.js/issues/71). - Bugfix: infinite loop when data indicates horizontal line [#66](https://github.com/morrisjs/morris.js/issues/66). ### 0.2.10 - 26th June 2012 - Support for decimal labels on y-axis [#58](https://github.com/morrisjs/morris.js/issues/58). - Better axis label clipping [#63](https://github.com/morrisjs/morris.js/issues/63). - Redraw graphs with updated data using `setData` method [#64](https://github.com/morrisjs/morris.js/issues/64). - Bugfix: series with zero or one non-null values [#65](https://github.com/morrisjs/morris.js/issues/65). ### 0.2.9 - 15th May 2012 - Bugfix: Fix zero-value regression - Bugfix: Don't modify user-supplied data ### 0.2.8 - 10th May 2012 - Customising x-axis labels with `xLabelFormat` option - Only use timezones when timezone info is specified - Fix old IE bugs (mostly in examples!) - Added `preunits` and `postunits` options - Better non-continuous series data support ### 0.2.7 - 2nd April 2012 - Added `xLabels` option - Refactored x-axis labelling - Better ISO date support - Fix bug with single value in non time-series graphs ### 0.2.6 - 18th March 2012 - Partial series support (see `null` y-values in `examples/quarters.html`) - `parseTime` option bugfix for non-time-series data ### 0.2.5 - 15th March 2012 - Raw millisecond timestamp support (with `dateFormat` option) - YYYY-MM-DD HH:MM[:SS[.SSS]] date support - Decimal number labels ### 0.2.4 - 8th March 2012 - Negative y-values support - `ymin` option - `units` options ### 0.2.3 - 6th Mar 2012 - jQuery no-conflict compatibility - Support ISO week-number dates - Optionally hide hover on mouseout (`hideHover`) - Optionally skip parsing dates, treating X values as an equally-spaced series (`parseTime`) ### 0.2.2 - 29th Feb 2012 - Bugfix: mouseover error when options.data.length == 2 - Automatically sort options.data ### 0.2.1 - 28th Feb 2012 - Accept a DOM element *or* an ID in `options.element` - Add `smooth` option - Bugfix: clone `@default` - Add `ymax` option ## License Copyright (c) 2012-2014, Olly Smith All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. less/morris.core.less000064400000000744150145030150010643 0ustar00.morris-hover { position: absolute; z-index: 1000; &.morris-default-style { border-radius: 10px; padding: 6px; color: #666; background: rgba(255, 255, 255, 0.8); border: solid 2px rgba(230, 230, 230, 0.8); font-family: sans-serif; font-size: 12px; text-align: center; .morris-hover-row-label { font-weight: bold; margin: 0.25em 0; } .morris-hover-point { white-space: nowrap; margin: 0.1em 0; } } } .gitignore000064400000000114150145030150006522 0ustar00build/ node_modules/ spec/viz/output/ spec/viz/diff/ bower_components .idea examples/_template.html000064400000001304150145030150011212 0ustar00

Title

// Insert code here:
// it'll get eval()-ed and prettyprinted.
examples/stacked_bars.html000064400000001763150145030150011676 0ustar00

Stacked Bars chart

// Use Morris.Bar
Morris.Bar({
  element: 'graph',
  data: [
    {x: '2011 Q1', y: 3, z: 2, a: 3},
    {x: '2011 Q2', y: 2, z: null, a: 1},
    {x: '2011 Q3', y: 0, z: 2, a: 4},
    {x: '2011 Q4', y: 2, z: 4, a: 3}
  ],
  xkey: 'x',
  ykeys: ['y', 'z', 'a'],
  labels: ['Y', 'Z', 'A'],
  stacked: true
});
examples/decimal-custom-hover.html000064400000002250150145030150013270 0ustar00

Decimal Data

var decimal_data = [];
for (var x = 0; x <= 360; x += 10) {
  decimal_data.push({
    x: x,
    y: 1.5 + 1.5 * Math.sin(Math.PI * x / 180).toFixed(4)
  });
}
window.m = Morris.Line({
  element: 'graph',
  data: decimal_data,
  xkey: 'x',
  ykeys: ['y'],
  labels: ['sin(x)'],
  parseTime: false,
  hoverCallback: function (index, options, default_content, row) {
    return default_content.replace("sin(x)", "1.5 + 1.5 sin(" + row.x + ")");
  },
  xLabelMargin: 10,
  integerYLabels: true
});
examples/donut-formatter.html000064400000002050150145030150012371 0ustar00

Donut Chart

Morris.Donut({
  element: 'graph',
  data: [
    {value: 70, label: 'foo', formatted: 'at least 70%' },
    {value: 15, label: 'bar', formatted: 'approx. 15%' },
    {value: 10, label: 'baz', formatted: 'approx. 10%' },
    {value: 5, label: 'A really really long label', formatted: 'at most 5%' }
  ],
  formatter: function (x, data) { return data.formatted; }
});
examples/bar.html000064400000002022150145030150010002 0ustar00

Bar charts

// Use Morris.Bar
Morris.Bar({
  element: 'graph',
  data: [
    {x: '2011 Q1', y: 3, z: 2, a: 3},
    {x: '2011 Q2', y: 2, z: null, a: 1},
    {x: '2011 Q3', y: 0, z: 2, a: 4},
    {x: '2011 Q4', y: 2, z: 4, a: 3}
  ],
  xkey: 'x',
  ykeys: ['y', 'z', 'a'],
  labels: ['Y', 'Z', 'A']
}).on('click', function(i, row){
  console.log(i, row);
});
examples/diagonal-xlabels-bar.html000064400000003062150145030150013213 0ustar00

Displaying X Labels Diagonally (Bar Chart)

/* data stolen from http://howmanyleft.co.uk/vehicle/jaguar_'e'_type */
var day_data = [
  {"period": "2012-10-01", "licensed": 3407, "sorned": 660},
  {"period": "2012-09-30", "licensed": 3351, "sorned": 629},
  {"period": "2012-09-29", "licensed": 3269, "sorned": 618},
  {"period": "2012-09-20", "licensed": 3246, "sorned": 661},
  {"period": "2012-09-19", "licensed": 3257, "sorned": 667},
  {"period": "2012-09-18", "licensed": 3248, "sorned": 627},
  {"period": "2012-09-17", "licensed": 3171, "sorned": 660},
  {"period": "2012-09-16", "licensed": 3171, "sorned": 676},
  {"period": "2012-09-15", "licensed": 3201, "sorned": 656},
  {"period": "2012-09-10", "licensed": 3215, "sorned": 622}
];
Morris.Bar({
  element: 'graph',
  data: day_data,
  xkey: 'period',
  ykeys: ['licensed', 'sorned'],
  labels: ['Licensed', 'SORN'],
  xLabelAngle: 60
});
examples/quarters.html000064400000004743150145030150011120 0ustar00

Formatting Dates with Quarters

/* data stolen from http://howmanyleft.co.uk/vehicle/jaguar_e_type */
var quarter_data = [
  {"period": "2011 Q3", "licensed": 3407, "sorned": 660},
  {"period": "2011 Q2", "licensed": 3351, "sorned": 629},
  {"period": "2011 Q1", "licensed": 3269, "sorned": 618},
  {"period": "2010 Q4", "licensed": 3246, "sorned": 661},
  {"period": "2010 Q3", "licensed": 3257, "sorned": 667},
  {"period": "2010 Q2", "licensed": 3248, "sorned": 627},
  {"period": "2010 Q1", "licensed": 3171, "sorned": 660},
  {"period": "2009 Q4", "licensed": 3171, "sorned": 676},
  {"period": "2009 Q3", "licensed": 3201, "sorned": 656},
  {"period": "2009 Q2", "licensed": 3215, "sorned": 622},
  {"period": "2009 Q1", "licensed": 3148, "sorned": 632},
  {"period": "2008 Q4", "licensed": 3155, "sorned": 681},
  {"period": "2008 Q3", "licensed": 3190, "sorned": 667},
  {"period": "2007 Q4", "licensed": 3226, "sorned": 620},
  {"period": "2006 Q4", "licensed": 3245, "sorned": null},
  {"period": "2005 Q4", "licensed": 3289, "sorned": null},
  {"period": "2004 Q4", "licensed": 3263, "sorned": null},
  {"period": "2003 Q4", "licensed": 3189, "sorned": null},
  {"period": "2002 Q4", "licensed": 3079, "sorned": null},
  {"period": "2001 Q4", "licensed": 3085, "sorned": null},
  {"period": "2000 Q4", "licensed": 3055, "sorned": null},
  {"period": "1999 Q4", "licensed": 3063, "sorned": null},
  {"period": "1998 Q4", "licensed": 2943, "sorned": null},
  {"period": "1997 Q4", "licensed": 2806, "sorned": null},
  {"period": "1996 Q4", "licensed": 2674, "sorned": null},
  {"period": "1995 Q4", "licensed": 1702, "sorned": null},
  {"period": "1994 Q4", "licensed": 1732, "sorned": null}
];
Morris.Line({
  element: 'graph',
  data: quarter_data,
  xkey: 'period',
  ykeys: ['licensed', 'sorned'],
  labels: ['Licensed', 'SORN']
});
examples/non-continuous.html000064400000003362150145030150012244 0ustar00

Non-continuous data

Null series values will break the line when rendering, missing values will be skipped

/* data stolen from http://howmanyleft.co.uk/vehicle/jaguar_'e'_type */
var day_data = [
  {"period": "2012-10-01", "licensed": 3407},
  {"period": "2012-09-30", "sorned": 0},
  {"period": "2012-09-29", "sorned": 618},
  {"period": "2012-09-20", "licensed": 3246, "sorned": 661},
  {"period": "2012-09-19", "licensed": 3257, "sorned": null},
  {"period": "2012-09-18", "licensed": 3248, "other": 1000},
  {"period": "2012-09-17", "sorned": 0},
  {"period": "2012-09-16", "sorned": 0},
  {"period": "2012-09-15", "licensed": 3201, "sorned": 656},
  {"period": "2012-09-10", "licensed": 3215}
];
Morris.Line({
  element: 'graph',
  data: day_data,
  xkey: 'period',
  ykeys: ['licensed', 'sorned', 'other'],
  labels: ['Licensed', 'SORN', 'Other'],
  /* custom label formatting with `xLabelFormat` */
  xLabelFormat: function(d) { return (d.getMonth()+1)+'/'+d.getDate()+'/'+d.getFullYear(); },
  /* setting `xLabels` is recommended when using xLabelFormat */
  xLabels: 'day'
});
examples/bar-colors.html000064400000002345150145030150011311 0ustar00

Bar charts

// Use Morris.Bar
Morris.Bar({
  element: 'graph',
  data: [
    {x: '2011 Q1', y: 0},
    {x: '2011 Q2', y: 1},
    {x: '2011 Q3', y: 2},
    {x: '2011 Q4', y: 3},
    {x: '2012 Q1', y: 4},
    {x: '2012 Q2', y: 5},
    {x: '2012 Q3', y: 6},
    {x: '2012 Q4', y: 7},
    {x: '2013 Q1', y: 8}
  ],
  xkey: 'x',
  ykeys: ['y'],
  labels: ['Y'],
  barColors: function (row, series, type) {
    if (type === 'bar') {
      var red = Math.ceil(255 * row.y / this.ymax);
      return 'rgb(' + red + ',0,0)';
    }
    else {
      return '#000';
    }
  }
});
examples/dst.html000064400000002011150145030150010026 0ustar00

Daylight-savings time

// This crosses a DST boundary in the UK.
Morris.Area({
  element: 'graph',
  data: [
    {x: '2013-03-30 22:00:00', y: 3, z: 3},
    {x: '2013-03-31 00:00:00', y: 2, z: 0},
    {x: '2013-03-31 02:00:00', y: 0, z: 2},
    {x: '2013-03-31 04:00:00', y: 4, z: 4}
  ],
  xkey: 'x',
  ykeys: ['y', 'z'],
  labels: ['Y', 'Z']
});
examples/timestamps.html000064400000003131150145030150011426 0ustar00

Timestamps

/* data stolen from http://howmanyleft.co.uk/vehicle/jaguar_'e'_type */
var timestamp_data = [
  {"period": 1349046000000, "licensed": 3407, "sorned": 660},
  {"period": 1313103600000, "licensed": 3351, "sorned": 629},
  {"period": 1299110400000, "licensed": 3269, "sorned": 618},
  {"period": 1281222000000, "licensed": 3246, "sorned": 661},
  {"period": 1273446000000, "licensed": 3257, "sorned": 667},
  {"period": 1268524800000, "licensed": 3248, "sorned": 627},
  {"period": 1263081600000, "licensed": 3171, "sorned": 660},
  {"period": 1260403200000, "licensed": 3171, "sorned": 676},
  {"period": 1254870000000, "licensed": 3201, "sorned": 656},
  {"period": 1253833200000, "licensed": 3215, "sorned": 622}
];
Morris.Line({
  element: 'graph',
  data: timestamp_data,
  xkey: 'period',
  ykeys: ['licensed', 'sorned'],
  labels: ['Licensed', 'SORN'],
  dateFormat: function (x) { return new Date(x).toDateString(); }
});
examples/resize.html000064400000003154150145030150010546 0ustar00

Formatting Dates YYYY-MM-DD

/* data stolen from http://howmanyleft.co.uk/vehicle/jaguar_'e'_type */
var day_data = [
  {"period": "2012-10-01", "licensed": 3407, "sorned": 660},
  {"period": "2012-09-30", "licensed": 3351, "sorned": 629},
  {"period": "2012-09-29", "licensed": 3269, "sorned": 618},
  {"period": "2012-09-20", "licensed": 3246, "sorned": 661},
  {"period": "2012-09-19", "licensed": 3257, "sorned": 667},
  {"period": "2012-09-18", "licensed": 3248, "sorned": 627},
  {"period": "2012-09-17", "licensed": 3171, "sorned": 660},
  {"period": "2012-09-16", "licensed": 3171, "sorned": 676},
  {"period": "2012-09-15", "licensed": 3201, "sorned": 656},
  {"period": "2012-09-10", "licensed": 3215, "sorned": 622}
];
Morris.Line({
  element: 'graph',
  data: day_data,
  xkey: 'period',
  ykeys: ['licensed', 'sorned'],
  labels: ['Licensed', 'SORN'],
  resize: true
});
examples/weeks.html000064400000004657150145030150010374 0ustar00

Formatting Dates With Weeks

var week_data = [
  {"period": "2011 W27", "licensed": 3407, "sorned": 660},
  {"period": "2011 W26", "licensed": 3351, "sorned": 629},
  {"period": "2011 W25", "licensed": 3269, "sorned": 618},
  {"period": "2011 W24", "licensed": 3246, "sorned": 661},
  {"period": "2011 W23", "licensed": 3257, "sorned": 667},
  {"period": "2011 W22", "licensed": 3248, "sorned": 627},
  {"period": "2011 W21", "licensed": 3171, "sorned": 660},
  {"period": "2011 W20", "licensed": 3171, "sorned": 676},
  {"period": "2011 W19", "licensed": 3201, "sorned": 656},
  {"period": "2011 W18", "licensed": 3215, "sorned": 622},
  {"period": "2011 W17", "licensed": 3148, "sorned": 632},
  {"period": "2011 W16", "licensed": 3155, "sorned": 681},
  {"period": "2011 W15", "licensed": 3190, "sorned": 667},
  {"period": "2011 W14", "licensed": 3226, "sorned": 620},
  {"period": "2011 W13", "licensed": 3245, "sorned": null},
  {"period": "2011 W12", "licensed": 3289, "sorned": null},
  {"period": "2011 W11", "licensed": 3263, "sorned": null},
  {"period": "2011 W10", "licensed": 3189, "sorned": null},
  {"period": "2011 W09", "licensed": 3079, "sorned": null},
  {"period": "2011 W08", "licensed": 3085, "sorned": null},
  {"period": "2011 W07", "licensed": 3055, "sorned": null},
  {"period": "2011 W06", "licensed": 3063, "sorned": null},
  {"period": "2011 W05", "licensed": 2943, "sorned": null},
  {"period": "2011 W04", "licensed": 2806, "sorned": null},
  {"period": "2011 W03", "licensed": 2674, "sorned": null},
  {"period": "2011 W02", "licensed": 1702, "sorned": null},
  {"period": "2011 W01", "licensed": 1732, "sorned": null}
];
Morris.Line({
  element: 'graph',
  data: week_data,
  xkey: 'period',
  ykeys: ['licensed', 'sorned'],
  labels: ['Licensed', 'SORN']
});
examples/months-no-smooth.html000064400000003012150145030150012467 0ustar00

Formatting Dates with YYYY-MM

/* data stolen from http://howmanyleft.co.uk/vehicle/jaguar_'e'_type */
var month_data = [
  {"period": "2012-10", "licensed": 3407, "sorned": 660},
  {"period": "2011-08", "licensed": 3351, "sorned": 629},
  {"period": "2011-03", "licensed": 3269, "sorned": 618},
  {"period": "2010-08", "licensed": 3246, "sorned": 661},
  {"period": "2010-05", "licensed": 3257, "sorned": 667},
  {"period": "2010-03", "licensed": 3248, "sorned": 627},
  {"period": "2010-01", "licensed": 3171, "sorned": 660},
  {"period": "2009-12", "licensed": 3171, "sorned": 676},
  {"period": "2009-10", "licensed": 3201, "sorned": 656},
  {"period": "2009-09", "licensed": 3215, "sorned": 622}
];
Morris.Line({
  element: 'graph',
  data: month_data,
  xkey: 'period',
  ykeys: ['licensed', 'sorned'],
  labels: ['Licensed', 'SORN'],
  smooth: false
});
examples/area-as-line.html000064400000002001150145030150011471 0ustar00

Area charts behaving like line charts

// Use Morris.Area instead of Morris.Line
Morris.Area({
  element: 'graph',
  behaveLikeLine: true,
  data: [
    {x: '2011 Q1', y: 3, z: 3},
    {x: '2011 Q2', y: 2, z: 1},
    {x: '2011 Q3', y: 2, z: 4},
    {x: '2011 Q4', y: 3, z: 3}
  ],
  xkey: 'x',
  ykeys: ['y', 'z'],
  labels: ['Y', 'Z']
});
examples/non-date.html000064400000002300150145030150010742 0ustar00

Formatting Non-date Arbitrary X-axis

var day_data = [
  {"elapsed": "I", "value": 34},
  {"elapsed": "II", "value": 24},
  {"elapsed": "III", "value": 3},
  {"elapsed": "IV", "value": 12},
  {"elapsed": "V", "value": 13},
  {"elapsed": "VI", "value": 22},
  {"elapsed": "VII", "value": 5},
  {"elapsed": "VIII", "value": 26},
  {"elapsed": "IX", "value": 12},
  {"elapsed": "X", "value": 19}
];
Morris.Line({
  element: 'graph',
  data: day_data,
  xkey: 'elapsed',
  ykeys: ['value'],
  labels: ['value'],
  parseTime: false
});
examples/bar-no-axes.html000064400000001751150145030150011362 0ustar00

Bar charts

// Use Morris.Bar
Morris.Bar({
  element: 'graph',
  axes: false,
  data: [
    {x: '2011 Q1', y: 3, z: 2, a: 3},
    {x: '2011 Q2', y: 2, z: null, a: 1},
    {x: '2011 Q3', y: 0, z: 2, a: 4},
    {x: '2011 Q4', y: 2, z: 4, a: 3}
  ],
  xkey: 'x',
  ykeys: ['y', 'z', 'a'],
  labels: ['Y', 'Z', 'A']
});
examples/diagonal-xlabels.html000064400000003047150145030150012454 0ustar00

Displaying X Labels Diagonally

/* data stolen from http://howmanyleft.co.uk/vehicle/jaguar_'e'_type */
var day_data = [
  {"period": "2012-10-30", "licensed": 3407, "sorned": 660},
  {"period": "2012-09-30", "licensed": 3351, "sorned": 629},
  {"period": "2012-09-29", "licensed": 3269, "sorned": 618},
  {"period": "2012-09-20", "licensed": 3246, "sorned": 661},
  {"period": "2012-09-19", "licensed": 3257, "sorned": 667},
  {"period": "2012-09-18", "licensed": 3248, "sorned": 627},
  {"period": "2012-09-17", "licensed": 3171, "sorned": 660},
  {"period": "2012-09-16", "licensed": 3171, "sorned": 676},
  {"period": "2012-09-15", "licensed": 3201, "sorned": 656},
  {"period": "2012-09-10", "licensed": 3215, "sorned": 622}
];
Morris.Line({
  element: 'graph',
  data: day_data,
  xkey: 'period',
  ykeys: ['licensed', 'sorned'],
  labels: ['Licensed', 'SORN'],
  xLabelAngle: 60
});
examples/area.html000064400000002112150145030150010146 0ustar00

Area charts

// Use Morris.Area instead of Morris.Line
Morris.Area({
  element: 'graph',
  data: [
    {x: '2010 Q4', y: 3, z: 7},
    {x: '2011 Q1', y: 3, z: 4},
    {x: '2011 Q2', y: null, z: 1},
    {x: '2011 Q3', y: 2, z: 5},
    {x: '2011 Q4', y: 8, z: 2},
    {x: '2012 Q1', y: 4, z: 4}
  ],
  xkey: 'x',
  ykeys: ['y', 'z'],
  labels: ['Y', 'Z']
}).on('click', function(i, row){
  console.log(i, row);
});
examples/events.html000064400000004715150145030150010555 0ustar00

Time Events

var week_data = [
  {"period": "2011 W27", "licensed": 3407, "sorned": 660},
  {"period": "2011 W26", "licensed": 3351, "sorned": 629},
  {"period": "2011 W25", "licensed": 3269, "sorned": 618},
  {"period": "2011 W24", "licensed": 3246, "sorned": 661},
  {"period": "2011 W23", "licensed": 3257, "sorned": 667},
  {"period": "2011 W22", "licensed": 3248, "sorned": 627},
  {"period": "2011 W21", "licensed": 3171, "sorned": 660},
  {"period": "2011 W20", "licensed": 3171, "sorned": 676},
  {"period": "2011 W19", "licensed": 3201, "sorned": 656},
  {"period": "2011 W18", "licensed": 3215, "sorned": 622},
  {"period": "2011 W17", "licensed": 3148, "sorned": 632},
  {"period": "2011 W16", "licensed": 3155, "sorned": 681},
  {"period": "2011 W15", "licensed": 3190, "sorned": 667},
  {"period": "2011 W14", "licensed": 3226, "sorned": 620},
  {"period": "2011 W13", "licensed": 3245, "sorned": null},
  {"period": "2011 W12", "licensed": 3289, "sorned": null},
  {"period": "2011 W11", "licensed": 3263, "sorned": null},
  {"period": "2011 W10", "licensed": 3189, "sorned": null},
  {"period": "2011 W09", "licensed": 3079, "sorned": null},
  {"period": "2011 W08", "licensed": 3085, "sorned": null},
  {"period": "2011 W07", "licensed": 3055, "sorned": null},
  {"period": "2011 W06", "licensed": 3063, "sorned": null},
  {"period": "2011 W05", "licensed": 2943, "sorned": null},
  {"period": "2011 W04", "licensed": 2806, "sorned": null},
  {"period": "2011 W03", "licensed": 2674, "sorned": null},
  {"period": "2011 W02", "licensed": 1702, "sorned": null},
  {"period": "2011 W01", "licensed": 1732, "sorned": null}
];
Morris.Line({
  element: 'graph',
  data: week_data,
  xkey: 'period',
  ykeys: ['licensed', 'sorned'],
  labels: ['Licensed', 'SORN'],
  events: [
    '2011-04',
    '2011-08'
  ]
});
examples/days.html000064400000003021150145030150010176 0ustar00

Formatting Dates YYYY-MM-DD

/* data stolen from http://howmanyleft.co.uk/vehicle/jaguar_'e'_type */
var day_data = [
  {"period": "2012-10-01", "licensed": 3407, "sorned": 660},
  {"period": "2012-09-30", "licensed": 3351, "sorned": 629},
  {"period": "2012-09-29", "licensed": 3269, "sorned": 618},
  {"period": "2012-09-20", "licensed": 3246, "sorned": 661},
  {"period": "2012-09-19", "licensed": 3257, "sorned": 667},
  {"period": "2012-09-18", "licensed": 3248, "sorned": 627},
  {"period": "2012-09-17", "licensed": 3171, "sorned": 660},
  {"period": "2012-09-16", "licensed": 3171, "sorned": 676},
  {"period": "2012-09-15", "licensed": 3201, "sorned": 656},
  {"period": "2012-09-10", "licensed": 3215, "sorned": 622}
];
Morris.Line({
  element: 'graph',
  data: day_data,
  xkey: 'period',
  ykeys: ['licensed', 'sorned'],
  labels: ['Licensed', 'SORN']
});
examples/donut.html000064400000001745150145030150010402 0ustar00

Donut Chart

Morris.Donut({
  element: 'graph',
  data: [
    {value: 70, label: 'foo'},
    {value: 15, label: 'bar'},
    {value: 10, label: 'baz'},
    {value: 5, label: 'A really really long label'}
  ],
  formatter: function (x) { return x + "%"}
}).on('click', function(i, row){
  console.log(i, row);
});
examples/no-grid.html000064400000003040150145030150010576 0ustar00

Formatting Dates YYYY-MM-DD

/* data stolen from http://howmanyleft.co.uk/vehicle/jaguar_'e'_type */
var day_data = [
  {"period": "2012-10-01", "licensed": 3407, "sorned": 660},
  {"period": "2012-09-30", "licensed": 3351, "sorned": 629},
  {"period": "2012-09-29", "licensed": 3269, "sorned": 618},
  {"period": "2012-09-20", "licensed": 3246, "sorned": 661},
  {"period": "2012-09-19", "licensed": 3257, "sorned": 667},
  {"period": "2012-09-18", "licensed": 3248, "sorned": 627},
  {"period": "2012-09-17", "licensed": 3171, "sorned": 660},
  {"period": "2012-09-16", "licensed": 3171, "sorned": 676},
  {"period": "2012-09-15", "licensed": 3201, "sorned": 656},
  {"period": "2012-09-10", "licensed": 3215, "sorned": 622}
];
Morris.Line({
  element: 'graph',
  grid: false,
  data: day_data,
  xkey: 'period',
  ykeys: ['licensed', 'sorned'],
  labels: ['Licensed', 'SORN']
});
examples/lib/example.js000064400000000077150145030150011117 0ustar00$(function () { eval($('#code').text()); prettyPrint(); });examples/lib/example.css000064400000000236150145030150011270 0ustar00body { width: 800px; margin: 0 auto; } #graph { width: 800px; height: 250px; margin: 20px auto 0 auto; } pre { height: 250px; overflow: auto; } examples/negative.html000064400000002242150145030150011044 0ustar00

Negative values

var neg_data = [
  {"period": "2011-08-12", "a": 100},
  {"period": "2011-03-03", "a": 75},
  {"period": "2010-08-08", "a": 50},
  {"period": "2010-05-10", "a": 25},
  {"period": "2010-03-14", "a": 0},
  {"period": "2010-01-10", "a": -25},
  {"period": "2009-12-10", "a": -50},
  {"period": "2009-10-07", "a": -75},
  {"period": "2009-09-25", "a": -100}
];
Morris.Line({
  element: 'graph',
  data: neg_data,
  xkey: 'period',
  ykeys: ['a'],
  labels: ['Series A'],
  units: '%'
});
examples/updating.html000064400000002531150145030150011056 0ustar00

Updating data


var nReloads = 0;
function data(offset) {
  var ret = [];
  for (var x = 0; x <= 360; x += 10) {
    var v = (offset + x) % 360;
    ret.push({
      x: x,
      y: Math.sin(Math.PI * v / 180).toFixed(4),
      z: Math.cos(Math.PI * v / 180).toFixed(4)
    });
  }
  return ret;
}
var graph = Morris.Line({
    element: 'graph',
    data: data(0),
    xkey: 'x',
    ykeys: ['y', 'z'],
    labels: ['sin()', 'cos()'],
    parseTime: false,
    ymin: -1.0,
    ymax: 1.0,
    hideHover: true
});
function update() {
  nReloads++;
  graph.setData(data(5 * nReloads));
  $('#reloadStatus').text(nReloads + ' reloads');
}
setInterval(update, 100);
examples/donut-colors.html000064400000002135150145030150011673 0ustar00

Donut Chart

Morris.Donut({
  element: 'graph',
  data: [
    {value: 70, label: 'foo'},
    {value: 15, label: 'bar'},
    {value: 10, label: 'baz'},
    {value: 5, label: 'A really really long label'}
  ],
  backgroundColor: '#ccc',
  labelColor: '#060',
  colors: [
    '#0BA462',
    '#39B580',
    '#67C69D',
    '#95D7BB'
  ],
  formatter: function (x) { return x + "%"}
});
examples/goals.html000064400000001756150145030150010360 0ustar00

Value Goals

var decimal_data = [];
for (var x = 0; x <= 360; x += 10) {
  decimal_data.push({
    x: x,
    y: Math.sin(Math.PI * x / 180).toFixed(4)
  });
}
window.m = Morris.Line({
  element: 'graph',
  data: decimal_data,
  xkey: 'x',
  ykeys: ['y'],
  labels: ['sin(x)'],
  parseTime: false,
  goals: [-1, 0, 1]
});
examples/years.html000064400000002721150145030150010367 0ustar00

Formatting Dates YYYY

/* data stolen from http://howmanyleft.co.uk/vehicle/jaguar_'e'_type */
var year_data = [
  {"period": "2012", "licensed": 3407, "sorned": 660},
  {"period": "2011", "licensed": 3351, "sorned": 629},
  {"period": "2010", "licensed": 3269, "sorned": 618},
  {"period": "2009", "licensed": 3246, "sorned": 661},
  {"period": "2008", "licensed": 3257, "sorned": 667},
  {"period": "2007", "licensed": 3248, "sorned": 627},
  {"period": "2006", "licensed": 3171, "sorned": 660},
  {"period": "2005", "licensed": 3171, "sorned": 676},
  {"period": "2004", "licensed": 3201, "sorned": 656},
  {"period": "2003", "licensed": 3215, "sorned": 622}
];
Morris.Line({
  element: 'graph',
  data: year_data,
  xkey: 'period',
  ykeys: ['licensed', 'sorned'],
  labels: ['Licensed', 'SORN']
});