test-prof 0.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +103 -0
- data/assets/flamegraph.demo.html +173 -0
- data/assets/flamegraph.template.html +196 -0
- data/assets/src/d3-tip.js +352 -0
- data/assets/src/d3-tip.min.js +1 -0
- data/assets/src/d3.flameGraph.css +92 -0
- data/assets/src/d3.flameGraph.js +459 -0
- data/assets/src/d3.flameGraph.min.css +1 -0
- data/assets/src/d3.flameGraph.min.js +1 -0
- data/assets/src/d3.v4.min.js +8 -0
- data/guides/any_fixture.md +60 -0
- data/guides/before_all.md +98 -0
- data/guides/event_prof.md +97 -0
- data/guides/factory_doctor.md +64 -0
- data/guides/factory_prof.md +85 -0
- data/guides/rspec_stamp.md +53 -0
- data/guides/rubocop.md +48 -0
- data/guides/ruby_prof.md +61 -0
- data/guides/stack_prof.md +43 -0
- data/lib/test-prof.rb +3 -0
- data/lib/test_prof/any_fixture.rb +67 -0
- data/lib/test_prof/cops/rspec/aggregate_failures.rb +140 -0
- data/lib/test_prof/event_prof/custom_events/factory_create.rb +51 -0
- data/lib/test_prof/event_prof/custom_events/sidekiq_inline.rb +48 -0
- data/lib/test_prof/event_prof/custom_events/sidekiq_jobs.rb +38 -0
- data/lib/test_prof/event_prof/custom_events.rb +5 -0
- data/lib/test_prof/event_prof/instrumentations/active_support.rb +16 -0
- data/lib/test_prof/event_prof/minitest.rb +3 -0
- data/lib/test_prof/event_prof/rspec.rb +94 -0
- data/lib/test_prof/event_prof.rb +177 -0
- data/lib/test_prof/ext/float_duration.rb +14 -0
- data/lib/test_prof/factory_doctor/factory_girl_patch.rb +12 -0
- data/lib/test_prof/factory_doctor/minitest.rb +3 -0
- data/lib/test_prof/factory_doctor/rspec.rb +96 -0
- data/lib/test_prof/factory_doctor.rb +133 -0
- data/lib/test_prof/factory_prof/factory_girl_patch.rb +12 -0
- data/lib/test_prof/factory_prof/printers/flamegraph.rb +71 -0
- data/lib/test_prof/factory_prof/printers/simple.rb +28 -0
- data/lib/test_prof/factory_prof.rb +140 -0
- data/lib/test_prof/logging.rb +25 -0
- data/lib/test_prof/recipes/rspec/any_fixture.rb +21 -0
- data/lib/test_prof/recipes/rspec/before_all.rb +23 -0
- data/lib/test_prof/rspec_stamp/parser.rb +103 -0
- data/lib/test_prof/rspec_stamp/rspec.rb +91 -0
- data/lib/test_prof/rspec_stamp.rb +135 -0
- data/lib/test_prof/rubocop.rb +3 -0
- data/lib/test_prof/ruby_prof/rspec.rb +13 -0
- data/lib/test_prof/ruby_prof.rb +194 -0
- data/lib/test_prof/stack_prof/rspec.rb +13 -0
- data/lib/test_prof/stack_prof.rb +120 -0
- data/lib/test_prof/version.rb +5 -0
- data/lib/test_prof.rb +108 -0
- metadata +227 -0
@@ -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
|
+
})();
|