summaryrefslogtreecommitdiff
path: root/deps/v8/tools/system-analyzer/timeline/timeline-track.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'deps/v8/tools/system-analyzer/timeline/timeline-track.mjs')
-rw-r--r--deps/v8/tools/system-analyzer/timeline/timeline-track.mjs904
1 files changed, 459 insertions, 445 deletions
diff --git a/deps/v8/tools/system-analyzer/timeline/timeline-track.mjs b/deps/v8/tools/system-analyzer/timeline/timeline-track.mjs
index 4905b782f2..a37bcce2c5 100644
--- a/deps/v8/tools/system-analyzer/timeline/timeline-track.mjs
+++ b/deps/v8/tools/system-analyzer/timeline/timeline-track.mjs
@@ -2,503 +2,517 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import {
- defineCustomElement, V8CustomElement,
- typeToColor, CSSColor
-} from '../helper.mjs';
-import { kChunkWidth, kChunkHeight } from "../log/map.mjs";
-import {
- SelectionEvent, FocusEvent, SelectTimeEvent,
- SynchronizeSelectionEvent
-} from '../events.mjs';
-
-defineCustomElement('./timeline/timeline-track', (templateText) =>
- class TimelineTrack extends V8CustomElement {
- static SELECTION_OFFSET = 20;
- #timeline;
- #nofChunks = 400;
- #chunks;
- #selectedEntry;
- #timeToPixel;
- #timeSelection = { start: 0, end: Infinity };
- #isSelected = false;
- #timeStartOffset;
- #mouseDownTime;
- constructor() {
- super(templateText);
- this.timeline.addEventListener("scroll",
- e => this.handleTimelineScroll(e));
- this.timeline.addEventListener("mousedown",
- e => this.handleTimeSelectionMouseDown(e));
- this.timeline.addEventListener("mouseup",
- e => this.handleTimeSelectionMouseUp(e));
- this.timeline.addEventListener("mousemove",
- e => this.handleTimeSelectionMouseMove(e));
- this.backgroundCanvas = document.createElement('canvas');
- this.isLocked = false;
- }
+import {FocusEvent, SelectionEvent, SelectTimeEvent, SynchronizeSelectionEvent} from '../events.mjs';
+import {CSSColor, delay, DOM, V8CustomElement} from '../helper.mjs';
+import {kChunkHeight, kChunkWidth} from '../log/map.mjs';
+
+const kColors = [
+ CSSColor.green,
+ CSSColor.violet,
+ CSSColor.orange,
+ CSSColor.yellow,
+ CSSColor.primaryColor,
+ CSSColor.red,
+ CSSColor.blue,
+ CSSColor.yellow,
+ CSSColor.secondaryColor,
+];
+
+DOM.defineCustomElement('./timeline/timeline-track',
+ (templateText) =>
+ class TimelineTrack extends V8CustomElement {
+ // TODO turn into static field once Safari supports it.
+ static get SELECTION_OFFSET() {
+ return 10
+ };
+ _timeline;
+ _nofChunks = 400;
+ _chunks;
+ _selectedEntry;
+ _timeToPixel;
+ _timeSelection = {start: -1, end: Infinity};
+ _timeStartOffset;
+ _selectionOriginTime;
+ _typeToColor;
+ constructor() {
+ super(templateText);
+ this.timeline.addEventListener('scroll', e => this.handleTimelineScroll(e));
+ this.timeline.addEventListener(
+ 'mousedown', e => this.handleTimeSelectionMouseDown(e));
+ this.timeline.addEventListener(
+ 'mouseup', e => this.handleTimeSelectionMouseUp(e));
+ this.timeline.addEventListener(
+ 'mousemove', e => this.handleTimeSelectionMouseMove(e));
+ this.backgroundCanvas = document.createElement('canvas');
+ this.isLocked = false;
+ }
- handleTimeSelectionMouseDown(e) {
- if (e.target.className === "chunk") return;
- this.#isSelected = true;
- this.#mouseDownTime = this.positionToTime(e.clientX);
- }
- handleTimeSelectionMouseMove(e) {
- if (!this.#isSelected) return;
- let mouseMoveTime = this.positionToTime(e.clientX);
- let startTime = this.#mouseDownTime;
- let endTime = mouseMoveTime;
- if (this.isOnLeftHandle(e.clientX)) {
- startTime = mouseMoveTime;
- endTime = this.positionToTime(this.rightHandlePosX);
- } else if (this.isOnRightHandle(e.clientX)) {
- startTime = this.positionToTime(this.leftHandlePosX);
- endTime = mouseMoveTime;
- }
- this.dispatchEvent(new SynchronizeSelectionEvent(
- Math.min(startTime, endTime),
- Math.max(startTime, endTime)));
- }
- handleTimeSelectionMouseUp(e) {
- this.#isSelected = false;
- this.dispatchEvent(new SelectTimeEvent(this.#timeSelection.start,
- this.#timeSelection.end));
+ handleTimeSelectionMouseDown(e) {
+ let xPosition = e.clientX
+ // Update origin time in case we click on a handle.
+ if (this.isOnLeftHandle(xPosition)) {
+ xPosition = this.rightHandlePosX;
}
- isOnLeftHandle(posX) {
- return (Math.abs(this.leftHandlePosX - posX)
- <= TimelineTrack.SELECTION_OFFSET);
- }
- isOnRightHandle(posX) {
- return (Math.abs(this.rightHandlePosX - posX)
- <= TimelineTrack.SELECTION_OFFSET);
+ else if (this.isOnRightHandle(xPosition)) {
+ xPosition = this.leftHandlePosX;
}
+ this._selectionOriginTime = this.positionToTime(xPosition);
+ }
+ isOnLeftHandle(posX) {
+ return (
+ Math.abs(this.leftHandlePosX - posX) <= TimelineTrack.SELECTION_OFFSET);
+ }
- set startTime(value) {
- console.assert(
- value <= this.#timeSelection.end,
- "Selection start time greater than end time!");
- this.#timeSelection.start = value;
- this.updateSelection();
- }
- set endTime(value) {
- console.assert(
- value > this.#timeSelection.start,
- "Selection end time smaller than start time!");
- this.#timeSelection.end = value;
- this.updateSelection();
- }
+ isOnRightHandle(posX) {
+ return (
+ Math.abs(this.rightHandlePosX - posX) <=
+ TimelineTrack.SELECTION_OFFSET);
+ }
- updateSelection() {
- let startTimePos = this.timeToPosition(this.#timeSelection.start);
- let endTimePos = this.timeToPosition(this.#timeSelection.end);
- this.leftHandle.style.left = startTimePos + "px";
- this.selection.style.left = startTimePos + "px";
- this.rightHandle.style.left = endTimePos + "px";
- this.selection.style.width =
- Math.abs(this.rightHandlePosX - this.leftHandlePosX) + "px";
- }
+ handleTimeSelectionMouseMove(e) {
+ if (!this._isSelecting) return;
+ const currentTime = this.positionToTime(e.clientX);
+ this.dispatchEvent(new SynchronizeSelectionEvent(
+ Math.min(this._selectionOriginTime, currentTime),
+ Math.max(this._selectionOriginTime, currentTime)));
+ }
- get leftHandlePosX() {
- let leftHandlePosX = this.leftHandle.getBoundingClientRect().x;
- return leftHandlePosX;
- }
- get rightHandlePosX() {
- let rightHandlePosX = this.rightHandle.getBoundingClientRect().x;
- return rightHandlePosX;
- }
+ handleTimeSelectionMouseUp(e) {
+ this._selectionOriginTime = -1;
+ const delta = this._timeSelection.end - this._timeSelection.start;
+ if (delta <= 1 || isNaN(delta)) return;
+ this.dispatchEvent(new SelectTimeEvent(
+ this._timeSelection.start, this._timeSelection.end));
+ }
- // Maps the clicked x position to the x position on timeline canvas
- positionOnTimeline(posX) {
- let rect = this.timeline.getBoundingClientRect();
- let posClickedX = posX - rect.left + this.timeline.scrollLeft;
- return posClickedX;
- }
+ set timeSelection(selection) {
+ this._timeSelection.start = selection.start;
+ this._timeSelection.end = selection.end;
+ this.updateSelection();
+ }
- positionToTime(posX) {
- let posTimelineX = this.positionOnTimeline(posX) + this.#timeStartOffset;
- return posTimelineX / this.#timeToPixel;
- }
+ get _isSelecting() {
+ return this._selectionOriginTime >= 0;
+ }
- timeToPosition(time) {
- let posX = time * this.#timeToPixel;
- posX -= this.#timeStartOffset
- return posX;
- }
+ updateSelection() {
+ const startPosition = this.timeToPosition(this._timeSelection.start);
+ const endPosition = this.timeToPosition(this._timeSelection.end);
+ const delta = endPosition - startPosition;
+ this.leftHandle.style.left = startPosition + 'px';
+ this.selection.style.left = startPosition + 'px';
+ this.rightHandle.style.left = endPosition + 'px';
+ this.selection.style.width = delta + 'px';
+ }
- get leftHandle() {
- return this.$('.leftHandle');
- }
- get rightHandle() {
- return this.$('.rightHandle');
- }
- get selection() {
- return this.$('.selection');
- }
+ get leftHandlePosX() {
+ return this.leftHandle.getBoundingClientRect().x;
+ }
- get timelineCanvas() {
- return this.$('#timelineCanvas');
- }
+ get rightHandlePosX() {
+ return this.rightHandle.getBoundingClientRect().x;
+ }
- get timelineChunks() {
- return this.$('#timelineChunks');
- }
+ // Maps the clicked x position to the x position on timeline canvas
+ positionOnTimeline(posX) {
+ let rect = this.timeline.getBoundingClientRect();
+ let posClickedX = posX - rect.left + this.timeline.scrollLeft;
+ return posClickedX;
+ }
- get timeline() {
- return this.$('#timeline');
- }
+ positionToTime(posX) {
+ let posTimelineX = this.positionOnTimeline(posX) + this._timeStartOffset;
+ return posTimelineX / this._timeToPixel;
+ }
- get timelineLegend() {
- return this.$('#legend');
- }
+ timeToPosition(time) {
+ let posX = time * this._timeToPixel;
+ posX -= this._timeStartOffset
+ return posX;
+ }
- get timelineLegendContent() {
- return this.$('#legendContent');
- }
- set data(value) {
- this.#timeline = value;
- this.updateChunks();
- this.updateTimeline();
- this.renderLegend();
- }
+ get leftHandle() {
+ return this.$('.leftHandle');
+ }
- get data() {
- return this.#timeline;
- }
+ get rightHandle() {
+ return this.$('.rightHandle');
+ }
- set nofChunks(count) {
- this.#nofChunks = count;
- this.updateChunks();
- this.updateTimeline();
- }
- get nofChunks() {
- return this.#nofChunks;
- }
- updateChunks() {
- this.#chunks = this.data.chunks(this.nofChunks);
- }
- get chunks() {
- return this.#chunks;
- }
- set selectedEntry(value) {
- this.#selectedEntry = value;
- if (value.edge) this.redraw();
- }
- get selectedEntry() {
- return this.#selectedEntry;
- }
+ get selection() {
+ return this.$('.selection');
+ }
+
+ get timelineCanvas() {
+ return this.$('#timelineCanvas');
+ }
+
+ get timelineChunks() {
+ return this.$('#timelineChunks');
+ }
+
+ get timeline() {
+ return this.$('#timeline');
+ }
+
+ get timelineLegend() {
+ return this.$('#legend');
+ }
+
+ get timelineLegendContent() {
+ return this.$('#legendContent');
+ }
+
+ set data(value) {
+ this._timeline = value;
+ this._resetTypeToColorCache();
+ this.update();
+ }
+
+ _update() {
+ this._updateChunks();
+ this._updateTimeline();
+ this._renderLegend();
+ }
- set scrollLeft(offset) {
- this.timeline.scrollLeft = offset;
+ _resetTypeToColorCache() {
+ this._typeToColor = new Map();
+ let lastIndex = 0;
+ for (const type of this.data.uniqueTypes.keys()) {
+ this._typeToColor.set(type, kColors[lastIndex++]);
}
+ }
+
+ get data() {
+ return this._timeline;
+ }
+
+ set nofChunks(count) {
+ this._nofChunks = count;
+ this.update();
+ }
+
+ get nofChunks() {
+ return this._nofChunks;
+ }
- renderLegend() {
- let timelineLegend = this.timelineLegend;
- let timelineLegendContent = this.timelineLegendContent;
- this.removeAllChildren(timelineLegendContent);
- let row = this.tr();
- row.entries = this.data.all;
- row.classList.add('clickable');
+ _updateChunks() {
+ this._chunks = this.data.chunks(this.nofChunks);
+ }
+
+ get chunks() {
+ return this._chunks;
+ }
+
+ set selectedEntry(value) {
+ this._selectedEntry = value;
+ if (value.edge) this.redraw();
+ }
+
+ get selectedEntry() {
+ return this._selectedEntry;
+ }
+
+ set scrollLeft(offset) {
+ this.timeline.scrollLeft = offset;
+ }
+
+ typeToColor(type) {
+ return this._typeToColor.get(type);
+ }
+
+ _renderLegend() {
+ let timelineLegendContent = this.timelineLegendContent;
+ DOM.removeAllChildren(timelineLegendContent);
+ this._timeline.uniqueTypes.forEach((entries, type) => {
+ let row = DOM.tr('clickable');
+ row.entries = entries;
row.addEventListener('dblclick', e => this.handleEntryTypeDblClick(e));
- row.appendChild(this.td(""));
- let td = this.td("All");
+ let color = this.typeToColor(type);
+ if (color !== null) {
+ let div = DOM.div('colorbox');
+ div.style.backgroundColor = color;
+ row.appendChild(DOM.td(div));
+ } else {
+ row.appendChild(DOM.td());
+ }
+ let td = DOM.td(type);
row.appendChild(td);
- row.appendChild(this.td(this.data.all.length));
- row.appendChild(this.td("100%"));
+ row.appendChild(DOM.td(entries.length));
+ let percent = (entries.length / this.data.all.length) * 100;
+ row.appendChild(DOM.td(percent.toFixed(1) + '%'));
timelineLegendContent.appendChild(row);
- let colorIterator = 0;
- this.#timeline.uniqueTypes.forEach((entries, type) => {
- let row = this.tr();
- row.entries = entries;
- row.classList.add('clickable');
- row.addEventListener('dblclick', e => this.handleEntryTypeDblClick(e));
- let color = typeToColor(type);
- if (color !== null) {
- let div = this.div(["colorbox"]);
- div.style.backgroundColor = color;
- row.appendChild(this.td(div));
- } else {
- row.appendChild(this.td(""));
- }
- let td = this.td(type);
- row.appendChild(td);
- row.appendChild(this.td(entries.length));
- let percent = (entries.length / this.data.all.length) * 100;
- row.appendChild(this.td(percent.toFixed(1) + "%"));
- timelineLegendContent.appendChild(row);
- colorIterator += 1;
- });
- timelineLegend.appendChild(timelineLegendContent);
- }
+ });
+ // Add Total row.
+ let row = DOM.tr();
+ row.appendChild(DOM.td(''));
+ row.appendChild(DOM.td('All'));
+ row.appendChild(DOM.td(this.data.all.length));
+ row.appendChild(DOM.td('100%'));
+ timelineLegendContent.appendChild(row);
+ this.timelineLegend.appendChild(timelineLegendContent);
+ }
- handleEntryTypeDblClick(e) {
- this.dispatchEvent(new SelectionEvent(e.target.parentNode.entries));
- }
+ handleEntryTypeDblClick(e) {
+ this.dispatchEvent(new SelectionEvent(e.target.parentNode.entries));
+ }
- timelineIndicatorMove(offset) {
- this.timeline.scrollLeft += offset;
- }
+ timelineIndicatorMove(offset) {
+ this.timeline.scrollLeft += offset;
+ }
- handleTimelineScroll(e) {
- let horizontal = e.currentTarget.scrollLeft;
- this.dispatchEvent(new CustomEvent(
- 'scrolltrack', {
- bubbles: true, composed: true,
- detail: horizontal
- }));
- }
+ handleTimelineScroll(e) {
+ let horizontal = e.currentTarget.scrollLeft;
+ this.dispatchEvent(new CustomEvent(
+ 'scrolltrack', {bubbles: true, composed: true, detail: horizontal}));
+ }
- asyncSetTimelineChunkBackground(backgroundTodo) {
- const kIncrement = 100;
- let start = 0;
- let delay = 1;
- while (start < backgroundTodo.length) {
- let end = Math.min(start + kIncrement, backgroundTodo.length);
- setTimeout((from, to) => {
- for (let i = from; i < to; i++) {
- let [chunk, node] = backgroundTodo[i];
- this.setTimelineChunkBackground(chunk, node);
- }
- }, delay++, start, end);
- start = end;
+ async setChunkBackgrounds(backgroundTodo) {
+ const kMaxDuration = 50;
+ let lastTime = 0;
+ for (let [chunk, node] of backgroundTodo) {
+ const current = performance.now();
+ if (current - lastTime > kMaxDuration) {
+ await delay(25);
+ lastTime = current;
}
+ this.setChunkBackground(chunk, node);
}
+ }
- setTimelineChunkBackground(chunk, node) {
- // Render the types of transitions as bar charts
- const kHeight = chunk.height;
- const kWidth = 1;
- this.backgroundCanvas.width = kWidth;
- this.backgroundCanvas.height = kHeight;
- let ctx = this.backgroundCanvas.getContext('2d');
- ctx.clearRect(0, 0, kWidth, kHeight);
- let y = 0;
- let total = chunk.size();
- let type, count;
- if (true) {
- chunk.getBreakdown(map => map.type).forEach(([type, count]) => {
- ctx.fillStyle = typeToColor(type);
- let height = count / total * kHeight;
- ctx.fillRect(0, y, kWidth, y + height);
- y += height;
- });
- } else {
- chunk.items.forEach(map => {
- ctx.fillStyle = typeToColor(map.type);
- let y = chunk.yOffset(map);
- ctx.fillRect(0, y, kWidth, y + 1);
- });
- }
-
- let imageData = this.backgroundCanvas.toDataURL('image/webp', 0.2);
- node.style.backgroundImage = 'url(' + imageData + ')';
+ setChunkBackground(chunk, node) {
+ // Render the types of transitions as bar charts
+ const kHeight = chunk.height;
+ const kWidth = 1;
+ this.backgroundCanvas.width = kWidth;
+ this.backgroundCanvas.height = kHeight;
+ let ctx = this.backgroundCanvas.getContext('2d');
+ ctx.clearRect(0, 0, kWidth, kHeight);
+ let y = 0;
+ let total = chunk.size();
+ let type, count;
+ if (true) {
+ chunk.getBreakdown(map => map.type).forEach(([type, count]) => {
+ ctx.fillStyle = this.typeToColor(type);
+ let height = count / total * kHeight;
+ ctx.fillRect(0, y, kWidth, y + height);
+ y += height;
+ });
+ } else {
+ chunk.items.forEach(map => {
+ ctx.fillStyle = this.typeToColor(map.type);
+ let y = chunk.yOffset(map);
+ ctx.fillRect(0, y, kWidth, y + 1);
+ });
}
- updateTimeline() {
- let chunksNode = this.timelineChunks;
- this.removeAllChildren(chunksNode);
- let chunks = this.chunks;
- let max = chunks.max(each => each.size());
- let start = this.data.startTime;
- let end = this.data.endTime;
- let duration = end - start;
- this.#timeToPixel = chunks.length * kChunkWidth / duration;
- this.#timeStartOffset = start * this.#timeToPixel;
- let addTimestamp = (time, name) => {
- let timeNode = this.div('timestamp');
- timeNode.innerText = name;
- timeNode.style.left = ((time - start) * this.#timeToPixel) + 'px';
- chunksNode.appendChild(timeNode);
- };
- let backgroundTodo = [];
- for (let i = 0; i < chunks.length; i++) {
- let chunk = chunks[i];
- let height = (chunk.size() / max * kChunkHeight);
- chunk.height = height;
- if (chunk.isEmpty()) continue;
- let node = this.div();
- node.className = 'chunk';
- node.style.left =
- ((chunks[i].start - start) * this.#timeToPixel) + 'px';
- node.style.height = height + 'px';
- node.chunk = chunk;
- node.addEventListener('mousemove', e => this.handleChunkMouseMove(e));
- node.addEventListener('click', e => this.handleChunkClick(e));
- node.addEventListener('dblclick', e => this.handleChunkDoubleClick(e));
- backgroundTodo.push([chunk, node])
- chunksNode.appendChild(node);
- }
- this.asyncSetTimelineChunkBackground(backgroundTodo)
-
- // Put a time marker roughly every 20 chunks.
- let expected = duration / chunks.length * 20;
- let interval = (10 ** Math.floor(Math.log10(expected)));
- let correction = Math.log10(expected / interval);
- correction = (correction < 0.33) ? 1 : (correction < 0.75) ? 2.5 : 5;
- interval *= correction;
-
- let time = start;
- while (time < end) {
- addTimestamp(time, ((time - start) / 1000) + ' ms');
- time += interval;
- }
- this.redraw();
- }
+ let imageData = this.backgroundCanvas.toDataURL('image/webp', 0.2);
+ node.style.backgroundImage = `url(${imageData})`;
+ }
- handleChunkMouseMove(event) {
- if (this.isLocked) return false;
- let chunk = event.target.chunk;
- if (!chunk) return;
- // topmost map (at chunk.height) == map #0.
- let relativeIndex =
+ _updateTimeline() {
+ let chunksNode = this.timelineChunks;
+ DOM.removeAllChildren(chunksNode);
+ let chunks = this.chunks;
+ let max = chunks.max(each => each.size());
+ let start = this.data.startTime;
+ let end = this.data.endTime;
+ let duration = end - start;
+ this._timeToPixel = chunks.length * kChunkWidth / duration;
+ this._timeStartOffset = start * this._timeToPixel;
+ let addTimestamp = (time, name) => {
+ let timeNode = DOM.div('timestamp');
+ timeNode.innerText = name;
+ timeNode.style.left = ((time - start) * this._timeToPixel) + 'px';
+ chunksNode.appendChild(timeNode);
+ };
+ let backgroundTodo = [];
+ for (let i = 0; i < chunks.length; i++) {
+ let chunk = chunks[i];
+ let height = (chunk.size() / max * kChunkHeight);
+ chunk.height = height;
+ if (chunk.isEmpty()) continue;
+ let node = DOM.div();
+ node.className = 'chunk';
+ node.style.left = ((chunks[i].start - start) * this._timeToPixel) + 'px';
+ node.style.height = height + 'px';
+ node.chunk = chunk;
+ node.addEventListener('mousemove', e => this.handleChunkMouseMove(e));
+ node.addEventListener('click', e => this.handleChunkClick(e));
+ node.addEventListener('dblclick', e => this.handleChunkDoubleClick(e));
+ backgroundTodo.push([chunk, node])
+ chunksNode.appendChild(node);
+ }
+ this.setChunkBackgrounds(backgroundTodo);
+
+ // Put a time marker roughly every 20 chunks.
+ let expected = duration / chunks.length * 20;
+ let interval = (10 ** Math.floor(Math.log10(expected)));
+ let correction = Math.log10(expected / interval);
+ correction = (correction < 0.33) ? 1 : (correction < 0.75) ? 2.5 : 5;
+ interval *= correction;
+
+ let time = start;
+ while (time < end) {
+ addTimestamp(time, ((time - start) / 1000) + ' ms');
+ time += interval;
+ }
+ this.redraw();
+ }
+
+ handleChunkMouseMove(event) {
+ if (this.isLocked) return false;
+ if (this._isSelecting) return false;
+ let chunk = event.target.chunk;
+ if (!chunk) return;
+ // topmost map (at chunk.height) == map #0.
+ let relativeIndex =
Math.round(event.layerY / event.target.offsetHeight * chunk.size());
- let map = chunk.at(relativeIndex);
- this.dispatchEvent(new FocusEvent(map));
- }
+ let map = chunk.at(relativeIndex);
+ this.dispatchEvent(new FocusEvent(map));
+ }
- handleChunkClick(event) {
- this.isLocked = !this.isLocked;
- }
+ handleChunkClick(event) {
+ this.isLocked = !this.isLocked;
+ }
- handleChunkDoubleClick(event) {
- this.isLocked = true;
- let chunk = event.target.chunk;
- if (!chunk) return;
- let maps = chunk.items;
- this.dispatchEvent(new SelectionEvent(maps));
- }
+ handleChunkDoubleClick(event) {
+ let chunk = event.target.chunk;
+ if (!chunk) return;
+ this.dispatchEvent(new SelectTimeEvent(chunk.start, chunk.end));
+ }
- redraw() {
- let canvas = this.timelineCanvas;
- canvas.width = (this.chunks.length + 1) * kChunkWidth;
- canvas.height = kChunkHeight;
- let ctx = canvas.getContext('2d');
- ctx.clearRect(0, 0, canvas.width, kChunkHeight);
- if (!this.selectedEntry || !this.selectedEntry.edge) return;
- this.drawEdges(ctx);
- }
- setMapStyle(map, ctx) {
- ctx.fillStyle = map.edge && map.edge.from ?
- CSSColor.onBackgroundColor : CSSColor.onPrimaryColor;
- }
+ redraw() {
+ let canvas = this.timelineCanvas;
+ canvas.width = (this.chunks.length + 1) * kChunkWidth;
+ canvas.height = kChunkHeight;
+ let ctx = canvas.getContext('2d');
+ ctx.clearRect(0, 0, canvas.width, kChunkHeight);
+ if (!this.selectedEntry || !this.selectedEntry.edge) return;
+ this.drawEdges(ctx);
+ }
+ setMapStyle(map, ctx) {
+ ctx.fillStyle = map.edge && map.edge.from ? CSSColor.onBackgroundColor :
+ CSSColor.onPrimaryColor;
+ }
- setEdgeStyle(edge, ctx) {
- let color = typeToColor(edge.type);
- ctx.strokeStyle = color;
- ctx.fillStyle = color;
- }
+ setEdgeStyle(edge, ctx) {
+ let color = this.typeToColor(edge.type);
+ ctx.strokeStyle = color;
+ ctx.fillStyle = color;
+ }
- markMap(ctx, map) {
- let [x, y] = map.position(this.chunks);
- ctx.beginPath();
- this.setMapStyle(map, ctx);
- ctx.arc(x, y, 3, 0, 2 * Math.PI);
- ctx.fill();
- ctx.beginPath();
- ctx.fillStyle = CSSColor.onBackgroundColor;
- ctx.arc(x, y, 2, 0, 2 * Math.PI);
- ctx.fill();
- }
+ markMap(ctx, map) {
+ let [x, y] = map.position(this.chunks);
+ ctx.beginPath();
+ this.setMapStyle(map, ctx);
+ ctx.arc(x, y, 3, 0, 2 * Math.PI);
+ ctx.fill();
+ ctx.beginPath();
+ ctx.fillStyle = CSSColor.onBackgroundColor;
+ ctx.arc(x, y, 2, 0, 2 * Math.PI);
+ ctx.fill();
+ }
- markSelectedMap(ctx, map) {
- let [x, y] = map.position(this.chunks);
- ctx.beginPath();
- this.setMapStyle(map, ctx);
- ctx.arc(x, y, 6, 0, 2 * Math.PI);
- ctx.strokeStyle = CSSColor.onBackgroundColor;
- ctx.stroke();
- }
+ markSelectedMap(ctx, map) {
+ let [x, y] = map.position(this.chunks);
+ ctx.beginPath();
+ this.setMapStyle(map, ctx);
+ ctx.arc(x, y, 6, 0, 2 * Math.PI);
+ ctx.strokeStyle = CSSColor.onBackgroundColor;
+ ctx.stroke();
+ }
- drawEdges(ctx) {
- // Draw the trace of maps in reverse order to make sure the outgoing
- // transitions of previous maps aren't drawn over.
- const kMaxOutgoingEdges = 100;
- let nofEdges = 0;
- let stack = [];
- let current = this.selectedEntry;
- while (current && nofEdges < kMaxOutgoingEdges) {
- nofEdges += current.children.length;
- stack.push(current);
- current = current.parent();
+ drawEdges(ctx) {
+ // Draw the trace of maps in reverse order to make sure the outgoing
+ // transitions of previous maps aren't drawn over.
+ const kMaxOutgoingEdges = 100;
+ let nofEdges = 0;
+ let stack = [];
+ let current = this.selectedEntry;
+ while (current && nofEdges < kMaxOutgoingEdges) {
+ nofEdges += current.children.length;
+ stack.push(current);
+ current = current.parent();
+ }
+ ctx.save();
+ this.drawOutgoingEdges(ctx, this.selectedEntry, 3);
+ ctx.restore();
+
+ let labelOffset = 15;
+ let xPrev = 0;
+ while (current = stack.pop()) {
+ if (current.edge) {
+ this.setEdgeStyle(current.edge, ctx);
+ let [xTo, yTo] = this.drawEdge(ctx, current.edge, true, labelOffset);
+ if (xTo == xPrev) {
+ labelOffset += 8;
+ } else {
+ labelOffset = 15
+ }
+ xPrev = xTo;
}
+ this.markMap(ctx, current);
+ current = current.parent();
ctx.save();
- this.drawOutgoingEdges(ctx, this.selectedEntry, 3);
+ // this.drawOutgoingEdges(ctx, current, 1);
ctx.restore();
-
- let labelOffset = 15;
- let xPrev = 0;
- while (current = stack.pop()) {
- if (current.edge) {
- this.setEdgeStyle(current.edge, ctx);
- let [xTo, yTo] = this.drawEdge(ctx, current.edge, true, labelOffset);
- if (xTo == xPrev) {
- labelOffset += 8;
- } else {
- labelOffset = 15
- }
- xPrev = xTo;
- }
- this.markMap(ctx, current);
- current = current.parent();
- ctx.save();
- // this.drawOutgoingEdges(ctx, current, 1);
- ctx.restore();
- }
- // Mark selected map
- this.markSelectedMap(ctx, this.selectedEntry);
}
+ // Mark selected map
+ this.markSelectedMap(ctx, this.selectedEntry);
+ }
- drawEdge(ctx, edge, showLabel = true, labelOffset = 20) {
- if (!edge.from || !edge.to) return [-1, -1];
- let [xFrom, yFrom] = edge.from.position(this.chunks);
- let [xTo, yTo] = edge.to.position(this.chunks);
- let sameChunk = xTo == xFrom;
- if (sameChunk) labelOffset += 8;
-
- ctx.beginPath();
- ctx.moveTo(xFrom, yFrom);
- let offsetX = 20;
- let offsetY = 20;
- let midX = xFrom + (xTo - xFrom) / 2;
- let midY = (yFrom + yTo) / 2 - 100;
+ drawEdge(ctx, edge, showLabel = true, labelOffset = 20) {
+ if (!edge.from || !edge.to) return [-1, -1];
+ let [xFrom, yFrom] = edge.from.position(this.chunks);
+ let [xTo, yTo] = edge.to.position(this.chunks);
+ let sameChunk = xTo == xFrom;
+ if (sameChunk) labelOffset += 8;
+
+ ctx.beginPath();
+ ctx.moveTo(xFrom, yFrom);
+ let offsetX = 20;
+ let offsetY = 20;
+ let midX = xFrom + (xTo - xFrom) / 2;
+ let midY = (yFrom + yTo) / 2 - 100;
+ if (!sameChunk) {
+ ctx.quadraticCurveTo(midX, midY, xTo, yTo);
+ } else {
+ ctx.lineTo(xTo, yTo);
+ }
+ if (!showLabel) {
+ ctx.stroke();
+ } else {
+ let centerX, centerY;
if (!sameChunk) {
- ctx.quadraticCurveTo(midX, midY, xTo, yTo);
+ centerX = (xFrom / 2 + midX + xTo / 2) / 2;
+ centerY = (yFrom / 2 + midY + yTo / 2) / 2;
} else {
- ctx.lineTo(xTo, yTo);
+ centerX = xTo;
+ centerY = yTo;
}
- if (!showLabel) {
- ctx.stroke();
- } else {
- let centerX, centerY;
- if (!sameChunk) {
- centerX = (xFrom / 2 + midX + xTo / 2) / 2;
- centerY = (yFrom / 2 + midY + yTo / 2) / 2;
- } else {
- centerX = xTo;
- centerY = yTo;
- }
- ctx.moveTo(centerX, centerY);
- ctx.lineTo(centerX + offsetX, centerY - labelOffset);
- ctx.stroke();
- ctx.textAlign = 'left';
- ctx.fillStyle = typeToColor(edge.type);
- ctx.fillText(
+ ctx.moveTo(centerX, centerY);
+ ctx.lineTo(centerX + offsetX, centerY - labelOffset);
+ ctx.stroke();
+ ctx.textAlign = 'left';
+ ctx.fillStyle = this.typeToColor(edge.type);
+ ctx.fillText(
edge.toString(), centerX + offsetX + 2, centerY - labelOffset);
- }
- return [xTo, yTo];
}
+ return [xTo, yTo];
+ }
- drawOutgoingEdges(ctx, map, max = 10, depth = 0) {
- if (!map) return;
- if (depth >= max) return;
- ctx.globalAlpha = 0.5 - depth * (0.3 / max);
- ctx.strokeStyle = CSSColor.timelineBackgroundColor;
- const limit = Math.min(map.children.length, 100)
- for (let i = 0; i < limit; i++) {
- let edge = map.children[i];
- this.drawEdge(ctx, edge, true);
- this.drawOutgoingEdges(ctx, edge.to, max, depth + 1);
- }
+ drawOutgoingEdges(ctx, map, max = 10, depth = 0) {
+ if (!map) return;
+ if (depth >= max) return;
+ ctx.globalAlpha = 0.5 - depth * (0.3 / max);
+ ctx.strokeStyle = CSSColor.timelineBackgroundColor;
+ const limit = Math.min(map.children.length, 100)
+ for (let i = 0; i < limit; i++) {
+ let edge = map.children[i];
+ this.drawEdge(ctx, edge, true);
+ this.drawOutgoingEdges(ctx, edge.to, max, depth + 1);
}
}
-);
+});