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
;