| <!DOCTYPE html> |
| <!-- |
| Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| Use of this source code is governed by a BSD-style license that can be |
| found in the LICENSE file. |
| --> |
| |
| <link rel="import" href="/tracing/ui/base/event_presenter.html"> |
| <link rel="import" href="/tracing/base/sorted_array_utils.html"> |
| <link rel="import" href="/tracing/ui/base/elided_cache.html"> |
| |
| <script> |
| 'use strict'; |
| |
| /** |
| * @fileoverview Provides various helper methods for drawing to a provided |
| * canvas. |
| */ |
| tr.exportTo('tr.ui.b', function() { |
| var elidedTitleCache = new tr.ui.b.ElidedTitleCache(); |
| var ColorScheme = tr.b.ColorScheme; |
| var colorsAsStrings = ColorScheme.colorsAsStrings; |
| |
| var EventPresenter = tr.ui.b.EventPresenter; |
| var blackColorId = ColorScheme.getColorIdForReservedName('black'); |
| |
| /** |
| * This value is used to allow for consistent style UI elements. |
| * Thread time visualisation uses a smaller rectangle that has this height. |
| * @const |
| */ |
| var THIN_SLICE_HEIGHT = 4; |
| |
| /** |
| * This value is used to for performance considerations when drawing large |
| * zoomed out traces that feature cpu time in the slices. If the waiting |
| * width is less than the threshold, we only draw the rectangle as a solid. |
| * @const |
| */ |
| var SLICE_WAITING_WIDTH_DRAW_THRESHOLD = 3; |
| |
| /** |
| * If the slice has mostly been waiting to be scheduled on the cpu, the |
| * wall clock will be far greater than the cpu clock. Draw the slice |
| * only as an idle slice, if the active width is not thicker than the |
| * threshold. |
| * @const |
| */ |
| var SLICE_ACTIVE_WIDTH_DRAW_THRESHOLD = 1; |
| |
| /** |
| * Should we elide text on trace labels? |
| * Without eliding, text that is too wide isn't drawn at all. |
| * Disable if you feel this causes a performance problem. |
| * This is a default value that can be overridden in tracks for testing. |
| * @const |
| */ |
| var SHOULD_ELIDE_TEXT = true; |
| |
| /** |
| * Draw the define line into |ctx|. |
| * |
| * @param {Context} ctx The context to draw into. |
| * @param {float} x1 The start x position of the line. |
| * @param {float} y1 The start y position of the line. |
| * @param {float} x2 The end x position of the line. |
| * @param {float} y2 The end y position of the line. |
| */ |
| function drawLine(ctx, x1, y1, x2, y2) { |
| ctx.moveTo(x1, y1); |
| ctx.lineTo(x2, y2); |
| } |
| |
| /** |
| * Draw the defined triangle into |ctx|. |
| * |
| * @param {Context} ctx The context to draw into. |
| * @param {float} x1 The first corner x. |
| * @param {float} y1 The first corner y. |
| * @param {float} x2 The second corner x. |
| * @param {float} y2 The second corner y. |
| * @param {float} x3 The third corner x. |
| * @param {float} y3 The third corner y. |
| */ |
| function drawTriangle(ctx, x1, y1, x2, y2, x3, y3) { |
| ctx.beginPath(); |
| ctx.moveTo(x1, y1); |
| ctx.lineTo(x2, y2); |
| ctx.lineTo(x3, y3); |
| ctx.closePath(); |
| } |
| |
| /** |
| * Draw an arrow into |ctx|. |
| * |
| * @param {Context} ctx The context to draw into. |
| * @param {float} x1 The shaft x. |
| * @param {float} y1 The shaft y. |
| * @param {float} x2 The head x. |
| * @param {float} y2 The head y. |
| * @param {float} arrowLength The length of the head. |
| * @param {float} arrowWidth The width of the head. |
| */ |
| function drawArrow(ctx, x1, y1, x2, y2, arrowLength, arrowWidth) { |
| var dx = x2 - x1; |
| var dy = y2 - y1; |
| var len = Math.sqrt(dx * dx + dy * dy); |
| var perc = (len - arrowLength) / len; |
| var bx = x1 + perc * dx; |
| var by = y1 + perc * dy; |
| var ux = dx / len; |
| var uy = dy / len; |
| var ax = uy * arrowWidth; |
| var ay = -ux * arrowWidth; |
| |
| ctx.beginPath(); |
| drawLine(ctx, x1, y1, x2, y2); |
| ctx.stroke(); |
| |
| drawTriangle(ctx, |
| bx + ax, by + ay, |
| x2, y2, |
| bx - ax, by - ay); |
| ctx.fill(); |
| } |
| |
| /** |
| * Draw the provided slices to the screen. |
| * |
| * Each of the elements in |slices| must provide the follow methods: |
| * * start |
| * * duration |
| * * colorId |
| * * selected |
| * |
| * @param {Context} ctx The canvas context. |
| * @param {TimelineDrawTransform} dt The draw transform. |
| * @param {float} viewLWorld The left most point of the world viewport. |
| * @param {float} viewRWorld The right most point of the world viewport. |
| * @param {float} viewHeight The height of the viewport. |
| * @param {Array} slices The slices to draw. |
| * @param {bool} async Whether the slices are drawn with async style. |
| */ |
| function drawSlices(ctx, dt, viewLWorld, viewRWorld, viewHeight, slices, |
| async) { |
| var pixelRatio = window.devicePixelRatio || 1; |
| var pixWidth = dt.xViewVectorToWorld(1); |
| var height = viewHeight * pixelRatio; |
| |
| var darkRectHeight = THIN_SLICE_HEIGHT * pixelRatio; |
| |
| // Not enough space for both colors, use light color only. |
| if (height < darkRectHeight) |
| darkRectHeight = 0; |
| |
| var lightRectHeight = height - darkRectHeight; |
| |
| // Begin rendering in world space. |
| ctx.save(); |
| dt.applyTransformToCanvas(ctx); |
| |
| var rect = new tr.ui.b.FastRectRenderer( |
| ctx, 2 * pixWidth, 2 * pixWidth, colorsAsStrings); |
| rect.setYandH(0, height); |
| |
| var lowSlice = tr.b.findLowIndexInSortedArray( |
| slices, |
| function(slice) { return slice.start + slice.duration; }, |
| viewLWorld); |
| |
| var hadTopLevel = false; |
| |
| for (var i = lowSlice; i < slices.length; ++i) { |
| var slice = slices[i]; |
| var x = slice.start; |
| if (x > viewRWorld) |
| break; |
| |
| var w = pixWidth; |
| if (slice.duration > 0) { |
| w = Math.max(slice.duration, 0.000001); |
| if (w < pixWidth) |
| w = pixWidth; |
| } |
| |
| var colorId = EventPresenter.getSliceColorId(slice); |
| var alpha = EventPresenter.getSliceAlpha(slice, async); |
| var lightAlpha = alpha * 0.70; |
| |
| if (async && slice.isTopLevel) { |
| rect.setYandH(3, height - 3); |
| hadTopLevel = true; |
| } else { |
| rect.setYandH(0, height); |
| } |
| |
| // If cpuDuration is available, draw rectangles proportional to the |
| // amount of cpu time taken. |
| if (!slice.cpuDuration) { |
| // No cpuDuration available, draw using only one alpha. |
| rect.fillRect(x, w, colorId, alpha); |
| continue; |
| } |
| |
| var activeWidth = w * (slice.cpuDuration / slice.duration); |
| var waitingWidth = w - activeWidth; |
| |
| // Check if we have enough screen space to draw the whole slice, with |
| // both color tones. |
| // |
| // Truncate the activeWidth to 0 if it is less than 'threshold' pixels. |
| if (activeWidth < SLICE_ACTIVE_WIDTH_DRAW_THRESHOLD * pixWidth) { |
| activeWidth = 0; |
| waitingWidth = w; |
| } |
| |
| // Truncate the waitingWidth to 0 if it is less than 'threshold' pixels. |
| if (waitingWidth < SLICE_WAITING_WIDTH_DRAW_THRESHOLD * pixWidth) { |
| activeWidth = w; |
| waitingWidth = 0; |
| } |
| |
| // We now draw the two rectangles making up the event slice. |
| // NOTE: The if statements are necessary for performance considerations. |
| // We do not want to force draws, if the width of the rectangle is 0. |
| // |
| // First draw the solid color, representing the 'active' part. |
| if (activeWidth > 0) { |
| rect.fillRect(x, activeWidth, colorId, alpha); |
| } |
| |
| // Next draw the two toned 'idle' part. |
| // NOTE: Substracting pixWidth and drawing one extra pixel is done to |
| // prevent drawing artifacts. Without it, the two parts of the slice, |
| // ('active' and 'idle') may appear split apart. |
| if (waitingWidth > 0) { |
| // First draw the light toned top part. |
| rect.setYandH(0, lightRectHeight); |
| rect.fillRect(x + activeWidth - pixWidth, |
| waitingWidth + pixWidth, colorId, lightAlpha); |
| // Then the solid bottom half. |
| rect.setYandH(lightRectHeight, darkRectHeight); |
| rect.fillRect(x + activeWidth - pixWidth, |
| waitingWidth + pixWidth, colorId, alpha); |
| // Reset for the next slice. |
| rect.setYandH(0, height); |
| } |
| } |
| rect.flush(); |
| |
| if (async && hadTopLevel) { |
| // Draw a top border over async slices in order to visually separate |
| // them from events above it. |
| // See https://github.com/google/trace-viewer/issues/725. |
| rect.setYandH(2, 1); |
| for (var i = lowSlice; i < slices.length; ++i) { |
| var slice = slices[i]; |
| var x = slice.start; |
| if (x > viewRWorld) |
| break; |
| |
| if (!slice.isTopLevel) |
| continue; |
| |
| var w = pixWidth; |
| if (slice.duration > 0) { |
| w = Math.max(slice.duration, 0.000001); |
| if (w < pixWidth) |
| w = pixWidth; |
| } |
| |
| rect.fillRect(x, w, blackColorId, 0.7); |
| } |
| rect.flush(); |
| } |
| |
| ctx.restore(); |
| } |
| |
| /** |
| * Draw the provided instant slices as lines to the screen. |
| * |
| * Each of the elements in |slices| must provide the follow methods: |
| * * start |
| * * duration with value of 0. |
| * * colorId |
| * * selected |
| * |
| * @param {Context} ctx The canvas context. |
| * @param {TimelineDrawTransform} dt The draw transform. |
| * @param {float} viewLWorld The left most point of the world viewport. |
| * @param {float} viewRWorld The right most point of the world viewport. |
| * @param {float} viewHeight The height of the viewport. |
| * @param {Array} slices The slices to draw. |
| * @param {Numer} lineWidthInPixels The width of the lines. |
| */ |
| function drawInstantSlicesAsLines( |
| ctx, dt, viewLWorld, viewRWorld, viewHeight, slices, lineWidthInPixels) { |
| var pixelRatio = window.devicePixelRatio || 1; |
| var height = viewHeight * pixelRatio; |
| |
| var pixWidth = dt.xViewVectorToWorld(1); |
| |
| // Begin rendering in world space. |
| ctx.save(); |
| ctx.lineWidth = pixWidth * lineWidthInPixels * pixelRatio; |
| dt.applyTransformToCanvas(ctx); |
| ctx.beginPath(); |
| |
| var lowSlice = tr.b.findLowIndexInSortedArray( |
| slices, |
| function(slice) { return slice.start; }, |
| viewLWorld); |
| |
| for (var i = lowSlice; i < slices.length; ++i) { |
| var slice = slices[i]; |
| var x = slice.start; |
| if (x > viewRWorld) |
| break; |
| |
| ctx.strokeStyle = EventPresenter.getInstantSliceColor(slice); |
| |
| ctx.beginPath(); |
| ctx.moveTo(x, 0); |
| ctx.lineTo(x, height); |
| ctx.stroke(); |
| } |
| ctx.restore(); |
| } |
| |
| /** |
| * Draws the labels for the given slices. |
| * |
| * The |slices| array must contain objects with the following API: |
| * * start |
| * * duration |
| * * title |
| * * didNotFinish (optional) |
| * |
| * @param {Context} ctx The graphics context. |
| * @param {TimelineDrawTransform} dt The draw transform. |
| * @param {float} viewLWorld The left most point of the world viewport. |
| * @param {float} viewRWorld The right most point of the world viewport. |
| * @param {Array} slices The slices to label. |
| * @param {bool} async Whether the slice labels are drawn with async style. |
| * @param {float} fontSize The font size. |
| * @param {float} yOffset The font offset. |
| */ |
| function drawLabels(ctx, dt, viewLWorld, viewRWorld, slices, async, |
| fontSize, yOffset) { |
| var pixelRatio = window.devicePixelRatio || 1; |
| var pixWidth = dt.xViewVectorToWorld(1); |
| |
| ctx.save(); |
| |
| ctx.textAlign = 'center'; |
| ctx.textBaseline = 'top'; |
| ctx.font = (fontSize * pixelRatio) + 'px sans-serif'; |
| |
| if (async) |
| ctx.font = 'italic ' + ctx.font; |
| |
| var cY = yOffset * pixelRatio; |
| |
| var lowSlice = tr.b.findLowIndexInSortedArray( |
| slices, |
| function(slice) { return slice.start + slice.duration; }, |
| viewLWorld); |
| |
| // Don't render text until until it is 20px wide |
| var quickDiscardThresshold = pixWidth * 20; |
| for (var i = lowSlice; i < slices.length; ++i) { |
| var slice = slices[i]; |
| if (slice.start > viewRWorld) |
| break; |
| |
| if (slice.duration <= quickDiscardThresshold) |
| continue; |
| |
| var title = slice.title + |
| (slice.didNotFinish ? ' (Did Not Finish)' : ''); |
| |
| var drawnTitle = title; |
| var drawnWidth = elidedTitleCache.labelWidth(ctx, drawnTitle); |
| var fullLabelWidth = elidedTitleCache.labelWidthWorld( |
| ctx, drawnTitle, pixWidth); |
| if (SHOULD_ELIDE_TEXT && fullLabelWidth > slice.duration) { |
| var elidedValues = elidedTitleCache.get( |
| ctx, pixWidth, |
| drawnTitle, drawnWidth, |
| slice.duration); |
| drawnTitle = elidedValues.string; |
| drawnWidth = elidedValues.width; |
| } |
| |
| if (drawnWidth * pixWidth < slice.duration) { |
| ctx.fillStyle = EventPresenter.getTextColor(slice); |
| var cX = dt.xWorldToView(slice.start + 0.5 * slice.duration); |
| ctx.fillText(drawnTitle, cX, cY, drawnWidth); |
| } |
| } |
| ctx.restore(); |
| } |
| |
| return { |
| drawSlices: drawSlices, |
| drawInstantSlicesAsLines: drawInstantSlicesAsLines, |
| drawLabels: drawLabels, |
| |
| drawLine: drawLine, |
| drawTriangle: drawTriangle, |
| drawArrow: drawArrow, |
| |
| elidedTitleCache_: elidedTitleCache, |
| |
| THIN_SLICE_HEIGHT: THIN_SLICE_HEIGHT |
| }; |
| }); |
| </script> |