test-prof 0.1.0.pre5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/README.md +16 -4
  4. data/assets/flamegraph.demo.html +173 -0
  5. data/assets/flamegraph.template.html +196 -0
  6. data/assets/src/d3-tip.js +352 -0
  7. data/assets/src/d3-tip.min.js +1 -0
  8. data/assets/src/d3.flameGraph.css +92 -0
  9. data/assets/src/d3.flameGraph.js +459 -0
  10. data/assets/src/d3.flameGraph.min.css +1 -0
  11. data/assets/src/d3.flameGraph.min.js +1 -0
  12. data/assets/src/d3.v4.min.js +8 -0
  13. data/guides/any_fixture.md +1 -1
  14. data/guides/event_prof.md +30 -0
  15. data/guides/factory_default.md +109 -0
  16. data/guides/factory_prof.md +85 -0
  17. data/guides/rubocop.md +48 -0
  18. data/guides/ruby_prof.md +2 -0
  19. data/guides/stack_prof.md +5 -1
  20. data/guides/tag_prof.md +52 -0
  21. data/guides/tests_sampling.md +24 -0
  22. data/lib/test_prof.rb +31 -7
  23. data/lib/test_prof/cops/rspec/aggregate_failures.rb +140 -0
  24. data/lib/test_prof/event_prof/custom_events.rb +3 -3
  25. data/lib/test_prof/event_prof/custom_events/factory_create.rb +10 -8
  26. data/lib/test_prof/event_prof/custom_events/sidekiq_inline.rb +10 -8
  27. data/lib/test_prof/event_prof/custom_events/sidekiq_jobs.rb +12 -10
  28. data/lib/test_prof/event_prof/rspec.rb +5 -1
  29. data/lib/test_prof/factory_default.rb +58 -0
  30. data/lib/test_prof/factory_default/factory_girl_patch.rb +22 -0
  31. data/lib/test_prof/factory_doctor.rb +11 -9
  32. data/lib/test_prof/factory_doctor/rspec.rb +5 -3
  33. data/lib/test_prof/factory_prof.rb +140 -0
  34. data/lib/test_prof/factory_prof/factory_girl_patch.rb +12 -0
  35. data/lib/test_prof/factory_prof/printers/flamegraph.rb +71 -0
  36. data/lib/test_prof/factory_prof/printers/simple.rb +28 -0
  37. data/lib/test_prof/recipes/minitest/sample.rb +29 -0
  38. data/lib/test_prof/recipes/rspec/factory_default.rb +9 -0
  39. data/lib/test_prof/recipes/rspec/sample.rb +13 -0
  40. data/lib/test_prof/rspec_stamp/rspec.rb +5 -1
  41. data/lib/test_prof/rubocop.rb +3 -0
  42. data/lib/test_prof/ruby_prof.rb +6 -12
  43. data/lib/test_prof/stack_prof.rb +14 -7
  44. data/lib/test_prof/tag_prof.rb +8 -0
  45. data/lib/test_prof/tag_prof/rspec.rb +84 -0
  46. data/lib/test_prof/version.rb +1 -1
  47. metadata +48 -41
  48. data/.gitignore +0 -10
  49. data/.rspec +0 -2
  50. data/.rubocop.yml +0 -69
  51. data/.travis.yml +0 -5
  52. data/Gemfile +0 -4
  53. data/Rakefile +0 -8
  54. data/bin/setup +0 -8
  55. data/circle.yml +0 -11
  56. data/spec/integrations/any_fixture_spec.rb +0 -11
  57. data/spec/integrations/before_all_spec.rb +0 -11
  58. data/spec/integrations/event_prof_spec.rb +0 -100
  59. data/spec/integrations/factory_doctor_spec.rb +0 -20
  60. data/spec/integrations/fixtures/rspec/any_fixture_fixture.rb +0 -37
  61. data/spec/integrations/fixtures/rspec/before_all_fixture.rb +0 -32
  62. data/spec/integrations/fixtures/rspec/event_prof_factory_create_fixture.rb +0 -23
  63. data/spec/integrations/fixtures/rspec/event_prof_fixture.rb +0 -51
  64. data/spec/integrations/fixtures/rspec/event_prof_sidekiq_fixture.rb +0 -53
  65. data/spec/integrations/fixtures/rspec/factory_doctor_fixture.rb +0 -33
  66. data/spec/integrations/fixtures/rspec/rspec_stamp_fixture_tmpl.rb +0 -33
  67. data/spec/integrations/rspec_stamp_spec.rb +0 -53
  68. data/spec/spec_helper.rb +0 -38
  69. data/spec/support/ar_models.rb +0 -43
  70. data/spec/support/instrumenter_stub.rb +0 -19
  71. data/spec/support/integration_helpers.rb +0 -13
  72. data/spec/support/transactional_context.rb +0 -11
  73. data/spec/test_prof/any_fixture_spec.rb +0 -66
  74. data/spec/test_prof/event_prof_spec.rb +0 -138
  75. data/spec/test_prof/ext/float_duration_spec.rb +0 -12
  76. data/spec/test_prof/factory_doctor_spec.rb +0 -84
  77. data/spec/test_prof/rspec_stamp/parser_spec.rb +0 -58
  78. data/spec/test_prof/rspec_stamp_spec.rb +0 -281
  79. data/spec/test_prof/ruby_prof_spec.rb +0 -109
  80. data/spec/test_prof/stack_prof_spec.rb +0 -73
  81. data/spec/test_prof_spec.rb +0 -23
  82. data/test-prof.gemspec +0 -35
