rack-mini-profiler 0.1.21 → 0.1.26

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack-mini-profiler might be problematic. Click here for more details.

Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/Ruby/CHANGELOG +135 -0
  3. data/{README.md → Ruby/README.md} +40 -9
  4. data/Ruby/lib/html/flamegraph.html +325 -0
  5. data/Ruby/lib/html/includes.css +451 -0
  6. data/{lib → Ruby/lib}/html/includes.js +135 -24
  7. data/{lib → Ruby/lib}/html/includes.less +38 -35
  8. data/{lib → Ruby/lib}/html/includes.tmpl +40 -15
  9. data/{lib → Ruby/lib}/html/jquery.1.7.1.js +1 -1
  10. data/{lib → Ruby/lib}/html/jquery.tmpl.js +1 -1
  11. data/{lib → Ruby/lib}/html/list.css +0 -0
  12. data/{lib → Ruby/lib}/html/list.js +7 -6
  13. data/{lib → Ruby/lib}/html/list.tmpl +0 -0
  14. data/Ruby/lib/html/profile_handler.js +1 -0
  15. data/{lib → Ruby/lib}/html/share.html +0 -0
  16. data/{lib → Ruby/lib}/mini_profiler/client_settings.rb +0 -0
  17. data/{lib → Ruby/lib}/mini_profiler/client_timer_struct.rb +1 -1
  18. data/{lib → Ruby/lib}/mini_profiler/config.rb +57 -52
  19. data/{lib → Ruby/lib}/mini_profiler/context.rb +11 -10
  20. data/Ruby/lib/mini_profiler/custom_timer_struct.rb +22 -0
  21. data/Ruby/lib/mini_profiler/flame_graph.rb +54 -0
  22. data/{lib → Ruby/lib}/mini_profiler/gc_profiler.rb +35 -12
  23. data/{lib → Ruby/lib}/mini_profiler/page_timer_struct.rb +7 -2
  24. data/{lib → Ruby/lib}/mini_profiler/profiler.rb +209 -144
  25. data/{lib → Ruby/lib}/mini_profiler/profiling_methods.rb +131 -108
  26. data/{lib → Ruby/lib}/mini_profiler/request_timer_struct.rb +20 -1
  27. data/{lib → Ruby/lib}/mini_profiler/sql_timer_struct.rb +0 -0
  28. data/{lib → Ruby/lib}/mini_profiler/storage/abstract_store.rb +31 -27
  29. data/{lib → Ruby/lib}/mini_profiler/storage/file_store.rb +111 -109
  30. data/{lib → Ruby/lib}/mini_profiler/storage/memcache_store.rb +11 -9
  31. data/{lib → Ruby/lib}/mini_profiler/storage/memory_store.rb +65 -63
  32. data/Ruby/lib/mini_profiler/storage/redis_store.rb +54 -0
  33. data/{lib → Ruby/lib}/mini_profiler/timer_struct.rb +0 -0
  34. data/Ruby/lib/mini_profiler/version.rb +5 -0
  35. data/Ruby/lib/mini_profiler_rails/railtie.rb +89 -0
  36. data/Ruby/lib/patches/net_patches.rb +14 -0
  37. data/{lib → Ruby/lib}/patches/sql_patches.rb +89 -48
  38. data/{lib → Ruby/lib}/rack-mini-profiler.rb +1 -0
  39. data/rack-mini-profiler.gemspec +6 -4
  40. metadata +53 -64
  41. data/CHANGELOG +0 -99
  42. data/lib/html/includes.css +0 -75
  43. data/lib/html/profile_handler.js +0 -62
  44. data/lib/mini_profiler/storage/redis_store.rb +0 -44
  45. data/lib/mini_profiler_rails/railtie.rb +0 -85
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 943d309dfe7e69d839ea284e6c9d525e61e5411e
4
+ data.tar.gz: bdf30a92d384c74ab69e5d5c19ca853135100d77
5
+ SHA512:
6
+ metadata.gz: db02f40f91da2a44a6e0d001d24e5d12946499a1e152a20068e252ff551c57627196648ee3b101fa4d746a0752e431497658c74610b37fb36b8e4e4061c4e71a
7
+ data.tar.gz: 5ca22681b6b47bdb4b0e1d5929a9199e7b4b8974d144a9d318b737e6c5988929d11aeff8dd06f890f7975093c63c3eeb8c8ebbc799121aca290f713306eb0f03
@@ -0,0 +1,135 @@
1
+ 28-June-2012 - Sam
2
+
3
+ * Started change log
4
+ * Corrected profiler so it properly captures POST requests (was supressing non 200s)
5
+ * Amended Rack.MiniProfiler.config[:user_provider] to use ip addres for identity
6
+ * Fixed bug where unviewed missing ids never got cleared
7
+ * Supress all '/assets/' in the rails tie (makes debugging easier)
8
+ * record_sql was mega buggy
9
+ * added MemcacheStore
10
+
11
+ 9-July-2012 - Sam
12
+
13
+ * Cleaned up mechanism for profiling in production, all you need to do now
14
+ is call Rack::MiniProfiler.authorize_request to get profiling working in
15
+ production
16
+ * Added option to display full backtraces pp=full-backtrace
17
+ * Cleaned up railties, got rid of the post authorize callback
18
+ * Version 0.1.3
19
+
20
+ 12-July-2012 - Sam
21
+
22
+ * Fixed incorrect profiling steps (was not indenting or measuring start time right
23
+ * Implemented native PG and MySql2 interceptors, this gives way more accurate times
24
+ * Refactored context so its a proper class and not a hash
25
+ * Added some more client probing built in to rails
26
+ * More tests
27
+
28
+ 18-July-2012 - Sam
29
+
30
+ * Added First Paint time for chrome
31
+ * Bug fix to ensure non Rails installs have mini profiler
32
+ * Version 0.1.7
33
+
34
+ 30-July-2012 - Sam
35
+
36
+ * Made compliant with ancient versions of Rack (including Rack used by Rails2)
37
+ * Fixed broken share link
38
+ * Fixed crashes on startup (in MemoryStore and FileStore)
39
+ * Version 0.1.8
40
+ * Unicode fix
41
+ * Version 0.1.9
42
+
43
+ 7-August-2012 - Sam
44
+
45
+ * Added option to disable profiler for the current session (pp=disable / pp=enable)
46
+ * yajl compatability contributed by Sven Riedel
47
+
48
+ 10-August-2012 - Sam
49
+
50
+ * Added basic prepared statement profiling for postgres
51
+
52
+ 20-August-2012 - Sam
53
+
54
+ * 1.12.pre
55
+ * Cap X-MiniProfiler-Ids at 10, otherwise the header can get killed
56
+
57
+ 3-September-2012 - Sam
58
+
59
+ * 1.13.pre
60
+ * pg gem prepared statements were not being logged correctly
61
+ * added setting config.backtrace_ignores = [] - an array of regexes that match on caller lines that get ignored
62
+ * added setting config.backtrace_includes = [] - an array of regexes that get included in the trace by default
63
+ * cleaned up the way client settings are stored
64
+ * made pp=full-backtrace "sticky"
65
+ * added pp=normal-backtrace to clear the "sticky" state
66
+ * change "pp=sample" to work with "caller" no need for stack trace gem
67
+
68
+ 4-September-2012 - Sam
69
+
70
+ * 1.15.pre
71
+ * fixed annoying bug where client settings were not sticking
72
+ * fixed long standing issue with Rack::ConditionalGet stopping MiniProfiler from working properly
73
+
74
+ 5-September-2012 - Sam
75
+
76
+ * 1.16
77
+ * fixed long standing problem specs (issue with memory store)
78
+ * fixed issue where profiler would be dumped when you got a 404 in production (and any time rails is bypassed)
79
+ * implemented stacktrace properly
80
+
81
+ 9-September-2012 - Sam
82
+
83
+ * 1.17
84
+ * pp=sample was bust unless stacktrace was installed
85
+
86
+ 10-September-2012 - Sam
87
+
88
+ * 1.19
89
+ * fix compat issue with 1.8.7
90
+
91
+ 12-September-2012 - Sam
92
+
93
+ * 1.20
94
+ * Added pp=profile-gc , it allows you to profile the GC in Ruby 1.9.3
95
+
96
+ 17-September-2012
97
+ * 1.21
98
+ * New MemchacedStore
99
+ * Rails 4 support
100
+
101
+ 17-September-2012
102
+ * Allow rack-mini-profiler to be sourced from github
103
+ * Extracted the pp=profile-gc-time out, the object space profiler needs to disable gc
104
+
105
+ 20-September-2012
106
+ * 1.22
107
+ * Fix permission issue in the gem
108
+
109
+ 8-April-2013
110
+ * 1.24
111
+ * Flame Graph Support see: http://samsaffron.com/archive/2013/03/19/flame-graphs-in-ruby-miniprofiler
112
+ * Fix file retention leak in file_store
113
+ * New toggle_shortcut and start_hidden options
114
+ * Fix for AngularJS support and MooTools
115
+ * More robust gc profiling
116
+ * Mongoid support
117
+ * Fix for html5 implicit body tags
118
+ * script tag initialized via data-attributes
119
+ * new - Rack::MiniProfiler.counter counter_name {}
120
+ * Allow usage of existing jQuery if its already loaded
121
+ * Fix pp=enable
122
+ * 1.8.7 support ... grrr
123
+ * Net:HTTP profiling
124
+ * pre authorize to run in all non development? and production? modes
125
+
126
+ 8-April-2013
127
+ * 1.25
128
+ * Missed flamegraph.html from build
129
+
130
+ 11-April-2013
131
+ * 1.26
132
+ * (minor) allow Rack::MiniProfilerRails.initialize!(Rails.application), for post config intialization
133
+
134
+
135
+
@@ -15,7 +15,7 @@ All you have to do is include the Gem and you're good to go in development.
15
15
 
16
16
  rack-mini-profiler is designed with production profiling in mind. To enable that just run `Rack::MiniProfiler.authorize_request` once you know a request is allowed to profile.
17
17
 
18
- For example:
18
+ Using Rails:
19
19
 
20
20
  ```ruby
21
21
  # A hook in your ApplicationController
@@ -47,23 +47,53 @@ class MyApp < Sinatra::Base
47
47
  end
48
48
  ```
49
49
 
50
+ ## Database profiling
51
+
52
+ Currently supports Mysql2, Postgres, and Mongoid3 (with fallback support to ActiveRecord)
53
+
50
54
  ## Storage
51
55
 
52
- By default, rack-mini-profiler stores its results in a memory store:
56
+ rack-mini-profiler stores it's results so they can be shared later and aren't lost at the end of the request.
57
+
58
+ There are 4 storage options: `MemoryStore`, `RedisStore`, `MemcacheStore`, and `FileStore`.
59
+
60
+ `FileStore` is the default in Rails environments and will write files to `tmp/miniprofiler/*`. `MemoryStore` is the default otherwise.
61
+
62
+ To change the default you can create a file in `config/initializers/mini_profiler.rb`
53
63
 
54
64
  ```ruby
55
- # our default
65
+ # set MemoryStore
56
66
  Rack::MiniProfiler.config.storage = Rack::MiniProfiler::MemoryStore
57
- ```
58
67
 
59
- There are 2 other available storage engines, `RedisStore`, `MemcacheStore`, and `FileStore`.
68
+ # set RedisStore
69
+ if Rails.env.production?
70
+ uri = URI.parse(ENV["REDIS_SERVER_URL"])
71
+ Rack::MiniProfiler.config.storage_options = { :host => uri.host, :port => uri.port, :password => uri.password }
72
+ Rack::MiniProfiler.config.storage = Rack::MiniProfiler::RedisStore
73
+ end
74
+ ```
60
75
 
61
- MemoryStore is stores results in a processes heap - something that does not work well in a multi process environment.
76
+ MemoryStore stores results in a processes heap - something that does not work well in a multi process environment.
62
77
  FileStore stores results in the file system - something that may not work well in a multi machine environment.
78
+ RedisStore/MemcacheStore work in multi process and multi machine environments (RedisStore only saves results for up to 24 hours so it won't continue to fill up Redis).
63
79
 
64
80
  Additionally you may implement an AbstractStore for your own provider.
65
81
 
66
- Rails hooks up a FileStore for all environments.
82
+ ## User result segregation
83
+
84
+ MiniProfiler will attempt to keep all user results isolated, out-of-the-box the user provider uses the ip address:
85
+
86
+ ```ruby
87
+ Rack::MiniProfiler.config.user_provider = Proc.new{|env| Rack::Request.new(env).ip}
88
+ ```
89
+
90
+ You can override (something that is very important in a multi-machine production setup):
91
+
92
+ ```ruby
93
+ Rack::MiniProfiler.config.user_provider = Proc.new{ |env| CurrentUser.get(env) }
94
+ ```
95
+
96
+ The string this function returns should be unique for each user on the system (for anonymous you may need to fall back to ip address)
67
97
 
68
98
  ## Running the Specs
69
99
 
@@ -120,11 +150,12 @@ end
120
150
  * pre_authorize_cb - A lambda callback you can set to determine whether or not mini_profiler should be visible on a given request. Default in a Rails environment is only on in development mode. If in a Rack app, the default is always on.
121
151
  * position - Can either be 'right' or 'left'. Default is 'left'.
122
152
  * skip_schema_queries - Whether or not you want to log the queries about the schema of your tables. Default is 'false', 'true' in rails development.
123
- * use_existing_jquery - Use the version of jQuery on the page as opposed to the self contained one
124
153
  * auto_inject (default true) - when false the miniprofiler script is not injected in the page
125
154
  * backtrace_filter - a regex you can use to filter out unwanted lines from the backtraces
155
+ * toggle_shortcut (default Alt+P) - a jquery.hotkeys.js-style keyboard shortcut, used to toggle the mini_profiler's visibility. See http://code.google.com/p/js-hotkeys/ for more info.
156
+ * start_hidden (default false) - Whether or not you want the mini_profiler to be visible when loading a page
126
157
 
127
158
  ## Special query strings
128
159
 
129
- If you include the query string `pp=help` at the end of your request you will see the various option you have. You can use these options to extend or contract the amount of diagnostics rack-mini-profiler gathers.
160
+ If you include the query string `pp=help` at the end of your request you will see the various options available. You can use these options to extend or contract the amount of diagnostics rack-mini-profiler gathers.
130
161
 
@@ -0,0 +1,325 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
5
+ <script src="http://cdnjs.cloudflare.com/ajax/libs/d3/3.0.8/d3.min.js"></script>
6
+ <!-- <script src="http://cdnjs.cloudflare.com/ajax/libs/sugar/1.3.8/sugar.min.js"></script> -->
7
+ <meta charset=utf-8 />
8
+ <title>Flame Graph of Page</title>
9
+ <style>
10
+ .info {height: 40px;}
11
+ .legend div {
12
+ display: block;
13
+ float: left;
14
+ width: 150px;
15
+ margin: 0 8px 8px;
16
+ padding: 4px;
17
+ height: 50px;
18
+ }
19
+ </style>
20
+ </head>
21
+ <body>
22
+ <div class="graph"></div>
23
+ <div class="info"></div>
24
+ <div class="legend"></div>
25
+
26
+ <script>
27
+
28
+ var data = /*DATA*/;
29
+ var maxX = 0;
30
+ var maxY = 0;
31
+
32
+ debounce = function(func, wait, trickle) {
33
+ var timeout;
34
+
35
+ timeout = null;
36
+ return function() {
37
+ var args, context, currentWait, later;
38
+ context = this;
39
+ args = arguments;
40
+ later = function() {
41
+ timeout = null;
42
+ return func.apply(context, args);
43
+ };
44
+
45
+ if (timeout && trickle) {
46
+ // already queued, let it through
47
+ return;
48
+ }
49
+
50
+ if (typeof wait === "function") {
51
+ currentWait = wait();
52
+ } else {
53
+ currentWait = wait;
54
+ }
55
+
56
+ if (timeout) {
57
+ clearTimeout(timeout);
58
+ }
59
+
60
+ timeout = setTimeout(later, currentWait);
61
+ return timeout;
62
+ };
63
+ };
64
+
65
+
66
+ var guessGem = function(frame)
67
+ {
68
+ var split = frame.split('/gems/');
69
+ if(split.length == 1) {
70
+ split = frame.split('/app/');
71
+ if(split.length == 1) {
72
+ split = frame.split('/lib/');
73
+ }
74
+
75
+ split = split[Math.max(split.length-2,0)].split('/');
76
+ return split[split.length-1];
77
+ }
78
+ else
79
+ {
80
+ return split[split.length -1].split('/')[0];
81
+ }
82
+ }
83
+
84
+ var guessMethod = function(frame) {
85
+ var split = frame.split('`');
86
+ if(split.length == 2) {
87
+ return split[1].split("'")[0];
88
+ }
89
+ return '?';
90
+ }
91
+
92
+ var guessFile = function(frame) {
93
+ var split = frame.split(".rb:");
94
+ if(split.length == 2) {
95
+ split = split[0].split('/');
96
+ return split[split.length - 1];
97
+ }
98
+ return "";
99
+ }
100
+
101
+ $.each(data, function(){
102
+ maxX = Math.max(maxX, this.x + this.width);
103
+ maxY = Math.max(maxY, this.y);
104
+ this.shortName = /* guessGem(this.frame) + " " + guessFile(this.frame) + " " */ guessMethod(this.frame);
105
+ });
106
+
107
+ var width = $(window).width();
108
+ var height = $(window).height() / 1.2;
109
+
110
+ $('.graph').width(width).height(height);
111
+
112
+ var xScale = d3.scale.linear()
113
+ .domain([0, maxX])
114
+ .range([0, width]);
115
+
116
+ var yScale = d3.scale.linear()
117
+ .domain([0, maxY])
118
+ .range([0,height]);
119
+
120
+ var realHeight = 0;
121
+ var debouncedHeightCheck = debounce(function(){
122
+ if (realHeight > 15) {
123
+ svg.selectAll('text').attr('display','show');
124
+ } else {
125
+ svg.selectAll('text').attr('display','none');
126
+ }
127
+ }, 200);
128
+
129
+ function zoom()
130
+ {
131
+ svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
132
+
133
+ realHeight = yScale(1) * d3.event.scale;
134
+ debouncedHeightCheck();
135
+ }
136
+
137
+ var svg = d3.select(".graph")
138
+ .append("svg")
139
+ .attr("width", "100%")
140
+ .attr("height", "100%")
141
+ .attr("pointer-events", "all")
142
+ .append('svg:g')
143
+ .call(d3.behavior.zoom().on("zoom", zoom))
144
+ .append('svg:g');
145
+
146
+
147
+ // so zoom works everywhere
148
+ svg.append("rect")
149
+ .attr("x",function(d) { return xScale(0); })
150
+ .attr("y",function(d) { return yScale(0);})
151
+ .attr("width", function(d){return xScale(maxX);})
152
+ .attr("height", yScale(maxY))
153
+ .attr("fill", "white");
154
+
155
+ var color = function() {
156
+ var r = parseInt(205 + Math.random() * 50);
157
+ var g = parseInt(Math.random() * 230);
158
+ var b = parseInt(Math.random() * 55);
159
+ return "rgb(" + r + "," + g + "," + b + ")";
160
+ }
161
+ var info = {};
162
+
163
+ // http://stackoverflow.com/questions/1960473/unique-values-in-an-array
164
+ Array.prototype.getUnique = function() {
165
+ var o = {}, a = []
166
+ for (var i = 0; i < this.length; i++) o[this[i]] = 1
167
+ for (var e in o) a.push(e)
168
+ return a
169
+ }
170
+
171
+ var samplePercent = function(samples){
172
+ return "(" + samples +
173
+ " sample" + (samples == 1 ? "" : "s") + " - " +
174
+ ((samples / maxX) * 100).toFixed(2) + "%)";
175
+ }
176
+
177
+ var mouseover = function(d) {
178
+ var i = info[d.frame];
179
+ $('.info').text( d.frame + " " + samplePercent(i.samples.length));
180
+ d3.selectAll(i.nodes)
181
+ .attr('opacity',0.5);
182
+ };
183
+
184
+ var mouseout = function(d) {
185
+ var i = info[d.frame];
186
+ $('.info').text("");
187
+ d3.selectAll(i.nodes)
188
+ .attr('opacity',1);
189
+ };
190
+
191
+ // http://stackoverflow.com/a/7419630
192
+ var rainbow = function(numOfSteps, step) {
193
+ // This function generates vibrant, "evenly spaced" colours (i.e. no clustering). This is ideal for creating easily distiguishable vibrant markers in Google Maps and other apps.
194
+ // Adam Cole, 2011-Sept-14
195
+ // HSV to RBG adapted from: http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
196
+ var r, g, b;
197
+ var h = step / numOfSteps;
198
+ var i = ~~(h * 6);
199
+ var f = h * 6 - i;
200
+ var q = 1 - f;
201
+ switch(i % 6){
202
+ case 0: r = 1, g = f, b = 0; break;
203
+ case 1: r = q, g = 1, b = 0; break;
204
+ case 2: r = 0, g = 1, b = f; break;
205
+ case 3: r = 0, g = q, b = 1; break;
206
+ case 4: r = f, g = 0, b = 1; break;
207
+ case 5: r = 1, g = 0, b = q; break;
208
+ }
209
+ var c = "#" + ("00" + (~ ~(r * 255)).toString(16)).slice(-2) + ("00" + (~ ~(g * 255)).toString(16)).slice(-2) + ("00" + (~ ~(b * 255)).toString(16)).slice(-2);
210
+ return (c);
211
+ }
212
+
213
+ // assign some colors, analyze samples per gem
214
+ var gemStats = {}
215
+
216
+ $.each(data, function(){
217
+
218
+ var gem = guessGem(this.frame);
219
+ var stat = gemStats[gem];
220
+
221
+ if(!stat) {
222
+ gemStats[gem] = stat = {samples: [], frames: []};
223
+ }
224
+
225
+ stat.frames.push(this.frame);
226
+ for(var j=0; j < this.width; j++){
227
+ stat.samples.push(this.x + j);
228
+ }
229
+ });
230
+
231
+ var totalGems = 0;
232
+ $.each(gemStats, function(){totalGems++;});
233
+
234
+
235
+ var currentIndex = 0;
236
+ $.each(gemStats, function(k,stat){
237
+
238
+ stat.color = rainbow(totalGems, currentIndex);
239
+ stat.samples = stat.samples.getUnique();
240
+
241
+ for(var x=0; x < stat.frames.length; x++) {
242
+ info[stat.frames[x]] = {nodes: [], samples: [], color: stat.color};
243
+ }
244
+
245
+ currentIndex += 1;
246
+ });
247
+
248
+
249
+ // see: http://bl.ocks.org/mundhradevang/1387786
250
+ function fontSize(d,i) {
251
+ var size = yScale(1) / 3;
252
+ // var words = d.shortName.split(' ');
253
+ var word = d.shortName; // words[0];
254
+ var width = xScale(d.width+100);
255
+ var height = yScale(1);
256
+ var length = 0;
257
+ d3.select(this).style("font-size", size + "px").text(word);
258
+ while(((this.getBBox().width >= width) || (this.getBBox().height >= height)) && (size > 12))
259
+ {
260
+ size -= 0.1;
261
+ d3.select(this).style("font-size", size + "px");
262
+ }
263
+
264
+ d3.select(this).attr("dy", size);
265
+ }
266
+
267
+ svg.selectAll("g")
268
+ .data(data)
269
+ .enter()
270
+ .append("g")
271
+ .each(function(){
272
+ d3.select(this)
273
+ .append("rect")
274
+ .attr("x",function(d) { return xScale(d.x-1); })
275
+ .attr("y",function(d) { return yScale(maxY - d.y);})
276
+ .attr("width", function(d){return xScale(d.width);})
277
+ .attr("height", yScale(1))
278
+ .attr("fill", function(d){
279
+ var i = info[d.frame];
280
+ if(!i) {
281
+ info[d.frame] = i = {nodes: [], samples: [], color: color()};
282
+ }
283
+ i.nodes.push(this);
284
+ for(var j=0; j < d.width; j++){
285
+ i.samples.push(d.x + j);
286
+ }
287
+ return i.color;
288
+ })
289
+ .on("mouseover", mouseover)
290
+ .on("mouseout", mouseout);
291
+
292
+ d3.select(this)
293
+ .append("text")
294
+ .attr("x",function(d) { return xScale(d.x - 0.98); })
295
+ .attr("y",function(d) { return yScale(maxY - d.y);})
296
+ .on("mouseover", mouseover)
297
+ .on("mouseout", mouseout)
298
+ .each(fontSize)
299
+ .attr("display", "none");
300
+
301
+ });
302
+
303
+
304
+ // Samples may overlap on the same line
305
+ for (var r in info) {
306
+ if (info[r].samples) {
307
+ info[r].samples = info[r].samples.getUnique();
308
+ }
309
+ };
310
+
311
+
312
+ // render the legend
313
+ $.each(gemStats, function(k,v){
314
+ var node = $("<div></div>")
315
+ .css("background-color", v.color)
316
+ .text(k + " " + samplePercent(v.samples.length)) ;
317
+ $('.legend').append(node);
318
+ });
319
+
320
+
321
+
322
+ </script>
323
+ </body>
324
+ </html>
325
+