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

Side by Side Diff: tools/profview/profview.js

Issue 2696903002: [profiler] Graphical front-end for tick processor. (Closed)
Patch Set: Fix test Created 3 years, 9 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 unified diff | Download patch
« no previous file with comments | « tools/profview/profview.css ('k') | tools/tickprocessor.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2017 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 "use strict"
6
7 function $(id) {
8 return document.getElementById(id);
9 }
10
11 let components = [];
12
13 function createViews() {
14 components.push(new CallTreeView());
15 components.push(new TimelineView());
16 components.push(new HelpView());
17
18 let modeBar = $("mode-bar");
19
20 function addMode(id, text, active) {
21 let div = document.createElement("div");
22 div.classList = "mode-button" + (active ? " active-mode-button" : "");
23 div.id = "mode-" + id;
24 div.textContent = text;
25 div.onclick = () => {
26 if (main.currentState.callTree.mode === id) return;
27 let old = $("mode-" + main.currentState.callTree.mode);
28 old.classList = "mode-button";
29 div.classList = "mode-button active-mode-button";
30 main.setMode(id);
31 };
32 modeBar.appendChild(div);
33 }
34
35 addMode("bottom-up", "Bottom up", true);
36 addMode("top-down", "Top down");
37 addMode("function-list", "Functions");
38
39 main.setMode("bottom-up");
40 }
41
42 function emptyState() {
43 return {
44 file : null,
45 start : 0,
46 end : Infinity,
47 timeLine : {
48 width : 100,
49 height : 100
50 },
51 callTree : {
52 mode : "none",
53 attribution : "js-exclude-bc",
54 categories : "code-type",
55 sort : "time"
56 }
57 };
58 }
59
60 function setCallTreeState(state, callTreeState) {
61 state = Object.assign({}, state);
62 state.callTree = callTreeState;
63 return state;
64 }
65
66 let main = {
67 currentState : emptyState(),
68
69 setMode(mode) {
70 if (mode != main.currentState.mode) {
71 let callTreeState = Object.assign({}, main.currentState.callTree);
72 callTreeState.mode = mode;
73 switch (mode) {
74 case "bottom-up":
75 callTreeState.attribution = "js-exclude-bc";
76 callTreeState.categories = "code-type";
77 callTreeState.sort = "time";
78 break;
79 case "top-down":
80 callTreeState.attribution = "js-exclude-bc";
81 callTreeState.categories = "none";
82 callTreeState.sort = "time";
83 break;
84 case "function-list":
85 callTreeState.attribution = "js-exclude-bc";
86 callTreeState.categories = "none";
87 callTreeState.sort = "own-time";
88 break;
89 default:
90 console.error("Invalid mode");
91 }
92 main.currentState = setCallTreeState(main.currentState, callTreeState);
93 main.delayRender();
94 }
95 },
96
97 setCallTreeAttribution(attribution) {
98 if (attribution != main.currentState.attribution) {
99 let callTreeState = Object.assign({}, main.currentState.callTree);
100 callTreeState.attribution = attribution;
101 main.currentState = setCallTreeState(main.currentState, callTreeState);
102 main.delayRender();
103 }
104 },
105
106 setCallTreeSort(sort) {
107 if (sort != main.currentState.sort) {
108 let callTreeState = Object.assign({}, main.currentState.callTree);
109 callTreeState.sort = sort;
110 main.currentState = setCallTreeState(main.currentState, callTreeState);
111 main.delayRender();
112 }
113 },
114
115 setCallTreeCategories(categories) {
116 if (categories != main.currentState.categories) {
117 let callTreeState = Object.assign({}, main.currentState.callTree);
118 callTreeState.categories = categories;
119 main.currentState = setCallTreeState(main.currentState, callTreeState);
120 main.delayRender();
121 }
122 },
123
124 setViewInterval(start, end) {
125 if (start != main.currentState.start ||
126 end != main.currentState.end) {
127 main.currentState = Object.assign({}, main.currentState);
128 main.currentState.start = start;
129 main.currentState.end = end;
130 main.delayRender();
131 }
132 },
133
134 setTimeLineDimensions(width, height) {
135 if (width != main.currentState.timeLine.width ||
136 height != main.currentState.timeLine.height) {
137 let timeLine = Object.assign({}, main.currentState.timeLine);
138 timeLine.width = width;
139 timeLine.height = height;
140 main.currentState = Object.assign({}, main.currentState);
141 main.currentState.timeLine = timeLine;
142 main.delayRender();
143 }
144 },
145
146 setFile(file) {
147 if (file != main.currentState.file) {
148 main.currentState = Object.assign({}, main.currentState);
149 main.currentState.file = file;
150 main.delayRender();
151 }
152 },
153
154 onResize() {
155 main.setTimeLineDimensions(
156 window.innerWidth - 20, window.innerHeight / 8);
157 },
158
159 onLoad() {
160 function loadHandler(evt) {
161 let f = evt.target.files[0];
162 if (f) {
163 let reader = new FileReader();
164 reader.onload = function(event) {
165 let profData = JSON.parse(event.target.result);
166 main.setViewInterval(0, Infinity);
167 main.setFile(profData);
168 };
169 reader.onerror = function(event) {
170 console.error(
171 "File could not be read! Code " + event.target.error.code);
172 };
173 reader.readAsText(f);
174 } else {
175 main.setFile(null);
176 }
177 }
178 $("fileinput").addEventListener(
179 "change", loadHandler, false);
180 createViews();
181 main.onResize();
182 },
183
184 delayRender() {
185 Promise.resolve().then(() => {
186 for (let c of components) {
187 c.render(main.currentState);
188 }
189 });
190 }
191 };
192
193 let bucketDescriptors =
194 [ { kinds : [ "JSOPT" ],
195 color : "#ffb000",
196 backgroundColor : "#ffe0c0",
197 text : "JS Optimized" },
198 { kinds : [ "JSUNOPT", "BC" ],
199 color : "#00ff00",
200 backgroundColor : "#c0ffc0",
201 text : "JS Unoptimized" },
202 { kinds : [ "IC" ],
203 color : "#ffff00",
204 backgroundColor : "#ffffc0",
205 text : "IC" },
206 { kinds : [ "STUB", "BUILTIN", "REGEXP" ],
207 color : "#ffb0b0",
208 backgroundColor : "#fff0f0",
209 text : "Other generated" },
210 { kinds : [ "CPP", "LIB" ],
211 color : "#0000ff",
212 backgroundColor : "#c0c0ff",
213 text : "C++" },
214 { kinds : [ "CPPEXT" ],
215 color : "#8080ff",
216 backgroundColor : "#e0e0ff",
217 text : "C++/external" },
218 { kinds : [ "CPPCOMP" ],
219 color : "#00ffff",
220 backgroundColor : "#c0ffff",
221 text : "C++/Compiler" },
222 { kinds : [ "CPPGC" ],
223 color : "#ff00ff",
224 backgroundColor : "#ffc0ff",
225 text : "C++/GC" },
226 { kinds : [ "UNKNOWN" ],
227 color : "#f0f0f0",
228 backgroundColor : "#e0e0e0",
229 text : "Unknown" }
230 ];
231
232 function bucketFromKind(kind) {
233 for (let i = 0; i < bucketDescriptors.length; i++) {
234 let bucket = bucketDescriptors[i];
235 for (let j = 0; j < bucket.kinds.length; j++) {
236 if (bucket.kinds[j] === kind) {
237 return bucket;
238 }
239 }
240 }
241 return null;
242 }
243
244 class CallTreeView {
245 constructor() {
246 this.element = $("calltree");
247 this.treeElement = $("calltree-table");
248 this.selectAttribution = $("calltree-attribution");
249 this.selectCategories = $("calltree-categories");
250 this.selectSort = $("calltree-sort");
251
252 this.selectAttribution.onchange = () => {
253 main.setCallTreeAttribution(this.selectAttribution.value);
254 };
255
256 this.selectCategories.onchange = () => {
257 main.setCallTreeCategories(this.selectCategories.value);
258 };
259
260 this.selectSort.onchange = () => {
261 main.setCallTreeSort(this.selectSort.value);
262 };
263
264 this.currentState = null;
265 }
266
267 filterFromFilterId(id) {
268 switch (id) {
269 case "full-tree":
270 return (type, kind) => true;
271 case "js-funs":
272 return (type, kind) => type !== 'CODE';
273 case "js-exclude-bc":
274 return (type, kind) =>
275 type !== 'CODE' || !CallTreeView.IsBytecodeHandler(kind);
276 }
277 }
278
279 sortFromId(id) {
280 switch (id) {
281 case "time":
282 return (c1, c2) => c2.ticks - c1.ticks;
283 case "own-time":
284 return (c1, c2) => c2.ownTicks - c1.ownTicks;
285 case "category-time":
286 return (c1, c2) => {
287 if (c1.type === c2.type) return c2.ticks - c1.ticks;
288 if (c1.type < c2.type) return 1;
289 return -1;
290 };
291 case "category-own-time":
292 return (c1, c2) => {
293 if (c1.type === c2.type) return c2.ownTicks - c1.ownTicks;
294 if (c1.type < c2.type) return 1;
295 return -1;
296 };
297 }
298 }
299
300 static IsBytecodeHandler(kind) {
301 return kind === "BytecodeHandler";
302 }
303
304 createExpander(indent) {
305 let div = document.createElement("div");
306 div.style.width = (1 + indent) + "em";
307 div.style.display = "inline-block";
308 div.style.textAlign = "right";
309 return div;
310 }
311
312 codeTypeToText(type) {
313 switch (type) {
314 case "UNKNOWN":
315 return "Unknown";
316 case "CPPCOMP":
317 return "C++ (compiler)";
318 case "CPPGC":
319 return "C++";
320 case "CPPEXT":
321 return "C++ External";
322 case "CPP":
323 return "C++";
324 case "LIB":
325 return "Library";
326 case "IC":
327 return "IC";
328 case "BC":
329 return "Bytecode";
330 case "STUB":
331 return "Stub";
332 case "BUILTIN":
333 return "Builtin";
334 case "REGEXP":
335 return "RegExp";
336 case "JSOPT":
337 return "JS opt";
338 case "JSUNOPT":
339 return "JS unopt";
340 }
341 console.error("Unknown type: " + type);
342 }
343
344 createTypeDiv(type) {
345 if (type === "CAT") {
346 return document.createTextNode("");
347 }
348 let div = document.createElement("div");
349 div.classList.add("code-type-chip");
350
351 let span = document.createElement("span");
352 span.classList.add("code-type-chip");
353 span.textContent = this.codeTypeToText(type);
354 div.appendChild(span);
355
356 span = document.createElement("span");
357 span.classList.add("code-type-chip-space");
358 div.appendChild(span);
359
360 return div;
361 }
362
363 expandTree(tree, indent) {
364 let that = this;
365 let index = 0;
366 let id = "R/";
367 let row = tree.row;
368 let expander = tree.expander;
369
370 if (row) {
371 console.assert("expander");
372 index = row.rowIndex;
373 id = row.id;
374
375 // Make sure we collapse the children when the row is clicked
376 // again.
377 expander.textContent = "\u25BE";
378 let expandHandler = expander.onclick;
379 expander.onclick = () => {
380 that.collapseRow(tree, expander, expandHandler);
381 }
382 }
383
384 // Collect the children, and sort them by ticks.
385 let children = [];
386 for (let child in tree.children) {
387 if (tree.children[child].ticks > 0) {
388 children.push(tree.children[child]);
389 }
390 }
391 children.sort(this.sortFromId(this.currentState.callTree.sort));
392
393 for (let i = 0; i < children.length; i++) {
394 let node = children[i];
395 let row = this.rows.insertRow(index);
396 row.id = id + i + "/";
397
398 if (node.type != "CAT") {
399 row.style.backgroundColor = bucketFromKind(node.type).backgroundColor;
400 }
401
402 // Inclusive time % cell.
403 let c = row.insertCell();
404 c.textContent = (node.ticks * 100 / this.tickCount).toFixed(2) + "%";
405 c.style.textAlign = "right";
406 // Percent-of-parent cell.
407 c = row.insertCell();
408 c.textContent = (node.ticks * 100 / tree.ticks).toFixed(2) + "%";
409 c.style.textAlign = "right";
410 // Exclusive time % cell.
411 if (this.currentState.callTree.mode !== "bottom-up") {
412 c = row.insertCell(-1);
413 c.textContent = (node.ownTicks * 100 / this.tickCount).toFixed(2) + "%";
414 c.style.textAlign = "right";
415 }
416
417 // Create the name cell.
418 let nameCell = row.insertCell();
419 let expander = this.createExpander(indent);
420 nameCell.appendChild(expander);
421 nameCell.appendChild(this.createTypeDiv(node.type));
422 nameCell.appendChild(document.createTextNode(node.name));
423
424 // Inclusive ticks cell.
425 c = row.insertCell();
426 c.textContent = node.ticks;
427 c.style.textAlign = "right";
428 if (this.currentState.callTree.mode !== "bottom-up") {
429 // Exclusive ticks cell.
430 c = row.insertCell(-1);
431 c.textContent = node.ownTicks;
432 c.style.textAlign = "right";
433 }
434 if (node.children.length > 0) {
435 expander.textContent = "\u25B8";
436 expander.onclick = () => { that.expandTree(node, indent + 1); };
437 }
438
439 node.row = row;
440 node.expander = expander;
441
442 index++;
443 }
444 }
445
446 collapseRow(tree, expander, expandHandler) {
447 let row = tree.row;
448 let id = row.id;
449 let index = row.rowIndex;
450 while (row.rowIndex < this.rows.rows.length &&
451 this.rows.rows[index].id.startsWith(id)) {
452 this.rows.deleteRow(index);
453 }
454
455 expander.textContent = "\u25B8";
456 expander.onclick = expandHandler;
457 }
458
459 fillSelects(calltree) {
460 function addOptions(e, values, current) {
461 while (e.options.length > 0) {
462 e.remove(0);
463 }
464 for (let i = 0; i < values.length; i++) {
465 let option = document.createElement("option");
466 option.value = values[i].value;
467 option.textContent = values[i].text;
468 e.appendChild(option);
469 }
470 e.value = current;
471 }
472
473 let attributions = [
474 { value : "js-exclude-bc",
475 text : "Attribute bytecode handlers to caller" },
476 { value : "full-tree",
477 text : "Count each code object separately" },
478 { value : "js-funs",
479 text : "Attribute non-functions to JS functions" }
480 ];
481
482 switch (calltree.mode) {
483 case "bottom-up":
484 addOptions(this.selectAttribution, attributions, calltree.attribution);
485 addOptions(this.selectCategories, [
486 { value : "code-type", text : "Code type" },
487 { value : "none", text : "None" }
488 ], calltree.categories);
489 addOptions(this.selectSort, [
490 { value : "time", text : "Time (including children)" },
491 { value : "category-time", text : "Code category, time" },
492 ], calltree.sort);
493 return;
494 case "top-down":
495 addOptions(this.selectAttribution, attributions, calltree.attribution);
496 addOptions(this.selectCategories, [
497 { value : "none", text : "None" }
498 ], calltree.categories);
499 addOptions(this.selectSort, [
500 { value : "time", text : "Time (including children)" },
501 { value : "own-time", text : "Own time" },
502 { value : "category-time", text : "Code category, time" },
503 { value : "category-own-time", text : "Code category, own time"}
504 ], calltree.sort);
505 return;
506 case "function-list":
507 addOptions(this.selectAttribution, attributions, calltree.attribution);
508 addOptions(this.selectCategories, [
509 { value : "none", text : "None" }
510 ], calltree.categories);
511 addOptions(this.selectSort, [
512 { value : "own-time", text : "Own time" },
513 { value : "time", text : "Time (including children)" },
514 { value : "category-own-time", text : "Code category, own time"},
515 { value : "category-time", text : "Code category, time" },
516 ], calltree.sort);
517 return;
518 }
519 console.error("Unexpected mode");
520 }
521
522 render(newState) {
523 let oldState = this.currentState;
524 if (!newState.file) {
525 this.element.style.display = "none";
526 return;
527 }
528
529 this.currentState = newState;
530 if (oldState) {
531 if (newState.file === oldState.file &&
532 newState.start === oldState.start &&
533 newState.end === oldState.end &&
534 newState.callTree.mode === oldState.callTree.mode &&
535 newState.callTree.attribution === oldState.callTree.attribution &&
536 newState.callTree.categories === oldState.callTree.categories &&
537 newState.callTree.sort === oldState.callTree.sort) {
538 // No change => just return.
539 return;
540 }
541 }
542
543 this.element.style.display = "inherit";
544
545 let mode = this.currentState.callTree.mode;
546 if (!oldState || mode !== oldState.callTree.mode) {
547 // Technically, we should also call this if attribution, categories or
548 // sort change, but the selection is already highlighted by the combobox
549 // itself, so we do need to do anything here.
550 this.fillSelects(newState.callTree);
551 }
552
553 let inclusiveDisplay = (mode === "bottom-up") ? "none" : "inherit";
554 let ownTimeTh = $(this.treeElement.id + "-own-time-header");
555 ownTimeTh.style.display = inclusiveDisplay;
556 let ownTicksTh = $(this.treeElement.id + "-own-ticks-header");
557 ownTicksTh.style.display = inclusiveDisplay;
558
559 // Build the tree.
560 let stackProcessor;
561 let filter = this.filterFromFilterId(this.currentState.callTree.attribution) ;
562 if (mode === "top-down") {
563 stackProcessor =
564 new PlainCallTreeProcessor(filter, false);
565 } else if (mode === "function-list") {
566 stackProcessor =
567 new FunctionListTree(filter);
568
569 } else {
570 console.assert(mode === "bottom-up");
571 if (this.currentState.callTree.categories == "none") {
572 stackProcessor =
573 new PlainCallTreeProcessor(filter, true);
574 } else {
575 console.assert(this.currentState.callTree.categories === "code-type");
576 stackProcessor =
577 new CategorizedCallTreeProcessor(filter, true);
578 }
579 }
580 this.tickCount =
581 generateTree(this.currentState.file,
582 this.currentState.start,
583 this.currentState.end,
584 stackProcessor);
585 // TODO(jarin) Handle the case when tick count is negative.
586
587 this.tree = stackProcessor.tree;
588
589 // Remove old content of the table, replace with new one.
590 let oldRows = this.treeElement.getElementsByTagName("tbody");
591 let newRows = document.createElement("tbody");
592 this.rows = newRows;
593
594 // Populate the table.
595 this.expandTree(this.tree, 0);
596
597 // Swap in the new rows.
598 this.treeElement.replaceChild(newRows, oldRows[0]);
599 }
600 }
601
602 class TimelineView {
603 constructor() {
604 this.element = $("timeline");
605 this.canvas = $("timeline-canvas");
606 this.legend = $("timeline-legend");
607
608 this.canvas.onmousedown = this.onMouseDown.bind(this);
609 this.canvas.onmouseup = this.onMouseUp.bind(this);
610 this.canvas.onmousemove = this.onMouseMove.bind(this);
611
612 this.selectionStart = null;
613 this.selectionEnd = null;
614 this.selecting = false;
615
616 this.currentState = null;
617 }
618
619 onMouseDown(e) {
620 this.selectionStart =
621 e.clientX - this.canvas.getBoundingClientRect().left;
622 this.selectionEnd = this.selectionStart + 1;
623 this.selecting = true;
624 }
625
626 onMouseMove(e) {
627 if (this.selecting) {
628 this.selectionEnd =
629 e.clientX - this.canvas.getBoundingClientRect().left;
630 this.drawSelection();
631 }
632 }
633
634 onMouseUp(e) {
635 if (this.selectionStart !== null) {
636 let x = e.clientX - this.canvas.getBoundingClientRect().left;
637 if (Math.abs(x - this.selectionStart) < 10) {
638 this.selectionStart = null;
639 this.selectionEnd = null;
640 let ctx = this.canvas.getContext("2d");
641 ctx.drawImage(this.buffer, 0, 0);
642 } else {
643 this.selectionEnd = x;
644 this.drawSelection();
645 }
646 let file = this.currentState.file;
647 if (file) {
648 let start = this.selectionStart === null ? 0 : this.selectionStart;
649 let end = this.selectionEnd === null ? Infinity : this.selectionEnd;
650 let firstTime = file.ticks[0].tm;
651 let lastTime = file.ticks[file.ticks.length - 1].tm;
652
653 let width = this.buffer.width;
654
655 start = (start / width) * (lastTime - firstTime) + firstTime;
656 end = (end / width) * (lastTime - firstTime) + firstTime;
657
658 if (end < start) {
659 let temp = start;
660 start = end;
661 end = temp;
662 }
663
664 main.setViewInterval(start, end);
665 }
666 }
667 this.selecting = false;
668 }
669
670 drawSelection() {
671 let ctx = this.canvas.getContext("2d");
672 ctx.drawImage(this.buffer, 0, 0);
673
674 if (this.selectionStart !== null && this.selectionEnd !== null) {
675 ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
676 let left = Math.min(this.selectionStart, this.selectionEnd);
677 let right = Math.max(this.selectionStart, this.selectionEnd);
678 ctx.fillRect(0, 0, left, this.buffer.height);
679 ctx.fillRect(right, 0, this.buffer.width - right, this.buffer.height);
680 }
681 }
682
683
684 render(newState) {
685 let oldState = this.currentState;
686
687 if (!newState.file) {
688 this.element.style.display = "none";
689 return;
690 }
691
692 this.currentState = newState;
693 if (oldState) {
694 if (newState.timeLine.width === oldState.timeLine.width &&
695 newState.timeLine.height === oldState.timeLine.height &&
696 newState.file === oldState.file &&
697 newState.start === oldState.start &&
698 newState.end === oldState.end) {
699 // No change, nothing to do.
700 return;
701 }
702 }
703
704 this.element.style.display = "inherit";
705
706 // Make sure the canvas has the right dimensions.
707 let width = this.currentState.timeLine.width;
708 this.canvas.width = width;
709 this.canvas.height = this.currentState.timeLine.height;
710
711 let file = this.currentState.file;
712 if (!file) return;
713
714 let firstTime = file.ticks[0].tm;
715 let lastTime = file.ticks[file.ticks.length - 1].tm;
716 let start = Math.max(this.currentState.start, firstTime);
717 let end = Math.min(this.currentState.end, lastTime);
718
719 this.selectionStart = (start - firstTime) / (lastTime - firstTime) * width;
720 this.selectionEnd = (end - firstTime) / (lastTime - firstTime) * width;
721
722 let tickCount = file.ticks.length;
723
724 let minBucketPixels = 10;
725 let minBucketSamples = 30;
726 let bucketCount = Math.min(width / minBucketPixels,
727 tickCount / minBucketSamples);
728
729 let stackProcessor = new CategorySampler(file, bucketCount);
730 generateTree(file, 0, Infinity, stackProcessor);
731
732 let buffer = document.createElement("canvas");
733
734 buffer.width = this.canvas.width;
735 buffer.height = this.canvas.height;
736
737 // Calculate the bar heights for each bucket.
738 let graphHeight = buffer.height;
739 let buckets = stackProcessor.buckets;
740 let bucketsGraph = [];
741 for (let i = 0; i < buckets.length; i++) {
742 let sum = 0;
743 let bucketData = [];
744 let total = buckets[i].total;
745 for (let j = 0; j < bucketDescriptors.length; j++) {
746 let desc = bucketDescriptors[j];
747 for (let k = 0; k < desc.kinds.length; k++) {
748 sum += buckets[i][desc.kinds[k]];
749 }
750 bucketData.push(graphHeight * sum / total);
751 }
752 bucketsGraph.push(bucketData);
753 }
754
755 // Draw the graph into the buffer.
756 let bucketWidth = width / bucketCount;
757 let ctx = buffer.getContext('2d');
758 for (let i = 0; i < bucketsGraph.length - 1; i++) {
759 let bucketData = bucketsGraph[i];
760 let nextBucketData = bucketsGraph[i + 1];
761 for (let j = 0; j < bucketData.length; j++) {
762 ctx.beginPath();
763 ctx.moveTo(i * bucketWidth, j && bucketData[j - 1]);
764 ctx.lineTo((i + 1) * bucketWidth, j && nextBucketData[j - 1]);
765 ctx.lineTo((i + 1) * bucketWidth, nextBucketData[j]);
766 ctx.lineTo(i * bucketWidth, bucketData[j]);
767 ctx.closePath();
768 ctx.fillStyle = bucketDescriptors[j].color;
769 ctx.fill();
770 }
771 }
772
773 // Remember stuff for later.
774 this.buffer = buffer;
775
776 // Draw the buffer.
777 this.drawSelection();
778
779 // (Re-)Populate the graph legend.
780 while (this.legend.cells.length > 0) {
781 this.legend.deleteCell(0);
782 }
783 let cell = this.legend.insertCell(-1);
784 cell.textContent = "Legend: ";
785 cell.style.padding = "1ex";
786 for (let i = 0; i < bucketDescriptors.length; i++) {
787 let cell = this.legend.insertCell(-1);
788 cell.style.padding = "1ex";
789 let desc = bucketDescriptors[i];
790 let div = document.createElement("div");
791 div.style.display = "inline-block";
792 div.style.width = "0.6em";
793 div.style.height = "1.2ex";
794 div.style.backgroundColor = desc.color;
795 div.style.borderStyle = "solid";
796 div.style.borderWidth = "1px";
797 div.style.borderColor = "Black";
798 cell.appendChild(div);
799 cell.appendChild(document.createTextNode(" " + desc.text));
800 }
801 }
802 }
803
804 class HelpView {
805 constructor() {
806 this.element = $("help");
807 }
808
809 render(newState) {
810 this.element.style.display = newState.file ? "none" : "inherit";
811 }
812 }
OLDNEW
« 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