├── HACKING ├── LICENSE ├── README.md ├── TODO ├── codestats.css ├── codestats.html ├── codestats.js ├── codestats.pl ├── d3.v2.min.js ├── data.js ├── examples.js ├── flowers.css ├── flowers.html ├── flowers.js ├── gg.css ├── gg.js ├── index.html ├── json2.js ├── notes.txt ├── server-side-example.js └── underscore-min.js /HACKING: -------------------------------------------------------------------------------- 1 | * Some notes if you want to hack on this and submit pull requests: 2 | 3 | If you use Emacs, please set indent-tab-modes to nil and run M-x 4 | whitespace-cleanup on files before submitting pull requests. (If you 5 | want to be hardcore, add whitespace-cleanup to before-save-hook to do 6 | it all the time.) If you don't use Emacs, please do your best to 7 | remove trailing spaces from lines. This reduces the number of lines of 8 | diff output we need to look at be removing all changes due to 9 | unimportant (and invisible) changes in spacing. 10 | 11 | * If you use VIM: 12 | 13 | Add the following to your ~/.vimrc file, as you like, removing the 14 | lines that start with the `-` character. 15 | 16 | - 4 space tabs 17 | set tabstop=4 18 | set shiftwidth=4 19 | set softtabstop=4 20 | set expandtab 21 | 22 | - Remove trailing whitespaces on save (:w) 23 | autocmd BufWritePre * :%s/\s\+$//e 24 | 25 | - Highlight trailing whitespace 26 | highlight ExtraWhitespace ctermbg=red guibg=red 27 | au ColorScheme * highlight ExtraWhitespace guibg=red 28 | au BufEnter * match ExtraWhitespace /\s\+$/ 29 | au InsertEnter * match ExtraWhitespace /\s\+\%#\@ [http://gigamonkey.github.io/gg/](http://gigamonkey.github.io/gg/) 9 | 10 | Some ideas of things to do next: 11 | 12 | - More styles of facets. 13 | 14 | - Implement positioners, especially for stacked and side-by-side bar charts. 15 | 16 | - Keys for aesthetics other than x and y. 17 | 18 | - Size scales for more geometries. 19 | 20 | - Coordinate systems (esp. pie charts) 21 | 22 | - Regularize/rationalize/document use of CSS for controlling appearence. 23 | 24 | - In-browser interactive graphic builder. (REPL?) 25 | 26 | 27 | *This project started life as a Hack Week project at Etsy. I’m no longer there but if you want to hack data or something else at Etsy, they’re probably still hiring. See 28 | [http://www.etsy.com/careers/](http://www.etsy.com/careers/)* 29 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | - Decide whether it's better to just use CSS for anything that can be 2 | controlled by CSS or to have ways to specify relevant appearance 3 | parameters in the graphics spec. 4 | 5 | - Come up with syntax for facets. -------------------------------------------------------------------------------- /codestats.css: -------------------------------------------------------------------------------- 1 | .graphicText { 2 | font-size: 16px; 3 | } 4 | -------------------------------------------------------------------------------- /codestats.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | gg codestats 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

Lines of code in gg over time.

