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

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

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

Powered by Google App Engine
This is Rietveld 408576698