stackprof 0.2.10 → 0.2.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,357 +1,983 @@
1
- var guessGem = function(frame) {
2
- var split = frame.split('/gems/');
3
- if(split.length == 1) {
4
- split = frame.split('/app/');
5
- if(split.length == 1) {
6
- split = frame.split('/lib/');
7
- } else {
8
- return split[split.length-1].split('/')[0]
1
+ if (typeof Element.prototype.matches !== 'function') {
2
+ Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.webkitMatchesSelector || function matches(selector) {
3
+ var element = this
4
+ var elements = (element.document || element.ownerDocument).querySelectorAll(selector)
5
+ var index = 0
6
+
7
+ while (elements[index] && elements[index] !== element) {
8
+ ++index
9
9
  }
10
10
 
11
- split = split[Math.max(split.length-2,0)].split('/');
12
- return split[split.length-1].split(':')[0];
13
- }
14
- else
15
- {
16
- return split[split.length -1].split('/')[0].split('-', 2)[0];
11
+ return Boolean(elements[index])
17
12
  }
18
13
  }
19
14
 
20
- var color = function() {
21
- var r = parseInt(205 + Math.random() * 50);
22
- var g = parseInt(Math.random() * 230);
23
- var b = parseInt(Math.random() * 55);
24
- return "rgb(" + r + "," + g + "," + b + ")";
15
+ if (typeof Element.prototype.closest !== 'function') {
16
+ Element.prototype.closest = function closest(selector) {
17
+ var element = this
18
+
19
+ while (element && element.nodeType === 1) {
20
+ if (element.matches(selector)) {
21
+ return element
22
+ }
23
+
24
+ element = element.parentNode
25
+ }
26
+
27
+ return null
28
+ }
25
29
  }
26
30
 
27
- // http://stackoverflow.com/a/7419630
28
- var rainbow = function(numOfSteps, step) {
29
- // 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.
30
- // Adam Cole, 2011-Sept-14
31
- // HSV to RBG adapted from: http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
32
- var r, g, b;
33
- var h = step / numOfSteps;
34
- var i = ~~(h * 6);
35
- var f = h * 6 - i;
36
- var q = 1 - f;
37
- switch(i % 6){
38
- case 0: r = 1, g = f, b = 0; break;
39
- case 1: r = q, g = 1, b = 0; break;
40
- case 2: r = 0, g = 1, b = f; break;
41
- case 3: r = 0, g = q, b = 1; break;
42
- case 4: r = f, g = 0, b = 1; break;
43
- case 5: r = 1, g = 0, b = q; break;
31
+ if (typeof Object.assign !== 'function') {
32
+ (function() {
33
+ Object.assign = function(target) {
34
+ 'use strict'
35
+ // We must check against these specific cases.
36
+ if (target === undefined || target === null) {
37
+ throw new TypeError('Cannot convert undefined or null to object')
38
+ }
39
+
40
+ var output = Object(target)
41
+ for (var index = 1; index < arguments.length; index++) {
42
+ var source = arguments[index]
43
+ if (source !== undefined && source !== null) {
44
+ for (var nextKey in source) {
45
+ if (source.hasOwnProperty(nextKey)) {
46
+ output[nextKey] = source[nextKey]
47
+ }
48
+ }
49
+ }
50
+ }
51
+ return output
44
52
  }
45
- var c = "#" + ("00" + (~ ~(r * 255)).toString(16)).slice(-2) + ("00" + (~ ~(g * 255)).toString(16)).slice(-2) + ("00" + (~ ~(b * 255)).toString(16)).slice(-2);
46
- return (c);
53
+ })()
47
54
  }
48
55
 
49
- // http://stackoverflow.com/questions/1960473/unique-values-in-an-array
50
- var getUnique = function(orig) {
51
- var o = {}, a = []
52
- for (var i = 0; i < orig.length; i++) o[orig[i]] = 1
53
- for (var e in o) a.push(e)
54
- return a
56
+ function EventSource() {
57
+ var self = this
58
+
59
+ self.eventListeners = {}
55
60
  }
56
61
 
57
- function flamegraph(data) {
58
- var maxX = 0;
59
- var maxY = 0;
60
- var minY = 10000;
61
- $.each(data, function(){
62
- maxX = Math.max(maxX, this.x + this.width);
63
- maxY = Math.max(maxY, this.y);
64
- minY = Math.min(minY, this.y);
65
- });
66
-
67
- // normalize Y
68
- if (minY > 0) {
69
- $.each(data, function(){
70
- this.y -= minY
62
+ EventSource.prototype.on = function(name, callback) {
63
+ var self = this
64
+
65
+ var listeners = self.eventListeners[name]
66
+ if (!listeners)
67
+ listeners = self.eventListeners[name] = []
68
+ listeners.push(callback)
69
+ }
70
+
71
+ EventSource.prototype.dispatch = function(name, data) {
72
+ var self = this
73
+
74
+ var listeners = self.eventListeners[name] || []
75
+ listeners.forEach(function(c) {
76
+ requestAnimationFrame(function() { c(data) })
77
+ })
78
+ }
79
+
80
+ function CanvasView(canvas) {
81
+ var self = this
82
+
83
+ self.canvas = canvas
84
+ }
85
+
86
+ CanvasView.prototype.setDimensions = function(width, height) {
87
+ var self = this
88
+
89
+ if (self.resizeRequestID)
90
+ cancelAnimationFrame(self.resizeRequestID)
91
+
92
+ self.resizeRequestID = requestAnimationFrame(self.setDimensionsNow.bind(self, width, height))
93
+ }
94
+
95
+ CanvasView.prototype.setDimensionsNow = function(width, height) {
96
+ var self = this
97
+
98
+ if (width === self.width && height === self.height)
99
+ return
100
+
101
+ self.width = width
102
+ self.height = height
103
+
104
+ self.canvas.style.width = width
105
+ self.canvas.style.height = height
106
+
107
+ var ratio = window.devicePixelRatio || 1
108
+ self.canvas.width = width * ratio
109
+ self.canvas.height = height * ratio
110
+
111
+ var ctx = self.canvas.getContext('2d')
112
+ ctx.setTransform(1, 0, 0, 1, 0, 0)
113
+ ctx.scale(ratio, ratio)
114
+
115
+ self.repaintNow()
116
+ }
117
+
118
+ CanvasView.prototype.paint = function() {
119
+ }
120
+
121
+ CanvasView.prototype.scheduleRepaint = function() {
122
+ var self = this
123
+
124
+ if (self.repaintRequestID)
125
+ return
126
+
127
+ self.repaintRequestID = requestAnimationFrame(function() {
128
+ self.repaintRequestID = null
129
+ self.repaintNow()
130
+ })
131
+ }
132
+
133
+ CanvasView.prototype.repaintNow = function() {
134
+ var self = this
135
+
136
+ self.canvas.getContext('2d').clearRect(0, 0, self.width, self.height)
137
+ self.paint()
138
+
139
+ if (self.repaintRequestID) {
140
+ cancelAnimationFrame(self.repaintRequestID)
141
+ self.repaintRequestID = null
142
+ }
143
+ }
144
+
145
+ function Flamechart(canvas, data, dataRange, info) {
146
+ var self = this
147
+
148
+ CanvasView.call(self, canvas)
149
+ EventSource.call(self)
150
+
151
+ self.canvas = canvas
152
+ self.data = data
153
+ self.dataRange = dataRange
154
+ self.info = info
155
+
156
+ self.viewport = {
157
+ x: dataRange.minX,
158
+ y: dataRange.minY,
159
+ width: dataRange.maxX - dataRange.minX,
160
+ height: dataRange.maxY - dataRange.minY,
161
+ }
162
+ }
163
+
164
+ Flamechart.prototype = Object.create(CanvasView.prototype)
165
+ Flamechart.prototype.constructor = Flamechart
166
+ Object.assign(Flamechart.prototype, EventSource.prototype)
167
+
168
+ Flamechart.prototype.xScale = function(x) {
169
+ var self = this
170
+ return self.widthScale(x - self.viewport.x)
171
+ }
172
+
173
+ Flamechart.prototype.yScale = function(y) {
174
+ var self = this
175
+ return self.heightScale(y - self.viewport.y)
176
+ }
177
+
178
+ Flamechart.prototype.widthScale = function(width) {
179
+ var self = this
180
+ return width * self.width / self.viewport.width
181
+ }
182
+
183
+ Flamechart.prototype.heightScale = function(height) {
184
+ var self = this
185
+ return height * self.height / self.viewport.height
186
+ }
187
+
188
+ Flamechart.prototype.frameRect = function(f) {
189
+ return {
190
+ x: f.x,
191
+ y: f.y,
192
+ width: f.width,
193
+ height: 1,
194
+ }
195
+ }
196
+
197
+ Flamechart.prototype.dataToCanvas = function(r) {
198
+ var self = this
199
+
200
+ return {
201
+ x: self.xScale(r.x),
202
+ y: self.yScale(r.y),
203
+ width: self.widthScale(r.width),
204
+ height: self.heightScale(r.height),
205
+ }
206
+ }
207
+
208
+ Flamechart.prototype.setViewport = function(viewport) {
209
+ var self = this
210
+
211
+ if (self.viewport.x === viewport.x &&
212
+ self.viewport.y === viewport.y &&
213
+ self.viewport.width === viewport.width &&
214
+ self.viewport.height === viewport.height)
215
+ return
216
+
217
+ self.viewport = viewport
218
+
219
+ self.scheduleRepaint()
220
+
221
+ self.dispatch('viewportchanged', { current: viewport })
222
+ }
223
+
224
+ Flamechart.prototype.paint = function(opacity, frames, gemName) {
225
+ var self = this
226
+
227
+ var ctx = self.canvas.getContext('2d')
228
+
229
+ ctx.strokeStyle = 'rgba(0, 0, 0, 0.2)'
230
+
231
+ if (self.showLabels) {
232
+ ctx.textBaseline = 'middle'
233
+ ctx.font = '11px ' + getComputedStyle(this.canvas).fontFamily
234
+ // W tends to be one of the widest characters (and if the font is truly
235
+ // fixed-width then any character will do).
236
+ var characterWidth = ctx.measureText('WWWW').width / 4
237
+ }
238
+
239
+ if (typeof opacity === 'undefined')
240
+ opacity = 1
241
+
242
+ frames = frames || self.data
243
+
244
+ var blocksByColor = {}
245
+
246
+ frames.forEach(function(f) {
247
+ if (gemName && f.gemName !== gemName)
248
+ return
249
+
250
+ var r = self.dataToCanvas(self.frameRect(f))
251
+
252
+ if (r.x >= self.width ||
253
+ r.y >= self.height ||
254
+ (r.x + r.width) <= 0 ||
255
+ (r.y + r.height) <= 0) {
256
+ return
257
+ }
258
+
259
+ var i = self.info[f.frame_id]
260
+ var color = colorString(i.color, opacity)
261
+ var colorBlocks = blocksByColor[color]
262
+ if (!colorBlocks)
263
+ colorBlocks = blocksByColor[color] = []
264
+ colorBlocks.push({ rect: r, text: f.frame })
265
+ })
266
+
267
+ var textBlocks = []
268
+
269
+ Object.keys(blocksByColor).forEach(function(color) {
270
+ ctx.fillStyle = color
271
+
272
+ blocksByColor[color].forEach(function(block) {
273
+ if (opacity < 1)
274
+ ctx.clearRect(block.rect.x, block.rect.y, block.rect.width, block.rect.height)
275
+
276
+ ctx.fillRect(block.rect.x, block.rect.y, block.rect.width, block.rect.height)
277
+
278
+ if (block.rect.width > 4 && block.rect.height > 4)
279
+ ctx.strokeRect(block.rect.x, block.rect.y, block.rect.width, block.rect.height)
280
+
281
+ if (!self.showLabels || block.rect.width / characterWidth < 4)
282
+ return
283
+
284
+ textBlocks.push(block)
71
285
  })
72
- maxY -= minY
73
- minY = 0
286
+ })
287
+
288
+ ctx.fillStyle = '#000'
289
+ textBlocks.forEach(function(block) {
290
+ var text = block.text
291
+ var textRect = Object.assign({}, block.rect)
292
+ textRect.x += 1
293
+ textRect.width -= 2
294
+ if (textRect.width < text.length * characterWidth * 0.75)
295
+ text = centerTruncate(block.text, Math.floor(textRect.width / characterWidth))
296
+ ctx.fillText(text, textRect.x, textRect.y + textRect.height / 2, textRect.width)
297
+ })
298
+ }
299
+
300
+ Flamechart.prototype.frameAtPoint = function(x, y) {
301
+ var self = this
302
+
303
+ return self.data.find(function(d) {
304
+ var r = self.dataToCanvas(self.frameRect(d))
305
+
306
+ return r.x <= x
307
+ && r.x + r.width >= x
308
+ && r.y <= y
309
+ && r.y + r.height >= y
310
+ })
311
+ }
312
+
313
+ function MainFlamechart(canvas, data, dataRange, info) {
314
+ var self = this
315
+
316
+ Flamechart.call(self, canvas, data, dataRange, info)
317
+
318
+ self.showLabels = true
319
+
320
+ self.canvas.addEventListener('mousedown', self.onMouseDown.bind(self))
321
+ self.canvas.addEventListener('mousemove', self.onMouseMove.bind(self))
322
+ self.canvas.addEventListener('mouseout', self.onMouseOut.bind(self))
323
+ self.canvas.addEventListener('wheel', self.onWheel.bind(self))
324
+ }
325
+
326
+ MainFlamechart.prototype = Object.create(Flamechart.prototype)
327
+
328
+ MainFlamechart.prototype.setDimensionsNow = function(width, height) {
329
+ var self = this
330
+
331
+ var viewport = Object.assign({}, self.viewport)
332
+ viewport.height = height / 16
333
+ self.setViewport(viewport)
334
+
335
+ CanvasView.prototype.setDimensionsNow.call(self, width, height)
336
+ }
337
+
338
+ MainFlamechart.prototype.onMouseDown = function(e) {
339
+ var self = this
340
+
341
+ if (e.button !== 0)
342
+ return
343
+
344
+ captureMouse({
345
+ mouseup: self.onMouseUp.bind(self),
346
+ mousemove: self.onMouseMove.bind(self),
347
+ })
348
+
349
+ var clientRect = self.canvas.getBoundingClientRect()
350
+ var currentX = e.clientX - clientRect.left
351
+ var currentY = e.clientY - clientRect.top
352
+
353
+ self.dragging = true
354
+ self.dragInfo = {
355
+ mouse: { x: currentX, y: currentY },
356
+ viewport: { x: self.viewport.x, y: self.viewport.y },
357
+ }
358
+
359
+ e.preventDefault()
360
+ }
361
+
362
+ MainFlamechart.prototype.onMouseUp = function(e) {
363
+ var self = this
364
+
365
+ if (!self.dragging)
366
+ return
367
+
368
+ releaseCapture()
369
+
370
+ self.dragging = false
371
+ e.preventDefault()
372
+ }
373
+
374
+ MainFlamechart.prototype.onMouseMove = function(e) {
375
+ var self = this
376
+
377
+ var clientRect = self.canvas.getBoundingClientRect()
378
+ var currentX = e.clientX - clientRect.left
379
+ var currentY = e.clientY - clientRect.top
380
+
381
+ if (self.dragging) {
382
+ var viewport = Object.assign({}, self.viewport)
383
+ viewport.x = self.dragInfo.viewport.x - (currentX - self.dragInfo.mouse.x) * viewport.width / self.width
384
+ viewport.y = self.dragInfo.viewport.y - (currentY - self.dragInfo.mouse.y) * viewport.height / self.height
385
+ viewport.x = Math.min(self.dataRange.maxX - viewport.width, Math.max(self.dataRange.minX, viewport.x))
386
+ viewport.y = Math.min(self.dataRange.maxY - viewport.height, Math.max(self.dataRange.minY, viewport.y))
387
+ self.setViewport(viewport)
388
+ return
74
389
  }
75
390
 
391
+ var frame = self.frameAtPoint(currentX, currentY)
392
+ self.setHoveredFrame(frame)
393
+ }
394
+
395
+ MainFlamechart.prototype.onMouseOut = function() {
396
+ var self = this
397
+
398
+ if (self.dragging)
399
+ return
400
+
401
+ self.setHoveredFrame(null)
402
+ }
403
+
404
+ MainFlamechart.prototype.onWheel = function(e) {
405
+ var self = this
406
+
407
+ var deltaX = e.deltaX
408
+ var deltaY = e.deltaY
409
+
410
+ if (e.deltaMode == WheelEvent.prototype.DOM_DELTA_LINE) {
411
+ deltaX *= 11
412
+ deltaY *= 11
413
+ }
414
+
415
+ if (e.shiftKey) {
416
+ if ('webkitDirectionInvertedFromDevice' in e) {
417
+ if (e.webkitDirectionInvertedFromDevice)
418
+ deltaY *= -1
419
+ } else if (/Mac OS X/.test(navigator.userAgent)) {
420
+ // Assume that most Mac users have "Scroll direction: Natural" enabled.
421
+ deltaY *= -1
422
+ }
423
+
424
+ var mouseWheelZoomSpeed = 1 / 120
425
+ self.handleZoomGesture(Math.pow(1.2, -(deltaY || deltaX) * mouseWheelZoomSpeed), e.offsetX)
426
+ e.preventDefault()
427
+ return
428
+ }
429
+
430
+ var viewport = Object.assign({}, self.viewport)
431
+ viewport.x += deltaX * viewport.width / (self.dataRange.maxX - self.dataRange.minX)
432
+ viewport.x = Math.min(self.dataRange.maxX - viewport.width, Math.max(self.dataRange.minX, viewport.x))
433
+ viewport.y += (deltaY / 8) * viewport.height / (self.dataRange.maxY - self.dataRange.minY)
434
+ viewport.y = Math.min(self.dataRange.maxY - viewport.height, Math.max(self.dataRange.minY, viewport.y))
435
+ self.setViewport(viewport)
436
+ e.preventDefault()
437
+ }
438
+
439
+ MainFlamechart.prototype.handleZoomGesture = function(zoom, originX) {
440
+ var self = this
441
+
442
+ var viewport = Object.assign({}, self.viewport)
443
+ var ratioX = originX / self.width
444
+
445
+ var newWidth = Math.min(viewport.width / zoom, self.dataRange.maxX - self.dataRange.minX)
446
+ viewport.x = Math.max(self.dataRange.minX, viewport.x + (viewport.width - newWidth) * ratioX)
447
+ viewport.width = Math.min(newWidth, self.dataRange.maxX - viewport.x)
448
+
449
+ self.setViewport(viewport)
450
+ }
451
+
452
+ MainFlamechart.prototype.setHoveredFrame = function(frame) {
453
+ var self = this
454
+
455
+ if (frame === self.hoveredFrame)
456
+ return
457
+
458
+ var previous = self.hoveredFrame
459
+ self.hoveredFrame = frame
460
+
461
+ self.dispatch('hoveredframechanged', { previous: previous, current: self.hoveredFrame })
462
+ }
463
+
464
+ function OverviewFlamechart(container, viewportOverlay, data, dataRange, info) {
465
+ var self = this
466
+
467
+ Flamechart.call(self, container.querySelector('.overview'), data, dataRange, info)
468
+
469
+ self.container = container
470
+
471
+ self.showLabels = false
472
+
473
+ self.viewportOverlay = viewportOverlay
474
+
475
+ self.canvas.addEventListener('mousedown', self.onMouseDown.bind(self))
476
+ self.viewportOverlay.addEventListener('mousedown', self.onOverlayMouseDown.bind(self))
477
+ }
478
+
479
+ OverviewFlamechart.prototype = Object.create(Flamechart.prototype)
480
+
481
+ OverviewFlamechart.prototype.setViewportOverlayRect = function(r) {
482
+ var self = this
483
+
484
+ self.viewportOverlayRect = r
485
+
486
+ r = self.dataToCanvas(r)
487
+ r.width = Math.max(2, r.width)
488
+ r.height = Math.max(2, r.height)
489
+
490
+ if ('transform' in self.viewportOverlay.style) {
491
+ self.viewportOverlay.style.transform = 'translate(' + r.x + 'px, ' + r.y + 'px) scale(' + r.width + ', ' + r.height + ')'
492
+ } else {
493
+ self.viewportOverlay.style.left = r.x
494
+ self.viewportOverlay.style.top = r.y
495
+ self.viewportOverlay.style.width = r.width
496
+ self.viewportOverlay.style.height = r.height
497
+ }
498
+ }
499
+
500
+ OverviewFlamechart.prototype.onMouseDown = function(e) {
501
+ var self = this
502
+
503
+ captureMouse({
504
+ mouseup: self.onMouseUp.bind(self),
505
+ mousemove: self.onMouseMove.bind(self),
506
+ })
507
+
508
+ self.dragging = true
509
+ self.dragStartX = e.clientX - self.canvas.getBoundingClientRect().left
510
+
511
+ self.handleDragGesture(e)
512
+
513
+ e.preventDefault()
514
+ }
515
+
516
+ OverviewFlamechart.prototype.onMouseUp = function(e) {
517
+ var self = this
518
+
519
+ if (!self.dragging)
520
+ return
521
+
522
+ releaseCapture()
523
+
524
+ self.dragging = false
525
+
526
+ self.handleDragGesture(e)
527
+
528
+ e.preventDefault()
529
+ }
530
+
531
+ OverviewFlamechart.prototype.onMouseMove = function(e) {
532
+ var self = this
533
+
534
+ if (!self.dragging)
535
+ return
536
+
537
+ self.handleDragGesture(e)
538
+
539
+ e.preventDefault()
540
+ }
541
+
542
+ OverviewFlamechart.prototype.handleDragGesture = function(e) {
543
+ var self = this
544
+
545
+ var clientRect = self.canvas.getBoundingClientRect()
546
+ var currentX = e.clientX - clientRect.left
547
+ var currentY = e.clientY - clientRect.top
548
+
549
+ if (self.dragCurrentX === currentX)
550
+ return
551
+
552
+ self.dragCurrentX = currentX
553
+
554
+ var minX = Math.min(self.dragStartX, self.dragCurrentX)
555
+ var maxX = Math.max(self.dragStartX, self.dragCurrentX)
556
+
557
+ var rect = Object.assign({}, self.viewportOverlayRect)
558
+ rect.x = minX / self.width * self.viewport.width + self.viewport.x
559
+ rect.width = Math.max(self.viewport.width / 1000, (maxX - minX) / self.width * self.viewport.width)
560
+
561
+ rect.y = Math.max(self.viewport.y, Math.min(self.viewport.height - self.viewport.y, currentY / self.height * self.viewport.height + self.viewport.y - rect.height / 2))
562
+
563
+ self.setViewportOverlayRect(rect)
564
+ self.dispatch('overlaychanged', { current: self.viewportOverlayRect })
565
+ }
566
+
567
+ OverviewFlamechart.prototype.onOverlayMouseDown = function(e) {
568
+ var self = this
569
+
570
+ captureMouse({
571
+ mouseup: self.onOverlayMouseUp.bind(self),
572
+ mousemove: self.onOverlayMouseMove.bind(self),
573
+ })
574
+
575
+ self.overlayDragging = true
576
+ self.overlayDragInfo = {
577
+ mouse: { x: e.clientX, y: e.clientY },
578
+ rect: Object.assign({}, self.viewportOverlayRect),
579
+ }
580
+ self.viewportOverlay.classList.add('moving')
581
+
582
+ self.handleOverlayDragGesture(e)
583
+
584
+ e.preventDefault()
585
+ }
586
+
587
+ OverviewFlamechart.prototype.onOverlayMouseUp = function(e) {
588
+ var self = this
589
+
590
+ if (!self.overlayDragging)
591
+ return
592
+
593
+ releaseCapture()
594
+
595
+ self.overlayDragging = false
596
+ self.viewportOverlay.classList.remove('moving')
597
+
598
+ self.handleOverlayDragGesture(e)
599
+
600
+ e.preventDefault()
601
+ }
602
+
603
+ OverviewFlamechart.prototype.onOverlayMouseMove = function(e) {
604
+ var self = this
605
+
606
+ if (!self.overlayDragging)
607
+ return
608
+
609
+ self.handleOverlayDragGesture(e)
610
+
611
+ e.preventDefault()
612
+ }
613
+
614
+ OverviewFlamechart.prototype.handleOverlayDragGesture = function(e) {
615
+ var self = this
616
+
617
+ var deltaX = (e.clientX - self.overlayDragInfo.mouse.x) / self.width * self.viewport.width
618
+ var deltaY = (e.clientY - self.overlayDragInfo.mouse.y) / self.height * self.viewport.height
619
+
620
+ var rect = Object.assign({}, self.overlayDragInfo.rect)
621
+ rect.x += deltaX
622
+ rect.y += deltaY
623
+ rect.x = Math.max(self.viewport.x, Math.min(self.viewport.x + self.viewport.width - rect.width, rect.x))
624
+ rect.y = Math.max(self.viewport.y, Math.min(self.viewport.y + self.viewport.height - rect.height, rect.y))
625
+
626
+ self.setViewportOverlayRect(rect)
627
+ self.dispatch('overlaychanged', { current: self.viewportOverlayRect })
628
+ }
629
+
630
+ function FlamegraphView(data, info, sortedGems) {
631
+ var self = this
632
+
633
+ self.data = data
634
+ self.info = info
635
+
636
+ self.dataRange = self.computeDataRange()
637
+
638
+ self.mainChart = new MainFlamechart(document.querySelector('.flamegraph'), data, self.dataRange, info)
639
+ self.overview = new OverviewFlamechart(document.querySelector('.overview-container'), document.querySelector('.overview-viewport-overlay'), data, self.dataRange, info)
640
+ self.infoElement = document.querySelector('.info')
641
+
642
+ self.mainChart.on('hoveredframechanged', self.onHoveredFrameChanged.bind(self))
643
+ self.mainChart.on('viewportchanged', self.onViewportChanged.bind(self))
644
+ self.overview.on('overlaychanged', self.onOverlayChanged.bind(self))
645
+
646
+ var legend = document.querySelector('.legend')
647
+ self.renderLegend(legend, sortedGems)
648
+
649
+ legend.addEventListener('mousemove', self.onLegendMouseMove.bind(self))
650
+ legend.addEventListener('mouseout', self.onLegendMouseOut.bind(self))
651
+
652
+ window.addEventListener('resize', self.updateDimensions.bind(self))
653
+
654
+ self.updateDimensions()
655
+ }
656
+
657
+ FlamegraphView.prototype.updateDimensions = function() {
658
+ var self = this
659
+
76
660
  var margin = {top: 10, right: 10, bottom: 10, left: 10}
77
- var width = $(window).width() - 200 - margin.left - margin.right;
78
- var height = $(window).height() * 0.70 - margin.top - margin.bottom;
79
- var height2 = $(window).height() * 0.30 - 60 - margin.top - margin.bottom;
80
-
81
- $('.flamegraph').width(width + margin.left + margin.right).height(height + margin.top + margin.bottom);
82
- $('.zoom').width(width + margin.left + margin.right).height(height2 + margin.top + margin.bottom);
83
-
84
- var xScale = d3.scale.linear()
85
- .domain([0, maxX])
86
- .range([0, width]);
87
-
88
- var xScale2 = d3.scale.linear()
89
- .domain([0, maxX])
90
- .range([0, width])
91
-
92
- var yScale = d3.scale.linear()
93
- .domain([0, maxY])
94
- .range([0,height]);
95
-
96
- var yScale2 = d3.scale.linear()
97
- .domain([0, maxY])
98
- .range([0,height2]);
99
-
100
- var zoomXRatio = 1
101
- var zoomed = function() {
102
- svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + (zoomXRatio*d3.event.scale) + "," + d3.event.scale + ")");
103
-
104
- var x = xScale.domain(), y = yScale.domain()
105
- brush.extent([ [x[0]/zoomXRatio, y[0]], [x[1]/zoomXRatio, y[1]] ])
106
- if (x[1] == maxX && y[1] == maxY)
107
- brush.clear()
108
- svg2.select('g.brush').call(brush)
661
+ var width = window.innerWidth - 200 - margin.left - margin.right
662
+ var mainChartHeight = Math.ceil(window.innerHeight * 0.80) - margin.top - margin.bottom
663
+ var overviewHeight = Math.floor(window.innerHeight * 0.20) - 60 - margin.top - margin.bottom
664
+
665
+ self.mainChart.setDimensions(width + margin.left + margin.right, mainChartHeight + margin.top + margin.bottom)
666
+ self.overview.setDimensions(width + margin.left + margin.right, overviewHeight + margin.top + margin.bottom)
667
+ self.overview.setViewportOverlayRect(self.mainChart.viewport)
668
+ }
669
+
670
+ FlamegraphView.prototype.computeDataRange = function() {
671
+ var self = this
672
+
673
+ var range = { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity }
674
+ self.data.forEach(function(d) {
675
+ range.minX = Math.min(range.minX, d.x)
676
+ range.minY = Math.min(range.minY, d.y)
677
+ range.maxX = Math.max(range.maxX, d.x + d.width)
678
+ range.maxY = Math.max(range.maxY, d.y + 1)
679
+ })
680
+
681
+ return range
682
+ }
683
+
684
+ FlamegraphView.prototype.onHoveredFrameChanged = function(data) {
685
+ var self = this
686
+
687
+ self.updateInfo(data.current)
688
+
689
+ if (data.previous)
690
+ self.repaintFrames(1, self.info[data.previous.frame_id].frames)
691
+
692
+ if (data.current)
693
+ self.repaintFrames(0.5, self.info[data.current.frame_id].frames)
694
+ }
695
+
696
+ FlamegraphView.prototype.repaintFrames = function(opacity, frames) {
697
+ var self = this
698
+
699
+ self.mainChart.paint(opacity, frames)
700
+ self.overview.paint(opacity, frames)
701
+ }
702
+
703
+ FlamegraphView.prototype.updateInfo = function(frame) {
704
+ var self = this
705
+
706
+ if (!frame) {
707
+ self.infoElement.style.backgroundColor = ''
708
+ self.infoElement.querySelector('.frame').textContent = ''
709
+ self.infoElement.querySelector('.file').textContent = ''
710
+ self.infoElement.querySelector('.samples').textContent = ''
711
+ self.infoElement.querySelector('.exclusive').textContent = ''
712
+ return
109
713
  }
110
714
 
111
- var zoom = d3.behavior.zoom().x(xScale).y(yScale).scaleExtent([1, 14]).on('zoom', zoomed)
112
-
113
- var svg2 = d3.select('.zoom').append('svg').attr('width', '100%').attr('height', '100%').append('svg:g')
114
- .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
115
- .append('g').attr('class', 'graph')
116
-
117
- var svg = d3.select(".flamegraph")
118
- .append("svg")
119
- .attr("width", "100%")
120
- .attr("height", "100%")
121
- .attr("pointer-events", "all")
122
- .append('svg:g')
123
- .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
124
- .call(zoom)
125
- .append('svg:g').attr('class', 'graph');
126
-
127
- // so zoom works everywhere
128
- svg.append("rect")
129
- .attr("x",function(d) { return xScale(0); })
130
- .attr("y",function(d) { return yScale(0);})
131
- .attr("width", function(d){return xScale(maxX);})
132
- .attr("height", yScale(maxY))
133
- .attr("fill", "white");
134
-
135
- var samplePercentRaw = function(samples, exclusive) {
136
- var ret = [samples, ((samples / maxX) * 100).toFixed(2)]
137
- if (exclusive)
138
- ret = ret.concat([exclusive, ((exclusive / maxX) * 100).toFixed(2)])
139
- return ret;
715
+ var i = self.info[frame.frame_id]
716
+ var shortFile = frame.file.replace(/^.+\/(gems|app|lib|config|jobs)/, '$1')
717
+ var sData = self.samplePercentRaw(i.samples.length, frame.topFrame ? frame.topFrame.exclusiveCount : 0)
718
+
719
+ self.infoElement.style.backgroundColor = colorString(i.color, 1)
720
+ self.infoElement.querySelector('.frame').textContent = frame.frame
721
+ self.infoElement.querySelector('.file').textContent = shortFile
722
+ self.infoElement.querySelector('.samples').textContent = sData[0] + ' samples (' + sData[1] + '%)'
723
+ if (sData[3])
724
+ self.infoElement.querySelector('.exclusive').textContent = sData[2] + ' exclusive (' + sData[3] + '%)'
725
+ else
726
+ self.infoElement.querySelector('.exclusive').textContent = ''
727
+ }
728
+
729
+ FlamegraphView.prototype.samplePercentRaw = function(samples, exclusive) {
730
+ var self = this
731
+
732
+ var ret = [samples, ((samples / self.dataRange.maxX) * 100).toFixed(2)]
733
+ if (exclusive)
734
+ ret = ret.concat([exclusive, ((exclusive / self.dataRange.maxX) * 100).toFixed(2)])
735
+ return ret
736
+ }
737
+
738
+ FlamegraphView.prototype.onViewportChanged = function(data) {
739
+ var self = this
740
+
741
+ self.overview.setViewportOverlayRect(data.current)
742
+ }
743
+
744
+ FlamegraphView.prototype.onOverlayChanged = function(data) {
745
+ var self = this
746
+
747
+ self.mainChart.setViewport(data.current)
748
+ }
749
+
750
+ FlamegraphView.prototype.renderLegend = function(element, sortedGems) {
751
+ var self = this
752
+
753
+ var fragment = document.createDocumentFragment()
754
+
755
+ sortedGems.forEach(function(gem) {
756
+ var sData = self.samplePercentRaw(gem.samples.length)
757
+ var node = document.createElement('div')
758
+ node.className = 'legend-gem'
759
+ node.setAttribute('data-gem-name', gem.name)
760
+ node.style.backgroundColor = colorString(gem.color, 1)
761
+
762
+ var span = document.createElement('span')
763
+ span.style.float = 'right'
764
+ span.textContent = sData[0] + 'x'
765
+ span.appendChild(document.createElement('br'))
766
+ span.appendChild(document.createTextNode(sData[1] + '%'))
767
+ node.appendChild(span)
768
+
769
+ var name = document.createElement('div')
770
+ name.className = 'name'
771
+ name.textContent = gem.name
772
+ name.appendChild(document.createElement('br'))
773
+ name.appendChild(document.createTextNode('\u00a0'))
774
+ node.appendChild(name)
775
+
776
+ fragment.appendChild(node)
777
+ })
778
+
779
+ element.appendChild(fragment)
780
+ }
781
+
782
+ FlamegraphView.prototype.onLegendMouseMove = function(e) {
783
+ var self = this
784
+
785
+ var gemElement = e.target.closest('.legend-gem')
786
+ var gemName = gemElement.getAttribute('data-gem-name')
787
+
788
+ if (self.hoveredGemName === gemName)
789
+ return
790
+
791
+ if (self.hoveredGemName) {
792
+ self.mainChart.paint(1, null, self.hoveredGemName)
793
+ self.overview.paint(1, null, self.hoveredGemName)
794
+ }
795
+
796
+ self.hoveredGemName = gemName
797
+
798
+ self.mainChart.paint(0.5, null, self.hoveredGemName)
799
+ self.overview.paint(0.5, null, self.hoveredGemName)
800
+ }
801
+
802
+ FlamegraphView.prototype.onLegendMouseOut = function() {
803
+ var self = this
804
+
805
+ if (!self.hoveredGemName)
806
+ return
807
+
808
+ self.mainChart.paint(1, null, self.hoveredGemName)
809
+ self.overview.paint(1, null, self.hoveredGemName)
810
+ self.hoveredGemName = null
811
+ }
812
+
813
+ var capturingListeners = null
814
+ function captureMouse(listeners) {
815
+ if (capturingListeners)
816
+ releaseCapture()
817
+
818
+ for (var name in listeners)
819
+ document.addEventListener(name, listeners[name], true)
820
+ capturingListeners = listeners
821
+ }
822
+
823
+ function releaseCapture() {
824
+ if (!capturingListeners)
825
+ return
826
+
827
+ for (var name in capturingListeners)
828
+ document.removeEventListener(name, capturingListeners[name], true)
829
+ capturingListeners = null
830
+ }
831
+
832
+ function guessGem(frame) {
833
+ var split = frame.split('/gems/')
834
+ if (split.length === 1) {
835
+ split = frame.split('/app/')
836
+ if (split.length === 1) {
837
+ split = frame.split('/lib/')
838
+ } else {
839
+ return split[split.length - 1].split('/')[0]
840
+ }
841
+
842
+ split = split[Math.max(split.length - 2, 0)].split('/')
843
+ return split[split.length - 1].split(':')[0]
844
+ }
845
+ else
846
+ {
847
+ return split[split.length - 1].split('/')[0].split('-', 2)[0]
140
848
  }
849
+ }
141
850
 
142
- var samplePercent = function(samples, exclusive) {
143
- var info = samplePercentRaw(samples, exclusive)
144
- var samplesPct = info[1], exclusivePct = info[3]
145
- var ret = " (" + samples + " sample" + (samples == 1 ? "" : "s") + " - " + samplesPct + "%) ";
146
- if (exclusive)
147
- ret += " (" + exclusive + " exclusive - " + exclusivePct + "%) ";
148
- return ret;
851
+ function color() {
852
+ var r = parseInt(205 + Math.random() * 50)
853
+ var g = parseInt(Math.random() * 230)
854
+ var b = parseInt(Math.random() * 55)
855
+ return [r, g, b]
856
+ }
857
+
858
+ // http://stackoverflow.com/a/7419630
859
+ function rainbow(numOfSteps, step) {
860
+ // 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.
861
+ // Adam Cole, 2011-Sept-14
862
+ // HSV to RBG adapted from: http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
863
+ var r, g, b
864
+ var h = step / numOfSteps
865
+ var i = ~~(h * 6)
866
+ var f = h * 6 - i
867
+ var q = 1 - f
868
+ switch (i % 6) {
869
+ case 0: r = 1, g = f, b = 0; break
870
+ case 1: r = q, g = 1, b = 0; break
871
+ case 2: r = 0, g = 1, b = f; break
872
+ case 3: r = 0, g = q, b = 1; break
873
+ case 4: r = f, g = 0, b = 1; break
874
+ case 5: r = 1, g = 0, b = q; break
149
875
  }
876
+ return [Math.floor(r * 255), Math.floor(g * 255), Math.floor(b * 255)]
877
+ }
150
878
 
151
- var info = {};
879
+ function colorString(color, opacity) {
880
+ if (typeof opacity === 'undefined')
881
+ opacity = 1
882
+ return 'rgba(' + color.join(',') + ',' + opacity + ')'
883
+ }
152
884
 
153
- var mouseover = function(d) {
154
- var i = info[d.frame_id];
155
- var shortFile = d.file.replace(/^.+\/(gems|app|lib|config|jobs)/, '$1')
156
- var data = samplePercentRaw(i.samples.length, d.topFrame ? d.topFrame.exclusiveCount : 0)
885
+ // http://stackoverflow.com/questions/1960473/unique-values-in-an-array
886
+ function getUnique(orig) {
887
+ var o = {}
888
+ for (var i = 0; i < orig.length; i++) o[orig[i]] = 1
889
+ return Object.keys(o)
890
+ }
157
891
 
158
- $('.info')
159
- .css('background-color', i.color)
160
- .find('.frame').text(d.frame).end()
161
- .find('.file').text(shortFile).end()
162
- .find('.samples').text(data[0] + ' samples ('+data[1]+'%)').end()
163
- .find('.exclusive').text('')
892
+ function centerTruncate(text, maxLength) {
893
+ var charactersToKeep = maxLength - 1
894
+ if (charactersToKeep <= 0)
895
+ return ''
896
+ if (text.length <= charactersToKeep)
897
+ return text
164
898
 
165
- if (data[3])
166
- $('.info .exclusive').text(data[2] + ' exclusive ('+data[3]+'%)')
899
+ var prefixLength = Math.ceil(charactersToKeep / 2)
900
+ var suffixLength = charactersToKeep - prefixLength
901
+ var prefix = text.substr(0, prefixLength)
902
+ var suffix = suffixLength > 0 ? text.substr(-suffixLength) : ''
167
903
 
168
- d3.selectAll(i.nodes)
169
- .attr('opacity',0.5);
170
- };
904
+ return [prefix, '\u2026', suffix].join('')
905
+ }
171
906
 
172
- var mouseout = function(d) {
173
- var i = info[d.frame_id];
174
- $('.info').css('background-color', 'none').find('.frame, .file, .samples, .exclusive').text('')
907
+ function flamegraph(data) {
908
+ var info = {}
909
+ data.forEach(function(d) {
910
+ var i = info[d.frame_id]
911
+ if (!i)
912
+ info[d.frame_id] = i = {frames: [], samples: [], color: color()}
913
+ i.frames.push(d)
914
+ for (var j = 0; j < d.width; j++) {
915
+ i.samples.push(d.x + j)
916
+ }
917
+ })
175
918
 
176
- d3.selectAll(i.nodes)
177
- .attr('opacity',1);
178
- };
919
+ // Samples may overlap on the same line
920
+ for (var r in info) {
921
+ if (info[r].samples) {
922
+ info[r].samples = getUnique(info[r].samples)
923
+ }
924
+ }
179
925
 
180
926
  // assign some colors, analyze samples per gem
181
927
  var gemStats = {}
182
928
  var topFrames = {}
183
929
  var lastFrame = {frame: 'd52e04d-df28-41ed-a215-b6ec840a8ea5', x: -1}
184
930
 
185
- $.each(data, function(){
186
- var gem = guessGem(this.file);
187
- var stat = gemStats[gem];
188
- this.gemName = gem
931
+ data.forEach(function(d) {
932
+ var gem = guessGem(d.file)
933
+ var stat = gemStats[gem]
934
+ d.gemName = gem
189
935
 
190
- if(!stat) {
191
- gemStats[gem] = stat = {name: gem, samples: [], frames: [], nodes:[]};
936
+ if (!stat) {
937
+ gemStats[gem] = stat = {name: gem, samples: [], frames: []}
192
938
  }
193
939
 
194
- stat.frames.push(this.frame_id);
195
- for(var j=0; j < this.width; j++){
196
- stat.samples.push(this.x + j);
940
+ stat.frames.push(d.frame_id)
941
+ for (var j = 0; j < d.width; j++) {
942
+ stat.samples.push(d.x + j)
197
943
  }
198
944
  // This assumes the traversal is in order
199
- if (lastFrame.x != this.x) {
945
+ if (lastFrame.x !== d.x) {
200
946
  var topFrame = topFrames[lastFrame.frame_id]
201
947
  if (!topFrame) {
202
948
  topFrames[lastFrame.frame_id] = topFrame = {exclusiveCount: 0}
203
949
  }
204
- topFrame.exclusiveCount += 1;
205
- lastFrame.topFrame = topFrame;
950
+ topFrame.exclusiveCount += 1
951
+ lastFrame.topFrame = topFrame
206
952
  }
207
- lastFrame = this;
208
-
209
- });
953
+ lastFrame = d
954
+ })
210
955
 
211
956
  var topFrame = topFrames[lastFrame.frame_id]
212
957
  if (!topFrame) {
213
958
  topFrames[lastFrame.frame_id] = topFrame = {exclusiveCount: 0}
214
959
  }
215
- topFrame.exclusiveCount += 1;
216
- lastFrame.topFrame = topFrame;
217
-
218
- var totalGems = 0;
219
- $.each(gemStats, function(k,stat){
220
- totalGems++;
221
- stat.samples = getUnique(stat.samples);
222
- });
223
-
224
- var gemsSorted = $.map(gemStats, function(v, k){ return v })
225
- gemsSorted.sort(function(a, b){ return b.samples.length - a.samples.length })
226
-
227
- var currentIndex = 0;
228
- $.each(gemsSorted, function(k,stat){
229
- stat.color = rainbow(totalGems, currentIndex);
230
- currentIndex += 1;
231
-
232
- for(var x=0; x < stat.frames.length; x++) {
233
- info[stat.frames[x]] = {nodes: [], samples: [], color: stat.color};
234
- }
235
- });
236
-
237
- function drawData(svg, data, xScale, yScale, mini) {
238
- svg.selectAll("g.flames")
239
- .data(data)
240
- .enter()
241
- .append("g")
242
- .attr('class', 'flames')
243
- .each(function(d){
244
- gemStats[d.gemName].nodes.push(this)
245
-
246
- var r = d3.select(this)
247
- .append("rect")
248
- .attr("x",function(d) { return xScale(d.x); })
249
- .attr("y",function(d) { return yScale(maxY - d.y);})
250
- .attr("width", function(d){return xScale(d.width);})
251
- .attr("height", yScale(1))
252
- .attr("fill", function(d){
253
- var i = info[d.frame_id];
254
- if(!i) {
255
- info[d.frame_id] = i = {nodes: [], samples: [], color: color()};
256
- }
257
- i.nodes.push(this);
258
- if (!mini)
259
- for(var j=0; j < d.width; j++){
260
- i.samples.push(d.x + j);
261
- }
262
- return i.color;
263
- })
264
-
265
- if (!mini)
266
- r
267
- .on("mouseover", mouseover)
268
- .on("mouseout", mouseout);
269
-
270
- if (!mini)
271
- d3.select(this)
272
- .append('foreignObject')
273
- .classed('label-body', true)
274
- .attr("x",function(d) { return xScale(d.x); })
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("line-height", yScale(1))
279
- .attr("font-size", yScale(0.42) + 'px')
280
- .attr('pointer-events', 'none')
281
- .append('xhtml:span')
282
- .style("height", yScale(1))
283
- .classed('label', true)
284
- .text(function(d){ return d.frame })
285
- });
286
- }
960
+ topFrame.exclusiveCount += 1
961
+ lastFrame.topFrame = topFrame
287
962
 
288
- drawData(svg, data, xScale, yScale, 0)
289
- drawData(svg2, data, xScale2, yScale2, 1)
290
-
291
- var brushed = function(){
292
- if (brush.empty()) {
293
- svg.attr('transform', '')
294
- zoomXRatio = 1
295
- zoom.scale(1).translate([0,0])
296
- svg.selectAll('.label-body')
297
- .attr('transform', 'scale(1,1)')
298
- .attr("x",function(d) { return xScale(d.x)*zoomXRatio; })
299
- .attr("width", function(d){return xScale(d.width)*zoomXRatio;})
300
- } else {
301
- var e = brush.extent()
302
- var x = [e[0][0],e[1][0]], y = [e[0][1],e[1][1]]
303
-
304
- xScale.domain([0, maxX])
305
- yScale.domain([0, maxY])
306
-
307
- var w = width, h = height2
308
- var dx = xScale2(1.0*x[1]-x[0]), dy = yScale2(1.0*y[1]-y[0])
309
- var sx = w/dx, sy = h/dy
310
- var trlx = -xScale(x[0])*sx, trly = -yScale(y[0])*sy
311
- var transform = "translate(" + trlx + ',' + trly + ")" + " scale(" + sx + ',' + sy + ")"
312
-
313
- zoomXRatio = sx/sy
314
-
315
- svg.selectAll('.label-body')
316
- .attr("x",function(d) { return xScale(d.x)*zoomXRatio; })
317
- .attr("width", function(d){return xScale(d.width)*zoomXRatio;})
318
- .attr('transform', function(d){
319
- var x = xScale(d.x)
320
- return "scale("+(1.0/zoomXRatio)+",1)"
321
- })
322
-
323
- svg.attr("transform", transform)
324
- zoom.translate([trlx, trly]).scale(sy)
325
- }
963
+ var totalGems = 0
964
+ for (var k in gemStats) {
965
+ totalGems++
966
+ gemStats[k].samples = getUnique(gemStats[k].samples)
326
967
  }
327
968
 
328
- var brush = d3.svg.brush()
329
- .x(xScale2)
330
- .y(yScale2)
331
- .on("brush", brushed);
969
+ var gemsSorted = Object.keys(gemStats).map(function(k) { return gemStats[k] })
970
+ gemsSorted.sort(function(a, b) { return b.samples.length - a.samples.length })
332
971
 
333
- svg2.append("g")
334
- .attr("class", "brush")
335
- .call(brush)
972
+ var currentIndex = 0
973
+ gemsSorted.forEach(function(stat) {
974
+ stat.color = rainbow(totalGems, currentIndex)
975
+ currentIndex += 1
336
976
 
337
- // Samples may overlap on the same line
338
- for (var r in info) {
339
- if (info[r].samples) {
340
- info[r].samples = getUnique(info[r].samples);
977
+ for (var x = 0; x < stat.frames.length; x++) {
978
+ info[stat.frames[x]].color = stat.color
341
979
  }
342
- };
343
-
344
- // render the legend
345
- $.each(gemsSorted, function(k,gem){
346
- var data = samplePercentRaw(gem.samples.length)
347
- var node = $("<div class='"+gem.name+"'></div>")
348
- .css("background-color", gem.color)
349
- .html("<span style='float: right'>" + data[0] + 'x<br>' + data[1] + '%' + '</span>' + '<div class="name">'+gem.name+'<br>&nbsp;</div>');
350
-
351
- node.on('mouseenter mouseleave', function(e){
352
- d3.selectAll(gemStats[gem.name].nodes).classed('highlighted', e.type == 'mouseenter')
353
- })
980
+ })
354
981
 
355
- $('.legend').append(node);
356
- });
982
+ new FlamegraphView(data, info, gemsSorted)
357
983
  }