15 |
16 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /codestats.js: -------------------------------------------------------------------------------- 1 | var codestats = [ 2 | { utc: 1343067625000, lines: 0, author: "peter@etsy.com" }, 3 | { utc: 1343067852000, lines: 46, author: "peter@etsy.com" }, 4 | { utc: 1343069438000, lines: 88, author: "peter@etsy.com" }, 5 | { utc: 1343069915000, lines: 89, author: "peter@etsy.com" }, 6 | { utc: 1343072068000, lines: 104, author: "peter@etsy.com" }, 7 | { utc: 1343073760000, lines: 128, author: "peter@etsy.com" }, 8 | { utc: 1343075877000, lines: 142, author: "peter@etsy.com" }, 9 | { utc: 1343076732000, lines: 152, author: "peter@etsy.com" }, 10 | { utc: 1343077067000, lines: 158, author: "peter@etsy.com" }, 11 | { utc: 1343081037000, lines: 253, author: "peter@etsy.com" }, 12 | { utc: 1343083397000, lines: 263, author: "peter@etsy.com" }, 13 | { utc: 1343087006000, lines: 296, author: "peter@etsy.com" }, 14 | { utc: 1343089358000, lines: 285, author: "peter@etsy.com" }, 15 | { utc: 1343144730000, lines: 285, author: "peter@etsy.com" }, 16 | { utc: 1343144753000, lines: 320, author: "peter@etsy.com" }, 17 | { utc: 1343145256000, lines: 320, author: "peter@etsy.com" }, 18 | { utc: 1343146149000, lines: 318, author: "peter@etsy.com" }, 19 | { utc: 1343147706000, lines: 316, author: "peter@etsy.com" }, 20 | { utc: 1343149053000, lines: 304, author: "peter@etsy.com" }, 21 | { utc: 1343154859000, lines: 367, author: "peter@etsy.com" }, 22 | { utc: 1343156150000, lines: 367, author: "peter@etsy.com" }, 23 | { utc: 1343159599000, lines: 386, author: "peter@etsy.com" }, 24 | { utc: 1343162730000, lines: 385, author: "peter@etsy.com" }, 25 | { utc: 1343162816000, lines: 385, author: "peter@etsy.com" }, 26 | { utc: 1343162829000, lines: 385, author: "peter@etsy.com" }, 27 | { utc: 1343163149000, lines: 385, author: "peter@etsy.com" }, 28 | { utc: 1343163957000, lines: 400, author: "peter@etsy.com" }, 29 | { utc: 1343164916000, lines: 388, author: "peter@etsy.com" }, 30 | { utc: 1343165758000, lines: 403, author: "peter@etsy.com" }, 31 | { utc: 1343166180000, lines: 398, author: "peter@etsy.com" }, 32 | { utc: 1343166687000, lines: 385, author: "peter@etsy.com" }, 33 | { utc: 1343166732000, lines: 385, author: "peter@etsy.com" }, 34 | { utc: 1343167607000, lines: 395, author: "peter@etsy.com" }, 35 | { utc: 1343167838000, lines: 391, author: "peter@etsy.com" }, 36 | { utc: 1343168517000, lines: 391, author: "peter@etsy.com" }, 37 | { utc: 1343172638000, lines: 402, author: "peter@etsy.com" }, 38 | { utc: 1343174547000, lines: 428, author: "peter@etsy.com" }, 39 | { utc: 1343175192000, lines: 428, author: "peter@etsy.com" }, 40 | { utc: 1343175234000, lines: 428, author: "peter@etsy.com" }, 41 | { utc: 1343193080000, lines: 564, author: "peter@etsy.com" }, 42 | { utc: 1343193293000, lines: 564, author: "peter@etsy.com" }, 43 | { utc: 1343239569000, lines: 542, author: "peter@etsy.com" }, 44 | { utc: 1343240396000, lines: 545, author: "peter@etsy.com" }, 45 | { utc: 1343240465000, lines: 557, author: "peter@etsy.com" }, 46 | { utc: 1343249219000, lines: 573, author: "peter@etsy.com" }, 47 | { utc: 1343249973000, lines: 584, author: "peter@etsy.com" }, 48 | { utc: 1343255769000, lines: 587, author: "peter@etsy.com" }, 49 | { utc: 1343255809000, lines: 587, author: "peter@etsy.com" }, 50 | { utc: 1343335812000, lines: 590, author: "peter@etsy.com" }, 51 | { utc: 1343336012000, lines: 590, author: "peter@etsy.com" }, 52 | { utc: 1343337516000, lines: 597, author: "peter@etsy.com" }, 53 | { utc: 1343337701000, lines: 597, author: "peter@etsy.com" }, 54 | { utc: 1343341419000, lines: 597, author: "peter@etsy.com" }, 55 | { utc: 1343345179000, lines: 704, author: "peter@etsy.com" }, 56 | { utc: 1343347264000, lines: 730, author: "peter@etsy.com" }, 57 | { utc: 1343351880000, lines: 824, author: "peter@etsy.com" }, 58 | { utc: 1343358069000, lines: 838, author: "peter@etsy.com" }, 59 | { utc: 1343358461000, lines: 839, author: "peter@etsy.com" }, 60 | { utc: 1343359816000, lines: 852, author: "peter@etsy.com" }, 61 | { utc: 1343359958000, lines: 855, author: "peter@etsy.com" }, 62 | { utc: 1343361987000, lines: 921, author: "peter@etsy.com" }, 63 | { utc: 1343362617000, lines: 852, author: "peter@etsy.com" }, 64 | { utc: 1343363166000, lines: 832, author: "peter@etsy.com" }, 65 | { utc: 1343363427000, lines: 832, author: "peter@etsy.com" }, 66 | { utc: 1343367858000, lines: 832, author: "peter@etsy.com" }, 67 | { utc: 1343368324000, lines: 834, author: "peter@etsy.com" }, 68 | { utc: 1343371919000, lines: 834, author: "peter@etsy.com" }, 69 | { utc: 1343400257000, lines: 834, author: "peter@etsy.com" }, 70 | { utc: 1343422533000, lines: 834, author: "peter@etsy.com" }, 71 | { utc: 1343490863000, lines: 834, author: "peter@gigamonkeys.com" }, 72 | { utc: 1343506744000, lines: 834, author: "peter@etsy.com" }, 73 | { utc: 1343508383000, lines: 824, author: "peter@etsy.com" }, 74 | { utc: 1343508625000, lines: 828, author: "peter@etsy.com" }, 75 | { utc: 1343512044000, lines: 835, author: "peter@etsy.com" }, 76 | { utc: 1343512154000, lines: 835, author: "peter@etsy.com" }, 77 | { utc: 1343512290000, lines: 835, author: "peter@etsy.com" }, 78 | { utc: 1343513531000, lines: 835, author: "peter@etsy.com" }, 79 | { utc: 1343513582000, lines: 835, author: "peter@etsy.com" }, 80 | { utc: 1343513683000, lines: 835, author: "peter@etsy.com" }, 81 | { utc: 1343528552000, lines: 853, author: "n8agrin@gmail.com" }, 82 | { utc: 1343528760000, lines: 849, author: "n8agrin@gmail.com" }, 83 | { utc: 1343587575000, lines: 820, author: "n8agrin@gmail.com" }, 84 | { utc: 1343597256000, lines: 835, author: "peter@etsy.com" }, 85 | { utc: 1343598535000, lines: 835, author: "peter@etsy.com" }, 86 | { utc: 1343601213000, lines: 835, author: "peter@etsy.com" }, 87 | { utc: 1343601230000, lines: 835, author: "peter@etsy.com" }, 88 | { utc: 1343602085000, lines: 906, author: "peter@etsy.com" }, 89 | { utc: 1343602298000, lines: 906, author: "peter@etsy.com" }, 90 | { utc: 1343621530000, lines: 902, author: "peter@gigamonkeys.com" }, 91 | { utc: 1343622277000, lines: 899, author: "peter@gigamonkeys.com" }, 92 | { utc: 1343622372000, lines: 899, author: "peter@gigamonkeys.com" }, 93 | { utc: 1343623453000, lines: 908, author: "peter@gigamonkeys.com" }, 94 | { utc: 1343624502000, lines: 899, author: "peter@gigamonkeys.com" }, 95 | { utc: 1343626839000, lines: 883, author: "peter@gigamonkeys.com" }, 96 | { utc: 1343627042000, lines: 878, author: "peter@gigamonkeys.com" }, 97 | { utc: 1343627655000, lines: 878, author: "peter@gigamonkeys.com" }, 98 | { utc: 1343671622000, lines: 995, author: "n8agrin@gmail.com" }, 99 | { utc: 1343679069000, lines: 858, author: "peter@gigamonkeys.com" }, 100 | { utc: 1343679143000, lines: 858, author: "peter@gigamonkeys.com" }, 101 | { utc: 1343679663000, lines: 853, author: "peter@gigamonkeys.com" }, 102 | { utc: 1343681275000, lines: 883, author: "peter@gigamonkeys.com" }, 103 | { utc: 1343683368000, lines: 867, author: "peter@gigamonkeys.com" }, 104 | { utc: 1343683553000, lines: 867, author: "peter@gigamonkeys.com" }, 105 | { utc: 1343874178000, lines: 865, author: "asolove@gmail.com" }, 106 | { utc: 1343875596000, lines: 866, author: "asolove@gmail.com" }, 107 | { utc: 1343877148000, lines: 866, author: "peter@gigamonkeys.com" }, 108 | { utc: 1343877346000, lines: 866, author: "peter@gigamonkeys.com" }, 109 | { utc: 1343953448000, lines: 866, author: "asolove@gmail.com" }, 110 | { utc: 1344013022000, lines: 878, author: "peter@gigamonkeys.com" }, 111 | { utc: 1344013725000, lines: 884, author: "peter@gigamonkeys.com" }, 112 | { utc: 1344017591000, lines: 1037, author: "n8agrin@gmail.com" }, 113 | { utc: 1344017778000, lines: 1036, author: "n8agrin@gmail.com" }, 114 | { utc: 1344056544000, lines: 884, author: "peter@gigamonkeys.com" }, 115 | { utc: 1344056649000, lines: 884, author: "peter@gigamonkeys.com" }, 116 | { utc: 1344056745000, lines: 884, author: "peter@gigamonkeys.com" }, 117 | { utc: 1344097260000, lines: 891, author: "peter@gigamonkeys.com" }, 118 | { utc: 1344099107000, lines: 891, author: "peter@gigamonkeys.com" }, 119 | { utc: 1344099129000, lines: 896, author: "peter@gigamonkeys.com" }, 120 | { utc: 1344099174000, lines: 896, author: "peter@gigamonkeys.com" }, 121 | { utc: 1344099329000, lines: 896, author: "peter@gigamonkeys.com" }, 122 | { utc: 1344099953000, lines: 896, author: "peter@gigamonkeys.com" }, 123 | { utc: 1344438764000, lines: 955, author: "asolove@gmail.com" }, 124 | { utc: 1344713448000, lines: 1058, author: "n8agrin@gmail.com" }, 125 | { utc: 1344714573000, lines: 955, author: "n8agrin@gmail.com" }, 126 | { utc: 1344716912000, lines: 955, author: "peter@gigamonkeys.com" }, 127 | { utc: 1344718134000, lines: 955, author: "peter@gigamonkeys.com" }, 128 | { utc: 1344718134000, lines: 955, author: "peter@gigamonkeys.com" }, 129 | { utc: 1344718193000, lines: 956, author: "peter@gigamonkeys.com" }, 130 | { utc: 1344718193000, lines: 956, author: "peter@gigamonkeys.com" }, 131 | { utc: 1344718485000, lines: 956, author: "peter@gigamonkeys.com" }, 132 | { utc: 1344718890000, lines: 956, author: "peter@gigamonkeys.com" }, 133 | { utc: 1344808580000, lines: 958, author: "peter@gigamonkeys.com" }, 134 | { utc: 1344808657000, lines: 953, author: "peter@gigamonkeys.com" }, 135 | { utc: 1344809588000, lines: 953, author: "peter@gigamonkeys.com" }, 136 | { utc: 1344811070000, lines: 952, author: "peter@gigamonkeys.com" }, 137 | { utc: 1344811633000, lines: 990, author: "peter@gigamonkeys.com" }, 138 | { utc: 1344815543000, lines: 990, author: "peter@gigamonkeys.com" }, 139 | { utc: 1345268135000, lines: 991, author: "peter@gigamonkeys.com" }, 140 | { utc: 1345401358000, lines: 1094, author: "n8agrin@gmail.com" }, 141 | { utc: 1345401991000, lines: 1055, author: "n8agrin@gmail.com" }, 142 | { utc: 1345402009000, lines: 1055, author: "n8agrin@gmail.com" }, 143 | { utc: 1345407213000, lines: 1070, author: "n8agrin@gmail.com" }, 144 | { utc: 1345407330000, lines: 1070, author: "n8agrin@gmail.com" }, 145 | { utc: 1346034703000, lines: 990, author: "n8agrin@gmail.com" }, 146 | { utc: 1346698697000, lines: 1067, author: "peter@gigamonkeys.com" }, 147 | { utc: 1346699003000, lines: 1045, author: "peter@gigamonkeys.com" }, 148 | { utc: 1346700340000, lines: 1045, author: "peter@gigamonkeys.com" }, 149 | { utc: 1351279043000, lines: 1045, author: "peter@etsy.com" }, 150 | { utc: 1354991866000, lines: 1053, author: "peter@gigamonkeys.com" }, 151 | { utc: 1354991963000, lines: 1081, author: "peter@gigamonkeys.com" }, 152 | { utc: 1354992744000, lines: 1081, author: "peter@gigamonkeys.com" }, 153 | { utc: 1355002263000, lines: 1120, author: "aconbere@etsy.com" }, 154 | { utc: 1355695693000, lines: 1120, author: "peter@etsy.com" }, 155 | { utc: 1356048574000, lines: 1168, author: "n8agrin@gmail.com" }, 156 | { utc: 1356051283000, lines: 1126, author: "n8agrin@gmail.com" }, 157 | { utc: 1356052557000, lines: 1120, author: "n8agrin@gmail.com" }, 158 | { utc: 1356053080000, lines: 1120, author: "n8agrin@gmail.com" }, 159 | { utc: 1356102468000, lines: 1166, author: "n8agrin@gmail.com" }, 160 | { utc: 1356274365000, lines: 1120, author: "peter@gigamonkeys.com" }, 161 | { utc: 1356279154000, lines: 1181, author: "peter@gigamonkeys.com" }, 162 | { utc: 1356394088000, lines: 1181, author: "peter@gigamonkeys.com" }, 163 | { utc: 1401301402000, lines: 1164, author: "pseibel@twitter.com" }, 164 | { utc: 1415477280000, lines: 1168, author: "pseibel@twitter.com" }, 165 | { utc: 1415504031000, lines: 1168, author: "pseibel@twitter.com" }, 166 | { utc: 1443369019000, lines: 1168, author: "peter@gigamonkeys.com" }, 167 | { utc: 1443369043000, lines: 1169, author: "peter@gigamonkeys.com" }, 168 | { utc: 1444513308000, lines: 1169, author: "peter@gigamonkeys.com" }, 169 | { utc: 1444513751000, lines: 1169, author: "peter@gigamonkeys.com" }, 170 | { utc: 1444513871000, lines: 1270, author: "peter@gigamonkeys.com" }, 171 | { utc: 1444515658000, lines: 1270, author: "peter@gigamonkeys.com" }, 172 | { utc: 1444519600000, lines: 1270, author: "peter@gigamonkeys.com" }, 173 | { utc: 1444519983000, lines: 1268, author: "peter@gigamonkeys.com" }, 174 | { utc: 1444521244000, lines: 1268, author: "peter@gigamonkeys.com" }, 175 | { utc: 1444524272000, lines: 1289, author: "peter@gigamonkeys.com" }, 176 | { utc: 1444532688000, lines: 1276, author: "peter@gigamonkeys.com" }, 177 | { utc: 1444532799000, lines: 1276, author: "peter@gigamonkeys.com" }, 178 | { utc: 1444536065000, lines: 1276, author: "peter@gigamonkeys.com" }, 179 | { utc: 1444573963000, lines: 1280, author: "peter@gigamonkeys.com" }, 180 | { utc: 1444576500000, lines: 1296, author: "peter@gigamonkeys.com" }, 181 | { utc: 1444580728000, lines: 1291, author: "peter@gigamonkeys.com" }, 182 | { utc: 1444582131000, lines: 1291, author: "peter@gigamonkeys.com" }, 183 | { utc: 1444593009000, lines: 1291, author: "peter@gigamonkeys.com" }, 184 | { utc: 1444593048000, lines: 1299, author: "peter@gigamonkeys.com" }, 185 | { utc: 1444593083000, lines: 1357, author: "peter@gigamonkeys.com" }, 186 | { utc: 1444593944000, lines: 1362, author: "peter@gigamonkeys.com" }, 187 | { utc: 1444599343000, lines: 1394, author: "peter@gigamonkeys.com" }, 188 | { utc: 1444600350000, lines: 1405, author: "peter@gigamonkeys.com" }, 189 | ]; 190 | -------------------------------------------------------------------------------- /codestats.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use warnings; 4 | use strict; 5 | 6 | my %excludes = ( 7 | 'd3.v2.min.js' => 1, 8 | 'jquery-1.7.2.min.js' => 1, 9 | 'json2.js' => 1, 10 | 'underscore-min.js' => 1, 11 | 'codestats.js' => 1, 12 | ); 13 | 14 | system("git stash &> /dev/null") == 0 or die "stash failed: $?"; 15 | system("git co master &> /dev/null") == 0 or die "checkout failed: $?"; 16 | 17 | open(IN, "git log --no-merges --pretty='format:%H %at %ae' |") or die $!; 18 | my @loglines = ; 19 | close IN; 20 | 21 | my @data = (); 22 | 23 | foreach (@loglines) { 24 | chomp; 25 | my ($sha, $utc, $author) = split ' ', $_, 4; 26 | 27 | $utc *= 1000; # for Javascript 28 | 29 | system("git co $sha &> /dev/null") == 0 or die "checkout failed: $?"; 30 | 31 | opendir(DIR, ".") or die $!; 32 | my @files = grep { /.js$/ and not $excludes{$_} } readdir(DIR); 33 | closedir DIR; 34 | 35 | my $lines = @files ? `cat @files | wc -l` : '0'; 36 | $lines =~ s/\s*(\S+)\s*/$1/; 37 | 38 | push @data, "{ utc: $utc, lines: $lines, author: \"$author\" },"; 39 | system("git co master &> /dev/null") == 0 or die "checkout failed: $?"; 40 | } 41 | 42 | 43 | print "var codestats = [\n"; 44 | foreach (sort @data) { 45 | print " $_\n"; 46 | } 47 | print "];\n"; 48 | system("git stash pop &> /dev/null") == 0 or die "stash failed: $?"; 49 | 50 | __END__ 51 | -------------------------------------------------------------------------------- /d3.v2.min.js: -------------------------------------------------------------------------------- 1 | (function(){function d(a,b){try{for(var c in b)Object.defineProperty(a.prototype,c,{value:b[c],enumerable:!1})}catch(d){a.prototype=b}}function f(a){var b=-1,c=a.length,d=[];while(++b=0?a.substring(b):(b=a.length,""),d=[];while(b>0)d.push(a.substring(b-=3,b+3));return d.reverse().join(",")+c}function H(a,b){var c=Math.pow(10,Math.abs(8-b)*3);return{scale:b>8?function(a){return a/c}:function(a){return a*c},symbol:a}}function N(a){return function(b){return b<=0?0:b>=1?1:a(b)}}function O(a){return function(b){return 1-a(1-b)}}function P(a){return function(b){return.5*(b<.5?a(2*b):2-a(2-2*b))}}function Q(a){return a}function R(a){return function(b){return Math.pow(b,a)}}function S(a){return 1-Math.cos(a*Math.PI/2)}function T(a){return Math.pow(2,10*(a-1))}function U(a){return 1-Math.sqrt(1-a*a)}function V(a,b){var c;return arguments.length<2&&(b=.45),arguments.length<1?(a=1,c=b/4):c=b/(2*Math.PI)*Math.asin(1/a),function(d){return 1+a*Math.pow(2,10*-d)*Math.sin((d-c)*2*Math.PI/b)}}function W(a){return a||(a=1.70158),function(b){return b*b*((a+1)*b-a)}}function X(a){return a<1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+.9375:7.5625*(a-=2.625/2.75)*a+.984375}function Y(){d3.event.stopPropagation(),d3.event.preventDefault()}function Z(){var a=d3.event,b;while(b=a.sourceEvent)a=b;return a}function $(a){var b=new z,c=0,d=arguments.length;while(++c360?a-=360:a<0&&(a+=360),a<60?d+(e-d)*a/60:a<180?e:a<240?d+(e-d)*(240-a)/60:d}function g(a){return Math.round(f(a)*255)}var d,e;return a%=360,a<0&&(a+=360),b=b<0?0:b>1?1:b,c=c<0?0:c>1?1:c,e=c<=.5?c*(1+b):c+b-c*b,d=2*c-e,bj(g(a+120),g(a),g(a-120))}function bv(a,b,c){return new bw(a,b,c)}function bw(a,b,c){this.h=a,this.c=b,this.l=c}function bx(a,b,c){return by(c,Math.cos(a*=Math.PI/180)*b,Math.sin(a)*b)}function by(a,b,c){return new bz(a,b,c)}function bz(a,b,c){this.l=a,this.a=b,this.b=c}function bE(a,b,c){var d=(a+16)/116,e=d+b/500,f=d-c/200;return e=bG(e)*bB,d=bG(d)*bC,f=bG(f)*bD,bj(bI(3.2404542*e-1.5371385*d-.4985314*f),bI(-0.969266*e+1.8760108*d+.041556*f),bI(.0556434*e-.2040259*d+1.0572252*f))}function bF(a,b,c){return bv(Math.atan2(c,b)/Math.PI*180,Math.sqrt(b*b+c*c),a)}function bG(a){return a>.206893034?a*a*a:(a-4/29)/7.787037}function bH(a){return a>.008856?Math.pow(a,1/3):7.787037*a+4/29}function bI(a){return Math.round(255*(a<=.00304?12.92*a:1.055*Math.pow(a,1/2.4)-.055))}function bJ(a){return i(a,bP),a}function bQ(a){return function(){return bK(a,this)}}function bR(a){return function(){return bL(a,this)}}function bS(a,b){function c(){this.removeAttribute(a)}function d(){this.removeAttributeNS(a.space,a.local)}function e(){this.setAttribute(a,b)}function f(){this.setAttributeNS(a.space,a.local,b)}function g(){var c=b.apply(this,arguments);c==null?this.removeAttribute(a):this.setAttribute(a,c)}function h(){var c=b.apply(this,arguments);c==null?this.removeAttributeNS(a.space,a.local):this.setAttributeNS(a.space,a.local,c)}return a=d3.ns.qualify(a),b==null?a.local?d:c:typeof b=="function"?a.local?h:g:a.local?f:e}function bT(a){return new RegExp("(?:^|\\s+)"+d3.requote(a)+"(?:\\s+|$)","g")}function bU(a,b){function d(){var d=-1;while(++d0&&(a=a.substring(0,e)),b?g:f}function ca(a,b){for(var c=0,d=a.length;cb?q():(m.active=b,d.forEach(function(b,c){(c=c.call(a,n,h))&&j.push(c)}),e.start.call(a,n,h),p(f)||d3.timer(p,0,c),1)}function p(c){if(m.active!==b)return q();var d=(c-k)/l,g=f(d),i=j.length;while(i>0)j[--i].call(a,g);if(d>=1)return q(),ch=b,e.end.call(a,n,h),ch=0,1}function q(){return--m.count||delete a.__transition__,1}var j=[],k=a.delay,l=a.duration,m=(a=a.node).__transition__||(a.__transition__={active:0,count:0}),n=a.__data__;++m.count,k<=g?o(g):d3.timer(o,k,c)})},0,c),a}function co(a){var b=ch,c=cn,d=cl,e=cm;return ch=this.id,cn=this.ease(),ca(this,function(b,c,d){cl=b.delay,cm=b.duration,a.call(b=b.node,b.__data__,c,d)}),ch=b,cn=c,cl=d,cm=e,this}function cq(a,b,c){return c!=""&&cp}function cr(a,b){return d3.tween(a,bg(b))}function cv(){var a,b=Date.now(),c=cs;while(c)a=b-c.then,a>=c.delay&&(c.flush=c.callback(a)),c=c.next;var d=cw()-b;d>24?(isFinite(d)&&(clearTimeout(cu),cu=setTimeout(cv,d)),ct=0):(ct=1,cx(cv))}function cw(){var a=null,b=cs,c=Infinity;while(b)b.flush?b=a?a.next=b.next:cs=b.next:(c=Math.min(c,b.then+b.delay),b=(a=b).next);return c}function cz(a,b){var c=a.ownerSVGElement||a;if(c.createSVGPoint){var d=c.createSVGPoint();if(cy<0&&(window.scrollX||window.scrollY)){c=d3.select(document.body).append("svg").style("position","absolute").style("top",0).style("left",0);var e=c[0][0].getScreenCTM();cy=!e.f&&!e.e,c.remove()}return cy?(d.x=b.pageX,d.y=b.pageY):(d.x=b.clientX,d.y=b.clientY),d=d.matrixTransform(a.getScreenCTM().inverse()),[d.x,d.y]}var f=a.getBoundingClientRect();return[b.clientX-f.left-a.clientLeft,b.clientY-f.top-a.clientTop]}function cA(){}function cB(a){var b=a[0],c=a[a.length-1];return b2?cM:cL,i=d?bi:bh;return e=g(a,b,i,c),f=g(b,a,i,d3.interpolate),h}function h(a){return e(a)}var e,f;return h.invert=function(a){return f(a)},h.domain=function(b){return arguments.length?(a=b.map(Number),g()):a},h.range=function(a){return arguments.length?(b=a,g()):b},h.rangeRound=function(a){return h.range(a).interpolate(d3.interpolateRound)},h.clamp=function(a){return arguments.length?(d=a,g()):d},h.interpolate=function(a){return arguments.length?(c=a,g()):c},h.ticks=function(b){return cJ(a,b)},h.tickFormat=function(b){return cK(a,b)},h.nice=function(){return cD(a,cH),g()},h.copy=function(){return cF(a,b,c,d)},g()}function cG(a,b){return d3.rebind(a,b,"range","rangeRound","interpolate","clamp")}function cH(a){return a=Math.pow(10,Math.round(Math.log(a)/Math.LN10)-1),a&&{floor:function(b){return Math.floor(b/a)*a},ceil:function(b){return Math.ceil(b/a)*a}}}function cI(a,b){var c=cB(a),d=c[1]-c[0],e=Math.pow(10,Math.floor(Math.log(d/b)/Math.LN10)),f=b/d*e;return f<=.15?e*=10:f<=.35?e*=5:f<=.75&&(e*=2),c[0]=Math.ceil(c[0]/e)*e,c[1]=Math.floor(c[1]/e)*e+e*.5,c[2]=e,c}function cJ(a,b){return d3.range.apply(d3,cI(a,b))}function cK(a,b){return d3.format(",."+Math.max(0,-Math.floor(Math.log(cI(a,b)[2])/Math.LN10+.01))+"f")}function cL(a,b,c,d){var e=c(a[0],a[1]),f=d(b[0],b[1]);return function(a){return f(e(a))}}function cM(a,b,c,d){var e=[],f=[],g=0,h=Math.min(a.length,b.length)-1;a[h]0;j--)e.push(c(f)*j)}else{for(;fi;g--);e=e.slice(f,g)}return e},d.tickFormat=function(a,e){arguments.length<2&&(e=cO);if(arguments.length<1)return e;var f=Math.max(.1,a/d.ticks().length),g=b===cQ?(h=-1e-12,Math.floor):(h=1e-12,Math.ceil),h;return function(a){return a/c(g(b(a)+h))<=f?e(a):""}},d.copy=function(){return cN(a.copy(),b)},cG(d,a)}function cP(a){return Math.log(a<0?0:a)/Math.LN10}function cQ(a){return-Math.log(a>0?0:-a)/Math.LN10}function cR(a,b){function e(b){return a(c(b))}var c=cS(b),d=cS(1/b);return e.invert=function(b){return d(a.invert(b))},e.domain=function(b){return arguments.length?(a.domain(b.map(c)),e):a.domain().map(d)},e.ticks=function(a){return cJ(e.domain(),a)},e.tickFormat=function(a){return cK(e.domain(),a)},e.nice=function(){return e.domain(cD(e.domain(),cH))},e.exponent=function(a){if(!arguments.length)return b;var f=e.domain();return c=cS(b=a),d=cS(1/b),e.domain(f)},e.copy=function(){return cR(a.copy(),b)},cG(e,a)}function cS(a){return function(b){return b<0?-Math.pow(-b,a):Math.pow(b,a)}}function cT(a,b){function f(b){return d[((c.get(b)||c.set(b,a.push(b)))-1)%d.length]}function g(b,c){return d3.range(a.length).map(function(a){return b+c*a})}var c,d,e;return f.domain=function(d){if(!arguments.length)return a;a=[],c=new j;var e=-1,g=d.length,h;while(++e1){h=b[1],f=a[i],i++,d+="C"+(e[0]+g[0])+","+(e[1]+g[1])+","+(f[0]-h[0])+","+(f[1]-h[1])+","+f[0]+","+f[1];for(var j=2;j9&&(f=c*3/Math.sqrt(f),g[h]=f*d,g[h+1]=f*e));h=-1;while(++h<=i)f=(a[Math.min(i,h+1)][0]-a[Math.max(0,h-1)][0])/(6*(1+g[h]*g[h])),b.push([f||0,g[h]*f||0]);return b}function dG(a){return a.length<3?dk(a):a[0]+ds(a,dF(a))}function dH(a){var b,c=-1,d=a.length,e,f;while(++c1){var d=cB(a.domain()),e,f=-1,g=b.length,h=(b[1]-b[0])/++c,i,j;while(++f0;)(j=+b[f]-i*h)>=d[0]&&e.push(j);for(--f,i=0;++id&&(c=b,d=e);return c}function ex(a){return a.reduce(ey,0)}function ey(a,b){return a+b[1]}function ez(a,b){return eA(a,Math.ceil(Math.log(b.length)/Math.LN2+1))}function eA(a,b){var c=-1,d=+a[0],e=(a[1]-d)/b,f=[];while(++c<=b)f[c]=e*c+d;return f}function eB(a){return[d3.min(a),d3.max(a)]}function eC(a,b){return d3.rebind(a,b,"sort","children","value"),a.links=eG,a.nodes=function(b){return eH=!0,(a.nodes=a)(b)},a}function eD(a){return a.children}function eE(a){return a.value}function eF(a,b){return b.value-a.value}function eG(a){return d3.merge(a.map(function(a){return(a.children||[]).map(function(b){return{source:a,target:b}})}))}function eI(a,b){return a.value-b.value}function eJ(a,b){var c=a._pack_next;a._pack_next=b,b._pack_prev=a,b._pack_next=c,c._pack_prev=b}function eK(a,b){a._pack_next=b,b._pack_prev=a}function eL(a,b){var c=b.x-a.x,d=b.y-a.y,e=a.r+b.r;return e*e-c*c-d*d>.001}function eM(a){function n(a){c=Math.min(a.x-a.r,c),d=Math.max(a.x+a.r,d),e=Math.min(a.y-a.r,e),f=Math.max(a.y+a.r,f)}if(!(b=a.children)||!(m=b.length))return;var b,c=Infinity,d=-Infinity,e=Infinity,f=-Infinity,g,h,i,j,k,l,m;b.forEach(eN),g=b[0],g.x=-g.r,g.y=0,n(g);if(m>1){h=b[1],h.x=h.r,h.y=0,n(h);if(m>2){i=b[2],eQ(g,h,i),n(i),eJ(g,i),g._pack_prev=i,eJ(i,h),h=g._pack_next;for(j=3;j0&&(a=d)}return a}function eZ(a,b){return a.x-b.x}function e$(a,b){return b.x-a.x}function e_(a,b){return a.depth-b.depth}function fa(a,b){function c(a,d){var e=a.children;if(e&&(i=e.length)){var f,g=null,h=-1,i;while(++h=0)f=d[e]._tree,f.prelim+=b,f.mod+=b,b+=f.shift+(c+=f.change)}function fc(a,b,c){a=a._tree,b=b._tree;var d=c/(b.number-a.number);a.change+=d,b.change-=d,b.shift+=c,b.prelim+=c,b.mod+=c}function fd(a,b,c){return a._tree.ancestor.parent==b.parent?a._tree.ancestor:c}function fe(a){return{x:a.x,y:a.y,dx:a.dx,dy:a.dy}}function ff(a,b){var c=a.x+b[3],d=a.y+b[0],e=a.dx-b[1]-b[3],f=a.dy-b[0]-b[2];return e<0&&(c+=e/2,e=0),f<0&&(d+=f/2,f=0),{x:c,y:d,dx:e,dy:f}}function fg(a,b){function f(a,c){d3.text(a,b,function(a){c(a&&f.parse(a))})}function g(b){return b.map(h).join(a)}function h(a){return d.test(a)?'"'+a.replace(/\"/g,'""')+'"':a}var c=new RegExp("\r\n|["+a+"\r\n]","g"),d=new RegExp('["'+a+"\n]"),e=a.charCodeAt(0);return f.parse=function(a){var b;return f.parseRows(a,function(a,c){if(c){var d={},e=-1,f=b.length;while(++e=a.length)return f;if(j)return j=!1,d;var b=c.lastIndex;if(a.charCodeAt(b)===34){var g=b;while(g++0}function fC(a,b,c){return(c[0]-b[0])*(a[1]-b[1])<(c[1]-b[1])*(a[0]-b[0])}function fD(a,b,c,d){var e=a[0],f=b[0],g=c[0],h=d[0],i=a[1],j=b[1],k=c[1],l=d[1],m=e-g,n=f-e,o=h-g,p=i-k,q=j-i,r=l-k,s=(o*p-r*m)/(r*n-o*q);return[e+s*n,i+s*q]}function fF(a,b){var c={list:a.map(function(a,b){return{index:b,x:a[0],y:a[1]}}).sort(function(a,b){return a.yb.y?1:a.xb.x?1:0}),bottomSite:null},d={list:[],leftEnd:null,rightEnd:null,init:function(){d.leftEnd=d.createHalfEdge(null,"l"),d.rightEnd=d.createHalfEdge(null,"l"),d.leftEnd.r=d.rightEnd,d.rightEnd.l=d.leftEnd,d.list.unshift(d.leftEnd,d.rightEnd)},createHalfEdge:function(a,b){return{edge:a,side:b,vertex:null,l:null,r:null}},insert:function(a,b){b.l=a,b.r=a.r,a.r.l=b,a.r=b},leftBound:function(a){var b=d.leftEnd;do b=b.r;while(b!=d.rightEnd&&e.rightOf(b,a));return b=b.l,b},del:function(a){a.l.r=a.r,a.r.l=a.l,a.edge=null},right:function(a){return a.r},left:function(a){return a.l},leftRegion:function(a){return a.edge==null?c.bottomSite:a.edge.region[a.side]},rightRegion:function(a){return a.edge==null?c.bottomSite:a.edge.region[fE[a.side]]}},e={bisect:function(a,b){var c={region:{l:a,r:b},ep:{l:null,r:null}},d=b.x-a.x,e=b.y-a.y,f=d>0?d:-d,g=e>0?e:-e;return c.c=a.x*d+a.y*e+(d*d+e*e)*.5,f>g?(c.a=1,c.b=e/d,c.c/=d):(c.b=1,c.a=d/e,c.c/=e),c},intersect:function(a,b){var c=a.edge,d=b.edge;if(!c||!d||c.region.r==d.region.r)return null;var e=c.a*d.b-c.b*d.a;if(Math.abs(e)<1e-10)return null;var f=(c.c*d.b-d.c*c.b)/e,g=(d.c*c.a-c.c*d.a)/e,h=c.region.r,i=d.region.r,j,k;h.y=k.region.r.x;return l&&j.side==="l"||!l&&j.side==="r"?null:{x:f,y:g}},rightOf:function(a,b){var c=a.edge,d=c.region.r,e=b.x>d.x;if(e&&a.side==="l")return 1;if(!e&&a.side==="r")return 0;if(c.a===1){var f=b.y-d.y,g=b.x-d.x,h=0,i=0;!e&&c.b<0||e&&c.b>=0?i=h=f>=c.b*g:(i=b.x+b.y*c.b>c.c,c.b<0&&(i=!i),i||(h=1));if(!h){var j=d.x-c.region.l.x;i=c.b*(g*g-f*f)m*m+n*n}return a.side==="l"?i:!i},endPoint:function(a,c,d){a.ep[c]=d;if(!a.ep[fE[c]])return;b(a)},distance:function(a,b){var c=a.x-b.x,d=a.y-b.y;return Math.sqrt(c*c+d*d)}},f={list:[],insert:function(a,b,c){a.vertex=b,a.ystar=b.y+c;for(var d=0,e=f.list,g=e.length;dh.ystar||a.ystar==h.ystar&&b.x>h.vertex.x)continue;break}e.splice(d,0,a)},del:function(a){for(var b=0,c=f.list,d=c.length;bo.y&&(p=n,n=o,o=p,t="r"),s=e.bisect(n,o),m=d.createHalfEdge(s,t),d.insert(k,m),e.endPoint(s,fE[t],r),q=e.intersect(k,m),q&&(f.del(k),f.insert(k,q,e.distance(q,n))),q=e.intersect(m,l),q&&f.insert(m,q,e.distance(q,n));else break}for(i=d.right(d.leftEnd);i!=d.rightEnd;i=d.right(i))b(i.edge)}function fG(){return{leaf:!0,nodes:[],point:null}}function fH(a,b,c,d,e,f){if(!a(b,c,d,e,f)){var g=(c+e)*.5,h=(d+f)*.5,i=b.nodes;i[0]&&fH(a,i[0],c,d,g,h),i[1]&&fH(a,i[1],g,d,e,h),i[2]&&fH(a,i[2],c,h,g,f),i[3]&&fH(a,i[3],g,h,e,f)}}function fI(a){return{x:a[0],y:a[1]}}function fL(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function fU(a){return a.substring(0,3)}function fV(a,b,c,d){var e,f,g=0,h=b.length,i=c.length;while(g=i)return-1;e=b.charCodeAt(g++);if(e==37){f=gh[b.charAt(g++)];if(!f||(d=f(a,c,d))<0)return-1}else if(e!=c.charCodeAt(d++))return-1}return d}function fW(a){return new RegExp("^(?:"+a.map(d3.requote).join("|")+")","i")}function fX(a){var b=new j,c=-1,d=a.length;while(++c68?1900:2e3)}function gs(a,b,c){gy.lastIndex=0;var d=gy.exec(b.substring(c,c+2));return d?(a.m=d[0]-1,c+=d[0].length):-1}function gt(a,b,c){gy.lastIndex=0;var d=gy.exec(b.substring(c,c+2));return d?(a.d=+d[0],c+=d[0].length):-1}function gu(a,b,c){gy.lastIndex=0;var d=gy.exec(b.substring(c,c+2));return d?(a.H=+d[0],c+=d[0].length):-1}function gv(a,b,c){gy.lastIndex=0;var d=gy.exec(b.substring(c,c+2));return d?(a.M=+d[0],c+=d[0].length):-1}function gw(a,b,c){gy.lastIndex=0;var d=gy.exec(b.substring(c,c+2));return d?(a.S=+d[0],c+=d[0].length):-1}function gx(a,b,c){gy.lastIndex=0;var d=gy.exec(b.substring(c,c+3));return d?(a.L=+d[0],c+=d[0].length):-1}function gz(a,b,c){var d=gA.get(b.substring(c,c+=2).toLowerCase());return d==null?-1:(a.p=d,c)}function gB(a){var b=a.getTimezoneOffset(),c=b>0?"-":"+",d=~~(Math.abs(b)/60),e=Math.abs(b)%60;return c+fY(d)+fY(e)}function gD(a){return a.toISOString()}function gE(a,b,c){function d(b){var c=a(b),d=f(c,1);return b-c1)while(gb?1:a>=b?0:NaN},d3.descending=function(a,b){return ba?1:b>=a?0:NaN},d3.mean=function(a,b){var c=a.length,d,e=0,f=-1,g=0;if(arguments.length===1)while(++f1&&(a=a.map(b)),a=a.filter(r),a.length?d3.quantile(a.sort(d3.ascending),.5):undefined},d3.min=function(a,b){var c=-1,d=a.length,e,f;if(arguments.length===1){while(++cf&&(e=f)}else{while(++cf&&(e=f)}return e},d3.max=function(a,b){var c=-1,d=a.length,e,f;if(arguments.length===1){while(++ce&&(e=f)}else{while(++ce&&(e=f)}return e},d3.extent=function(a,b){var c=-1,d=a.length,e,f,g;if(arguments.length===1){while(++cf&&(e=f),gf&&(e=f),g1);return a+b*c*Math.sqrt(-2*Math.log(e)/e)}},logNormal:function(a,b){var c=arguments.length;c<2&&(b=1),c<1&&(a=0);var d=d3.random.normal();return function(){return Math.exp(a+b*d())}},irwinHall:function(a){return function(){for(var b=0,c=0;c>>1;a.call(b,b[f],f)>>1;c0&&(e=f);return e},d3.last=function(a,b){var c=0,d=a.length,e=a[0],f;arguments.length===1&&(b=d3.ascending);while(++c=b.length)return e?e.call(a,c):d?c.sort(d):c;var h=-1,i=c.length,k=b[g++],l,m,n=new j,o,p={};while(++h=b.length)return a;var e=[],f=c[d++],h;for(h in a)e.push({key:h,values:g(a[h],d)});return f&&e.sort(function(a,b){return f(a.key,b.key)}),e}var a={},b=[],c=[],d,e;return a.map=function(a){return f(a,0)},a.entries=function(a){return g(f(a,0),0)},a.key=function(c){return b.push(c),a},a.sortKeys=function(d){return c[b.length-1]=d,a},a.sortValues=function(b){return d=b,a},a.rollup=function(b){return e=b,a},a},d3.keys=function(a){var b=[];for(var c in a)b.push(c);return b},d3.values=function(a){var b=[];for(var c in a)b.push(a[c]);return b},d3.entries=function(a){var b=[];for(var c in a)b.push({key:c,value:a[c]});return b},d3.permute=function(a,b){var c=[],d=-1,e=b.length;while(++db)d.push(g/e);else while((g=a+c*++f)=200&&a<300||a===304?d:null)}},d.send(null)},d3.text=function(a,b,c){function d(a){c(a&&a.responseText)}arguments.length<3&&(c=b,b=null),d3.xhr(a,b,d)},d3.json=function(a,b){d3.text(a,"application/json",function(a){b(a?JSON.parse(a):null)})},d3.html=function(a,b){d3.text(a,"text/html",function(a){if(a!=null){var c=document.createRange();c.selectNode(document.body),a=c.createContextualFragment(a)}b(a)})},d3.xml=function(a,b,c){function d(a){c(a&&a.responseXML)}arguments.length<3&&(c=b,b=null),d3.xhr(a,b,d)};var y={svg:"http://www.w3.org/2000/svg",xhtml:"http://www.w3.org/1999/xhtml",xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"};d3.ns={prefix:y,qualify:function(a){var b=a.indexOf(":"),c=a;return b>=0&&(c=a.substring(0,b),a=a.substring(b+1)),y.hasOwnProperty(c)?{space:y[c],local:a}:a}},d3.dispatch=function(){var a=new z,b=-1,c=arguments.length;while(++b0&&(d=a.substring(c+1),a=a.substring(0,c)),arguments.length<2?this[a].on(d):this[a].on(d,b)},d3.format=function(a){var b=B.exec(a),c=b[1]||" ",d=b[3]||"",e=b[5],f=+b[6],g=b[7],h=b[8],i=b[9],j=1,k="",l=!1;h&&(h=+h.substring(1)),e&&(c="0",g&&(f-=Math.floor((f-1)/4)));switch(i){case"n":g=!0,i="g";break;case"%":j=100,k="%",i="f";break;case"p":j=100,k="%",i="r";break;case"d":l=!0,h=0;break;case"s":j=-1,i="r"}return i=="r"&&!h&&(i="g"),i=C.get(i)||E,function(a){if(l&&a%1)return"";var b=a<0&&(a=-a)?"-":d;if(j<0){var m=d3.formatPrefix(a,h);a=m.scale(a),k=m.symbol}else a*=j;a=i(a,h);if(e){var n=a.length+b.length;n=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/,C=d3.map({g:function(a,b){return a.toPrecision(b)},e:function(a,b){return a.toExponential(b)},f:function(a,b){return a.toFixed(b)},r:function(a,b){return d3.round(a,b=D(a,b)).toFixed(Math.max(0,Math.min(20,b)))}}),G=["y","z","a","f","p","n","μ","m","","k","M","G","T","P","E","Z","Y"].map(H);d3.formatPrefix=function(a,b){var c=0;return a&&(a<0&&(a*=-1),b&&(a=d3.round(a,D(a,b))),c=1+Math.floor(1e-12+Math.log(a)/Math.LN10),c=Math.max(-24,Math.min(24,Math.floor((c<=0?c+1:c-1)/3)*3))),G[8+c/3]};var I=R(2),J=R(3),K=function(){return Q},L=d3.map({linear:K,poly:R,quad:function(){return I},cubic:function(){return J},sin:function(){return S},exp:function(){return T},circle:function(){return U},elastic:V,back:W,bounce:function(){return X}}),M=d3.map({"in":Q,out:O,"in-out":P,"out-in":function(a){return P(O(a))}});d3.ease=function(a){var b=a.indexOf("-"),c=b>=0?a.substring(0,b):a,d=b>=0?a.substring(b+1):"in";return c=L.get(c)||K,d=M.get(d)||Q,N(d(c.apply(null,Array.prototype.slice.call(arguments,1))))},d3.event=null,d3.transform=function(a){var b=document.createElementNS(d3.ns.prefix.svg,"g");return(d3.transform=function(a){b.setAttribute("transform",a);var c=b.transform.baseVal.consolidate();return new _(c?c.matrix:be)})(a)},_.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var bd=180/Math.PI,be={a:1,b:0,c:0,d:1,e:0,f:0};d3.interpolate=function(a,b){var c=d3.interpolators.length,d;while(--c>=0&&!(d=d3.interpolators[c](a,b)));return d},d3.interpolateNumber=function(a,b){return b-=a,function(c){return a+b*c}},d3.interpolateRound=function(a,b){return b-=a,function(c){return Math.round(a+b*c)}},d3.interpolateString=function(a,b){var c,d,e,f=0,g=0,h=[],i=[],j,k;bf.lastIndex=0;for(d=0;c=bf.exec(b);++d)c.index&&h.push(b.substring(f,g=c.index)),i.push({i:h.length,x:c[0]}),h.push(null),f=bf.lastIndex;f180?k+=360:k-j>180&&(j+=360),d.push({i:c.push(c.pop()+"rotate(",null,")")-2,x:d3.interpolateNumber(j,k)})):k&&c.push(c.pop()+"rotate("+k+")"),l!=m?d.push({i:c.push(c.pop()+"skewX(",null,")")-2,x:d3.interpolateNumber(l,m)}):m&&c.push(c.pop()+"skewX("+m+")"),n[0]!=o[0]||n[1]!=o[1]?(e=c.push(c.pop()+"scale(",null,",",null,")"),d.push({i:e-4,x:d3.interpolateNumber(n[0],o[0])},{i:e-2,x:d3.interpolateNumber(n[1],o[1])})):(o[0]!=1||o[1]!=1)&&c.push(c.pop()+"scale("+o+")"),e=d.length,function(a){var b=-1,f;while(++b180?f-=360:f<-180&&(f+=360),function(a){return bu(c+f*a,d+g*a,e+h*a)+""}},d3.interpolateLab=function(a,b){a=d3.lab(a),b=d3.lab(b);var c=a.l,d=a.a,e=a.b,f=b.l-c,g=b.a-d,h=b.b-e;return function(a){return bE(c+f*a,d+g*a,e+h*a)+""}},d3.interpolateHcl=function(a,b){a=d3.hcl(a),b=d3.hcl(b);var c=a.h,d=a.c,e=a.l,f=b.h-c,g=b.c-d,h=b.l-e;return f>180?f-=360:f<-180&&(f+=360),function(a){return bx(c+f*a,d+g*a,e+h*a)+""}},d3.interpolateArray=function(a,b){var c=[],d=[],e=a.length,f=b.length,g=Math.min(a.length,b.length),h;for(h=0;h=0;)if(f=c[d])e&&e!==f.nextSibling&&e.parentNode.insertBefore(f,e),e=f;return this},bP.sort=function(a){a=b$.apply(this,arguments);for(var b=-1,c=this.length;++b=db?e?"M0,"+f+"A"+f+","+f+" 0 1,1 0,"+ -f+"A"+f+","+f+" 0 1,1 0,"+f+"M0,"+e+"A"+e+","+e+" 0 1,0 0,"+ -e+"A"+e+","+e+" 0 1,0 0,"+e+"Z":"M0,"+f+"A"+f+","+f+" 0 1,1 0,"+ -f+"A"+f+","+f+" 0 1,1 0,"+f+"Z":e?"M"+f*k+","+f*l+"A"+f+","+f+" 0 "+j+",1 "+f*m+","+f*n+"L"+e*m+","+e*n+"A"+e+","+e+" 0 "+j+",0 "+e*k+","+e*l+"Z":"M"+f*k+","+f*l+"A"+f+","+f+" 0 "+j+",1 "+f*m+","+f*n+"L0,0"+"Z"}var a=dc,b=dd,c=de,d=df;return e.innerRadius=function(b){return arguments.length?(a=p(b),e):a},e.outerRadius=function(a){return arguments.length?(b=p(a),e):b},e.startAngle=function(a){return arguments.length?(c=p(a),e):c},e.endAngle=function(a){return arguments.length?(d=p(a),e):d},e.centroid=function(){var e=(a.apply(this,arguments)+b.apply(this,arguments))/2,f=(c.apply(this,arguments)+d.apply(this,arguments))/2+da;return[Math.cos(f)*e,Math.sin(f)*e]},e};var da=-Math.PI/2,db=2*Math.PI-1e-6;d3.svg.line=function(){return dg(m)};var dj=d3.map({linear:dk,"linear-closed":dl,"step-before":dm,"step-after":dn,basis:du,"basis-open":dv,"basis-closed":dw,bundle:dx,cardinal:dr,"cardinal-open":dp,"cardinal-closed":dq,monotone:dG});dj.forEach(function(a,b){b.key=a,b.closed=/-closed$/.test(a)});var dz=[0,2/3,1/3,0],dA=[0,1/3,2/3,0],dB=[0,1/6,2/3,1/6];d3.svg.line.radial=function(){var a=dg(dH);return a.radius=a.x,delete a.x,a.angle=a.y,delete a.y,a},dm.reverse=dn,dn.reverse=dm,d3.svg.area=function(){return dI(m)},d3.svg.area.radial=function(){var a=dI(dH);return a.radius=a.x,delete a.x,a.innerRadius=a.x0,delete a.x0,a.outerRadius=a.x1,delete a.x1,a.angle=a.y,delete a.y,a.startAngle=a.y0,delete a.y0,a.endAngle=a.y1,delete a.y1,a},d3.svg.chord=function(){function f(c,d){var e=g(this,a,c,d),f=g(this,b,c,d);return"M"+e.p0+i(e.r,e.p1,e.a1-e.a0)+(h(e,f)?j(e.r,e.p1,e.r,e.p0):j(e.r,e.p1,f.r,f.p0)+i(f.r,f.p1,f.a1-f.a0)+j(f.r,f.p1,e.r,e.p0))+"Z"}function g(a,b,f,g){var h=b.call(a,f,g),i=c.call(a,h,g),j=d.call(a,h,g)+da,k=e.call(a,h,g)+da;return{r:i,a0:j,a1:k,p0:[i*Math.cos(j),i*Math.sin(j)],p1:[i*Math.cos(k),i*Math.sin(k)]}}function h(a,b){return a.a0==b.a0&&a.a1==b.a1}function i(a,b,c){return"A"+a+","+a+" 0 "+ +(c>Math.PI)+",1 "+b}function j(a,b,c,d){return"Q 0,0 "+d}var a=dJ,b=dK,c=dL,d=de,e=df;return f.radius=function(a){return arguments.length?(c=p(a),f):c},f.source=function(b){return arguments.length?(a=p(b),f):a},f.target=function(a){return arguments.length?(b=p(a),f):b},f.startAngle=function(a){return arguments.length?(d=p(a),f):d},f.endAngle=function(a){return arguments.length?(e=p(a),f):e},f},d3.svg.diagonal=function(){function d(d,e){var f=a.call(this,d,e),g=b.call(this,d,e),h=(f.y+g.y)/2,i=[f,{x:f.x,y:h},{x:g.x,y:h},g];return i=i.map(c),"M"+i[0]+"C"+i[1]+" "+i[2]+" "+i[3]}var a=dJ,b=dK,c=dO;return d.source=function(b){return arguments.length?(a=p(b),d):a},d.target=function(a){return arguments.length?(b=p(a),d):b},d.projection=function(a){return arguments.length?(c=a,d):c},d},d3.svg.diagonal.radial=function(){var a=d3.svg.diagonal(),b=dO,c=a.projection;return a.projection=function(a){return arguments.length?c(dP(b=a)):b},a},d3.svg.mouse=d3.mouse,d3.svg.touches=d3.touches,d3.svg.symbol=function(){function c(c,d){return(dT.get(a.call(this,c,d))||dS)(b.call(this,c,d))}var a=dR,b=dQ;return c.type=function(b){return arguments.length?(a=p(b),c):a},c.size=function(a){return arguments.length?(b=p(a),c):b},c};var dT=d3.map({circle:dS,cross:function(a){var b=Math.sqrt(a/5)/2;return"M"+ -3*b+","+ -b+"H"+ -b+"V"+ -3*b+"H"+b+"V"+ -b+"H"+3*b+"V"+b+"H"+b+"V"+3*b+"H"+ -b+"V"+b+"H"+ -3*b+"Z"},diamond:function(a){var b=Math.sqrt(a/(2*dV)),c=b*dV;return"M0,"+ -b+"L"+c+",0"+" 0,"+b+" "+ -c+",0"+"Z"},square:function(a){var b=Math.sqrt(a)/2;return"M"+ -b+","+ -b+"L"+b+","+ -b+" "+b+","+b+" "+ -b+","+b+"Z"},"triangle-down":function(a){var b=Math.sqrt(a/dU),c=b*dU/2;return"M0,"+c+"L"+b+","+ -c+" "+ -b+","+ -c+"Z"},"triangle-up":function(a){var b=Math.sqrt(a/dU),c=b*dU/2;return"M0,"+ -c+"L"+b+","+c+" "+ -b+","+c+"Z"}});d3.svg.symbolTypes=dT.keys();var dU=Math.sqrt(3),dV=Math.tan(30*Math.PI/180);d3.svg.axis=function(){function k(k){k.each(function(){var k=d3.select(this),l=h==null?a.ticks?a.ticks.apply(a,g):a.domain():h,m=i==null?a.tickFormat?a.tickFormat.apply(a,g):String:i,n=dY(a,l,j),o=k.selectAll(".minor").data(n,String),p=o.enter().insert("line","g").attr("class","tick minor").style("opacity",1e-6),q=d3.transition(o.exit()).style("opacity",1e-6).remove(),r=d3.transition(o).style("opacity",1),s=k.selectAll("g").data(l,String),t=s.enter().insert("g","path").style("opacity",1e-6),u=d3.transition(s.exit()).style("opacity",1e-6).remove(),v=d3.transition(s).style("opacity",1),w,x=cC(a),y=k.selectAll(".domain").data([0]),z=y.enter().append("path").attr("class","domain"),A=d3.transition(y),B=a.copy(),C=this.__chart__||B;this.__chart__=B,t.append("line").attr("class","tick"),t.append("text");var D=t.select("line"),E=v.select("line"),F=s.select("text").text(m),G=t.select("text"),H=v.select("text");switch(b){case"bottom":w=dW,p.attr("y2",d),r.attr("x2",0).attr("y2",d),D.attr("y2",c),G.attr("y",Math.max(c,0)+f),E.attr("x2",0).attr("y2",c),H.attr("x",0).attr("y",Math.max(c,0)+f),F.attr("dy",".71em").attr("text-anchor","middle"),A.attr("d","M"+x[0]+","+e+"V0H"+x[1]+"V"+e);break;case"top":w=dW,p.attr("y2",-d),r.attr("x2",0).attr("y2",-d),D.attr("y2",-c),G.attr("y",-(Math.max(c,0)+f)),E.attr("x2",0).attr("y2",-c),H.attr("x",0).attr("y",-(Math.max(c,0)+f)),F.attr("dy","0em").attr("text-anchor","middle"),A.attr("d","M"+x[0]+","+ -e+"V0H"+x[1]+"V"+ -e);break;case"left":w=dX,p.attr("x2",-d),r.attr("x2",-d).attr("y2",0),D.attr("x2",-c),G.attr("x",-(Math.max(c,0)+f)),E.attr("x2",-c).attr("y2",0),H.attr("x",-(Math.max(c,0)+f)).attr("y",0),F.attr("dy",".32em").attr("text-anchor","end"),A.attr("d","M"+ -e+","+x[0]+"H0V"+x[1]+"H"+ -e);break;case"right":w=dX,p.attr("x2",d),r.attr("x2",d).attr("y2",0),D.attr("x2",c),G.attr("x",Math.max(c,0)+f),E.attr("x2",c).attr("y2",0),H.attr("x",Math.max(c,0)+f).attr("y",0),F.attr("dy",".32em").attr("text-anchor","start"),A.attr("d","M"+e+","+x[0]+"H0V"+x[1]+"H"+e)}if(a.ticks)t.call(w,C),v.call(w,B),u.call(w,B),p.call(w,C),r.call(w,B),q.call(w,B);else{var I=B.rangeBand()/2,J=function(a){return B(a)+I};t.call(w,J),v.call(w,J)}})}var a=d3.scale.linear(),b="bottom",c=6,d=6,e=6,f=3,g=[10],h=null,i,j=0;return k.scale=function(b){return arguments.length?(a=b,k):a},k.orient=function(a){return arguments.length?(b=a,k):b},k.ticks=function(){return arguments.length?(g=arguments,k):g},k.tickValues=function(a){return arguments.length?(h=a,k):h},k.tickFormat=function(a){return arguments.length?(i=a,k):i},k.tickSize=function(a,b,f){if(!arguments.length)return c;var g=arguments.length-1;return c=+a,d=g>1?+b:c,e=g>0?+arguments[g]:c,k},k.tickPadding=function(a){return arguments.length?(f=+a,k):f},k.tickSubdivide=function(a){return arguments.length?(j=+a,k):j},k},d3.svg.brush=function(){function g(a){a.each(function(){var a=d3.select(this),e=a.selectAll(".background").data([0]),f=a.selectAll(".extent").data([0]),l=a.selectAll(".resize").data(d,String),m;a.style("pointer-events","all").on("mousedown.brush",k).on("touchstart.brush",k),e.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),f.enter().append("rect").attr("class","extent").style("cursor","move"),l.enter().append("g").attr("class",function(a){return"resize "+a}).style("cursor",function(a){return dZ[a]}).append("rect").attr("x",function(a){return/[ew]$/.test(a)?-3:null}).attr("y",function(a){return/^[ns]/.test(a)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),l.style("display",g.empty()?"none":null),l.exit().remove(),b&&(m=cC(b),e.attr("x",m[0]).attr("width",m[1]-m[0]),i(a)),c&&(m=cC(c),e.attr("y",m[0]).attr("height",m[1]-m[0]),j(a)),h(a)})}function h(a){a.selectAll(".resize").attr("transform",function(a){return"translate("+e[+/e$/.test(a)][0]+","+e[+/^s/.test(a)][1]+")"})}function i(a){a.select(".extent").attr("x",e[0][0]),a.selectAll(".extent,.n>rect,.s>rect").attr("width",e[1][0]-e[0][0])}function j(a){a.select(".extent").attr("y",e[0][1]),a.selectAll(".extent,.e>rect,.w>rect").attr("height",e[1][1]-e[0][1])}function k(){function x(){var a=d3.event.changedTouches;return a?d3.touches(d,a)[0]:d3.mouse(d)}function y(){d3.event.keyCode==32&&(q||(r=null,s[0]-=e[1][0],s[1]-=e[1][1],q=2),Y())}function z(){d3.event.keyCode==32&&q==2&&(s[0]+=e[1][0],s[1]+=e[1][1],q=0,Y())}function A(){var a=x(),d=!1;t&&(a[0]+=t[0],a[1]+=t[1]),q||(d3.event.altKey?(r||(r=[(e[0][0]+e[1][0])/2,(e[0][1]+e[1][1])/2]),s[0]=e[+(a[0]0?e=c:e=0:c>0&&(b.start({type:"start",alpha:e=c}),d3.timer(a.tick)),a):e},a.start=function(){function p(a,c){var d=t(b),e=-1,f=d.length,g;while(++ee&&(e=h),d.push(h)}for(g=0;g0){f=-1;while(++f=i[0]&&o<=i[1]&&(k=g[d3.bisect(j,o,1,m)-1],k.y+=n,k.push(e[f]))}return g}var a=!0,b=Number,c=eB,d=ez;return e.value=function(a){return arguments.length?(b=a,e):b},e.range=function(a){return arguments.length?(c=p(a),e):c},e.bins=function(a){return arguments.length?(d=typeof a=="number"?function(b){return eA(b,a)}:p(a),e):d},e.frequency=function(b){return arguments.length?(a=!!b,e):a},e},d3.layout.hierarchy=function(){function d(e,g,h){var i=b.call(f,e,g),j=eH?e:{data:e};j.depth=g,h.push(j);if(i&&(l=i.length)){var k=-1,l,m=j.children=[],n=0,o=g+1,p;while(++k0){var k=b*j/2;fa(g,function(a){a.r+=k}),fa(g,eM),fa(g,function(a){a.r-=k}),j=Math.max(2*g.r/h,2*g.r/i)}return eP(g,h/2,i/2,1/j),f}var a=d3.layout.hierarchy().sort(eI),b=0,c=[1,1];return d.size=function(a){return arguments.length?(c=a,d):c},d.padding=function(a){return arguments.length?(b=+a,d):b},eC(d,a)},d3.layout.cluster=function(){function d(d,e){var f=a.call(this,d,e),g=f[0],h,i=0,j,k;fa(g,function(a){var c=a.children;c&&c.length?(a.x=eS(c),a.y=eR(c)):(a.x=h?i+=b(a,h):0,a.y=0,h=a)});var l=eT(g),m=eU(g),n=l.x-b(l,m)/2,o=m.x+b(m,l)/2;return fa(g,function(a){a.x=(a.x-n)/(o-n)*c[0],a.y=(1-(g.y?a.y/g.y:1))*c[1]}),f}var a=d3.layout.hierarchy().sort(null).value(null),b=eV,c=[1,1];return d.separation=function(a){return arguments.length?(b=a,d):b},d.size=function(a){return arguments.length?(c=a,d):c},eC(d,a)},d3.layout.tree=function(){function d(d,e){function h(a,c){var d=a.children,e=a._tree;if(d&&(f=d.length)){var f,g=d[0],i,k=g,l,m=-1;while(++m0&&(fc(fd(g,a,d),a,m),i+=m,j+=m),k+=g._tree.mod,i+=e._tree.mod,l+=h._tree.mod,j+=f._tree.mod;g&&!eX(f)&&(f._tree.thread=g,f._tree.mod+=k-j),e&&!eW(h)&&(h._tree.thread=e,h._tree.mod+=i-l,d=a)}return d}var f=a.call(this,d,e),g=f[0];fa(g,function(a,b){a._tree={ancestor:a,prelim:0,mod:0,change:0,shift:0,number:b?b._tree.number+1:0}}),h(g),i(g,-g._tree.prelim);var k=eY(g,e$),l=eY(g,eZ),m=eY(g,e_),n=k.x-b(k,l)/2,o=l.x+b(l,k)/2,p=m.depth||1;return fa(g,function(a){a.x=(a.x-n)/(o-n)*c[0],a.y=a.depth/p*c[1],delete a._tree}),f}var a=d3.layout.hierarchy().sort(null).value(null),b=eV,c=[1,1];return d.separation=function(a){return arguments.length?(b=a,d):b},d.size=function(a){return arguments.length?(c=a,d):c},eC(d,a)},d3.layout.treemap=function(){function i(a,b){var c=-1,d=a.length,e,f;while(++c0)d.push(g=f[o-1]),d.area+=g.area,(k=l(d,n))<=h?(f.pop(),h=k):(d.area-=d.pop().area,m(d,n,c,!1),n=Math.min(c.dx,c.dy),d.length=d.area=0,h=Infinity);d.length&&(m(d,n,c,!0),d.length=d.area=0),b.forEach(j)}}function k(a){var b=a.children;if(b&&b.length){var c=e(a),d=b.slice(),f,g=[];i(d,c.dx*c.dy/a.value),g.area=0;while(f=d.pop())g.push(f),g.area+=f.area,f.z!=null&&(m(g,f.z?c.dx:c.dy,c,!d.length),g.length=g.area=0);b.forEach(k)}}function l(a,b){var c=a.area,d,e=0,f=Infinity,g=-1,i=a.length;while(++ge&&(e=d)}return c*=c,b*=b,c?Math.max(b*e*h/c,c/(b*f*h)):Infinity}function m(a,c,d,e){var f=-1,g=a.length,h=d.x,i=d.y,j=c?b(a.area/c):0,k;if(c==d.dx){if(e||j>d.dy)j=d.dy;while(++fd.dx)j=d.dx;while(++f50?b:f<-140?c:g<21?d:a)(e)}var a=d3.geo.albers(),b=d3.geo.albers().origin([-160,60]).parallels([55,65]),c=d3.geo.albers().origin([-160,20]).parallels([8,18]),d=d3.geo.albers().origin([-60,10]).parallels([8,18]);return e.scale=function(f){return arguments.length?(a.scale(f),b.scale(f*.6),c.scale(f),d.scale(f*1.5),e.translate(a.translate())):a.scale()},e.translate=function(f){if(!arguments.length)return a.translate();var g=a.scale()/1e3,h=f[0],i=f[1];return a.translate(f),b.translate([h-400*g,i+170*g]),c.translate([h-190*g,i+200*g]),d.translate([h+580*g,i+430*g]),e},e.scale(a.scale())},d3.geo.bonne=function(){function g(g){var h=g[0]*fh-c,i=g[1]*fh-d;if(e){var j=f+e-i,k=h*Math.cos(i)/j;h=j*Math.sin(k),i=j*Math.cos(k)-f}else h*=Math.cos(i),i*=-1;return[a*h+b[0],a*i+b[1]]}var a=200,b=[480,250],c,d,e,f;return g.invert=function(d){var g=(d[0]-b[0])/a,h=(d[1]-b[1])/a;if(e){var i=f+h,j=Math.sqrt(g*g+i*i);h=f+e-j,g=c+j*Math.atan2(g,i)/Math.cos(h)}else h*=-1,g/=Math.cos(h);return[g/fh,h/fh]},g.parallel=function(a){return arguments.length?(f=1/ 4 | Math.tan(e=a*fh),g):e/fh},g.origin=function(a){return arguments.length?(c=a[0]*fh,d=a[1]*fh,g):[c/fh,d/fh]},g.scale=function(b){return arguments.length?(a=+b,g):a},g.translate=function(a){return arguments.length?(b=[+a[0],+a[1]],g):b},g.origin([0,0]).parallel(45)},d3.geo.equirectangular=function(){function c(c){var d=c[0]/360,e=-c[1]/360;return[a*d+b[0],a*e+b[1]]}var a=500,b=[480,250];return c.invert=function(c){var d=(c[0]-b[0])/a,e=(c[1]-b[1])/a;return[360*d,-360*e]},c.scale=function(b){return arguments.length?(a=+b,c):a},c.translate=function(a){return arguments.length?(b=[+a[0],+a[1]],c):b},c},d3.geo.mercator=function(){function c(c){var d=c[0]/360,e=-(Math.log(Math.tan(Math.PI/4+c[1]*fh/2))/fh)/360;return[a*d+b[0],a*Math.max(-0.5,Math.min(.5,e))+b[1]]}var a=500,b=[480,250];return c.invert=function(c){var d=(c[0]-b[0])/a,e=(c[1]-b[1])/a;return[360*d,2*Math.atan(Math.exp(-360*e*fh))/fh-90]},c.scale=function(b){return arguments.length?(a=+b,c):a},c.translate=function(a){return arguments.length?(b=[+a[0],+a[1]],c):b},c},d3.geo.path=function(){function e(c,e){typeof a=="function"&&(b=fj(a.apply(this,arguments))),g(c);var f=d.length?d.join(""):null;return d=[],f}function f(a){return c(a).join(",")}function i(a){var b=l(a[0]),c=0,d=a.length;while(++c0){d.push("M");while(++h0){d.push("M");while(++kd&&(d=a),fe&&(e=f)}),[[b,c],[d,e]]};var fl={Feature:fm,FeatureCollection:fn,GeometryCollection:fo,LineString:fp,MultiLineString:fq,MultiPoint:fp,MultiPolygon:fr,Point:fs,Polygon:ft};d3.geo.circle=function(){function e(){}function f(a){return d.distance(a)=k*k+l*l?d[f].index=-1:(d[m].index=-1,o=d[f].angle,m=f,n=g)):(o=d[f].angle,m=f,n=g);e.push(h);for(f=0,g=0;f<2;++g)d[g].index!==-1&&(e.push(d[g].index),f++);p=e.length;for(;g=0?(c=a.ep.r,d=a.ep.l):(c=a.ep.l,d=a.ep.r),a.a===1?(g=c?c.y:-1e6,e=a.c-a.b*g,h=d?d.y:1e6,f=a.c-a.b*h):(e=c?c.x:-1e6,g=a.c-a.a*e,f=d?d.x:1e6,h=a.c-a.a*f);var i=[e,g],j=[f,h];b[a.region.l.index].push(i,j),b[a.region.r.index].push(i,j)}),b.map(function(b,c){var d=a[c][0],e=a[c][1];return b.forEach(function(a){a.angle=Math.atan2(a[0]-d,a[1]-e)}),b.sort(function(a,b){return a.angle-b.angle}).filter(function(a,c){return!c||a.angle-b[c-1].angle>1e-10})})};var fE={l:"r",r:"l"};d3.geom.delaunay=function(a){var b=a.map(function(){return[]}),c=[];return fF(a,function(c){b[c.region.l.index].push(a[c.region.r.index])}),b.forEach(function(b,d){var e=a[d],f=e[0],g=e[1];b.forEach(function(a){a.angle=Math.atan2(a[0]-f,a[1]-g)}),b.sort(function(a,b){return a.angle-b.angle});for(var h=0,i=b.length-1;h=g,j=b.y>=h,l=(j<<1)+i;a.leaf=!1,a=a.nodes[l]||(a.nodes[l]=fG()),i?c=g:e=g,j?d=h:f=h,k(a,b,c,d,e,f)}var f,g=-1,h=a.length;h&&isNaN(a[0].x)&&(a=a.map(fI));if(arguments.length<5)if(arguments.length===3)e=d=c,c=b;else{b=c=Infinity,d=e=-Infinity;while(++gd&&(d=f.x),f.y>e&&(e=f.y);var i=d-b,j=e-c;i>j?e=c+i:d=b+j}var m=fG();return m.add=function(a){k(m,a,b,c,d,e)},m.visit=function(a){fH(a,m,b,c,d,e)},a.forEach(m.add),m},d3.time={};var fJ=Date,fK=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];fL.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){fM.setUTCDate.apply(this._,arguments)},setDay:function(){fM.setUTCDay.apply(this._,arguments)},setFullYear:function(){fM.setUTCFullYear.apply(this._,arguments)},setHours:function(){fM.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){fM.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){fM.setUTCMinutes.apply(this._,arguments)},setMonth:function(){fM.setUTCMonth.apply(this._,arguments)},setSeconds:function(){fM.setUTCSeconds.apply(this._,arguments)},setTime:function(){fM.setTime.apply(this._,arguments)}};var fM=Date.prototype,fN="%a %b %e %H:%M:%S %Y",fO="%m/%d/%y",fP="%H:%M:%S",fQ=fK,fR=fQ.map(fU),fS=["January","February","March","April","May","June","July","August","September","October","November","December"],fT=fS.map(fU);d3.time.format=function(a){function c(c){var d=[],e=-1,f=0,g,h;while(++e=12?"PM":"AM"},S:function(a){return fY(a.getSeconds())},U:function(a){return fY(d3.time.sundayOfYear(a))},w:function(a){return a.getDay()},W:function(a){return fY(d3.time.mondayOfYear(a))},x:d3.time.format(fO),X:d3.time.format(fP),y:function(a){return fY(a.getFullYear()%100)},Y:function(a){return f$(a.getFullYear()%1e4)},Z:gB,"%":function(a){return"%"}},gh={a:gi,A:gj,b:gk,B:gl,c:gm,d:gt,e:gt,H:gu,I:gu,L:gx,m:gs,M:gv,p:gz,S:gw,x:gn,X:go,y:gq,Y:gp},gy=/^\s*\d+/,gA=d3.map({am:0,pm:1});d3.time.format.utc=function(a){function c(a){try{fJ=fL;var c=new fJ;return c._=a,b(c)}finally{fJ=Date}}var b=d3.time.format(a);return c.parse=function(a){try{fJ=fL;var c=b.parse(a);return c&&c._}finally{fJ=Date}},c.toString=b.toString,c};var gC=d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ");d3.time.format.iso=Date.prototype.toISOString?gD:gC,gD.parse=function(a){var b=new Date(a);return isNaN(b)?null:b},gD.toString=gC.toString,d3.time.second=gE(function(a){return new fJ(Math.floor(a/1e3)*1e3)},function(a,b){a.setTime(a.getTime()+Math.floor(b)*1e3)},function(a){return a.getSeconds()}),d3.time.seconds=d3.time.second.range,d3.time.seconds.utc=d3.time.second.utc.range,d3.time.minute=gE(function(a){return new fJ(Math.floor(a/6e4)*6e4)},function(a,b){a.setTime(a.getTime()+Math.floor(b)*6e4)},function(a){return a.getMinutes()}),d3.time.minutes=d3.time.minute.range,d3.time.minutes.utc=d3.time.minute.utc.range,d3.time.hour=gE(function(a){var b=a.getTimezoneOffset()/60;return new fJ((Math.floor(a/36e5-b)+b)*36e5)},function(a,b){a.setTime(a.getTime()+Math.floor(b)*36e5)},function(a){return a.getHours()}),d3.time.hours=d3.time.hour.range,d3.time.hours.utc=d3.time.hour.utc.range,d3.time.day=gE(function(a){var b=new fJ(0,a.getMonth(),a.getDate());return b.setFullYear(a.getFullYear()),b},function(a,b){a.setDate(a.getDate()+b)},function(a){return a.getDate()-1}),d3.time.days=d3.time.day.range,d3.time.days.utc=d3.time.day.utc.range,d3.time.dayOfYear=function(a){var b=d3.time.year(a);return Math.floor((a-b-(a.getTimezoneOffset()-b.getTimezoneOffset())*6e4)/864e5)},fK.forEach(function(a,b){a=a.toLowerCase(),b=7-b;var c=d3.time[a]=gE(function(a){return(a=d3.time.day(a)).setDate(a.getDate()-(a.getDay()+b)%7),a},function(a,b){a.setDate(a.getDate()+Math.floor(b)*7)},function(a){var c=d3.time.year(a).getDay();return Math.floor((d3.time.dayOfYear(a)+(c+b)%7)/7)-(c!==b)});d3.time[a+"s"]=c.range,d3.time[a+"s"].utc=c.utc.range,d3.time[a+"OfYear"]=function(a){var c=d3.time.year(a).getDay();return Math.floor((d3.time.dayOfYear(a)+(c+b)%7)/7)}}),d3.time.week=d3.time.sunday,d3.time.weeks=d3.time.sunday.range,d3.time.weeks.utc=d3.time.sunday.utc.range,d3.time.weekOfYear=d3.time.sundayOfYear,d3.time.month=gE(function(a){return a=d3.time.day(a),a.setDate(1),a},function(a,b){a.setMonth(a.getMonth()+b)},function(a){return a.getMonth()}),d3.time.months=d3.time.month.range,d3.time.months.utc=d3.time.month.utc.range,d3.time.year=gE(function(a){return a=d3.time.day(a),a.setMonth(0,1),a},function(a,b){a.setFullYear(a.getFullYear()+b)},function(a){return a.getFullYear()}),d3.time.years=d3.time.year.range,d3.time.years.utc=d3.time.year.utc.range;var gM=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],gN=[[d3.time.second,1],[d3.time.second,5],[d3.time.second,15],[d3.time.second,30],[d3.time.minute,1],[d3.time.minute,5],[d3.time.minute,15],[d3.time.minute,30],[d3.time.hour,1],[d3.time.hour,3],[d3.time.hour,6],[d3.time.hour,12],[d3.time.day,1],[d3.time.day,2],[d3.time.week,1],[d3.time.month,1],[d3.time.month,3],[d3.time.year,1]],gO=[[d3.time.format("%Y"),function(a){return!0}],[d3.time.format("%B"),function(a){return a.getMonth()}],[d3.time.format("%b %d"),function(a){return a.getDate()!=1}],[d3.time.format("%a %d"),function(a){return a.getDay()&&a.getDate()!=1}],[d3.time.format("%I %p"),function(a){return a.getHours()}],[d3.time.format("%I:%M"),function(a){return a.getMinutes()}],[d3.time.format(":%S"),function(a){return a.getSeconds()}],[d3.time.format(".%L"),function(a){return a.getMilliseconds()}]],gP=d3.scale.linear(),gQ=gJ(gO);gN.year=function(a,b){return gP.domain(a.map(gL)).ticks(b).map(gK)},d3.time.scale=function(){return gG(d3.scale.linear(),gN,gQ)};var gR=gN.map(function(a){return[a[0].utc,a[1]]}),gS=[[d3.time.format.utc("%Y"),function(a){return!0}],[d3.time.format.utc("%B"),function(a){return a.getUTCMonth()}],[d3.time.format.utc("%b %d"),function(a){return a.getUTCDate()!=1}],[d3.time.format.utc("%a %d"),function(a){return a.getUTCDay()&&a.getUTCDate()!=1}],[d3.time.format.utc("%I %p"),function(a){return a.getUTCHours()}],[d3.time.format.utc("%I:%M"),function(a){return a.getUTCMinutes()}],[d3.time.format.utc(":%S"),function(a){return a.getUTCSeconds()}],[d3.time.format.utc(".%L"),function(a){return a.getUTCMilliseconds()}]],gT=gJ(gS);gR.year=function(a,b){return gP.domain(a.map(gV)).ticks(b).map(gU)},d3.time.scale.utc=function(){return gG(d3.scale.linear(),gR,gT)}})(); -------------------------------------------------------------------------------- /data.js: -------------------------------------------------------------------------------- 1 | ;(function (exports) { 2 | 3 | exports.gg.sampleData = {}; 4 | 5 | // Random upward trending data 6 | gg.sampleData.upward = (function () { 7 | var x = 0; 8 | var y = 0; 9 | return _.map(_.range(20), function () { 10 | x += Math.random() * 30; 11 | y += 20 - Math.random() * 30; 12 | return { d: x, r: y }; 13 | }); 14 | }()); 15 | 16 | // Random upward trending paired data 17 | gg.sampleData.upwardPairs = (function () { 18 | var x = 0; 19 | var y0 = 0; 20 | var y1 = 0; 21 | return _.map(_.range(20), function () { 22 | x += Math.random() * 30; 23 | y0 += 20 - Math.random() * 30; 24 | y1 = y0 + Math.random() * 10; 25 | return { a: x, top: y0, bottom: y1 }; 26 | }); 27 | }()); 28 | 29 | 30 | // Data above and below zero. 31 | gg.sampleData.toBeCentered = (function () { 32 | var x = 0; 33 | var y = 0; 34 | return _.map(_.range(20), function () { 35 | x += Math.round(Math.random() * 30); 36 | if (Math.random() > .5) { 37 | y = (Math.round(Math.random() * 30)); 38 | } else { 39 | y = -1 * (Math.round(Math.random() * 20)) 40 | } 41 | return { d: x, r: y }; 42 | }); 43 | }()); 44 | 45 | // Random upward trending data for four subjects 46 | gg.sampleData.upwardSubjects = (function () { 47 | var subjects = ['a', 'b', 'c', 'd']; 48 | var x = 0; 49 | var y = 0; 50 | return _.flatten(_.map(_.range(20), function (i) { 51 | x += Math.round(Math.random() * 30); 52 | y += Math.round(Math.abs(20 - Math.random() * 30)); 53 | return _.map(subjects, function(subject, i) { 54 | var skew = i + 1; 55 | return { d: x, r: y * (Math.random() * skew), subject: subject }; 56 | }) 57 | })); 58 | }()); 59 | 60 | 61 | 62 | // Random purchases by four different groups 63 | gg.sampleData.purchases = (function () { 64 | var names = [ 'foo', 'bar', 'baz', 'quux' ]; 65 | var randomMeans = d3.random.normal(200, 10); 66 | var groups = _.map(names, function (n) { 67 | var mean = randomMeans(); 68 | return { 69 | name: n, 70 | rng: d3.random.normal(mean, mean/3) 71 | }; 72 | }); 73 | 74 | return _.map(_.range(20), function () { 75 | var g = groups[Math.floor(Math.random() * groups.length)]; 76 | return { 77 | who: g.name, 78 | purchases: Math.round(Math.max(0, g.rng())) 79 | }; 80 | }); 81 | }()); 82 | 83 | // Random data for plotting semi-log. 84 | gg.sampleData.semiLogData = (function () { 85 | var data = []; 86 | var x = 0; 87 | var y = 1; 88 | _.times(20, function () { 89 | x += Math.random() * 30; 90 | y *= Math.random() * 5; 91 | data.push({ 92 | d: x, 93 | r: y 94 | }); 95 | }); 96 | return data; 97 | }()); 98 | 99 | 100 | // Random height weight data for binned histogram and scatter plot 101 | gg.sampleData.heightWeight = (function () { 102 | // Some random variables. 103 | var randomHeight = d3.random.normal(66, 18); 104 | var randomBMI = d3.random.normal(21.75, 3); 105 | var standardNormal = d3.random.normal(); 106 | 107 | return _.map(_.range(20000), function () { 108 | var inches = randomHeight(); 109 | var lbs = randomBMI() * inches * inches / 703.06958; 110 | return { 111 | height: inches, 112 | weight: lbs 113 | }; 114 | }); 115 | }()); 116 | 117 | // Random data for box plot graphic 118 | gg.sampleData.forBoxPlots = (function () { 119 | var names = ['a', 'b', 'c', 'd' ]; 120 | var randomMeans = d3.random.normal(500, 100); 121 | var randomStddevs = d3.random.normal(150, 20); 122 | var outlierRates = d3.random.normal(0.01, 0.001); 123 | 124 | function makeRNG (mean, stddev, outlierRate) { 125 | var baseRNG = d3.random.normal(mean, stddev); 126 | return function () { 127 | var r = baseRNG(); 128 | var sign = Math.abs(r - mean) / (r - mean); 129 | return (Math.random() < outlierRate) 130 | ? r + (sign * stddev * (3 + Math.random() * 2)) : r; 131 | }; 132 | } 133 | 134 | var groups = _.map(names, function (n) { 135 | return { 136 | name: n, 137 | rng: makeRNG(randomMeans(), Math.abs(randomStddevs()), Math.abs(outlierRates())) 138 | }; 139 | }); 140 | 141 | return _.map(_.range(2000), function () { 142 | var g = groups[Math.floor(Math.random() * groups.length)]; 143 | return { grade: g.name, score: g.rng() }; 144 | }); 145 | }()); 146 | 147 | gg.sampleData.twoPopulations = (function () { 148 | 149 | var groups = [ 150 | { 151 | name: 'zorks', 152 | rng1: d3.random.normal(100, 15), 153 | rng2: d3.random.normal(0, 0.1) 154 | }, 155 | { 156 | name: 'florgs', 157 | rng1: d3.random.normal(90, 20), 158 | rng2: d3.random.normal(0, 0.2) 159 | } 160 | ]; 161 | 162 | return _.map(_.range(2000), function (i) { 163 | var g = groups[i % groups.length]; 164 | var intelligence = g.rng1(); 165 | var wisdom = (1 + g.rng2()) * intelligence; 166 | return { 167 | group: g.name, 168 | intelligence: intelligence, 169 | wisdom: wisdom 170 | }; 171 | }); 172 | }()); 173 | 174 | gg.sampleData.quadrants = (function () { 175 | 176 | var randomX = d3.random.normal(500, 100); 177 | var randomY = d3.random.normal(200, 50); 178 | var randomSize = d3.random.normal(10, 2); 179 | var randomId = d3.random.normal(1000, 100); 180 | 181 | return _.map(_.range(50), function () { 182 | return { 183 | x: randomX(), 184 | y: randomY(), 185 | size: Math.max(0, randomSize()) * 100, 186 | name: "Patient #" + Math.round(randomId()) 187 | }; 188 | }); 189 | }()); 190 | 191 | gg.sampleData.facets = (function () { 192 | 193 | function choice (list) { 194 | return function () { return list[Math.floor(Math.random() * list.length)]; } 195 | } 196 | 197 | var gender = choice(['Male', 'Female']); 198 | var job = choice(['Programmer', 'Manager', 'Designer']); 199 | 200 | var pay = { 201 | Male: { 202 | Programmer: d3.random.normal(30, 5), 203 | Manager: d3.random.normal(50, 10), 204 | Designer: d3.random.normal(20, 5) 205 | }, 206 | Female: { 207 | Programmer: d3.random.normal(25, 4), 208 | Manager: d3.random.normal(40, 12), 209 | Designer: d3.random.normal(20, 6) 210 | } 211 | } 212 | 213 | var experience = { 214 | Male: { 215 | Programmer: d3.random.normal(10, 3), 216 | Manager: d3.random.normal(15, 5), 217 | Designer: d3.random.normal(5, 2) 218 | }, 219 | Female: { 220 | Programmer: d3.random.normal(7, 4), 221 | Manager: d3.random.normal(10, 4), 222 | Designer: d3.random.normal(6, 3) 223 | } 224 | } 225 | 226 | return _.map(_.range(1000), function () { 227 | var g = gender(); 228 | var j = job(); 229 | var e = Math.max(1, experience[g][j]()); 230 | var p = Math.max(5, pay[g][j]()) * e; 231 | 232 | return { 233 | gender: g, 234 | job: j, 235 | pay: p, 236 | experience: e 237 | } 238 | }); 239 | }()); 240 | 241 | })(window); 242 | -------------------------------------------------------------------------------- /examples.js: -------------------------------------------------------------------------------- 1 | ;(function () { 2 | 3 | // This file contains the code to define the graphics and then 4 | // renders them using data randomly generated by data.js. 5 | 6 | // Define graphics ... 7 | 8 | var linechart = gg( 9 | { geometry: 'line', x: 'd', y: 'r', group: 'subject', color: 'subject' }, 10 | { geometry: 'text', x: 'd', y: 'r', text: '{d}, {r}', show: 'hover' }, 11 | { aesthetic: 'color', range: ['#CFF09E', '#A8DBA8', '#79BD9A', '#3B8686'] }); 12 | 13 | var combined = gg( 14 | { geometry: 'point', x: 'd', y: 'r', size: 3 }, 15 | { geometry: 'line', x: 'd', y: 'r' }); 16 | 17 | var barchart = gg( 18 | { geometry: 'interval', x: 'd', y: 'r', width: 2, name: 'barchart' }, 19 | { aesthetic: 'y', min: 0 }); 20 | 21 | var quadrants = gg( 22 | { geometry: 'point', x: 'x', y: 'y', size: 'size' }, 23 | { geometry: 'text', x: 'x', y: 'y', text: '{name}: {size}', show: 'hover' }, 24 | { aesthetic: 'size', range: [ 1, 5 ]}); 25 | 26 | var histogram = gg( 27 | { geometry: 'interval', x: 'group', y: 'count', color: 'group', width: 20, statistic: 'sum', group: 'who', variable: 'purchases' }, 28 | { aesthetic: 'x', type: 'categorical' }, 29 | { aesthetic: 'y', min: 0 }); 30 | 31 | var semilog = gg( 32 | { geometry: 'point', x: 'd', y: 'r', size: 3 }, 33 | { geometry: 'line', x: 'd', y: 'r' }, 34 | { aesthetic: 'y', type: 'log', legend: 'whatever' }, 35 | { aesthetic: 'x', legend: 'foo' }); 36 | 37 | var heightHistogram = gg( 38 | { geometry: 'interval', x: 'bin', y: 'count', statistic: 'bin', variable: 'height', bins: 30 }, 39 | { aesthetic: 'x', type: 'categorical' }, 40 | { aesthetic: 'y', min: 0 }); 41 | 42 | var twoPopulations = gg( 43 | { geometry: 'point', x: 'intelligence', y: 'wisdom', color: 'group', name: 'twoPopulations' }); 44 | 45 | var boxplot = gg( 46 | { geometry: 'box', x: 'group', group: 'grade', variable: 'score' }, 47 | { aesthetic: 'x', type: 'categorical' }); 48 | 49 | var symmetric = gg( 50 | { geometry: 'line', x: 'd', y: 'r' }, 51 | { aesthetic: 'y', center: 0 }); 52 | 53 | var areachartSmooth = gg( 54 | { geometry: 'area', x: 'a', y0: 'top', y1: 'bottom', smooth: true }); 55 | 56 | var areachart = gg( 57 | { geometry: 'area', x: 'a', y0: 'top', y1: 'bottom' }); 58 | 59 | var facets = gg( 60 | { facets: 'xy', x: 'gender', y: 'job' }, 61 | { geometry: 'point', x: 'experience', y: 'pay', size: 2 } 62 | ); 63 | 64 | // ... and render 'em 65 | 66 | var data = gg.sampleData; 67 | var div = d3.select('#examples'); 68 | var opts = { width: 300, height: 200, padding: 35 }; 69 | var wide = { width: 915, height: 600, paddingX: 35, paddingY: 20 }; 70 | 71 | linechart(data.upwardSubjects, div, opts); 72 | combined(data.upward, div, opts); 73 | barchart(_.map(data.upward, function (d) { return { d: d.d, r: Math.max(d.r, 0) }; }), div, opts); 74 | quadrants(data.quadrants, div, opts); 75 | histogram(data.purchases, div, opts); 76 | semilog(data.semiLogData, div, opts); 77 | heightHistogram(data.heightWeight, div, opts); 78 | twoPopulations(data.twoPopulations, div, opts); 79 | boxplot(data.forBoxPlots, div, opts); 80 | symmetric(data.toBeCentered, div, opts); 81 | areachartSmooth(data.upwardPairs, div, opts); 82 | areachart(data.upwardPairs, div, opts); 83 | facets(data.facets, div, wide); 84 | 85 | })(); 86 | -------------------------------------------------------------------------------- /flowers.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Gill Sans"; 3 | width: 1000px; 4 | margin: .25in auto; 5 | background: rgba(221, 204, 187, .15); 6 | } 7 | 8 | h1 { 9 | font-weight: 300; 10 | } 11 | 12 | rect.base { fill: rgba(221, 204, 187, 1); } 13 | 14 | div#examples span { margin: 3px; } 15 | 16 | .axis { 17 | shape-rendering: crispEdges; 18 | } 19 | 20 | .axis line { 21 | stroke: #fff; 22 | } 23 | 24 | .axis .minor { 25 | stroke-opacity: .5; 26 | } 27 | 28 | .axis path { 29 | stroke: #fff; 30 | fill: none; 31 | } 32 | .axis text { 33 | font-size: 8px; 34 | font-weight: 300; 35 | } 36 | 37 | .legend { 38 | font-size: 10px; 39 | font-weight: 300; 40 | } 41 | 42 | .graphicText { 43 | font-size: 10px; 44 | cursor: pointer; 45 | fill: black; 46 | text-shadow: 1px 1px 0px white; 47 | } 48 | 49 | .graphicText.showOnHover { 50 | fill: transparent; 51 | text-shadow: none; 52 | } 53 | 54 | .graphicText.showOnHover:hover { 55 | fill: black; 56 | text-shadow: 1px 1px 0px white; 57 | } 58 | 59 | /*.arrow { 60 | stroke: #88f; 61 | fill: #88f; 62 | stroke-width: 1px; 63 | }*/ 64 | 65 | 66 | .lines .line { stroke: #338; } -------------------------------------------------------------------------------- /flowers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flowers 6 | 7 | 8 | 9 | 10 | 11 | 12 |

Productivity

13 |
14 |
15 |
16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /flowers.js: -------------------------------------------------------------------------------- 1 | !function () { 2 | 3 | var s = 0.7; 4 | var b = 0.02; 5 | 6 | function effectiveness (eng, ee, s, b) { 7 | return (eng - ee) * (1 + (Math.pow(ee, s) * b)); 8 | } 9 | 10 | function data (eng) { 11 | return _.map(_.range(eng + 1), function (x) { 12 | return { x: x, y: effectiveness(eng, x, s, b) } 13 | }); 14 | } 15 | 16 | function maxY (data) { 17 | return _.max(data, function (d) { return d.y; }); 18 | } 19 | 20 | function plotter(label) { 21 | return gg( 22 | { geometry: 'line', x: 'x', y: 'y', smooth: true, width: 2 }, 23 | { 24 | geometry: 'arrow', 25 | x: 'x', 26 | y: 'y', 27 | arrow: { length: 10, width: 3 }, 28 | head: function (d) { return maxY(d); }, 29 | tail: function (d) { return { x: maxY(d).x, y: 0 }; }, 30 | color: '#338', 31 | linewidth: 1, 32 | }, 33 | { aesthetic: 'x', legend: label }, 34 | { aesthetic: 'y', legend: 'Net productivity' } 35 | ) 36 | } 37 | 38 | var opts = { width: 500, height: 300, padding: 50 }; 39 | 40 | var commify = d3.format(",d") 41 | 42 | _.map([10, 100, 1000, 10000], function (n) { 43 | var label = 'EE Engineers out of ' + commify(n) + ' (s = ' + s + '; b = ' + b + ')'; 44 | plotter(label)(data(n), d3.select('#chart' + n), opts); 45 | }); 46 | 47 | }(); 48 | -------------------------------------------------------------------------------- /gg.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Gill Sans"; 3 | width: 1000px; 4 | margin: .25in auto; 5 | background: rgba(221, 204, 187, .15); 6 | } 7 | 8 | h1 { 9 | font-weight: 300; 10 | } 11 | 12 | rect.base { fill: rgba(221, 204, 187, 1); } 13 | 14 | rect.facet.label { fill: rgba(255, 224, 187, .5);} 15 | 16 | rect.boxplot { 17 | fill: none; 18 | stroke: #000; 19 | stroke-width: 1px; 20 | } 21 | 22 | line.boxplot { 23 | stroke: #000; 24 | stroke-width: 1px; 25 | } 26 | 27 | div#examples svg { margin: 3px; } 28 | 29 | .axis { 30 | shape-rendering: crispEdges; 31 | } 32 | 33 | .axis line { 34 | stroke: #fff; 35 | } 36 | 37 | .axis .minor { 38 | stroke-opacity: .5; 39 | } 40 | 41 | .axis path { 42 | stroke: #fff; 43 | fill: none; 44 | } 45 | .axis text { 46 | font-size: 8px; 47 | font-weight: 300; 48 | } 49 | 50 | .legend { 51 | font-size: 10px; 52 | font-weight: 300; 53 | } 54 | 55 | .graphicText { 56 | font-size: 10px; 57 | cursor: pointer; 58 | fill: black; 59 | text-shadow: 1px 1px 0px white; 60 | } 61 | 62 | .graphicText.showOnHover { 63 | fill: transparent; 64 | text-shadow: none; 65 | } 66 | 67 | .graphicText.showOnHover:hover { 68 | fill: black; 69 | text-shadow: 1px 1px 0px white; 70 | } 71 | 72 | 73 | 74 | /* 75 | // Explicit styling of lines in first line chart. 76 | 77 | path.line.a { stroke: #f00; } 78 | path.line.b { stroke: #0f0; } 79 | path.line.c { stroke: #00f; } 80 | path.line.d { stroke: #f0f; } 81 | */ 82 | 83 | .twoPopulations circle.points { 84 | fill-opacity: 0.5; 85 | r: 2; 86 | } 87 | 88 | .barchart rect.bar { 89 | fill: blue; 90 | } -------------------------------------------------------------------------------- /gg.js: -------------------------------------------------------------------------------- 1 | ;(function (exports, undefined) { 2 | 3 | var _ = exports._; 4 | var d3 = exports.d3; 5 | 6 | // Provide Node compatibility 7 | if (!_ && !d3 && typeof require !== 'undefined') { 8 | d3 = require('d3'); 9 | _ = require('underscore'); 10 | } 11 | 12 | //////////////////////////////////////////////////////////////////////// 13 | // Graphic -- the outermost object responsible for rendering a 14 | // statistical graphic. 15 | 16 | function Graphic (spec) { 17 | this.facet = Facet.fromSpec(spec); 18 | } 19 | 20 | /* 21 | * Return a function that will render the graphic using the given 22 | * data into the given HTML element (a div or span usually). 23 | */ 24 | Graphic.prototype.render = function (data, where, opts) { 25 | var w = opts.width; 26 | var h = opts.height; 27 | var pX = opts.paddingX || opts.padding; 28 | var pY = opts.paddingY || opts.padding; 29 | 30 | var svg = where.append('svg').attr('width', w).attr('height', h); 31 | this.facet.render(0, 0, w, h, pX, pY, svg, data); 32 | /* 33 | var p = 12; 34 | this.facet.render(0, 0, w/2, h/2, p, p, svg, data); 35 | this.facet.render(w/2, 0, w/2, h/2, p, p, svg, data); 36 | this.facet.render(0, h/2, w/2, h/2, p, p, svg, data); 37 | this.facet.render(w/2, h/2, w/2, h/2, p, p, svg, data); 38 | */ 39 | }; 40 | 41 | 42 | 43 | //////////////////////////////////////////////////////////////////////// 44 | // Facet -- Responsible for rendering into some rectangular area 45 | // of the graphic. A facet can contain sub-facets, each 46 | // responsible for rendering some subset of the data to some 47 | // rectangle of the graphic. 48 | // 49 | // The Facet object knows how to split up the data into groups 50 | // that will each be rendered into a separate facet, and how to 51 | // split up the area of the graphic appropriately. There are five 52 | // layouts: horizontal, vertical, and horizontal flow, vertical 53 | // flow, and grid. The horizontal layout divides the graphic into 54 | // evenly sized elements that are arranged in a single row; 55 | // vertical divides the graphic into evenly sized elements that 56 | // are arranged in a single column. ... (Note, none of this is 57 | // actually implemented yet except the trivial single facet case. 58 | // -Peter) 59 | 60 | function Facet() {} 61 | 62 | Facet.fromSpec = function (spec) { 63 | return new ({ 64 | single: SingleFacet, 65 | xy: XYFacet 66 | }[spec.facets ? spec.facets.facets : 'single'])(spec); 67 | } 68 | 69 | Facet.makeLayers = function (spec) { 70 | return _.map(spec.layers, function (s) { return new Layer(s); }); 71 | } 72 | Facet.extractAesthetics = function (layers) { 73 | return _.uniq(_.flatten(_.map(_.pluck(layers, 'mappings'), _.keys))); 74 | } 75 | 76 | Facet.prototype.render = function (x, y, width, height, paddingX, paddingY, svg, data, scaleData) { 77 | 78 | svg.append('rect') 79 | .attr('class', 'base') 80 | .attr('x', x) 81 | .attr('y', y) 82 | .attr('width', width) 83 | .attr('height', height); 84 | 85 | this.subrender(x, y, width, height, paddingX, paddingY, svg, data, scaleData); 86 | }; 87 | 88 | function SingleFacet(spec) { 89 | this.layers = Facet.makeLayers(spec); 90 | this.scaleSpecs = spec.scales; 91 | this.aesthetics = Facet.extractAesthetics(this.layers); 92 | } 93 | 94 | SingleFacet.prototype = new Facet(); 95 | 96 | SingleFacet.prototype.subrender = function (x, y, width, height, paddingX, paddingY, svg, data, scaleData) { 97 | 98 | function g () { return svg.append('g').attr('transform', translate(x, y)); } 99 | 100 | var scales = makeScales(this.scaleSpecs, this.layers, this.aesthetics, scaleData || data, width, height, paddingX, paddingY); 101 | 102 | var xAxis = d3.svg.axis() 103 | .scale(scales['x'].d3Scale) 104 | .tickSize(2 * paddingY - height) 105 | .orient('bottom'); 106 | 107 | var yAxis = d3.svg.axis() 108 | .scale(scales['y'].d3Scale) 109 | .tickSize(2 * paddingX - width) 110 | .orient('left'); 111 | 112 | svg.append('g') 113 | .attr('class', 'x axis') 114 | .attr('transform', translate(x, y + height - paddingY)) 115 | .call(xAxis); 116 | 117 | svg.append('g') 118 | .attr('class', 'y axis') 119 | .attr('transform', translate(x + paddingX, y)) 120 | .call(yAxis); 121 | 122 | svg.append('g') 123 | .attr('class', 'x legend') 124 | .attr('transform', translate(x + (width / 2), y + (height - 5))) 125 | .append('text') 126 | .text(legend(scales, this.layers, 'x')) 127 | .attr('text-anchor', 'middle'); 128 | 129 | svg.append('g') 130 | .attr('class', 'y legend') 131 | .attr('transform', translate(x + 10, y + (height / 2)) + ' rotate(270)') 132 | .append('text') 133 | .text(legend(scales, this.layers, 'y')) 134 | .attr('text-anchor', 'middle'); 135 | 136 | 137 | _.each(this.layers, function (l) { l.render(g(), data, scales); }, this); 138 | }; 139 | 140 | function XYFacet(spec) { 141 | this.spec = spec; 142 | this.x = spec.facets.x; 143 | this.y = spec.facets.y; 144 | } 145 | 146 | XYFacet.prototype = new Facet(); 147 | 148 | XYFacet.prototype.subrender = function (x, y, width, height, paddingX, paddingY, svg, data) { 149 | 150 | var xs = _.sortBy(_.uniq(_.pluck(data, this.x)), function (x) { return x; }); 151 | var ys = _.sortBy(_.uniq(_.pluck(data, this.y)), function (y) { return y; }); 152 | var grouped = _.mapObject(_.groupBy(data, this.x), _.bind(function (ds) { return _.groupBy(ds, this.y); }, this)) 153 | 154 | var labelHeight = 20; 155 | var labelWidth = 20; 156 | 157 | var subWidth = Math.floor((width - labelWidth) / xs.length); 158 | var subHeight = Math.floor((height - labelHeight) / ys.length); 159 | var subfacet = new SingleFacet(this.spec); 160 | 161 | // Draw sub-facets 162 | _.each(xs, function (x, xindex) { 163 | _.each(ys, function (y, yindex) { 164 | subfacet.subrender( 165 | xindex * subWidth, 166 | labelHeight + yindex * subHeight, 167 | subWidth, 168 | subHeight, 169 | paddingX, 170 | paddingY, 171 | svg, 172 | grouped[x][y], 173 | data); 174 | }); 175 | }); 176 | 177 | // Draw X facet labels 178 | _.each(xs, function (xlabel, xindex) { 179 | var xcoord = (xindex * subWidth) + subWidth/2; 180 | svg.append('rect') 181 | .attr('class', 'x facet label') 182 | .attr('x', xindex * subWidth) 183 | .attr('y', 0) 184 | .attr('width', subWidth) 185 | .attr('height', labelHeight); 186 | svg.append('g') 187 | .attr('transform', translate(xcoord, 15)) 188 | .append('text') 189 | .attr('class', 'x facet label') 190 | .text(xlabel) 191 | .attr('text-anchor', 'middle'); 192 | }); 193 | 194 | // Draw Y facet labels 195 | _.each(ys, function (ylabel, yindex) { 196 | var xcoord = width - labelWidth; 197 | var ycoord = labelHeight + (yindex * subHeight) + subHeight/2; 198 | svg.append('rect') 199 | .attr('class', 'y facet label') 200 | .attr('x', xcoord) 201 | .attr('y', labelHeight + (yindex * subHeight)) 202 | .attr('width', labelWidth) 203 | .attr('height', subHeight); 204 | svg.append('g') 205 | .attr('transform', translate(xcoord + (labelWidth * .75), ycoord) + ' rotate(270)') 206 | .append('text') 207 | .attr('class', 'y facet label') 208 | .text(ylabel) 209 | .attr('text-anchor', 'middle'); 210 | }); 211 | }; 212 | 213 | /* 214 | * Make the scales to render a specific data set. 215 | */ 216 | function makeScales (specs, layers, aesthetics, data, width, height, paddingX, paddingY) { 217 | 218 | var scaleSpecs = _.object(_.map(specs, function (s) { return [ s.aesthetic, s ] })); 219 | 220 | // Get all possible values for aesthetic a. 221 | function allValues (a) { 222 | function hasAesthetic (layer) { return a in layer.mappings; } 223 | function vals (layer) { 224 | function v (d) { return layer.dataValues(d, a); } 225 | return _.flatten(_.map(layer.statistic.compute(data), v)); 226 | } 227 | return _.uniq(_.flatten(_.map(_.filter(layers, hasAesthetic), vals))); 228 | } 229 | 230 | function makeScale (a) { 231 | var scale = a in scaleSpecs ? Scale.fromSpec(scaleSpecs[a]) : Scale.defaultFor(a); 232 | if (!scale.domainSet) { 233 | scale.defaultDomain(allValues(a)); 234 | } 235 | if (a === 'x') { 236 | scale.range([paddingX, width - paddingX]); 237 | } else if (a === 'y') { 238 | scale.range([height - paddingY, paddingY]); 239 | } 240 | return [ a, scale ]; 241 | } 242 | 243 | return _.object(_.map(aesthetics, makeScale)); 244 | }; 245 | 246 | function legend (scales, layers, aesthetic) { 247 | return scales[aesthetic].legend || layers[0].legend(aesthetic); 248 | }; 249 | 250 | //////////////////////////////////////////////////////////////////////// 251 | // Layers -- each layer is responsible for drawing one geometry 252 | // into the graphic to which the layer belongs. The layer is also 253 | // responsible for mapping data from the keys in the original data 254 | // to aesthetics. 255 | 256 | function Layer (spec) { 257 | this.geometry = Geometry.fromSpec(spec); 258 | this.group = spec.group 259 | this.statistic = Statistic.fromSpec(spec, this.geometry); 260 | this.mappings = _.object(_.without(_.map(this.geometry.aesthetics, function (a) { 261 | if (a in spec) { 262 | // This is where I wish we had first-class symbols 263 | // with a nice syntax. This means that any literal 264 | // values for things that could come from mappings 265 | // need to be specially encoded if their natural 266 | // representation is a as a string. So far color seems 267 | // like the only thing and maybe that can always be 268 | // done via CSS. 269 | return _.isString(spec[a]) ? [ a, spec[a] ] : null; 270 | } else { 271 | return [ a, false ] 272 | } 273 | }, this), null)); 274 | } 275 | 276 | /** 277 | * Given a datum and an aesthetic, extract the corresponding value 278 | * (e.g. if the aesthetic is 'x' and it's mapped to the field 279 | * 'foo', extract the 'foo' field from d) and then scales it using 280 | * the appropriate scale for the aesthetic. 281 | */ 282 | Layer.prototype.aestheticValue = function (scales, d, aesthetic, mapKey) { 283 | return this.scale(this.dataValue(d, mapKey || aesthetic), aesthetic, scales); 284 | }; 285 | 286 | /** 287 | * For a given aesthetic, if there is a mapping for the aesthetic, 288 | * return a function that will extract the appropriate value from 289 | * the a datum and map it to the aesthetic value. Otherwise return 290 | * the default value. 291 | */ 292 | Layer.prototype.attributeValue = function (scales, aesthetic, defaultValue) { 293 | return (this.mappings[aesthetic]) ? 294 | _.bind(function (d) { return this.aestheticValue(scales, d, aesthetic); }, this) : defaultValue; 295 | } 296 | 297 | /** 298 | * Extract the field from the datum corresponding to the given 299 | * aesthetic. 300 | */ 301 | Layer.prototype.dataValue = function (datum, mapKey) { 302 | return datum[this.mappings[mapKey]]; 303 | }; 304 | 305 | Layer.prototype.dataValues = function (datum, aesthetic) { 306 | // Given a datum (produced by a Statistic), return a list of 307 | // values for the given aesthetic. Most of the time this is 308 | // just the single value returned by mapping the aesthetic to 309 | // the field in the data. For the BoxStatistic, however, it's 310 | // all the fields except for whatever the x aesthetic maps to. 311 | return this.geometry.valuesForAesthetic(datum, this.mappings[aesthetic], this); 312 | }; 313 | 314 | /** 315 | * Given a value in data space and an aesthetic, scale it using 316 | * the appropriate scale for the aesthetic. 317 | */ 318 | Layer.prototype.scale = function (v, aesthetic, scales) { 319 | return scales[aesthetic].scale(v); 320 | }; 321 | 322 | Layer.prototype.scaledMin = function (aesthetic, scales) { 323 | var s = scales[aesthetic]; 324 | return s.scale(s.min); 325 | }; 326 | 327 | Layer.prototype.aesthetics = function () { 328 | return _.without(_.keys(this.mappings), 'group'); 329 | }; 330 | 331 | Layer.prototype.render = function (g, data, scales) { 332 | var s = this.statistic.compute(data) 333 | this.geometry.render(g, _.values(groupData(s, this.group)), this, scales); 334 | }; 335 | 336 | Layer.prototype.legend = function (aesthetic) { 337 | return this.mappings[aesthetic] || this.statistic.variable; 338 | }; 339 | 340 | //////////////////////////////////////////////////////////////////////// 341 | // Geometries -- objects that actually draw stuff onto the Graphic. 342 | // They only care about scaled values which they can get from 343 | // their layer. 344 | 345 | function Geometry (aesthetics) { 346 | this.aesthetics = aesthetics; 347 | this.defaultStatistic = 'identity' 348 | } 349 | 350 | Geometry.fromSpec = function (spec) { 351 | return new ({ 352 | point: PointGeometry, 353 | line: LineGeometry, 354 | area: AreaGeometry, 355 | interval: IntervalGeometry, 356 | box: BoxPlotGeometry, 357 | arrow: ArrowGeometry, 358 | text: TextGeometry 359 | }[spec.geometry || 'point'])(spec); 360 | }; 361 | 362 | Geometry.prototype.valuesForAesthetic = function (datum, field, layer) { 363 | return [ datum[field] ]; 364 | }; 365 | 366 | function PointGeometry (spec) { 367 | this.name = spec.name; 368 | this.size = spec.size || 5; 369 | this.color = spec.color || 'black'; 370 | } 371 | 372 | PointGeometry.prototype = new Geometry(['x', 'y', 'size', 'color']); 373 | 374 | PointGeometry.prototype.render = function (g, data, layer, scales) { 375 | if (this.name) g = g.attr('class', this.name); 376 | groups(g, 'circles', data).selectAll('circle') 377 | .data(Object) 378 | .enter() 379 | .append('circle') 380 | .attr('class', 'points') 381 | .attr('cx', function (d) { return layer.aestheticValue(scales, d, 'x'); }) 382 | .attr('cy', function (d) { return layer.aestheticValue(scales, d, 'y'); }) 383 | .attr('fill-opacity', 1) 384 | .attr('fill', layer.attributeValue(scales, 'color', this.color)) 385 | .attr('r', layer.attributeValue(scales, 'size', this.size)); 386 | }; 387 | 388 | function AreaGeometry (spec) { 389 | this.color = spec.color || 'black'; 390 | this.width = spec.width || 2; 391 | this.fill = spec.fill || 'black'; 392 | this.alpha = spec.alpha || 1; 393 | this.stroke = spec.stroke || this.fill; 394 | this.smooth = spec.smooth || false; 395 | } 396 | 397 | AreaGeometry.prototype = new Geometry(['x', 'y', 'color', 'y0', 'y1']); 398 | 399 | AreaGeometry.prototype.valuesForAesthetic = function (datum, field, layer) { 400 | return field 401 | ? [ datum[field] ] 402 | : _.map(['y0', 'y1'], function (x) { return layer.dataValue(datum, x); }) 403 | } 404 | 405 | AreaGeometry.prototype.render = function (g, data, layer, scales) { 406 | var area = d3.svg.area() 407 | .x(function (d) { return layer.aestheticValue(scales, d, 'x') }) 408 | .y1(function (d) { return layer.aestheticValue(scales, d, 'y', 'y1') }) 409 | .y0(function (d) { return layer.aestheticValue(scales, d, 'y', 'y0') }) 410 | .interpolate(this.smooth ? 'basis' : 'linear'); 411 | 412 | groups(g, 'lines', data).selectAll('polyline') 413 | .data(function(d) { return [d]; }) 414 | .enter() 415 | .append('svg:path') 416 | .attr('d', area) 417 | .attr('stroke-width', this.width) 418 | .attr('stroke', this.stroke) 419 | .attr('fill', this.fill) 420 | .attr('fill-opacity', this.alpha) 421 | .attr('stroke-opacity', this.alpha) 422 | }; 423 | 424 | function LineGeometry (spec) { 425 | this.color = spec.color || 'black'; 426 | this.width = spec.width || 2; 427 | this.smooth = spec.smooth || false; 428 | } 429 | 430 | 431 | /** 432 | * Line geometry draws one or more lines. Lines can be either 433 | * smoothed or straight from point to point. If there are multiple 434 | * lines, they can be colored differently with a color scale. The 435 | * lines path element are also given a class corresponding to the 436 | * name of the group so they can be styled with CSS. 437 | */ 438 | LineGeometry.prototype = new Geometry(['x', 'y', 'color']); 439 | 440 | LineGeometry.prototype.render = function (g, data, layer, scales) { 441 | function scale (d, aesthetic) { return layer.aestheticValue(scales, d, aesthetic); } 442 | 443 | // Can't use attributeValue here like the other geometries 444 | // because we always group the data and then turn each group 445 | // into a single array to be used to draw a polyline. 446 | var color = layer.mappings.color ? function (d) { return scale(d[0], 'color'); } : this.color; 447 | 448 | function classname (d) { 449 | var g = layer.dataValue(d[0], 'group'); 450 | return g ? 'line ' + g : 'line'; 451 | } 452 | 453 | var line = d3.svg.line() 454 | .x(function (d) { return scale(d, 'x') }) 455 | .y(function (d) { return scale(d, 'y') }) 456 | .interpolate(this.smooth ? 'basis' : 'linear'); 457 | 458 | groups(g, 'lines', data).selectAll('polyline') 459 | .data(function(d) { return [d]; }) 460 | .enter() 461 | .append('svg:path') 462 | .attr('class', classname) 463 | .attr('d', line) 464 | .attr('fill', 'none') 465 | .attr('stroke-width', this.width) 466 | .attr('stroke', color); 467 | }; 468 | 469 | function IntervalGeometry (spec) { 470 | this.name = spec.name; 471 | this.width = spec.width || 5; 472 | this.color = spec.color || 'black'; 473 | } 474 | 475 | IntervalGeometry.prototype = new Geometry(['x', 'y', 'color']); 476 | 477 | IntervalGeometry.prototype.render = function (g, data, layer, scales) { 478 | var width = this.width; 479 | 480 | function scale (d, aesthetic) { return layer.aestheticValue(scales, d, aesthetic); } 481 | if (this.name) g = g.attr('class', this.name); 482 | groups(g, 'rects', data).selectAll('rect') 483 | .data(Object) 484 | .enter() 485 | .append('rect') 486 | .attr('class', 'bar') 487 | .attr('x', function (d) { return scale(d, 'x') - width/2; }) 488 | .attr('y', function (d) { return scale(d, 'y'); }) 489 | .attr('width', width) 490 | .attr('height', function (d) { return layer.scaledMin('y', scales) - scale(d, 'y'); }) 491 | .attr('fill', layer.attributeValue(scales, 'color', this.color)); 492 | }; 493 | 494 | function BoxPlotGeometry (spec) { 495 | this.width = spec.width || 10; 496 | this.color = spec.color || 'black'; 497 | } 498 | 499 | BoxPlotGeometry.prototype = new Geometry(['x', 'y']); 500 | BoxPlotGeometry.prototype.defaultStatistic = 'box'; 501 | 502 | BoxPlotGeometry.prototype.valuesForAesthetic = function (datum, mapped, layer) { 503 | return mapped 504 | ? [ datum[mapped] ] 505 | : _.values(_.omit(datum, ['group', 'outliers'])).concat(datum.outliers); 506 | } 507 | 508 | BoxPlotGeometry.prototype.render = function (g, data, layer, scales) { 509 | // Data points are { group, median, q1, q3, upper, lower, outliers } 510 | var width = this.width; 511 | 512 | function scale (v, a) { return layer.scale(v, a, scales); } 513 | 514 | function iqrBox(s) { 515 | s.append('rect') 516 | .attr('class', 'boxplot iqr') 517 | .attr('x', function (d) { return scale(d.group, 'x') - width/2; }) 518 | .attr('y', function (d) { return scale(d.q3, 'y'); }) 519 | .attr('width', width) 520 | .attr('height', function (d) { return scale(d.q1, 'y') - scale(d.q3, 'y'); }) 521 | .attr('fill', 'none'); 522 | s.call(medianLine); 523 | } 524 | 525 | function medianLine(s) { 526 | s.append('line') 527 | .attr('class', 'boxplot median') 528 | .attr('x1', function (d) { return scale(d.group, 'x') - width/2; }) 529 | .attr('x2', function (d) { return scale(d.group, 'x') + width/2; }) 530 | .attr('y1', function (d) { return scale(d.median, 'y'); }) 531 | .attr('y2', function (d) { return scale(d.median, 'y'); }); 532 | } 533 | 534 | function whisker(s, y1, y2) { 535 | s.append('line') 536 | .attr('class', 'boxplot whisker') 537 | .attr('x1', function (d) { return scale(d.group, 'x'); }) 538 | .attr('x2', function (d) { return scale(d.group, 'x'); }) 539 | .attr('y1', function (d) { return scale(d[y1], 'y'); }) 540 | .attr('y2', function (d) { return scale(d[y2], 'y'); }); 541 | s.call(whiskerTick, y2); 542 | } 543 | 544 | function whiskerTick(s, y) { 545 | s.append('line') 546 | .attr('class', 'boxplot whisker') 547 | .attr('x1', function (d) { return scale(d.group, 'x') - (width * 0.4); }) 548 | .attr('x2', function (d) { return scale(d.group, 'x') + (width * 0.4); }) 549 | .attr('y1', function (d) { return scale(d[y], 'y'); }) 550 | .attr('y2', function (d) { return scale(d[y], 'y'); }); 551 | } 552 | 553 | function outliers(s) { 554 | s.selectAll('circle') 555 | .data(function (d) { return _.map(d.outliers, function (o) { return { x: scale(d.group, 'x'), y: scale(o, 'y') }; }); }) 556 | .enter() 557 | .append('circle') 558 | .attr('class', 'boxplot outlier') 559 | .attr('cx', function (o) { return o.x; }) 560 | .attr('cy', function (o) { return o.y; }) 561 | .attr('r', 2); 562 | } 563 | 564 | function render(s) { 565 | s.call(iqrBox).call(whisker, 'q3', 'upper').call(whisker, 'q1', 'lower').call(outliers); 566 | } 567 | 568 | var color = ('color' in layer.mappings) ? function(d) { return scale(d, 'color'); } : this.color; 569 | 570 | g.selectAll('g.boxes') 571 | .data(data) 572 | .enter() 573 | .append('g') 574 | .attr('class', 'boxes') 575 | .selectAll('g') 576 | .data(Object) 577 | .enter() 578 | .append('g') 579 | .call(render); 580 | }; 581 | 582 | function ArrowGeometry (spec) { 583 | this.arrowLength = spec.arrow.length || 10; 584 | this.arrowWidth = spec.arrow.width || 3; 585 | this.color = spec.color || 'black'; 586 | } 587 | 588 | ArrowGeometry.prototype = new Geometry(['x', 'y']); 589 | ArrowGeometry.prototype.defaultStatistic = 'arrow'; 590 | 591 | ArrowGeometry.prototype.render = function (g, data, layer, scales) { 592 | var len = this.arrowLength; 593 | var width = this.arrowWidth; 594 | var color = this.color; 595 | var linewidth = this.width; 596 | 597 | function scale (v, a) { return layer.scale(v, a, scales); } 598 | 599 | function arrowline (s) { 600 | s.append('line') 601 | .attr('x1', function (d) { return scale(d.tail.x, 'x'); }) 602 | .attr('x2', function (d) { return scale(d.head.x, 'x'); }) 603 | .attr('y1', function (d) { return scale(d.tail.y, 'y'); }) 604 | .attr('y2', function (d) { return scale(d.head.y, 'y'); }) 605 | .attr('fill', 'none') 606 | .attr('stroke-width', linewidth) 607 | .attr('stroke', color); 608 | } 609 | 610 | function arrowhead (s) { 611 | 612 | function arrowheadPoints (d, length, width) { 613 | var x1 = scale(d.tail.x, 'x'); 614 | var y1 = scale(d.tail.y, 'y'); 615 | var x2 = scale(d.head.x, 'x'); 616 | var y2 = scale(d.head.y, 'y'); 617 | 618 | var rise = y2 - y1; 619 | var run = x2 - x1; 620 | 621 | var len = Math.sqrt((rise * rise) + (run * run)); 622 | 623 | var cross_x = x2 - (length * (run / len)); 624 | var cross_y = y2 - (length * (rise / len)); 625 | 626 | return [ 627 | { x: x2, y: y2 }, // the point of the arrow. 628 | { x: cross_x + width * rise/len, y: cross_y - width * run/len }, 629 | { x: cross_x - width * rise/len, y: cross_y + width * run/len } 630 | ]; 631 | } 632 | 633 | var line = d3.svg.line() 634 | .x(function (d) { return d.x }) 635 | .y(function (d) { return d.y }) 636 | .interpolate('linear'); 637 | 638 | s.append('svg:path') 639 | .attr('class', 'arrow') 640 | .attr('d', function (d) { return line(arrowheadPoints(d, len, width)) + 'Z'; }) 641 | .attr('fill', color) 642 | .attr('stroke-width', linewidth) 643 | .attr('stroke', color); 644 | } 645 | 646 | function render (s) { 647 | s.call(arrowline).call(arrowhead); 648 | } 649 | 650 | g.selectAll('g.arrows') 651 | .data(data) 652 | .enter() 653 | .append('g') 654 | .attr('class', 'arrows') 655 | .call(render); 656 | 657 | }; 658 | 659 | function TextGeometry (spec) { 660 | this.text = spec.text 661 | this.show = spec.show; 662 | } 663 | 664 | TextGeometry.prototype = new Geometry(['x', 'y', 'size', 'color']); 665 | 666 | TextGeometry.prototype.render = function (g, data, layer, scales) { 667 | var text = this.text; 668 | 669 | function formatter (d) { 670 | function fmt (_, key) { 671 | var v = d[key]; 672 | return String(typeof v === 'number' ? v.toFixed(2) : v); 673 | } 674 | return text.replace(/{(.*?)}/g, fmt); 675 | } 676 | 677 | var area = g.append('g'); 678 | var text = groups(area, 'texts', data).selectAll('circle') 679 | .data(Object) 680 | .enter() 681 | .append('text') 682 | .attr('class', 'graphicText') 683 | .attr('x', function (d) { return layer.aestheticValue(scales, d, 'x'); }) 684 | .attr('y', function (d) { return layer.aestheticValue(scales, d, 'y'); }) 685 | .text(formatter); 686 | 687 | if ( this.show === 'hover' ){ 688 | text.attr('class', 'graphicText showOnHover'); 689 | } 690 | }; 691 | 692 | function groups (g, clazz, data) { 693 | return g.selectAll('g.' + clazz) 694 | .data(data) 695 | .enter() 696 | .append('g') 697 | .attr('class', clazz); 698 | } 699 | 700 | 701 | //////////////////////////////////////////////////////////////////////// 702 | // Scales -- a scale is used to map from data values to aesthetic 703 | // values. 704 | 705 | function Scale () {} 706 | 707 | Scale.fromSpec = function (spec) { 708 | 709 | var nonLinearAesthetics = { color: 'color', fill: 'color' }; 710 | 711 | var s = new ({ 712 | linear: LinearScale, 713 | time: TimeScale, 714 | log: LogScale, 715 | categorical: CategoricalScale, 716 | color: ColorScale 717 | }[spec.type || nonLinearAesthetics[spec.aesthetic] || 'linear'])(); 718 | 719 | spec.aesthetic !== undefined && (s.aesthetic = spec.aesthetic); 720 | spec.values !== undefined && (s.values = spec.values); 721 | spec.min !== undefined && (s.min = spec.min); 722 | spec.max !== undefined && (s.max = spec.max); 723 | spec.range !== undefined && s.range(spec.range); 724 | spec.legend !== undefined && (s.legend = spec.legend); 725 | spec.center !== undefined && (s.center = spec.center); 726 | return s; 727 | }; 728 | 729 | Scale.defaultFor = function (aesthetic) { 730 | return Scale.fromSpec({ aesthetic: aesthetic }); 731 | }; 732 | 733 | Scale.prototype.defaultDomain = function (values) { 734 | if (this.min === undefined) this.min = _.min(values) 735 | if (this.max === undefined) this.max = _.max(values); 736 | this.domain(this.center !== undefined ? centered(this.min, this.max, this.center) : [this.min, this.max]) 737 | this.domainSet = true; 738 | }; 739 | 740 | function centered (min, max, center) { 741 | var halfWidth = Math.max(max - center, Math.abs(min - center)); 742 | return [center - halfWidth, center + halfWidth]; 743 | } 744 | 745 | Scale.prototype.domain = function (interval) { 746 | this.d3Scale = this.d3Scale.domain(interval).nice(); 747 | }; 748 | 749 | Scale.prototype.range = function (interval) { 750 | this.d3Scale = this.d3Scale.range(interval); 751 | }; 752 | 753 | Scale.prototype.scale = function (v) { 754 | return this.d3Scale(v); 755 | }; 756 | 757 | function LinearScale () { this.d3Scale = d3.scale.linear(); } 758 | 759 | LinearScale.prototype = new Scale(); 760 | 761 | function TimeScale () { this.d3Scale = d3.time.scale(); } 762 | 763 | TimeScale.prototype = new Scale(); 764 | 765 | function LogScale () { this.d3Scale = d3.scale.log(); } 766 | 767 | LogScale.prototype = new Scale(); 768 | 769 | function CategoricalScale () { this.d3Scale = d3.scale.ordinal(); } 770 | 771 | CategoricalScale.prototype = new Scale(); 772 | 773 | CategoricalScale.prototype.defaultDomain = function (values) { 774 | if (this.values !== undefined) { 775 | // Values were passed in the spec 776 | this.d3Scale.domain(this.values); 777 | } else { 778 | // Otherwise, extracted from data. 779 | values.sort(function (a, b) { return a - b; }); 780 | this.d3Scale.domain(values); 781 | } 782 | this.domainSet = true; 783 | }; 784 | 785 | CategoricalScale.prototype.range = function (interval) { 786 | // Setting padding to 1 seems to be required to get bars to 787 | // line up with axis ticks. Needs more investigation. 788 | this.d3Scale = this.d3Scale.rangeRoundBands(interval, 1); 789 | }; 790 | 791 | function ColorScale() { this.d3Scale = d3.scale.category20(); } 792 | 793 | ColorScale.prototype = new Scale(); 794 | 795 | ColorScale.prototype.defaultDomain = CategoricalScale.prototype.defaultDomain; 796 | 797 | 798 | //////////////////////////////////////////////////////////////////////// 799 | // Statistics 800 | 801 | function Statistic () {} 802 | 803 | Statistic.fromSpec = function (spec, geometry) { 804 | return new ({ 805 | identity: IdentityStatistic, 806 | bin: BinStatistic, 807 | box: BoxPlotStatistic, 808 | arrow: ArrowStatistic, 809 | sum: SumStatistic 810 | }[spec.statistic || geometry.defaultStatistic])(spec, geometry); 811 | }; 812 | 813 | function IdentityStatistic () {} 814 | 815 | IdentityStatistic.prototype = new Statistic(); 816 | 817 | IdentityStatistic.prototype.compute = function (data) { return data; }; 818 | 819 | function BinStatistic (spec) { 820 | this.variable = spec.variable; 821 | this.bins = spec.bins || 20; 822 | } 823 | 824 | BinStatistic.prototype = new Statistic(); 825 | 826 | BinStatistic.prototype.compute = function (data) { 827 | var values = _.pluck(data, this.variable); 828 | var histogram = d3.layout.histogram().bins(this.bins); 829 | var frequency = histogram(values); 830 | histogram.frequency(false); 831 | var density = histogram(values); 832 | return _.map(frequency, function (bin, i) { 833 | return { 834 | bin: i, 835 | count: bin.y, 836 | density: density[i].y, 837 | ncount: bin.y / data.length || 0 838 | // Not clear to me how to implement the ndensity metric 839 | //ndensity: null 840 | }; 841 | }); 842 | }; 843 | 844 | function SumStatistic (spec, geometry) { 845 | this.group = spec.group || false; 846 | this.variable = spec.variable; 847 | } 848 | 849 | SumStatistic.prototype = new Statistic(); 850 | 851 | SumStatistic.prototype.compute = function (data) { 852 | var groups = groupData(data, this.group); 853 | var value = _.bind(function(point) { return point[this.variable]; }, this); 854 | return _.map(groups, function (values, name) { 855 | sum = d3.sum(values, value); 856 | return { 857 | group: name, 858 | count: values.length, 859 | sum: sum, 860 | min: d3.min(values, value), 861 | max: d3.max(values, value), 862 | mean: sum/values.length 863 | }; 864 | }); 865 | }; 866 | 867 | function BoxPlotStatistic (spec) { 868 | this.group = spec.group || false; 869 | this.groupOrdering = spec.groupOrdering || function (x) { return x; }; 870 | this.variable = spec.variable || false; 871 | } 872 | 873 | BoxPlotStatistic.prototype = new Statistic(); 874 | 875 | BoxPlotStatistic.prototype.dataRange = function (data) { 876 | var flattened = _.flatten(data); 877 | return [ 878 | _.min(_.pluck(flattened, 'min')), 879 | _.max(_.pluck(flattened, 'max')) 880 | ]; 881 | }; 882 | 883 | BoxPlotStatistic.prototype.compute = function (data) { 884 | // Split data by the group variable (if provided) and for each 885 | // group return an object with: 886 | // 887 | // { 888 | // group: , 889 | // median: , 890 | // q1: , 891 | // q3: , 892 | // upper: , 893 | // lower: , 894 | // outliers: 895 | // min: 896 | // max: 897 | // } 898 | 899 | var groups = groupData(data, this.group); 900 | var variable = this.variable; 901 | var ordering = this.groupOrdering; 902 | 903 | return _.map(_.sortBy(_.pairs(groups), function (p) { return ordering(p[0]); }), function (g) { 904 | var name = g[0]; 905 | var values = variable ? _.pluck(g[1], variable) : g[1]; 906 | values.sort(d3.ascending); 907 | 908 | var q1 = d3.quantile(values, 0.25); 909 | var median = d3.quantile(values, 0.5); 910 | var q3 = d3.quantile(values, 0.75); 911 | var min = values[0]; 912 | var max = values[values.length - 1]; 913 | 914 | var fenceRange = 1.5 * (q3 - q1); 915 | var lowerFenceIndex = d3.bisectLeft(values, q1 - fenceRange); 916 | var upperFenceIndex = d3.bisectRight(values, q3 + fenceRange, lowerFenceIndex) - 1; 917 | var lower = values[lowerFenceIndex]; 918 | var upper = values[upperFenceIndex]; 919 | var outliers = values.slice(0, lowerFenceIndex).concat(values.slice(upperFenceIndex + 1)); 920 | 921 | var r = { 922 | group: name, 923 | q1: q1, 924 | median: median, 925 | q3: q3, 926 | lower: lower, 927 | upper: upper, 928 | outliers: outliers, 929 | min: min, 930 | max: max 931 | }; 932 | return r; 933 | }, this); 934 | }; 935 | 936 | function ArrowStatistic (spec) { 937 | // A function that returns the data point (in data space) the 938 | // arrow should point at. 939 | this.head = spec.head; 940 | 941 | // A function that returns the data point (in data space) the 942 | // arrow should point from. 943 | this.tail = spec.tail; 944 | } 945 | 946 | ArrowStatistic.prototype = new Statistic(); 947 | 948 | ArrowStatistic.prototype.compute = function (data) { 949 | return { 950 | head: this.head(data), 951 | tail: this.tail(data) 952 | }; 953 | }; 954 | 955 | /*** 956 | * Returns a grouping of data based on a data set's attribute. 957 | * If groupBy is not defined returns the data nested as a single group. 958 | */ 959 | function groupData(data, groupBy) { 960 | return _.isUndefined(groupBy) ? { 'data': data } : _.groupBy(data, groupBy); 961 | } 962 | 963 | function translate(x, y) { return 'translate(' + x + ',' + y + ')'; } 964 | 965 | //////////////////////////////////////////////////////////////////////// 966 | // API 967 | 968 | /* 969 | * Given a spec for a graphic, return a rendering function that 970 | * can render the graphic given data, a DOM element in which to 971 | * render it, and graphics options. 972 | */ 973 | exports.gg = function gg () { 974 | var graphic = new Graphic({ 975 | facets: _.find(arguments, function (x) { return _.has(x, 'facets'); }), 976 | layers: _.filter(arguments, function (x) { return _.has(x, 'geometry'); }), 977 | scales: _.filter(arguments, function (x) { return _.has(x, 'aesthetic'); }) 978 | }); 979 | return function (data, where, opts) { graphic.render(data, where, opts); }; 980 | }; 981 | 982 | })(this); 983 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | The Grammar of Graphics 6 | 7 | 8 | 9 |

The Grammar of Graphics

10 | 11 |

See code for these graphics.

12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /json2.js: -------------------------------------------------------------------------------- 1 | /* json2.js 2 | * 2008-01-17 3 | * Public Domain 4 | * No warranty expressed or implied. Use at your own risk. 5 | * See http://www.JSON.org/js.html 6 | */ 7 | (function(){if(!this.JSON){JSON=function(){function f(n){return n<10?'0'+n:n;}Date.prototype.toJSON=function(){return this.getUTCFullYear()+'-'+f(this.getUTCMonth()+1)+'-'+f(this.getUTCDate())+'T'+f(this.getUTCHours())+':'+f(this.getUTCMinutes())+':'+f(this.getUTCSeconds())+'Z';};var m={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'};function stringify(value,whitelist){var a,i,k,l,r=/["\\\x00-\x1f\x7f-\x9f]/g,v;switch(typeof value){case'string':return r.test(value)?'"'+value.replace(r,function(a){var c=m[a];if(c){return c;}c=a.charCodeAt();return'\\u00'+Math.floor(c/16).toString(16)+(c%16).toString(16);})+'"':'"'+value+'"';case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';}if(typeof value.toJSON==='function'){return stringify(value.toJSON());}a=[];if(typeof value.length==='number'&&!(value.propertyIsEnumerable('length'))){l=value.length;for(i=0;i=0&&o>i;i+=n){var a=u?u[i]:i;e=r(e,t[a],a,t)}return e}return function(r,e,u,i){e=b(e,i,4);var o=!k(r)&&m.keys(r),a=(o||r).length,c=n>0?0:a-1;return arguments.length<3&&(u=r[o?o[c]:c],c+=n),t(r,e,u,o,c,a)}}function t(n){return function(t,r,e){r=x(r,e);for(var u=O(t),i=n>0?0:u-1;i>=0&&u>i;i+=n)if(r(t[i],i,t))return i;return-1}}function r(n,t,r){return function(e,u,i){var o=0,a=O(e);if("number"==typeof i)n>0?o=i>=0?i:Math.max(i+a,o):a=i>=0?Math.min(i+1,a):i+a+1;else if(r&&i&&a)return i=r(e,u),e[i]===u?i:-1;if(u!==u)return i=t(l.call(e,o,a),m.isNaN),i>=0?i+o:-1;for(i=n>0?o:a-1;i>=0&&a>i;i+=n)if(e[i]===u)return i;return-1}}function e(n,t){var r=I.length,e=n.constructor,u=m.isFunction(e)&&e.prototype||a,i="constructor";for(m.has(n,i)&&!m.contains(t,i)&&t.push(i);r--;)i=I[r],i in n&&n[i]!==u[i]&&!m.contains(t,i)&&t.push(i)}var u=this,i=u._,o=Array.prototype,a=Object.prototype,c=Function.prototype,f=o.push,l=o.slice,s=a.toString,p=a.hasOwnProperty,h=Array.isArray,v=Object.keys,g=c.bind,y=Object.create,d=function(){},m=function(n){return n instanceof m?n:this instanceof m?void(this._wrapped=n):new m(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=m),exports._=m):u._=m,m.VERSION="1.8.3";var b=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}},x=function(n,t,r){return null==n?m.identity:m.isFunction(n)?b(n,t,r):m.isObject(n)?m.matcher(n):m.property(n)};m.iteratee=function(n,t){return x(n,t,1/0)};var _=function(n,t){return function(r){var e=arguments.length;if(2>e||null==r)return r;for(var u=1;e>u;u++)for(var i=arguments[u],o=n(i),a=o.length,c=0;a>c;c++){var f=o[c];t&&r[f]!==void 0||(r[f]=i[f])}return r}},j=function(n){if(!m.isObject(n))return{};if(y)return y(n);d.prototype=n;var t=new d;return d.prototype=null,t},w=function(n){return function(t){return null==t?void 0:t[n]}},A=Math.pow(2,53)-1,O=w("length"),k=function(n){var t=O(n);return"number"==typeof t&&t>=0&&A>=t};m.each=m.forEach=function(n,t,r){t=b(t,r);var e,u;if(k(n))for(e=0,u=n.length;u>e;e++)t(n[e],e,n);else{var i=m.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},m.map=m.collect=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=Array(u),o=0;u>o;o++){var a=e?e[o]:o;i[o]=t(n[a],a,n)}return i},m.reduce=m.foldl=m.inject=n(1),m.reduceRight=m.foldr=n(-1),m.find=m.detect=function(n,t,r){var e;return e=k(n)?m.findIndex(n,t,r):m.findKey(n,t,r),e!==void 0&&e!==-1?n[e]:void 0},m.filter=m.select=function(n,t,r){var e=[];return t=x(t,r),m.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e},m.reject=function(n,t,r){return m.filter(n,m.negate(x(t)),r)},m.every=m.all=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(!t(n[o],o,n))return!1}return!0},m.some=m.any=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(t(n[o],o,n))return!0}return!1},m.contains=m.includes=m.include=function(n,t,r,e){return k(n)||(n=m.values(n)),("number"!=typeof r||e)&&(r=0),m.indexOf(n,t,r)>=0},m.invoke=function(n,t){var r=l.call(arguments,2),e=m.isFunction(t);return m.map(n,function(n){var u=e?t:n[t];return null==u?u:u.apply(n,r)})},m.pluck=function(n,t){return m.map(n,m.property(t))},m.where=function(n,t){return m.filter(n,m.matcher(t))},m.findWhere=function(n,t){return m.find(n,m.matcher(t))},m.max=function(n,t,r){var e,u,i=-1/0,o=-1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],e>i&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(u>o||u===-1/0&&i===-1/0)&&(i=n,o=u)});return i},m.min=function(n,t,r){var e,u,i=1/0,o=1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],i>e&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(o>u||1/0===u&&1/0===i)&&(i=n,o=u)});return i},m.shuffle=function(n){for(var t,r=k(n)?n:m.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=m.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},m.sample=function(n,t,r){return null==t||r?(k(n)||(n=m.values(n)),n[m.random(n.length-1)]):m.shuffle(n).slice(0,Math.max(0,t))},m.sortBy=function(n,t,r){return t=x(t,r),m.pluck(m.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={};return r=x(r,e),m.each(t,function(e,i){var o=r(e,i,t);n(u,e,o)}),u}};m.groupBy=F(function(n,t,r){m.has(n,r)?n[r].push(t):n[r]=[t]}),m.indexBy=F(function(n,t,r){n[r]=t}),m.countBy=F(function(n,t,r){m.has(n,r)?n[r]++:n[r]=1}),m.toArray=function(n){return n?m.isArray(n)?l.call(n):k(n)?m.map(n,m.identity):m.values(n):[]},m.size=function(n){return null==n?0:k(n)?n.length:m.keys(n).length},m.partition=function(n,t,r){t=x(t,r);var e=[],u=[];return m.each(n,function(n,r,i){(t(n,r,i)?e:u).push(n)}),[e,u]},m.first=m.head=m.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:m.initial(n,n.length-t)},m.initial=function(n,t,r){return l.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},m.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:m.rest(n,Math.max(0,n.length-t))},m.rest=m.tail=m.drop=function(n,t,r){return l.call(n,null==t||r?1:t)},m.compact=function(n){return m.filter(n,m.identity)};var S=function(n,t,r,e){for(var u=[],i=0,o=e||0,a=O(n);a>o;o++){var c=n[o];if(k(c)&&(m.isArray(c)||m.isArguments(c))){t||(c=S(c,t,r));var f=0,l=c.length;for(u.length+=l;l>f;)u[i++]=c[f++]}else r||(u[i++]=c)}return u};m.flatten=function(n,t){return S(n,t,!1)},m.without=function(n){return m.difference(n,l.call(arguments,1))},m.uniq=m.unique=function(n,t,r,e){m.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=x(r,e));for(var u=[],i=[],o=0,a=O(n);a>o;o++){var c=n[o],f=r?r(c,o,n):c;t?(o&&i===f||u.push(c),i=f):r?m.contains(i,f)||(i.push(f),u.push(c)):m.contains(u,c)||u.push(c)}return u},m.union=function(){return m.uniq(S(arguments,!0,!0))},m.intersection=function(n){for(var t=[],r=arguments.length,e=0,u=O(n);u>e;e++){var i=n[e];if(!m.contains(t,i)){for(var o=1;r>o&&m.contains(arguments[o],i);o++);o===r&&t.push(i)}}return t},m.difference=function(n){var t=S(arguments,!0,!0,1);return m.filter(n,function(n){return!m.contains(t,n)})},m.zip=function(){return m.unzip(arguments)},m.unzip=function(n){for(var t=n&&m.max(n,O).length||0,r=Array(t),e=0;t>e;e++)r[e]=m.pluck(n,e);return r},m.object=function(n,t){for(var r={},e=0,u=O(n);u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},m.findIndex=t(1),m.findLastIndex=t(-1),m.sortedIndex=function(n,t,r,e){r=x(r,e,1);for(var u=r(t),i=0,o=O(n);o>i;){var a=Math.floor((i+o)/2);r(n[a])i;i++,n+=r)u[i]=n;return u};var E=function(n,t,r,e,u){if(!(e instanceof t))return n.apply(r,u);var i=j(n.prototype),o=n.apply(i,u);return m.isObject(o)?o:i};m.bind=function(n,t){if(g&&n.bind===g)return g.apply(n,l.call(arguments,1));if(!m.isFunction(n))throw new TypeError("Bind must be called on a function");var r=l.call(arguments,2),e=function(){return E(n,e,t,this,r.concat(l.call(arguments)))};return e},m.partial=function(n){var t=l.call(arguments,1),r=function(){for(var e=0,u=t.length,i=Array(u),o=0;u>o;o++)i[o]=t[o]===m?arguments[e++]:t[o];for(;e=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=m.bind(n[r],n);return n},m.memoize=function(n,t){var r=function(e){var u=r.cache,i=""+(t?t.apply(this,arguments):e);return m.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},m.delay=function(n,t){var r=l.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},m.defer=m.partial(m.delay,m,1),m.throttle=function(n,t,r){var e,u,i,o=null,a=0;r||(r={});var c=function(){a=r.leading===!1?0:m.now(),o=null,i=n.apply(e,u),o||(e=u=null)};return function(){var f=m.now();a||r.leading!==!1||(a=f);var l=t-(f-a);return e=this,u=arguments,0>=l||l>t?(o&&(clearTimeout(o),o=null),a=f,i=n.apply(e,u),o||(e=u=null)):o||r.trailing===!1||(o=setTimeout(c,l)),i}},m.debounce=function(n,t,r){var e,u,i,o,a,c=function(){var f=m.now()-o;t>f&&f>=0?e=setTimeout(c,t-f):(e=null,r||(a=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,o=m.now();var f=r&&!e;return e||(e=setTimeout(c,t)),f&&(a=n.apply(i,u),i=u=null),a}},m.wrap=function(n,t){return m.partial(t,n)},m.negate=function(n){return function(){return!n.apply(this,arguments)}},m.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},m.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},m.before=function(n,t){var r;return function(){return--n>0&&(r=t.apply(this,arguments)),1>=n&&(t=null),r}},m.once=m.partial(m.before,2);var M=!{toString:null}.propertyIsEnumerable("toString"),I=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];m.keys=function(n){if(!m.isObject(n))return[];if(v)return v(n);var t=[];for(var r in n)m.has(n,r)&&t.push(r);return M&&e(n,t),t},m.allKeys=function(n){if(!m.isObject(n))return[];var t=[];for(var r in n)t.push(r);return M&&e(n,t),t},m.values=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},m.mapObject=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=u.length,o={},a=0;i>a;a++)e=u[a],o[e]=t(n[e],e,n);return o},m.pairs=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},m.invert=function(n){for(var t={},r=m.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},m.functions=m.methods=function(n){var t=[];for(var r in n)m.isFunction(n[r])&&t.push(r);return t.sort()},m.extend=_(m.allKeys),m.extendOwn=m.assign=_(m.keys),m.findKey=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=0,o=u.length;o>i;i++)if(e=u[i],t(n[e],e,n))return e},m.pick=function(n,t,r){var e,u,i={},o=n;if(null==o)return i;m.isFunction(t)?(u=m.allKeys(o),e=b(t,r)):(u=S(arguments,!1,!1,1),e=function(n,t,r){return t in r},o=Object(o));for(var a=0,c=u.length;c>a;a++){var f=u[a],l=o[f];e(l,f,o)&&(i[f]=l)}return i},m.omit=function(n,t,r){if(m.isFunction(t))t=m.negate(t);else{var e=m.map(S(arguments,!1,!1,1),String);t=function(n,t){return!m.contains(e,t)}}return m.pick(n,t,r)},m.defaults=_(m.allKeys,!0),m.create=function(n,t){var r=j(n);return t&&m.extendOwn(r,t),r},m.clone=function(n){return m.isObject(n)?m.isArray(n)?n.slice():m.extend({},n):n},m.tap=function(n,t){return t(n),n},m.isMatch=function(n,t){var r=m.keys(t),e=r.length;if(null==n)return!e;for(var u=Object(n),i=0;e>i;i++){var o=r[i];if(t[o]!==u[o]||!(o in u))return!1}return!0};var N=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof m&&(n=n._wrapped),t instanceof m&&(t=t._wrapped);var u=s.call(n);if(u!==s.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}var i="[object Array]"===u;if(!i){if("object"!=typeof n||"object"!=typeof t)return!1;var o=n.constructor,a=t.constructor;if(o!==a&&!(m.isFunction(o)&&o instanceof o&&m.isFunction(a)&&a instanceof a)&&"constructor"in n&&"constructor"in t)return!1}r=r||[],e=e||[];for(var c=r.length;c--;)if(r[c]===n)return e[c]===t;if(r.push(n),e.push(t),i){if(c=n.length,c!==t.length)return!1;for(;c--;)if(!N(n[c],t[c],r,e))return!1}else{var f,l=m.keys(n);if(c=l.length,m.keys(t).length!==c)return!1;for(;c--;)if(f=l[c],!m.has(t,f)||!N(n[f],t[f],r,e))return!1}return r.pop(),e.pop(),!0};m.isEqual=function(n,t){return N(n,t)},m.isEmpty=function(n){return null==n?!0:k(n)&&(m.isArray(n)||m.isString(n)||m.isArguments(n))?0===n.length:0===m.keys(n).length},m.isElement=function(n){return!(!n||1!==n.nodeType)},m.isArray=h||function(n){return"[object Array]"===s.call(n)},m.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},m.each(["Arguments","Function","String","Number","Date","RegExp","Error"],function(n){m["is"+n]=function(t){return s.call(t)==="[object "+n+"]"}}),m.isArguments(arguments)||(m.isArguments=function(n){return m.has(n,"callee")}),"function"!=typeof/./&&"object"!=typeof Int8Array&&(m.isFunction=function(n){return"function"==typeof n||!1}),m.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},m.isNaN=function(n){return m.isNumber(n)&&n!==+n},m.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===s.call(n)},m.isNull=function(n){return null===n},m.isUndefined=function(n){return n===void 0},m.has=function(n,t){return null!=n&&p.call(n,t)},m.noConflict=function(){return u._=i,this},m.identity=function(n){return n},m.constant=function(n){return function(){return n}},m.noop=function(){},m.property=w,m.propertyOf=function(n){return null==n?function(){}:function(t){return n[t]}},m.matcher=m.matches=function(n){return n=m.extendOwn({},n),function(t){return m.isMatch(t,n)}},m.times=function(n,t,r){var e=Array(Math.max(0,n));t=b(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},m.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},m.now=Date.now||function(){return(new Date).getTime()};var B={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},T=m.invert(B),R=function(n){var t=function(t){return n[t]},r="(?:"+m.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};m.escape=R(B),m.unescape=R(T),m.result=function(n,t,r){var e=null==n?void 0:n[t];return e===void 0&&(e=r),m.isFunction(e)?e.call(n):e};var q=0;m.uniqueId=function(n){var t=++q+"";return n?n+t:t},m.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var K=/(.)^/,z={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\u2028|\u2029/g,L=function(n){return"\\"+z[n]};m.template=function(n,t,r){!t&&r&&(t=r),t=m.defaults({},t,m.templateSettings);var e=RegExp([(t.escape||K).source,(t.interpolate||K).source,(t.evaluate||K).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,o,a){return i+=n.slice(u,a).replace(D,L),u=a+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":o&&(i+="';\n"+o+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var o=new Function(t.variable||"obj","_",i)}catch(a){throw a.source=i,a}var c=function(n){return o.call(this,n,m)},f=t.variable||"obj";return c.source="function("+f+"){\n"+i+"}",c},m.chain=function(n){var t=m(n);return t._chain=!0,t};var P=function(n,t){return n._chain?m(t).chain():t};m.mixin=function(n){m.each(m.functions(n),function(t){var r=m[t]=n[t];m.prototype[t]=function(){var n=[this._wrapped];return f.apply(n,arguments),P(this,r.apply(m,n))}})},m.mixin(m),m.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=o[n];m.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],P(this,r)}}),m.each(["concat","join","slice"],function(n){var t=o[n];m.prototype[n]=function(){return P(this,t.apply(this._wrapped,arguments))}}),m.prototype.value=function(){return this._wrapped},m.prototype.valueOf=m.prototype.toJSON=m.prototype.value,m.prototype.toString=function(){return""+this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return m})}).call(this); 6 | //# sourceMappingURL=underscore-min.map 7 | --------------------------------------------------------------------------------