1 // Copyright 2013 The Chromium 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.
6 * This visualizer displays the log in a timeline graph
9 * - Can zoom in result by select time range
10 * - Display different levels of logs in different layers of canvases
13 var CrosLogVisualizer = (function() {
16 // HTML attributes of canvas
17 var LOG_VISUALIZER_CANVAS_CLASS = 'cros-log-visualizer-visualizer-canvas';
18 var LOG_VISUALIZER_CANVAS_WIDTH = 980;
19 var LOG_VISUALIZER_CANVAS_HEIGHT = 100;
21 // Special HTML classes
22 var LOG_VISUALIZER_TIMELINE_ID = 'cros-log-visualizer-visualizer-timeline';
23 var LOG_VISUALIZER_TIME_DISPLAY_CLASS =
24 'cros-log-visualizer-visualizer-time-display';
25 var LOG_VISUALIZER_RESET_BTN_ID =
26 'cros-log-visualizer-visualizer-reset-btn';
27 var LOG_VISUALIZER_TRACKING_LAYER_ID =
28 'cros-log-visualizer-visualizer-tracking-layer';
32 * This list is used for intialization of canvases. And the canvas
33 * with lowest priority should be created first. Hence the list is
34 * sorted in decreasing order.
36 var LOG_EVENT_LEVEL_PRIORITY_LIST = {
43 // Color mapping of different levels
44 var LOG_EVENT_COLORS_LIST = {
54 function CrosLogVisualizer(logVisualizer, containerID) {
56 * Pass the LogVisualizer in as a reference so the visualizer can
57 * synchrous with the log filter.
59 this.logVisualizer = logVisualizer;
61 // If the data is initialized
62 this.dataIntialized = false;
63 // Stores all the log entries as events
65 // A front layer that handles control events
66 this.trackingLayer = this.createTrackingLayer();
68 // References to HTML elements
69 this.container = document.getElementById(containerID);
70 this.timeline = this.createTimeline();
71 this.timeDisplay = this.createTimeDisplay();
72 this.btnReset = this.createBtnReset();
75 for (var level in LOG_EVENT_LEVEL_PRIORITY_LIST) {
76 this.canvases[level] = this.createCanvas();
77 this.container.appendChild(this.canvases[level]);
80 // Append all the elements to the container
81 this.container.appendChild(this.timeline);
82 this.container.appendChild(this.timeDisplay);
83 this.container.appendChild(this.trackingLayer);
84 this.container.appendChild(this.btnReset);
86 this.container.addEventListener('webkitAnimationEnd', function() {
87 this.container.classList.remove('cros-log-visualizer-flash');
91 CrosLogVisualizer.prototype = {
93 * Called during the initialization of the View. Create a overlay
94 * DIV on top of the canvas that handles the mouse events
96 createTrackingLayer: function() {
97 var trackingLayer = document.createElement('div');
98 trackingLayer.setAttribute('id', LOG_VISUALIZER_TRACKING_LAYER_ID);
99 trackingLayer.addEventListener('mousemove', this.onHovered_.bind(this));
100 trackingLayer.addEventListener('mousedown', this.onMouseDown_.bind(this));
101 trackingLayer.addEventListener('mouseup', this.onMouseUp_.bind(this));
102 return trackingLayer;
106 * This function is called during the initialization of the view.
107 * It creates the timeline that moves along with the mouse on canvas.
108 * When user click, a rectangle can be dragged out to select the range
111 createTimeline: function() {
112 var timeline = document.createElement('div');
113 timeline.setAttribute('id', LOG_VISUALIZER_TIMELINE_ID);
114 timeline.style.height = LOG_VISUALIZER_CANVAS_HEIGHT + 'px';
115 timeline.addEventListener('mousedown', function(event) { return false; });
120 * This function is called during the initialization of the view.
121 * It creates a time display that moves with the timeline
123 createTimeDisplay: function() {
124 var timeDisplay = document.createElement('p');
125 timeDisplay.className = LOG_VISUALIZER_TIME_DISPLAY_CLASS;
126 timeDisplay.style.top = LOG_VISUALIZER_CANVAS_HEIGHT + 'px';
131 * Called during the initialization of the View. Create a button that
132 * resets the canvas to initial status (without zoom)
134 createBtnReset: function() {
135 var btnReset = document.createElement('input');
136 btnReset.setAttribute('type', 'button');
137 btnReset.setAttribute('value', 'Reset');
138 btnReset.setAttribute('id', LOG_VISUALIZER_RESET_BTN_ID);
139 btnReset.addEventListener('click', this.reset.bind(this));
144 * Called during the initialization of the View. Create a empty canvas
145 * that visualizes log when the data is ready
147 createCanvas: function() {
148 var canvas = document.createElement('canvas');
149 canvas.width = LOG_VISUALIZER_CANVAS_WIDTH;
150 canvas.height = LOG_VISUALIZER_CANVAS_HEIGHT;
151 canvas.className = LOG_VISUALIZER_CANVAS_CLASS;
156 * Returns the context of corresponding canvas based on level
158 getContext: function(level) {
159 return this.canvases[level].getContext('2d');
163 * Erases everything from all the canvases
165 clearCanvas: function() {
166 for (var level in LOG_EVENT_LEVEL_PRIORITY_LIST) {
167 var ctx = this.getContext(level);
168 ctx.clearRect(0, 0, LOG_VISUALIZER_CANVAS_WIDTH,
169 LOG_VISUALIZER_CANVAS_HEIGHT);
174 * Initializes the parameters needed for drawing:
175 * - lower/upperBound: Time range (Events out of range will be skipped)
176 * - totalDuration: The length of time range
177 * - unitDuration: The unit time length per pixel
179 initialize: function() {
180 if (this.events.length == 0)
182 this.dragMode = false;
183 this.dataIntialized = true;
184 this.events.sort(this.compareTime);
185 this.lowerBound = this.events[0].time;
186 this.upperBound = this.events[this.events.length - 1].time;
187 this.totalDuration = Math.abs(this.upperBound.getTime() -
188 this.lowerBound.getTime());
189 this.unitDuration = this.totalDuration / LOG_VISUALIZER_CANVAS_WIDTH;
193 * CSS3 fadeIn/fadeOut effects
195 flashEffect: function() {
196 this.container.classList.add('cros-log-visualizer-flash');
200 * Reset the canvas to the initial time range
201 * Redraw everything on the canvas
202 * Fade in/out effects while redrawing
205 // Reset all the parameters as initial
207 // Reset the visibility of the entries in the log table
208 this.logVisualizer.filterLog();
213 * A wrapper function for drawing
215 drawEvents: function() {
216 if (this.events.length == 0)
218 for (var i in this.events) {
219 this.drawEvent(this.events[i]);
224 * The main function that handles drawing on the canvas.
225 * Every event is represented as a vertical line.
227 drawEvent: function(event) {
228 if (!event.visibility) {
229 // Skip hidden events
232 var ctx = this.getContext(event.level);
234 // Get the x-coordinate of the line
235 var startPosition = this.getPosition(event.time);
236 if (startPosition != this.old) {
237 this.old = startPosition;
239 ctx.rect(startPosition, 0, 2, LOG_VISUALIZER_CANVAS_HEIGHT);
240 // Get the color of the line
241 ctx.fillStyle = LOG_EVENT_COLORS_LIST[event.level];
247 * This function is called every time the graph is zoomed.
248 * It recalculates all the parameters based on the distance and direction
251 reCalculate: function() {
252 if (this.dragDistance >= 0) {
253 // if user drags to right
254 this.upperBound = new Date((this.timelineLeft + this.dragDistance) *
255 this.unitDuration + this.lowerBound.getTime());
256 this.lowerBound = new Date(this.timelineLeft * this.unitDuration +
257 this.lowerBound.getTime());
259 // if user drags to left
260 this.upperBound = new Date(this.timelineLeft * this.unitDuration +
261 this.lowerBound.getTime());
262 this.lowerBound = new Date((this.timelineLeft + this.dragDistance) *
263 this.unitDuration + this.lowerBound.getTime());
265 this.totalDuration = this.upperBound.getTime() -
266 this.lowerBound.getTime();
267 this.unitDuration = this.totalDuration / LOG_VISUALIZER_CANVAS_WIDTH;
271 * Check if the time of a event is out of bound
273 isOutOfBound: function(event) {
274 return event.time.getTime() < this.lowerBound.getTime() ||
275 event.time.getTime() > this.upperBound.getTime();
279 * This function returns the offset on x-coordinate of canvas based on
282 getPosition: function(time) {
283 return (time.getTime() - this.lowerBound.getTime()) / this.unitDuration;
287 * This function updates the events array and refresh the canvas.
289 updateEvents: function(newEvents) {
290 this.events.length = 0;
291 for (var i in newEvents) {
292 this.events.push(newEvents[i]);
294 if (!this.dataIntialized) {
302 * This is a helper function that returns the time object based on the
303 * offset of x-coordinate on the canvs.
305 getOffsetTime: function(offset) {
306 return new Date(this.lowerBound.getTime() + offset * this.unitDuration);
310 * This function is triggered when the hovering event is detected
311 * When the mouse is hovering we have two control mode:
312 * - If it is in drag mode, we need to resize the width of the timeline
313 * - If not, we need to move the timeline and time display to the
314 * x-coordinate position of the mouse
316 onHovered_: function(event) {
317 var offsetX = event.offsetX;
318 if (this.lastOffsetX == offsetX) {
319 // If the mouse does not move, we just skip the event
323 if (this.dragMode == true) {
324 // If the mouse is in drag mode
325 this.dragDistance = offsetX - this.timelineLeft;
326 if (this.dragDistance >= 0) {
327 // If the mouse is moving right
328 this.timeline.style.width = this.dragDistance + 'px';
330 // If the mouse is moving left
331 this.timeline.style.width = -this.dragDistance + 'px';
332 this.timeline.style.left = offsetX + 'px';
335 // If the mouse is not in drag mode we just move the timeline
336 this.timeline.style.width = '2px';
337 this.timeline.style.left = offsetX + 'px';
340 // update time display
341 this.timeDisplay.style.left = offsetX + 'px';
342 this.timeDisplay.textContent =
343 this.getOffsetTime(offsetX).toTimeString().substr(0, 8);
344 // update the last offset
345 this.lastOffsetX = offsetX;
349 * This function is the handler for the onMouseDown event on the canvas
351 onMouseDown_: function(event) {
352 // Enter drag mode which let user choose a time range to zoom in
353 this.dragMode = true;
354 this.timelineLeft = event.offsetX;
355 // Create a duration display to indicate the duration of range.
356 this.timeDurationDisplay = this.createTimeDisplay();
357 this.container.appendChild(this.timeDurationDisplay);
361 * This function is the handler for the onMouseUp event on the canvas
363 onMouseUp_: function(event) {
364 // Remove the duration display
365 this.container.removeChild(this.timeDurationDisplay);
367 this.dragMode = false;
368 // Recalculate the pamameter based on the range user select
370 // Filter the log table and hide the entries that are not in the range
371 this.logVisualizer.filterLog();
375 return CrosLogVisualizer;