Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(46)

Unified Diff: tools/profview/profview.js

Issue 2696903002: [profiler] Graphical front-end for tick processor. (Closed)
Patch Set: Fix test Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « tools/profview/profview.css ('k') | tools/tickprocessor.js » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/profview/profview.js
diff --git a/tools/profview/profview.js b/tools/profview/profview.js
new file mode 100644
index 0000000000000000000000000000000000000000..b45b4bc1d29015aed211cc0aa6dede88ca82d403
--- /dev/null
+++ b/tools/profview/profview.js
@@ -0,0 +1,812 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+"use strict"
+
+function $(id) {
+ return document.getElementById(id);
+}
+
+let components = [];
+
+function createViews() {
+ components.push(new CallTreeView());
+ components.push(new TimelineView());
+ components.push(new HelpView());
+
+ let modeBar = $("mode-bar");
+
+ function addMode(id, text, active) {
+ let div = document.createElement("div");
+ div.classList = "mode-button" + (active ? " active-mode-button" : "");
+ div.id = "mode-" + id;
+ div.textContent = text;
+ div.onclick = () => {
+ if (main.currentState.callTree.mode === id) return;
+ let old = $("mode-" + main.currentState.callTree.mode);
+ old.classList = "mode-button";
+ div.classList = "mode-button active-mode-button";
+ main.setMode(id);
+ };
+ modeBar.appendChild(div);
+ }
+
+ addMode("bottom-up", "Bottom up", true);
+ addMode("top-down", "Top down");
+ addMode("function-list", "Functions");
+
+ main.setMode("bottom-up");
+}
+
+function emptyState() {
+ return {
+ file : null,
+ start : 0,
+ end : Infinity,
+ timeLine : {
+ width : 100,
+ height : 100
+ },
+ callTree : {
+ mode : "none",
+ attribution : "js-exclude-bc",
+ categories : "code-type",
+ sort : "time"
+ }
+ };
+}
+
+function setCallTreeState(state, callTreeState) {
+ state = Object.assign({}, state);
+ state.callTree = callTreeState;
+ return state;
+}
+
+let main = {
+ currentState : emptyState(),
+
+ setMode(mode) {
+ if (mode != main.currentState.mode) {
+ let callTreeState = Object.assign({}, main.currentState.callTree);
+ callTreeState.mode = mode;
+ switch (mode) {
+ case "bottom-up":
+ callTreeState.attribution = "js-exclude-bc";
+ callTreeState.categories = "code-type";
+ callTreeState.sort = "time";
+ break;
+ case "top-down":
+ callTreeState.attribution = "js-exclude-bc";
+ callTreeState.categories = "none";
+ callTreeState.sort = "time";
+ break;
+ case "function-list":
+ callTreeState.attribution = "js-exclude-bc";
+ callTreeState.categories = "none";
+ callTreeState.sort = "own-time";
+ break;
+ default:
+ console.error("Invalid mode");
+ }
+ main.currentState = setCallTreeState(main.currentState, callTreeState);
+ main.delayRender();
+ }
+ },
+
+ setCallTreeAttribution(attribution) {
+ if (attribution != main.currentState.attribution) {
+ let callTreeState = Object.assign({}, main.currentState.callTree);
+ callTreeState.attribution = attribution;
+ main.currentState = setCallTreeState(main.currentState, callTreeState);
+ main.delayRender();
+ }
+ },
+
+ setCallTreeSort(sort) {
+ if (sort != main.currentState.sort) {
+ let callTreeState = Object.assign({}, main.currentState.callTree);
+ callTreeState.sort = sort;
+ main.currentState = setCallTreeState(main.currentState, callTreeState);
+ main.delayRender();
+ }
+ },
+
+ setCallTreeCategories(categories) {
+ if (categories != main.currentState.categories) {
+ let callTreeState = Object.assign({}, main.currentState.callTree);
+ callTreeState.categories = categories;
+ main.currentState = setCallTreeState(main.currentState, callTreeState);
+ main.delayRender();
+ }
+ },
+
+ setViewInterval(start, end) {
+ if (start != main.currentState.start ||
+ end != main.currentState.end) {
+ main.currentState = Object.assign({}, main.currentState);
+ main.currentState.start = start;
+ main.currentState.end = end;
+ main.delayRender();
+ }
+ },
+
+ setTimeLineDimensions(width, height) {
+ if (width != main.currentState.timeLine.width ||
+ height != main.currentState.timeLine.height) {
+ let timeLine = Object.assign({}, main.currentState.timeLine);
+ timeLine.width = width;
+ timeLine.height = height;
+ main.currentState = Object.assign({}, main.currentState);
+ main.currentState.timeLine = timeLine;
+ main.delayRender();
+ }
+ },
+
+ setFile(file) {
+ if (file != main.currentState.file) {
+ main.currentState = Object.assign({}, main.currentState);
+ main.currentState.file = file;
+ main.delayRender();
+ }
+ },
+
+ onResize() {
+ main.setTimeLineDimensions(
+ window.innerWidth - 20, window.innerHeight / 8);
+ },
+
+ onLoad() {
+ function loadHandler(evt) {
+ let f = evt.target.files[0];
+ if (f) {
+ let reader = new FileReader();
+ reader.onload = function(event) {
+ let profData = JSON.parse(event.target.result);
+ main.setViewInterval(0, Infinity);
+ main.setFile(profData);
+ };
+ reader.onerror = function(event) {
+ console.error(
+ "File could not be read! Code " + event.target.error.code);
+ };
+ reader.readAsText(f);
+ } else {
+ main.setFile(null);
+ }
+ }
+ $("fileinput").addEventListener(
+ "change", loadHandler, false);
+ createViews();
+ main.onResize();
+ },
+
+ delayRender() {
+ Promise.resolve().then(() => {
+ for (let c of components) {
+ c.render(main.currentState);
+ }
+ });
+ }
+};
+
+let bucketDescriptors =
+ [ { kinds : [ "JSOPT" ],
+ color : "#ffb000",
+ backgroundColor : "#ffe0c0",
+ text : "JS Optimized" },
+ { kinds : [ "JSUNOPT", "BC" ],
+ color : "#00ff00",
+ backgroundColor : "#c0ffc0",
+ text : "JS Unoptimized" },
+ { kinds : [ "IC" ],
+ color : "#ffff00",
+ backgroundColor : "#ffffc0",
+ text : "IC" },
+ { kinds : [ "STUB", "BUILTIN", "REGEXP" ],
+ color : "#ffb0b0",
+ backgroundColor : "#fff0f0",
+ text : "Other generated" },
+ { kinds : [ "CPP", "LIB" ],
+ color : "#0000ff",
+ backgroundColor : "#c0c0ff",
+ text : "C++" },
+ { kinds : [ "CPPEXT" ],
+ color : "#8080ff",
+ backgroundColor : "#e0e0ff",
+ text : "C++/external" },
+ { kinds : [ "CPPCOMP" ],
+ color : "#00ffff",
+ backgroundColor : "#c0ffff",
+ text : "C++/Compiler" },
+ { kinds : [ "CPPGC" ],
+ color : "#ff00ff",
+ backgroundColor : "#ffc0ff",
+ text : "C++/GC" },
+ { kinds : [ "UNKNOWN" ],
+ color : "#f0f0f0",
+ backgroundColor : "#e0e0e0",
+ text : "Unknown" }
+ ];
+
+function bucketFromKind(kind) {
+ for (let i = 0; i < bucketDescriptors.length; i++) {
+ let bucket = bucketDescriptors[i];
+ for (let j = 0; j < bucket.kinds.length; j++) {
+ if (bucket.kinds[j] === kind) {
+ return bucket;
+ }
+ }
+ }
+ return null;
+}
+
+class CallTreeView {
+ constructor() {
+ this.element = $("calltree");
+ this.treeElement = $("calltree-table");
+ this.selectAttribution = $("calltree-attribution");
+ this.selectCategories = $("calltree-categories");
+ this.selectSort = $("calltree-sort");
+
+ this.selectAttribution.onchange = () => {
+ main.setCallTreeAttribution(this.selectAttribution.value);
+ };
+
+ this.selectCategories.onchange = () => {
+ main.setCallTreeCategories(this.selectCategories.value);
+ };
+
+ this.selectSort.onchange = () => {
+ main.setCallTreeSort(this.selectSort.value);
+ };
+
+ this.currentState = null;
+ }
+
+ filterFromFilterId(id) {
+ switch (id) {
+ case "full-tree":
+ return (type, kind) => true;
+ case "js-funs":
+ return (type, kind) => type !== 'CODE';
+ case "js-exclude-bc":
+ return (type, kind) =>
+ type !== 'CODE' || !CallTreeView.IsBytecodeHandler(kind);
+ }
+ }
+
+ sortFromId(id) {
+ switch (id) {
+ case "time":
+ return (c1, c2) => c2.ticks - c1.ticks;
+ case "own-time":
+ return (c1, c2) => c2.ownTicks - c1.ownTicks;
+ case "category-time":
+ return (c1, c2) => {
+ if (c1.type === c2.type) return c2.ticks - c1.ticks;
+ if (c1.type < c2.type) return 1;
+ return -1;
+ };
+ case "category-own-time":
+ return (c1, c2) => {
+ if (c1.type === c2.type) return c2.ownTicks - c1.ownTicks;
+ if (c1.type < c2.type) return 1;
+ return -1;
+ };
+ }
+ }
+
+ static IsBytecodeHandler(kind) {
+ return kind === "BytecodeHandler";
+ }
+
+ createExpander(indent) {
+ let div = document.createElement("div");
+ div.style.width = (1 + indent) + "em";
+ div.style.display = "inline-block";
+ div.style.textAlign = "right";
+ return div;
+ }
+
+ codeTypeToText(type) {
+ switch (type) {
+ case "UNKNOWN":
+ return "Unknown";
+ case "CPPCOMP":
+ return "C++ (compiler)";
+ case "CPPGC":
+ return "C++";
+ case "CPPEXT":
+ return "C++ External";
+ case "CPP":
+ return "C++";
+ case "LIB":
+ return "Library";
+ case "IC":
+ return "IC";
+ case "BC":
+ return "Bytecode";
+ case "STUB":
+ return "Stub";
+ case "BUILTIN":
+ return "Builtin";
+ case "REGEXP":
+ return "RegExp";
+ case "JSOPT":
+ return "JS opt";
+ case "JSUNOPT":
+ return "JS unopt";
+ }
+ console.error("Unknown type: " + type);
+ }
+
+ createTypeDiv(type) {
+ if (type === "CAT") {
+ return document.createTextNode("");
+ }
+ let div = document.createElement("div");
+ div.classList.add("code-type-chip");
+
+ let span = document.createElement("span");
+ span.classList.add("code-type-chip");
+ span.textContent = this.codeTypeToText(type);
+ div.appendChild(span);
+
+ span = document.createElement("span");
+ span.classList.add("code-type-chip-space");
+ div.appendChild(span);
+
+ return div;
+ }
+
+ expandTree(tree, indent) {
+ let that = this;
+ let index = 0;
+ let id = "R/";
+ let row = tree.row;
+ let expander = tree.expander;
+
+ if (row) {
+ console.assert("expander");
+ index = row.rowIndex;
+ id = row.id;
+
+ // Make sure we collapse the children when the row is clicked
+ // again.
+ expander.textContent = "\u25BE";
+ let expandHandler = expander.onclick;
+ expander.onclick = () => {
+ that.collapseRow(tree, expander, expandHandler);
+ }
+ }
+
+ // Collect the children, and sort them by ticks.
+ let children = [];
+ for (let child in tree.children) {
+ if (tree.children[child].ticks > 0) {
+ children.push(tree.children[child]);
+ }
+ }
+ children.sort(this.sortFromId(this.currentState.callTree.sort));
+
+ for (let i = 0; i < children.length; i++) {
+ let node = children[i];
+ let row = this.rows.insertRow(index);
+ row.id = id + i + "/";
+
+ if (node.type != "CAT") {
+ row.style.backgroundColor = bucketFromKind(node.type).backgroundColor;
+ }
+
+ // Inclusive time % cell.
+ let c = row.insertCell();
+ c.textContent = (node.ticks * 100 / this.tickCount).toFixed(2) + "%";
+ c.style.textAlign = "right";
+ // Percent-of-parent cell.
+ c = row.insertCell();
+ c.textContent = (node.ticks * 100 / tree.ticks).toFixed(2) + "%";
+ c.style.textAlign = "right";
+ // Exclusive time % cell.
+ if (this.currentState.callTree.mode !== "bottom-up") {
+ c = row.insertCell(-1);
+ c.textContent = (node.ownTicks * 100 / this.tickCount).toFixed(2) + "%";
+ c.style.textAlign = "right";
+ }
+
+ // Create the name cell.
+ let nameCell = row.insertCell();
+ let expander = this.createExpander(indent);
+ nameCell.appendChild(expander);
+ nameCell.appendChild(this.createTypeDiv(node.type));
+ nameCell.appendChild(document.createTextNode(node.name));
+
+ // Inclusive ticks cell.
+ c = row.insertCell();
+ c.textContent = node.ticks;
+ c.style.textAlign = "right";
+ if (this.currentState.callTree.mode !== "bottom-up") {
+ // Exclusive ticks cell.
+ c = row.insertCell(-1);
+ c.textContent = node.ownTicks;
+ c.style.textAlign = "right";
+ }
+ if (node.children.length > 0) {
+ expander.textContent = "\u25B8";
+ expander.onclick = () => { that.expandTree(node, indent + 1); };
+ }
+
+ node.row = row;
+ node.expander = expander;
+
+ index++;
+ }
+ }
+
+ collapseRow(tree, expander, expandHandler) {
+ let row = tree.row;
+ let id = row.id;
+ let index = row.rowIndex;
+ while (row.rowIndex < this.rows.rows.length &&
+ this.rows.rows[index].id.startsWith(id)) {
+ this.rows.deleteRow(index);
+ }
+
+ expander.textContent = "\u25B8";
+ expander.onclick = expandHandler;
+ }
+
+ fillSelects(calltree) {
+ function addOptions(e, values, current) {
+ while (e.options.length > 0) {
+ e.remove(0);
+ }
+ for (let i = 0; i < values.length; i++) {
+ let option = document.createElement("option");
+ option.value = values[i].value;
+ option.textContent = values[i].text;
+ e.appendChild(option);
+ }
+ e.value = current;
+ }
+
+ let attributions = [
+ { value : "js-exclude-bc",
+ text : "Attribute bytecode handlers to caller" },
+ { value : "full-tree",
+ text : "Count each code object separately" },
+ { value : "js-funs",
+ text : "Attribute non-functions to JS functions" }
+ ];
+
+ switch (calltree.mode) {
+ case "bottom-up":
+ addOptions(this.selectAttribution, attributions, calltree.attribution);
+ addOptions(this.selectCategories, [
+ { value : "code-type", text : "Code type" },
+ { value : "none", text : "None" }
+ ], calltree.categories);
+ addOptions(this.selectSort, [
+ { value : "time", text : "Time (including children)" },
+ { value : "category-time", text : "Code category, time" },
+ ], calltree.sort);
+ return;
+ case "top-down":
+ addOptions(this.selectAttribution, attributions, calltree.attribution);
+ addOptions(this.selectCategories, [
+ { value : "none", text : "None" }
+ ], calltree.categories);
+ addOptions(this.selectSort, [
+ { value : "time", text : "Time (including children)" },
+ { value : "own-time", text : "Own time" },
+ { value : "category-time", text : "Code category, time" },
+ { value : "category-own-time", text : "Code category, own time"}
+ ], calltree.sort);
+ return;
+ case "function-list":
+ addOptions(this.selectAttribution, attributions, calltree.attribution);
+ addOptions(this.selectCategories, [
+ { value : "none", text : "None" }
+ ], calltree.categories);
+ addOptions(this.selectSort, [
+ { value : "own-time", text : "Own time" },
+ { value : "time", text : "Time (including children)" },
+ { value : "category-own-time", text : "Code category, own time"},
+ { value : "category-time", text : "Code category, time" },
+ ], calltree.sort);
+ return;
+ }
+ console.error("Unexpected mode");
+ }
+
+ render(newState) {
+ let oldState = this.currentState;
+ if (!newState.file) {
+ this.element.style.display = "none";
+ return;
+ }
+
+ this.currentState = newState;
+ if (oldState) {
+ if (newState.file === oldState.file &&
+ newState.start === oldState.start &&
+ newState.end === oldState.end &&
+ newState.callTree.mode === oldState.callTree.mode &&
+ newState.callTree.attribution === oldState.callTree.attribution &&
+ newState.callTree.categories === oldState.callTree.categories &&
+ newState.callTree.sort === oldState.callTree.sort) {
+ // No change => just return.
+ return;
+ }
+ }
+
+ this.element.style.display = "inherit";
+
+ let mode = this.currentState.callTree.mode;
+ if (!oldState || mode !== oldState.callTree.mode) {
+ // Technically, we should also call this if attribution, categories or
+ // sort change, but the selection is already highlighted by the combobox
+ // itself, so we do need to do anything here.
+ this.fillSelects(newState.callTree);
+ }
+
+ let inclusiveDisplay = (mode === "bottom-up") ? "none" : "inherit";
+ let ownTimeTh = $(this.treeElement.id + "-own-time-header");
+ ownTimeTh.style.display = inclusiveDisplay;
+ let ownTicksTh = $(this.treeElement.id + "-own-ticks-header");
+ ownTicksTh.style.display = inclusiveDisplay;
+
+ // Build the tree.
+ let stackProcessor;
+ let filter = this.filterFromFilterId(this.currentState.callTree.attribution);
+ if (mode === "top-down") {
+ stackProcessor =
+ new PlainCallTreeProcessor(filter, false);
+ } else if (mode === "function-list") {
+ stackProcessor =
+ new FunctionListTree(filter);
+
+ } else {
+ console.assert(mode === "bottom-up");
+ if (this.currentState.callTree.categories == "none") {
+ stackProcessor =
+ new PlainCallTreeProcessor(filter, true);
+ } else {
+ console.assert(this.currentState.callTree.categories === "code-type");
+ stackProcessor =
+ new CategorizedCallTreeProcessor(filter, true);
+ }
+ }
+ this.tickCount =
+ generateTree(this.currentState.file,
+ this.currentState.start,
+ this.currentState.end,
+ stackProcessor);
+ // TODO(jarin) Handle the case when tick count is negative.
+
+ this.tree = stackProcessor.tree;
+
+ // Remove old content of the table, replace with new one.
+ let oldRows = this.treeElement.getElementsByTagName("tbody");
+ let newRows = document.createElement("tbody");
+ this.rows = newRows;
+
+ // Populate the table.
+ this.expandTree(this.tree, 0);
+
+ // Swap in the new rows.
+ this.treeElement.replaceChild(newRows, oldRows[0]);
+ }
+}
+
+class TimelineView {
+ constructor() {
+ this.element = $("timeline");
+ this.canvas = $("timeline-canvas");
+ this.legend = $("timeline-legend");
+
+ this.canvas.onmousedown = this.onMouseDown.bind(this);
+ this.canvas.onmouseup = this.onMouseUp.bind(this);
+ this.canvas.onmousemove = this.onMouseMove.bind(this);
+
+ this.selectionStart = null;
+ this.selectionEnd = null;
+ this.selecting = false;
+
+ this.currentState = null;
+ }
+
+ onMouseDown(e) {
+ this.selectionStart =
+ e.clientX - this.canvas.getBoundingClientRect().left;
+ this.selectionEnd = this.selectionStart + 1;
+ this.selecting = true;
+ }
+
+ onMouseMove(e) {
+ if (this.selecting) {
+ this.selectionEnd =
+ e.clientX - this.canvas.getBoundingClientRect().left;
+ this.drawSelection();
+ }
+ }
+
+ onMouseUp(e) {
+ if (this.selectionStart !== null) {
+ let x = e.clientX - this.canvas.getBoundingClientRect().left;
+ if (Math.abs(x - this.selectionStart) < 10) {
+ this.selectionStart = null;
+ this.selectionEnd = null;
+ let ctx = this.canvas.getContext("2d");
+ ctx.drawImage(this.buffer, 0, 0);
+ } else {
+ this.selectionEnd = x;
+ this.drawSelection();
+ }
+ let file = this.currentState.file;
+ if (file) {
+ let start = this.selectionStart === null ? 0 : this.selectionStart;
+ let end = this.selectionEnd === null ? Infinity : this.selectionEnd;
+ let firstTime = file.ticks[0].tm;
+ let lastTime = file.ticks[file.ticks.length - 1].tm;
+
+ let width = this.buffer.width;
+
+ start = (start / width) * (lastTime - firstTime) + firstTime;
+ end = (end / width) * (lastTime - firstTime) + firstTime;
+
+ if (end < start) {
+ let temp = start;
+ start = end;
+ end = temp;
+ }
+
+ main.setViewInterval(start, end);
+ }
+ }
+ this.selecting = false;
+ }
+
+ drawSelection() {
+ let ctx = this.canvas.getContext("2d");
+ ctx.drawImage(this.buffer, 0, 0);
+
+ if (this.selectionStart !== null && this.selectionEnd !== null) {
+ ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
+ let left = Math.min(this.selectionStart, this.selectionEnd);
+ let right = Math.max(this.selectionStart, this.selectionEnd);
+ ctx.fillRect(0, 0, left, this.buffer.height);
+ ctx.fillRect(right, 0, this.buffer.width - right, this.buffer.height);
+ }
+ }
+
+
+ render(newState) {
+ let oldState = this.currentState;
+
+ if (!newState.file) {
+ this.element.style.display = "none";
+ return;
+ }
+
+ this.currentState = newState;
+ if (oldState) {
+ if (newState.timeLine.width === oldState.timeLine.width &&
+ newState.timeLine.height === oldState.timeLine.height &&
+ newState.file === oldState.file &&
+ newState.start === oldState.start &&
+ newState.end === oldState.end) {
+ // No change, nothing to do.
+ return;
+ }
+ }
+
+ this.element.style.display = "inherit";
+
+ // Make sure the canvas has the right dimensions.
+ let width = this.currentState.timeLine.width;
+ this.canvas.width = width;
+ this.canvas.height = this.currentState.timeLine.height;
+
+ let file = this.currentState.file;
+ if (!file) return;
+
+ let firstTime = file.ticks[0].tm;
+ let lastTime = file.ticks[file.ticks.length - 1].tm;
+ let start = Math.max(this.currentState.start, firstTime);
+ let end = Math.min(this.currentState.end, lastTime);
+
+ this.selectionStart = (start - firstTime) / (lastTime - firstTime) * width;
+ this.selectionEnd = (end - firstTime) / (lastTime - firstTime) * width;
+
+ let tickCount = file.ticks.length;
+
+ let minBucketPixels = 10;
+ let minBucketSamples = 30;
+ let bucketCount = Math.min(width / minBucketPixels,
+ tickCount / minBucketSamples);
+
+ let stackProcessor = new CategorySampler(file, bucketCount);
+ generateTree(file, 0, Infinity, stackProcessor);
+
+ let buffer = document.createElement("canvas");
+
+ buffer.width = this.canvas.width;
+ buffer.height = this.canvas.height;
+
+ // Calculate the bar heights for each bucket.
+ let graphHeight = buffer.height;
+ let buckets = stackProcessor.buckets;
+ let bucketsGraph = [];
+ for (let i = 0; i < buckets.length; i++) {
+ let sum = 0;
+ let bucketData = [];
+ let total = buckets[i].total;
+ for (let j = 0; j < bucketDescriptors.length; j++) {
+ let desc = bucketDescriptors[j];
+ for (let k = 0; k < desc.kinds.length; k++) {
+ sum += buckets[i][desc.kinds[k]];
+ }
+ bucketData.push(graphHeight * sum / total);
+ }
+ bucketsGraph.push(bucketData);
+ }
+
+ // Draw the graph into the buffer.
+ let bucketWidth = width / bucketCount;
+ let ctx = buffer.getContext('2d');
+ for (let i = 0; i < bucketsGraph.length - 1; i++) {
+ let bucketData = bucketsGraph[i];
+ let nextBucketData = bucketsGraph[i + 1];
+ for (let j = 0; j < bucketData.length; j++) {
+ ctx.beginPath();
+ ctx.moveTo(i * bucketWidth, j && bucketData[j - 1]);
+ ctx.lineTo((i + 1) * bucketWidth, j && nextBucketData[j - 1]);
+ ctx.lineTo((i + 1) * bucketWidth, nextBucketData[j]);
+ ctx.lineTo(i * bucketWidth, bucketData[j]);
+ ctx.closePath();
+ ctx.fillStyle = bucketDescriptors[j].color;
+ ctx.fill();
+ }
+ }
+
+ // Remember stuff for later.
+ this.buffer = buffer;
+
+ // Draw the buffer.
+ this.drawSelection();
+
+ // (Re-)Populate the graph legend.
+ while (this.legend.cells.length > 0) {
+ this.legend.deleteCell(0);
+ }
+ let cell = this.legend.insertCell(-1);
+ cell.textContent = "Legend: ";
+ cell.style.padding = "1ex";
+ for (let i = 0; i < bucketDescriptors.length; i++) {
+ let cell = this.legend.insertCell(-1);
+ cell.style.padding = "1ex";
+ let desc = bucketDescriptors[i];
+ let div = document.createElement("div");
+ div.style.display = "inline-block";
+ div.style.width = "0.6em";
+ div.style.height = "1.2ex";
+ div.style.backgroundColor = desc.color;
+ div.style.borderStyle = "solid";
+ div.style.borderWidth = "1px";
+ div.style.borderColor = "Black";
+ cell.appendChild(div);
+ cell.appendChild(document.createTextNode(" " + desc.text));
+ }
+ }
+}
+
+class HelpView {
+ constructor() {
+ this.element = $("help");
+ }
+
+ render(newState) {
+ this.element.style.display = newState.file ? "none" : "inherit";
+ }
+}
« no previous file with comments | « tools/profview/profview.css ('k') | tools/tickprocessor.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698