@@ -0,0 +1,352 @@
1
+ /**
2
+ * d3.tip
3
+ * Copyright (c) 2013-2017 Justin Palmer
4
+ *
5
+ * Tooltips for d3.js SVG visualizations
6
+ */
7
+ // eslint-disable-next-line no-extra-semi
8
+ ;(function(root, factory) {
9
+ if (typeof define === 'function' && define.amd) {
10
+ // AMD. Register as an anonymous module with d3 as a dependency.
11
+ define([
12
+ 'd3-collection',
13
+ 'd3-selection'
14
+ ], factory)
15
+ } else if (typeof module === 'object' && module.exports) {
16
+ /* eslint-disable global-require */
17
+ // CommonJS
18
+ var d3Collection = require('d3-collection'),
19
+ d3Selection = require('d3-selection')
20
+ module.exports = factory(d3Collection, d3Selection)
21
+ /* eslint-enable global-require */
22
+ } else {
23
+ // Browser global.
24
+ var d3 = root.d3
25
+ // eslint-disable-next-line no-param-reassign
26
+ root.d3.tip = factory(d3, d3)
27
+ }
28
+ }(this, function(d3Collection, d3Selection) {
29
+ // Public - contructs a new tooltip
30
+ //
31
+ // Returns a tip
32
+ return function() {
33
+ var direction = d3TipDirection,
34
+ offset = d3TipOffset,
35
+ html = d3TipHTML,
36
+ rootElement = document.body,
37
+ node = initNode(),
38
+ svg = null,
39
+ point = null,
40
+ target = null
41
+
42
+ function tip(vis) {
43
+ svg = getSVGNode(vis)
44
+ if (!svg) return
45
+ point = svg.createSVGPoint()
46
+ rootElement.appendChild(node)
47
+ }
48
+
49
+ // Public - show the tooltip on the screen
50
+ //
51
+ // Returns a tip
52
+ tip.show = function() {
53
+ var args = Array.prototype.slice.call(arguments)
54
+ if (args[args.length - 1] instanceof SVGElement) target = args.pop()
55
+
56
+ var content = html.apply(this, args),
57
+ poffset = offset.apply(this, args),
58
+ dir = direction.apply(this, args),
59
+ nodel = getNodeEl(),
60
+ i = directions.length,
61
+ coords,
62
+ scrollTop = document.documentElement.scrollTop ||
63
+ rootElement.scrollTop,
64
+ scrollLeft = document.documentElement.scrollLeft ||
65
+ rootElement.scrollLeft
66
+
67
+ nodel.html(content)
68
+ .style('opacity', 1).style('pointer-events', 'all')
69
+
70
+ while (i--) nodel.classed(directions[i], false)
71
+ coords = directionCallbacks.get(dir).apply(this)
72
+ nodel.classed(dir, true)
73
+ .style('top', (coords.top + poffset[0]) + scrollTop + 'px')
74
+ .style('left', (coords.left + poffset[1]) + scrollLeft + 'px')
75
+
76
+ return tip
77
+ }
78
+
79
+ // Public - hide the tooltip
80
+ //
81
+ // Returns a tip
82
+ tip.hide = function() {
83
+ var nodel = getNodeEl()
84
+ nodel.style('opacity', 0).style('pointer-events', 'none')
85
+ return tip
86
+ }
87
+
88
+ // Public: Proxy attr calls to the d3 tip container.
89
+ // Sets or gets attribute value.
90
+ //
91
+ // n - name of the attribute
92
+ // v - value of the attribute
93
+ //
94
+ // Returns tip or attribute value
95
+ // eslint-disable-next-line no-unused-vars
96
+ tip.attr = function(n, v) {
97
+ if (arguments.length < 2 && typeof n === 'string') {
98
+ return getNodeEl().attr(n)
99
+ }
100
+
101
+ var args = Array.prototype.slice.call(arguments)
102
+ d3Selection.selection.prototype.attr.apply(getNodeEl(), args)
103
+ return tip
104
+ }
105
+
106
+ // Public: Proxy style calls to the d3 tip container.
107
+ // Sets or gets a style value.
108
+ //
109
+ // n - name of the property
110
+ // v - value of the property
111
+ //
112
+ // Returns tip or style property value
113
+ // eslint-disable-next-line no-unused-vars
114
+ tip.style = function(n, v) {
115
+ if (arguments.length < 2 && typeof n === 'string') {
116
+ return getNodeEl().style(n)
117
+ }
118
+
119
+ var args = Array.prototype.slice.call(arguments)
120
+ d3Selection.selection.prototype.style.apply(getNodeEl(), args)
121
+ return tip
122
+ }
123
+
124
+ // Public: Set or get the direction of the tooltip
125
+ //
126
+ // v - One of n(north), s(south), e(east), or w(west), nw(northwest),
127
+ // sw(southwest), ne(northeast) or se(southeast)
128
+ //
129
+ // Returns tip or direction
130
+ tip.direction = function(v) {
131
+ if (!arguments.length) return direction
132
+ direction = v == null ? v : functor(v)
133
+
134
+ return tip
135
+ }
136
+
137
+ // Public: Sets or gets the offset of the tip
138
+ //
139
+ // v - Array of [x, y] offset
140
+ //
141
+ // Returns offset or
142
+ tip.offset = function(v) {
143
+ if (!arguments.length) return offset
144
+ offset = v == null ? v : functor(v)
145
+
146
+ return tip
147
+ }
148
+
149
+ // Public: sets or gets the html value of the tooltip
150
+ //
151
+ // v - String value of the tip
152
+ //
153
+ // Returns html value or tip
154
+ tip.html = function(v) {
155
+ if (!arguments.length) return html
156
+ html = v == null ? v : functor(v)
157
+
158
+ return tip
159
+ }
160
+
161
+ // Public: sets or gets the root element anchor of the tooltip
162
+ //
163
+ // v - root element of the tooltip
164
+ //
165
+ // Returns root node of tip
166
+ tip.rootElement = function(v) {
167
+ if (!arguments.length) return rootElement
168
+ rootElement = v == null ? v : functor(v)
169
+
170
+ return tip
171
+ }
172
+
173
+ // Public: destroys the tooltip and removes it from the DOM
174
+ //
175
+ // Returns a tip
176
+ tip.destroy = function() {
177
+ if (node) {
178
+ getNodeEl().remove()
179
+ node = null
180
+ }
181
+ return tip
182
+ }
183
+
184
+ function d3TipDirection() { return 'n' }
185
+ function d3TipOffset() { return [0, 0] }
186
+ function d3TipHTML() { return ' ' }
187
+
188
+ var directionCallbacks = d3Collection.map({
189
+ n: directionNorth,
190
+ s: directionSouth,
191
+ e: directionEast,
192
+ w: directionWest,
193
+ nw: directionNorthWest,
194
+ ne: directionNorthEast,
195
+ sw: directionSouthWest,
196
+ se: directionSouthEast
197
+ }),
198
+ directions = directionCallbacks.keys()
199
+
200
+ function directionNorth() {
201
+ var bbox = getScreenBBox()
202
+ return {
203
+ top: bbox.n.y - node.offsetHeight,
204
+ left: bbox.n.x - node.offsetWidth / 2
205
+ }
206
+ }
207
+
208
+ function directionSouth() {
209
+ var bbox = getScreenBBox()
210
+ return {
211
+ top: bbox.s.y,
212
+ left: bbox.s.x - node.offsetWidth / 2
213
+ }
214
+ }
215
+
216
+ function directionEast() {
217
+ var bbox = getScreenBBox()
218
+ return {
219
+ top: bbox.e.y - node.offsetHeight / 2,
220
+ left: bbox.e.x
221
+ }
222
+ }
223
+
224
+ function directionWest() {
225
+ var bbox = getScreenBBox()
226
+ return {
227
+ top: bbox.w.y - node.offsetHeight / 2,
228
+ left: bbox.w.x - node.offsetWidth
229
+ }
230
+ }
231
+
232
+ function directionNorthWest() {
233
+ var bbox = getScreenBBox()
234
+ return {
235
+ top: bbox.nw.y - node.offsetHeight,
236
+ left: bbox.nw.x - node.offsetWidth
237
+ }
238
+ }
239
+
240
+ function directionNorthEast() {
241
+ var bbox = getScreenBBox()
242
+ return {
243
+ top: bbox.ne.y - node.offsetHeight,
244
+ left: bbox.ne.x
245
+ }
246
+ }
247
+
248
+ function directionSouthWest() {
249
+ var bbox = getScreenBBox()
250
+ return {
251
+ top: bbox.sw.y,
252
+ left: bbox.sw.x - node.offsetWidth
253
+ }
254
+ }
255
+
256
+ function directionSouthEast() {
257
+ var bbox = getScreenBBox()
258
+ return {
259
+ top: bbox.se.y,
260
+ left: bbox.se.x
261
+ }
262
+ }
263
+
264
+ function initNode() {
265
+ var div = d3Selection.select(document.createElement('div'))
266
+ div
267
+ .style('position', 'absolute')
268
+ .style('top', 0)
269
+ .style('opacity', 0)
270
+ .style('pointer-events', 'none')
271
+ .style('box-sizing', 'border-box')
272
+
273
+ return div.node()
274
+ }
275
+
276
+ function getSVGNode(element) {
277
+ var svgNode = element.node()
278
+ if (!svgNode) return null
279
+ if (svgNode.tagName.toLowerCase() === 'svg') return svgNode
280
+ return svgNode.ownerSVGElement
281
+ }
282
+
283
+ function getNodeEl() {
284
+ if (node == null) {
285
+ node = initNode()
286
+ // re-add node to DOM
287
+ rootElement.appendChild(node)
288
+ }
289
+ return d3Selection.select(node)
290
+ }
291
+
292
+ // Private - gets the screen coordinates of a shape
293
+ //
294
+ // Given a shape on the screen, will return an SVGPoint for the directions
295
+ // n(north), s(south), e(east), w(west), ne(northeast), se(southeast),
296
+ // nw(northwest), sw(southwest).
297
+ //
298
+ // +-+-+
299
+ // | |
300
+ // + +
301
+ // | |
302
+ // +-+-+
303
+ //
304
+ // Returns an Object {n, s, e, w, nw, sw, ne, se}
305
+ function getScreenBBox() {
306
+ var targetel = target || d3Selection.event.target
307
+
308
+ while (targetel.getScreenCTM == null && targetel.parentNode == null) {
309
+ targetel = targetel.parentNode
310
+ }
311
+
312
+ var bbox = {},
313
+ matrix = targetel.getScreenCTM(),
314
+ tbbox = targetel.getBBox(),
315
+ width = tbbox.width,
316
+ height = tbbox.height,
317
+ x = tbbox.x,
318
+ y = tbbox.y
319
+
320
+ point.x = x
321
+ point.y = y
322
+ bbox.nw = point.matrixTransform(matrix)
323
+ point.x += width
324
+ bbox.ne = point.matrixTransform(matrix)
325
+ point.y += height
326
+ bbox.se = point.matrixTransform(matrix)
327
+ point.x -= width
328
+ bbox.sw = point.matrixTransform(matrix)
329
+ point.y -= height / 2
330
+ bbox.w = point.matrixTransform(matrix)
331
+ point.x += width
332
+ bbox.e = point.matrixTransform(matrix)
333
+ point.x -= width / 2
334
+ point.y -= height / 2
335
+ bbox.n = point.matrixTransform(matrix)
336
+ point.y += height
337
+ bbox.s = point.matrixTransform(matrix)
338
+
339
+ return bbox
340
+ }
341
+
342
+ // Private - replace D3JS 3.X d3.functor() function
343
+ function functor(v) {
344
+ return typeof v === 'function' ? v : function() {
345
+ return v
346
+ }
347
+ }
348
+
349
+ return tip
350
+ }
351
+ // eslint-disable-next-line semi
352
+ }));
@@ -0,0 +1 @@
1
+ !function(t,e){if("function"==typeof define&&define.amd)define(["d3-collection","d3-selection"],e);else if("object"==typeof module&&module.exports){var n=require("d3-collection"),r=require("d3-selection");module.exports=e(n,r)}else{var o=t.d3;t.d3.tip=e(o,o)}}(this,function(t,e){return function(){function n(t){(C=m(t))&&(H=C.createSVGPoint(),b.appendChild(E))}function r(){return"n"}function o(){return[0,0]}function l(){return" "}function i(){var t=x();return{top:t.n.y-E.offsetHeight,left:t.n.x-E.offsetWidth/2}}function f(){var t=x();return{top:t.s.y,left:t.s.x-E.offsetWidth/2}}function u(){var t=x();return{top:t.e.y-E.offsetHeight/2,left:t.e.x}}function s(){var t=x();return{top:t.w.y-E.offsetHeight/2,left:t.w.x-E.offsetWidth}}function a(){var t=x();return{top:t.nw.y-E.offsetHeight,left:t.nw.x-E.offsetWidth}}function c(){var t=x();return{top:t.ne.y-E.offsetHeight,left:t.ne.x}}function p(){var t=x();return{top:t.sw.y,left:t.sw.x-E.offsetWidth}}function y(){var t=x();return{top:t.se.y,left:t.se.x}}function d(){var t=e.select(document.createElement("div"));return t.style("position","absolute").style("top",0).style("opacity",0).style("pointer-events","none").style("box-sizing","border-box"),t.node()}function m(t){var e=t.node();return e?"svg"===e.tagName.toLowerCase()?e:e.ownerSVGElement:null}function h(){return null==E&&(E=d(),b.appendChild(E)),e.select(E)}function x(){for(var t=S||e.event.target;null==t.getScreenCTM&&null==t.parentNode;)t=t.parentNode;var n={},r=t.getScreenCTM(),o=t.getBBox(),l=o.width,i=o.height,f=o.x,u=o.y;return H.x=f,H.y=u,n.nw=H.matrixTransform(r),H.x+=l,n.ne=H.matrixTransform(r),H.y+=i,n.se=H.matrixTransform(r),H.x-=l,n.sw=H.matrixTransform(r),H.y-=i/2,n.w=H.matrixTransform(r),H.x+=l,n.e=H.matrixTransform(r),H.x-=l/2,H.y-=i/2,n.n=H.matrixTransform(r),H.y+=i,n.s=H.matrixTransform(r),n}function v(t){return"function"==typeof t?t:function(){return t}}var g=r,w=o,T=l,b=document.body,E=d(),C=null,H=null,S=null;n.show=function(){var t=Array.prototype.slice.call(arguments);t[t.length-1]instanceof SVGElement&&(S=t.pop());var e,r=T.apply(this,t),o=w.apply(this,t),l=g.apply(this,t),i=h(),f=A.length,u=document.documentElement.scrollTop||b.scrollTop,s=document.documentElement.scrollLeft||b.scrollLeft;for(i.html(r).style("opacity",1).style("pointer-events","all");f--;)i.classed(A[f],!1);return e=W.get(l).apply(this),i.classed(l,!0).style("top",e.top+o[0]+u+"px").style("left",e.left+o[1]+s+"px"),n},n.hide=function(){return h().style("opacity",0).style("pointer-events","none"),n},n.attr=function(t,r){if(arguments.length<2&&"string"==typeof t)return h().attr(t);var o=Array.prototype.slice.call(arguments);return e.selection.prototype.attr.apply(h(),o),n},n.style=function(t,r){if(arguments.length<2&&"string"==typeof t)return h().style(t);var o=Array.prototype.slice.call(arguments);return e.selection.prototype.style.apply(h(),o),n},n.direction=function(t){return arguments.length?(g=null==t?t:v(t),n):g},n.offset=function(t){return arguments.length?(w=null==t?t:v(t),n):w},n.html=function(t){return arguments.length?(T=null==t?t:v(t),n):T},n.rootElement=function(t){return arguments.length?(b=null==t?t:v(t),n):b},n.destroy=function(){return E&&(h().remove(),E=null),n};var W=t.map({n:i,s:f,e:u,w:s,nw:a,ne:c,sw:p,se:y}),A=W.keys();return n}});
@@ -0,0 +1,92 @@
1
+ .d3-flame-graph rect {
2
+ stroke: #EEEEEE;
3
+ fill-opacity: .8;
4
+ }
5
+
6
+ .d3-flame-graph rect:hover {
7
+ stroke: #474747;
8
+ stroke-width: 0.5;
9
+ cursor: pointer;
10
+ }
11
+
12
+ .d3-flame-graph .label {
13
+ pointer-events: none;
14
+ white-space: nowrap;
15
+ text-overflow: ellipsis;
16
+ overflow: hidden;
17
+ font-size: 12px;
18
+ font-family: Verdana;
19
+ margin-left: 4px;
20
+ margin-right: 4px;
21
+ line-height: 1.5;
22
+ padding: 0 0 0;
23
+ font-weight: 400;
24
+ color: black;
25
+ text-align: left;
26
+ }
27
+
28
+ .d3-flame-graph .fade {
29
+ opacity: 0.6 !important;
30
+ }
31
+
32
+ .d3-flame-graph .title {
33
+ font-size: 20px;
34
+ font-family: Verdana;
35
+ }
36
+
37
+ .d3-flame-graph-tip {
38
+ line-height: 1;
39
+ font-family: Verdana;
40
+ font-size: 12px;
41
+ padding: 12px;
42
+ background: rgba(0, 0, 0, 0.8);
43
+ color: #fff;
44
+ border-radius: 2px;
45
+ pointer-events: none;
46
+ }
47
+
48
+ /* Creates a small triangle extender for the tooltip */
49
+ .d3-flame-graph-tip:after {
50
+ box-sizing: border-box;
51
+ display: inline;
52
+ font-size: 10px;
53
+ width: 100%;
54
+ line-height: 1;
55
+ color: rgba(0, 0, 0, 0.8);
56
+ position: absolute;
57
+ pointer-events: none;
58
+ }
59
+
60
+ /* Northward tooltips */
61
+ .d3-flame-graph-tip.n:after {
62
+ content: "\25BC";
63
+ margin: -1px 0 0 0;
64
+ top: 100%;
65
+ left: 0;
66
+ text-align: center;
67
+ }
68
+
69
+ /* Eastward tooltips */
70
+ .d3-flame-graph-tip.e:after {
71
+ content: "\25C0";
72
+ margin: -4px 0 0 0;
73
+ top: 50%;
74
+ left: -8px;
75
+ }
76
+
77
+ /* Southward tooltips */
78
+ .d3-flame-graph-tip.s:after {
79
+ content: "\25B2";
80
+ margin: 0 0 1px 0;
81
+ top: -8px;
82
+ left: 0;
83
+ text-align: center;
84
+ }
85
+
86
+ /* Westward tooltips */
87
+ .d3-flame-graph-tip.w:after {
88
+ content: "\25B6";
89
+ margin: -4px 0 0 -1px;
90
+ top: 50%;
91
+ left: 100%;
92
+ }
@@ -0,0 +1,459 @@
1
+ (function() {
2
+ 'use strict';
3
+
4
+ function flameGraph() {
5
+
6
+ var w = 960, // graph width
7
+ h = 540, // graph height
8
+ c = 18, // cell height
9
+ selection = null, // selection
10
+ tooltip = true, // enable tooltip
11
+ title = "", // graph title
12
+ transitionDuration = 750,
13
+ transitionEase = d3.easeCubic, // tooltip offset
14
+ sort = true,
15
+ reversed = false, // reverse the graph direction
16
+ clickHandler = null;
17
+
18
+ var tip = d3.tip()
19
+ .direction("s")
20
+ .offset([8, 0])
21
+ .attr('class', 'd3-flame-graph-tip')
22
+ .html(function(d) { return label(d); });
23
+
24
+ var svg;
25
+
26
+ var label = function(d) {
27
+ return d.data.name + " (" + d3.format(".3f")(100 * (d.x1 - d.x0), 3) + "%, " + d.data.value + " samples)";
28
+ };
29
+
30
+ function setDetails(t) {
31
+ var details = document.getElementById("details");
32
+ if (details)
33
+ details.innerHTML = t;
34
+ }
35
+
36
+ function name(d) {
37
+ return d.data.name;
38
+ }
39
+
40
+ var colorMapper = function(d) {
41
+ return d.highlight ? "#E600E6" : colorHash(d.data);
42
+ };
43
+
44
+ function generateHash(name) {
45
+ // Return a vector (0.0->1.0) that is a hash of the input string.
46
+ // The hash is computed to favor early characters over later ones, so
47
+ // that strings with similar starts have similar vectors. Only the first
48
+ // 6 characters are considered.
49
+ var hash = 0, weight = 1, max_hash = 0, mod = 10, max_char = 6;
50
+ if (name) {
51
+ for (var i = 0; i < name.length; i++) {
52
+ if (i > max_char) { break; }
53
+ hash += weight * (name.charCodeAt(i) % mod);
54
+ max_hash += weight * (mod - 1);
55
+ weight *= 0.70;
56
+ }
57
+ if (max_hash > 0) { hash = hash / max_hash; }
58
+ }
59
+ return hash;
60
+ }
61
+
62
+ function colorHash(data) {
63
+ var name = data.name;
64
+
65
+ if (data.color) {
66
+ return data.color;
67
+ }
68
+ // Return an rgb() color string that is a hash of the provided name,
69
+ // and with a warm palette.
70
+ var vector = 0;
71
+ if (name) {
72
+ name = name.replace(/.*`/, ""); // drop module name if present
73
+ name = name.replace(/\(.*/, ""); // drop extra info
74
+ vector = generateHash(name);
75
+ }
76
+ var r = 200 + Math.round(55 * vector);
77
+ var g = 0 + Math.round(230 * vector);
78
+ var b = 0 + Math.round(55 * vector);
79
+ data.color = "rgb(" + r + "," + g + "," + b + ")";
80
+ return data.color;
81
+ }
82
+
83
+ function hide(d) {
84
+ if(!d.data.original) {
85
+ d.data.original = d.data.value;
86
+ }
87
+ d.data.value = 0;
88
+ if(d.children) {
89
+ d.children.forEach(hide);
90
+ }
91
+ }
92
+
93
+ function show(d) {
94
+ d.data.fade = false;
95
+ if(d.data.original) {
96
+ d.data.value = d.data.original;
97
+ }
98
+ if(d.children) {
99
+ d.children.forEach(show);
100
+ }
101
+ }
102
+
103
+ function getSiblings(d) {
104
+ var siblings = [];
105
+ if (d.parent) {
106
+ var me = d.parent.children.indexOf(d);
107
+ siblings = d.parent.children.slice(0);
108
+ siblings.splice(me, 1);
109
+ }
110
+ return siblings;
111
+ }
112
+
113
+ function hideSiblings(d) {
114
+ var siblings = getSiblings(d);
115
+ siblings.forEach(function(s) {
116
+ hide(s);
117
+ });
118
+ if(d.parent) {
119
+ hideSiblings(d.parent);
120
+ }
121
+ }
122
+
123
+ function fadeAncestors(d) {
124
+ if(d.parent) {
125
+ d.parent.data.fade = true;
126
+ fadeAncestors(d.parent);
127
+ }
128
+ }
129
+
130
+ function getRoot(d) {
131
+ if(d.parent) {
132
+ return getRoot(d.parent);
133
+ }
134
+ return d;
135
+ }
136
+
137
+ function zoom(d) {
138
+ tip.hide(d);
139
+ hideSiblings(d);
140
+ show(d);
141
+ fadeAncestors(d);
142
+ update();
143
+ if (typeof clickHandler === 'function') {
144
+ clickHandler(d);
145
+ }
146
+ }
147
+
148
+ function searchTree(d, term) {
149
+ var re = new RegExp(term),
150
+ searchResults = [];
151
+
152
+ function searchInner(d) {
153
+ var label = d.data.name;
154
+
155
+ if (d.children) {
156
+ d.children.forEach(function (child) {
157
+ searchInner(child);
158
+ });
159
+ }
160
+
161
+ if (label.match(re)) {
162
+ d.highlight = true;
163
+ searchResults.push(d);
164
+ } else {
165
+ d.highlight = false;
166
+ }
167
+ }
168
+
169
+ searchInner(d);
170
+ return searchResults;
171
+ }
172
+
173
+ function clear(d) {
174
+ d.highlight = false;
175
+ if(d.children) {
176
+ d.children.forEach(function(child) {
177
+ clear(child);
178
+ });
179
+ }
180
+ }
181
+
182
+ function doSort(a, b) {
183
+ if (typeof sort === 'function') {
184
+ return sort(a, b);
185
+ } else if (sort) {
186
+ return d3.ascending(a.data.name, b.data.name);
187
+ } else {
188
+ return 0;
189
+ }
190
+ }
191
+
192
+ var partition = d3.partition();
193
+
194
+ function update() {
195
+
196
+ selection.each(function(root) {
197
+ var x = d3.scaleLinear().range([0, w]),
198
+ y = d3.scaleLinear().range([0, c]);
199
+
200
+ root.sort(doSort);
201
+ root.sum(function(d) {
202
+ if (d.fade) {
203
+ return 0;
204
+ }
205
+ // The node's self value is its total value minus all children.
206
+ var v = d.v || d.value || 0;
207
+ if (d.children) {
208
+ for (var i = 0; i < d.children.length; i++) {
209
+ v -= d.children[i].value;
210
+ }
211
+ }
212
+ return v;
213
+ });
214
+ partition(root);
215
+
216
+ var kx = w / (root.x1 - root.x0);
217
+ function width(d) { return (d.x1 - d.x0) * kx; }
218
+
219
+ var g = d3.select(this).select("svg").selectAll("g").data(root.descendants());
220
+
221
+ g.transition()
222
+ .duration(transitionDuration)
223
+ .ease(transitionEase)
224
+ .attr("transform", function(d) { return "translate(" + x(d.x0) + ","
225
+ + (reversed ? y(d.depth) : (h - y(d.depth) - c)) + ")"; });
226
+
227
+ g.select("rect").transition()
228
+ .duration(transitionDuration)
229
+ .ease(transitionEase)
230
+ .attr("width", width);
231
+
232
+ var node = g.enter()
233
+ .append("svg:g")
234
+ .attr("transform", function(d) { return "translate(" + x(d.x0) + ","
235
+ + (reversed ? y(d.depth) : (h - y(d.depth) - c)) + ")"; });
236
+
237
+ node.append("svg:rect").attr("width", width);
238
+
239
+ if (!tooltip)
240
+ node.append("svg:title");
241
+
242
+ node.append("foreignObject")
243
+ .append("xhtml:div");
244
+
245
+ // Now we have to re-select to see the new elements (why?).
246
+ g = d3.select(this).select("svg").selectAll("g").data(root.descendants());
247
+
248
+ g.attr("width", width)
249
+ .attr("height", function(d) { return c; })
250
+ .attr("name", function(d) { return d.data.name; })
251
+ .attr("class", function(d) { return d.data.fade ? "frame fade" : "frame"; });
252
+
253
+ g.select("rect")
254
+ .attr("height", function(d) { return c; })
255
+ .attr("fill", function(d) { return colorMapper(d); });
256
+
257
+ if (!tooltip)
258
+ g.select("title")
259
+ .text(label);
260
+
261
+ g.select("foreignObject")
262
+ .attr("width", width)
263
+ .attr("height", function(d) { return c; })
264
+ .select("div")
265
+ .attr("class", "label")
266
+ .style("display", function(d) { return (width(d) < 35) ? "none" : "block";})
267
+ .text(name);
268
+
269
+ g.on('click', zoom);
270
+
271
+ g.exit().remove();
272
+
273
+ g.on('mouseover', function(d) {
274
+ if (tooltip) tip.show(d);
275
+ setDetails(label(d));
276
+ }).on('mouseout', function(d) {
277
+ if (tooltip) tip.hide(d);
278
+ setDetails("");
279
+ });
280
+ });
281
+ }
282
+
283
+ function merge(data, samples) {
284
+ samples.forEach(function (sample) {
285
+ var node = _.find(data, function (element) {
286
+ return element.name === sample.name;
287
+ });
288
+
289
+ if (node) {
290
+ if (node.original) {
291
+ node.original += sample.value;
292
+ } else {
293
+ node.value += sample.value;
294
+ }
295
+ if (sample.children) {
296
+ if (!node.children) {
297
+ node.children = [];
298
+ }
299
+ merge(node.children, sample.children)
300
+ }
301
+ } else {
302
+ data.push(sample);
303
+ }
304
+ });
305
+ }
306
+
307
+ function chart(s) {
308
+ var root = d3.hierarchy(s.datum(), function(d) { return d.c || d.children; });
309
+ selection = s.datum(root);
310
+
311
+ if (!arguments.length) return chart;
312
+
313
+ selection.each(function(data) {
314
+
315
+ if (!svg) {
316
+ svg = d3.select(this)
317
+ .append("svg:svg")
318
+ .attr("width", w)
319
+ .attr("height", h)
320
+ .attr("class", "partition d3-flame-graph")
321
+ .call(tip);
322
+
323
+ svg.append("svg:text")
324
+ .attr("class", "title")
325
+ .attr("text-anchor", "middle")
326
+ .attr("y", "25")
327
+ .attr("x", w/2)
328
+ .attr("fill", "#808080")
329
+ .text(title);
330
+ }
331
+ });
332
+
333
+ // first draw
334
+ update();
335
+ }
336
+
337
+ chart.height = function (_) {
338
+ if (!arguments.length) { return h; }
339
+ h = _;
340
+ return chart;
341
+ };
342
+
343
+ chart.width = function (_) {
344
+ if (!arguments.length) { return w; }
345
+ w = _;
346
+ return chart;
347
+ };
348
+
349
+ chart.cellHeight = function (_) {
350
+ if (!arguments.length) { return c; }
351
+ c = _;
352
+ return chart;
353
+ };
354
+
355
+ chart.tooltip = function (_) {
356
+ if (!arguments.length) { return tooltip; }
357
+ if (typeof _ === "function") {
358
+ tip = _;
359
+ }
360
+ tooltip = true;
361
+ return chart;
362
+ };
363
+
364
+ chart.title = function (_) {
365
+ if (!arguments.length) { return title; }
366
+ title = _;
367
+ return chart;
368
+ };
369
+
370
+ chart.transitionDuration = function (_) {
371
+ if (!arguments.length) { return transitionDuration; }
372
+ transitionDuration = _;
373
+ return chart;
374
+ };
375
+
376
+ chart.transitionEase = function (_) {
377
+ if (!arguments.length) { return transitionEase; }
378
+ transitionEase = _;
379
+ return chart;
380
+ };
381
+
382
+ chart.sort = function (_) {
383
+ if (!arguments.length) { return sort; }
384
+ sort = _;
385
+ return chart;
386
+ };
387
+
388
+ chart.reversed = function (_) {
389
+ if (!arguments.length) { return reversed; }
390
+ reversed = _;
391
+ return chart;
392
+ };
393
+
394
+ chart.label = function(_) {
395
+ if (!arguments.length) { return label; }
396
+ label = _;
397
+ return chart;
398
+ };
399
+
400
+ chart.search = function(term) {
401
+ var searchResults = [];
402
+ selection.each(function(data) {
403
+ searchResults = searchTree(data, term);
404
+ update();
405
+ });
406
+ return searchResults;
407
+ };
408
+
409
+ chart.clear = function() {
410
+ selection.each(function(data) {
411
+ clear(data);
412
+ update();
413
+ });
414
+ };
415
+
416
+ chart.zoomTo = function(d) {
417
+ zoom(d);
418
+ };
419
+
420
+ chart.resetZoom = function() {
421
+ selection.each(function (data) {
422
+ zoom(data); // zoom to root
423
+ });
424
+ };
425
+
426
+ chart.onClick = function(_) {
427
+ if (!arguments.length) {
428
+ return clickHandler;
429
+ }
430
+ clickHandler = _;
431
+ return chart;
432
+ };
433
+
434
+ chart.merge = function(samples) {
435
+ var newRoot; // Need to re-create hierarchy after data changes.
436
+ selection.each(function (root) {
437
+ merge([root.data], [samples]);
438
+ newRoot = d3.hierarchy(root.data, function(d) { return d.c || d.children; });
439
+ });
440
+ selection = selection.datum(newRoot);
441
+ update();
442
+ }
443
+
444
+ chart.color = function(_) {
445
+ if (!arguments.length) { return colorMapper; }
446
+ colorMapper = _;
447
+ return chart;
448
+ };
449
+
450
+ return chart;
451
+ }
452
+
453
+ if (typeof module !== 'undefined' && module.exports){
454
+ module.exports = flameGraph;
455
+ }
456
+ else {
457
+ d3.flameGraph = flameGraph;
458
+ }
459
+ })();