| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 3 Use of this source code is governed by a BSD-style license that can be | |
| 4 found in the LICENSE file. | |
| 5 */ | |
| 6 | |
| 7 /* | |
| 8 Fetch all graph data files, prepare the data, and create Plotter() to | |
| 9 generate a graph. | |
| 10 | |
| 11 To use: | |
| 12 var graph = new Graph('output_div', graphList) | |
| 13 graph.setTitle('Title'); | |
| 14 graph.graph(); | |
| 15 */ | |
| 16 | |
| 17 function JsonToJs(data) { | |
| 18 return eval('(' + data + ')'); | |
| 19 } | |
| 20 | |
| 21 /** | |
| 22 * Insert element a after element b. | |
| 23 */ | |
| 24 function AppendChild(a, b) { | |
| 25 var elementA = (typeof(a) == 'object') ? a : document.getElementById(a); | |
| 26 var elementB = (typeof(b) == 'object') ? b : document.getElementById(b); | |
| 27 elementB.appendChild(elementA); | |
| 28 } | |
| 29 | |
| 30 /** | |
| 31 * Insert element a before element b. | |
| 32 */ | |
| 33 function InsertBefore(a, b) { | |
| 34 var elementA = (typeof(a) == 'object') ? a : document.getElementById(a); | |
| 35 var elementB = (typeof(b) == 'object') ? b : document.getElementById(b); | |
| 36 elementB.insertBefore(elementA); | |
| 37 } | |
| 38 | |
| 39 | |
| 40 /** | |
| 41 * Graph class. | |
| 42 * @constructor | |
| 43 * | |
| 44 * Fetch each graph in |graphList| and create Plotter(). Create Graph() | |
| 45 * and call graph() to display graph. | |
| 46 * | |
| 47 * @param div {String|DOMElement} The container that the graph should be | |
| 48 * rendered to. | |
| 49 * @param graphList {Array} List of graphs to be plotted. | |
| 50 * @param options {Object} Options to configure graph. | |
| 51 * - width {int} Width of graph. | |
| 52 * - height {int} Height of graph. | |
| 53 * - history {int} Number of row to show. | |
| 54 * - showDetail {Boolean} Specifies whether or not to display detail. | |
| 55 * Default false. | |
| 56 * - showTabs {Boolean} Specifies whether or not to show tabs. | |
| 57 * Default false. | |
| 58 * - enableMouseScroll {Boolean} Specifies whether or not to enable | |
| 59 * mouse wheel zooming. Default false. | |
| 60 * - channels {Array} Display graph by channels. | |
| 61 * - orderDataByVersion {Boolean} Order plot data by version number. | |
| 62 * Default false. | |
| 63 * | |
| 64 * Example of the graphList: | |
| 65 * [ | |
| 66 * {"units": "ms", "important": false, "name": "SunSpider-individual", | |
| 67 * "SunSpider-individual-summary.dat"}, | |
| 68 * ] | |
| 69 */ | |
| 70 function Graph(div, graphList, opt_options) { | |
| 71 this.graphList_ = graphList; | |
| 72 this.options_ = (opt_options) ? opt_options : {}; | |
| 73 this.history_ = (this.options_.history) ? this.options_.history : 150; | |
| 74 this.rev_ = (this.options_.rev) ? this.options_.rev : -1; | |
| 75 this.channels_ = (this.options_.channels) ? this.options_.channels : []; | |
| 76 this.firstTrace_ = ''; | |
| 77 this.rows_ = []; | |
| 78 this.isDetailViewAdded_ = false; | |
| 79 this.selectedGraph_ = null; | |
| 80 this.plotterDiv_ = null; | |
| 81 this.tabs_ = []; | |
| 82 this.width = this.options_.width; | |
| 83 this.height = this.options_.height; | |
| 84 | |
| 85 this.graphContainer = document.createElement('div'); | |
| 86 this.graphContainer.setAttribute( | |
| 87 'style', 'display: block; overflow: hidden; ' + | |
| 88 'margin: 5px; padding: 5px; width:' + this.width); | |
| 89 AppendChild(this.graphContainer, div); | |
| 90 | |
| 91 this.title = document.createElement('div'); | |
| 92 this.title.setAttribute('style', 'text-align: center'); | |
| 93 AppendChild(this.title, this.graphContainer); | |
| 94 } | |
| 95 | |
| 96 /** | |
| 97 * Start fetching graph data. | |
| 98 */ | |
| 99 Graph.prototype.graph = function() { | |
| 100 this.fetchSummary_(); | |
| 101 | |
| 102 if (this.options_.showTabs) | |
| 103 this.addTabs_(); | |
| 104 } | |
| 105 | |
| 106 /** | |
| 107 * Set graph title. | |
| 108 */ | |
| 109 Graph.prototype.setTitle = function(title) { | |
| 110 this.title.innerHTML = title; | |
| 111 } | |
| 112 | |
| 113 /** | |
| 114 * Display tabs for each graph. | |
| 115 */ | |
| 116 Graph.prototype.addTabs_ = function() { | |
| 117 this.tabs_ = []; | |
| 118 var tabPane = document.createElement('div'); | |
| 119 tabPane.setAttribute('class', 'switcher'); | |
| 120 AppendChild(tabPane, this.graphContainer); | |
| 121 | |
| 122 var graphNames = [] | |
| 123 var inserted = {}; | |
| 124 for (var i = 0; i < this.graphList_.length; i++) { | |
| 125 if (!inserted[this.graphList_[i].name]) { | |
| 126 graphNames.push(this.graphList_[i].name); | |
| 127 inserted[this.graphList_[i].name] = 1; | |
| 128 } | |
| 129 } | |
| 130 | |
| 131 var obj = this; | |
| 132 for (var i = 0; i < graphNames.length; i++) { | |
| 133 var name = graphNames[i]; | |
| 134 var tab = document.createElement('span'); | |
| 135 if (name != this.selectedGraph_.name) { | |
| 136 tab.setAttribute('class', 'select'); | |
| 137 } | |
| 138 tab.addEventListener( | |
| 139 "click", | |
| 140 (function(){ | |
| 141 var cur = name; return function() {obj.switchGraph_(cur)} | |
| 142 })(), | |
| 143 false); | |
| 144 tab.appendChild(document.createTextNode(name + " ")); | |
| 145 AppendChild(tab, tabPane); | |
| 146 this.tabs_.push(tab); | |
| 147 } | |
| 148 } | |
| 149 | |
| 150 /** | |
| 151 * Fetch graph summary data files. | |
| 152 */ | |
| 153 Graph.prototype.fetchSummary_ = function() { | |
| 154 this.rows_ = []; | |
| 155 if (!this.selectedGraph_) { | |
| 156 this.selectedGraph_ = this.graphList_[0]; | |
| 157 } | |
| 158 var graphFiles = []; | |
| 159 this.selectedGraphList_ = []; | |
| 160 for (var i = 0; i < this.graphList_.length; i++) { | |
| 161 if (this.graphList_[i].name == this.selectedGraph_.name) { | |
| 162 graphFiles.push(this.graphList_[i].loc); | |
| 163 this.selectedGraphList_.push(this.graphList_[i]); | |
| 164 } | |
| 165 } | |
| 166 var obj = this; | |
| 167 new FetchList(graphFiles, function(data) {obj.onSummaryReceived_(data)}); | |
| 168 } | |
| 169 | |
| 170 /** | |
| 171 * Call addPlot_ once all graph summary data are received. | |
| 172 */ | |
| 173 Graph.prototype.onSummaryReceived_ = function(data) { | |
| 174 // Parse the summary data file. | |
| 175 for (var i = 0; i < data.length; i++) { | |
| 176 if (data[i]) { | |
| 177 var rows = new Rows(data[i]); | |
| 178 this.rows_[i] = rows; | |
| 179 } | |
| 180 } | |
| 181 this.addPlot_(); | |
| 182 } | |
| 183 | |
| 184 /** | |
| 185 * Merge all data rows by channel and version. This is use in platform | |
| 186 * comparison graph. | |
| 187 * | |
| 188 * Example: | |
| 189 * Two rows: | |
| 190 * {"traces": {"score": ["777", "0.0"]}, "rev": "9", | |
| 191 * "ver": "17.1.963.19", "chan": "stable"} | |
| 192 * {"traces": {"score": ["888", "0.0"]}, "rev": "10", | |
| 193 * "ver": "17.1.963.19", "chan": "stable"} | |
| 194 * Become: | |
| 195 * {"traces": {"score_windows": ["777", "0.0"], | |
| 196 * "score_linux": ["888", "0.0"]}, | |
| 197 * "rev": "9", "ver": "17.1.963.19", "chan": "stable"} | |
| 198 * | |
| 199 * @return {Array} Array of rows. | |
| 200 */ | |
| 201 Graph.prototype.getMergedRowsByVersion_ = function() { | |
| 202 var channels = {}; | |
| 203 for (var i = 0; i < this.channels_.length; i++) | |
| 204 channels[this.channels_[i]] = 1; | |
| 205 var allRows = []; | |
| 206 // Combind all rows to one list. | |
| 207 for (var i = 0; i < this.rows_.length; i++) { | |
| 208 if (this.rows_[i]) { | |
| 209 for (var j = 0; j < this.rows_[i].length; j++) { | |
| 210 var row = this.rows_[i].get(j); | |
| 211 if (row && row.chan in channels) { | |
| 212 row.machine = this.selectedGraphList_[i].machine; | |
| 213 allRows.push(row); | |
| 214 } | |
| 215 } | |
| 216 } | |
| 217 } | |
| 218 | |
| 219 // Sort by version number. | |
| 220 allRows.sort( | |
| 221 function(a, b) { | |
| 222 var a_arr = a.version.split('.'); | |
| 223 var b_arr = b.version.split('.'); | |
| 224 var len = Math.min(b_arr.length, b_arr.length); | |
| 225 for (var i = 0; i < len; i++) { | |
| 226 if (parseInt(a_arr[i], 10) > parseInt(b_arr[i], 10)) | |
| 227 return 1; | |
| 228 else if (parseInt(a_arr[i], 10) < parseInt(b_arr[i], 10)) | |
| 229 return -1; | |
| 230 } | |
| 231 return a_arr.length - b_arr.length; | |
| 232 }); | |
| 233 | |
| 234 // Merge all rows by version number. | |
| 235 var combindedRows = []; | |
| 236 var index = 0; | |
| 237 while (index < allRows.length) { | |
| 238 var currentRow = allRows[index]; | |
| 239 var traces = currentRow['traces']; | |
| 240 for (var traceName in traces) { | |
| 241 var traceRenamed = traceName + '_' + currentRow.machine.toLowerCase(); | |
| 242 traces[traceRenamed] = traces[traceName]; | |
| 243 delete(traces[traceName]); | |
| 244 } | |
| 245 while (index < allRows.length - 1 && | |
| 246 allRows[index + 1].version == currentRow.version) { | |
| 247 var row = allRows[index + 1]; | |
| 248 var traces = row['traces']; | |
| 249 for (var traceName in traces) { | |
| 250 var traceRenamed = traceName + '_' + row.machine.toLowerCase(); | |
| 251 currentRow['traces'][traceRenamed] = traces[traceName]; | |
| 252 } | |
| 253 index++; | |
| 254 } | |
| 255 combindedRows.push(currentRow); | |
| 256 index++; | |
| 257 } | |
| 258 return combindedRows; | |
| 259 } | |
| 260 | |
| 261 /** | |
| 262 * Merge all channel data by their index in file. This is use in channel | |
| 263 * comparison graph. | |
| 264 * | |
| 265 * @return {Array} Array of rows. | |
| 266 */ | |
| 267 Graph.prototype.getMergedRowByIndex_ = function() { | |
| 268 var rowByChannel = {}; | |
| 269 for (var i = 0; i < this.channels_.length; i++) | |
| 270 rowByChannel[this.channels_[i]] = []; | |
| 271 | |
| 272 // Order by channel. | |
| 273 for (var i = 0; i < this.rows_.length; i++) { | |
| 274 if (this.rows_[i]) { | |
| 275 for (var j = 0; j < this.rows_[i].length; j++) { | |
| 276 var row = this.rows_[i].get(j); | |
| 277 if (row && row.chan in rowByChannel) { | |
| 278 rowByChannel[row.chan].push(row); | |
| 279 } | |
| 280 } | |
| 281 } | |
| 282 } | |
| 283 | |
| 284 var max = 0; | |
| 285 for (var channel in rowByChannel) | |
| 286 max = Math.max(rowByChannel[channel].length, max); | |
| 287 | |
| 288 // Merge data. | |
| 289 var combindedRows = []; | |
| 290 for (var i = 0; i < max; i++) { | |
| 291 var currentRow = null; | |
| 292 for (var channel in rowByChannel) { | |
| 293 if (rowByChannel[channel].length > i) { | |
| 294 var row = rowByChannel[channel][i]; | |
| 295 var traces = row['traces']; | |
| 296 for (var traceName in traces) { | |
| 297 traces[traceName + '_' + channel] = traces[traceName]; | |
| 298 delete(traces[traceName]); | |
| 299 } | |
| 300 if (!currentRow) { | |
| 301 currentRow = row; | |
| 302 } else { | |
| 303 for (var traceName in traces) | |
| 304 currentRow['traces'][traceName] = traces[traceName]; | |
| 305 currentRow.version += ', ' + row.version; | |
| 306 } | |
| 307 } | |
| 308 } | |
| 309 combindedRows.push(currentRow); | |
| 310 } | |
| 311 return combindedRows; | |
| 312 } | |
| 313 | |
| 314 /** | |
| 315 * Get rows for a specific channel. | |
| 316 * | |
| 317 * @return {Array} Array of rows. | |
| 318 */ | |
| 319 Graph.prototype.getRowByChannel_ = function() { | |
| 320 // Combind channel data. | |
| 321 var rows = []; | |
| 322 for (var i = 0; i < this.rows_.length; i++) { | |
| 323 if (this.rows_[i]) { | |
| 324 for (var j = 0; j < this.rows_[i].length; j++) { | |
| 325 var row = this.rows_[i].get(j); | |
| 326 if (row && row.chan == this.channels_[0]) | |
| 327 rows.push(row); | |
| 328 } | |
| 329 } | |
| 330 } | |
| 331 return rows; | |
| 332 } | |
| 333 | |
| 334 /** | |
| 335 * Prepare the data and create Plotter() to generate a graph. | |
| 336 */ | |
| 337 Graph.prototype.addPlot_ = function() { | |
| 338 var rows = []; | |
| 339 if (this.options_.orderDataByVersion) | |
| 340 rows = this.getMergedRowsByVersion_(); | |
| 341 else if (this.channels_.length > 1) | |
| 342 rows = this.getMergedRowByIndex_(); | |
| 343 else | |
| 344 rows = this.getRowByChannel_(); | |
| 345 | |
| 346 var maxRows = rows.length; | |
| 347 if (maxRows > this.history_) | |
| 348 maxRows = this.history_; | |
| 349 | |
| 350 // Find the start and end of the data slice we will focus on. | |
| 351 var startRow = 0; | |
| 352 if (this.rev_ > 0) { | |
| 353 var i = 0; | |
| 354 while (i < rows.length) { | |
| 355 var row = rows[i]; | |
| 356 // If the current row's revision is higher than the desired revision, | |
| 357 // continue searching. | |
| 358 if (row.revision > this.rev_) { | |
| 359 i++; | |
| 360 continue; | |
| 361 } | |
| 362 // We're either just under or at the desired revision. | |
| 363 startRow = i; | |
| 364 // If the desired revision does not exist, use the row before it. | |
| 365 if (row.revision < this.rev_ && startRow > 0) | |
| 366 startRow -= 1; | |
| 367 break; | |
| 368 } | |
| 369 } | |
| 370 | |
| 371 // Some summary files contain data not listed in rev-descending order. For | |
| 372 // those cases, it is possible we will find a start row in the middle of the | |
| 373 // data whose neighboring data is not nearby. See xp-release-dual-core | |
| 374 // moz rev 265 => no graph. | |
| 375 var endRow = startRow + maxRows; | |
| 376 | |
| 377 // Build and order a list of revision numbers. | |
| 378 var allTraces = {}; | |
| 379 var revisionNumbers = []; | |
| 380 var versionMap = {}; | |
| 381 var hasNumericRevisions = true; | |
| 382 // graphData[rev] = {trace1:[value, stddev], trace2:[value, stddev], ...} | |
| 383 var graphData = {}; | |
| 384 for (var i = startRow; i < endRow; ++i) { | |
| 385 var row = rows[i]; | |
| 386 if (!row) | |
| 387 continue; | |
| 388 var traces = row['traces']; | |
| 389 for (var j = 0; j < traces.length; ++j) | |
| 390 traces[j] = parseFloat(traces[j]); | |
| 391 | |
| 392 graphData[row.revision] = traces; | |
| 393 if (isNaN(row.revision)) { | |
| 394 hasNumericRevisions = false; | |
| 395 } | |
| 396 revisionNumbers.push(row.revision); | |
| 397 | |
| 398 versionMap[row.revision] = row.version; | |
| 399 | |
| 400 // Collect unique trace names. If traces are explicitly specified in | |
| 401 // params, delete unspecified trace data. | |
| 402 for (var traceName in traces) { | |
| 403 if (typeof(params['trace']) != 'undefined' && | |
| 404 params['trace'][traceName] != 1) { | |
| 405 delete(traces[traceName]); | |
| 406 } | |
| 407 allTraces[traceName] = 1; | |
| 408 } | |
| 409 } | |
| 410 | |
| 411 // Build a list of all the trace names we've seen, in the order in which | |
| 412 // they appear in the data file. Although JS objects are not required by | |
| 413 // the spec to iterate their properties in order, in practice they do, | |
| 414 // because it causes compatibility problems otherwise. | |
| 415 var traceNames = []; | |
| 416 for (var traceName in allTraces) | |
| 417 traceNames.push(traceName); | |
| 418 this.firstTrace_ = traceNames[0]; | |
| 419 | |
| 420 // If the revisions are numeric (svn), sort them numerically to ensure they | |
| 421 // are in ascending order. Otherwise, if the revisions aren't numeric (git), | |
| 422 // reverse them under the assumption the rows were prepended to the file. | |
| 423 if (hasNumericRevisions) { | |
| 424 revisionNumbers.sort( | |
| 425 function(a, b) { return parseInt(a, 10) - parseInt(b, 10) }); | |
| 426 } else { | |
| 427 revisionNumbers.reverse(); | |
| 428 } | |
| 429 | |
| 430 // Build separate ordered lists of trace data. | |
| 431 var traceData = {}; | |
| 432 var versionList = []; | |
| 433 for (var revIndex = 0; revIndex < revisionNumbers.length; ++revIndex) { | |
| 434 var rev = revisionNumbers[revIndex]; | |
| 435 var revisionData = graphData[rev]; | |
| 436 for (var nameIndex = 0; nameIndex < traceNames.length; ++nameIndex) { | |
| 437 var traceName = traceNames[nameIndex]; | |
| 438 if (!traceData[traceName]) | |
| 439 traceData[traceName] = []; | |
| 440 if (!revisionData[traceName]) | |
| 441 traceData[traceName].push([NaN, NaN]); | |
| 442 else | |
| 443 traceData[traceName].push([parseFloat(revisionData[traceName][0]), | |
| 444 parseFloat(revisionData[traceName][1])]); | |
| 445 } | |
| 446 versionList.push(versionMap[rev]); | |
| 447 } | |
| 448 | |
| 449 var plotData = []; | |
| 450 for (var traceName in traceData) | |
| 451 plotData.push(traceData[traceName]); | |
| 452 | |
| 453 var plotterDiv = document.createElement('div'); | |
| 454 if (!this.plotterDiv_) | |
| 455 AppendChild(plotterDiv, this.graphContainer) | |
| 456 else | |
| 457 this.graphContainer.replaceChild(plotterDiv, this.plotterDiv_); | |
| 458 this.plotterDiv_ = plotterDiv; | |
| 459 | |
| 460 var plotter = new Plotter( | |
| 461 versionList, plotData, traceNames, this.selectedGraph_.units, | |
| 462 this.plotterDiv_, this.width, this.height); | |
| 463 | |
| 464 var obj = this; | |
| 465 plotter.onclick = function(){obj.onPlotClicked.apply(obj, arguments)}; | |
| 466 plotter.enableMouseScroll = this.options_.enableMouseScroll; | |
| 467 plotter.plot(); | |
| 468 } | |
| 469 | |
| 470 /** | |
| 471 * Handle switching graph when tab is clicked. | |
| 472 */ | |
| 473 Graph.prototype.switchGraph_ = function(graphName) { | |
| 474 if (graphName == this.selectedGraph_.name) | |
| 475 return; | |
| 476 | |
| 477 for (var i = 0; i < this.tabs_.length; i++) { | |
| 478 var name = this.tabs_[i].innerHTML; | |
| 479 if (graphName + ' ' == name) { | |
| 480 this.tabs_[i].removeAttribute('class'); | |
| 481 } else { | |
| 482 this.tabs_[i].setAttribute('class', 'select'); | |
| 483 } | |
| 484 } | |
| 485 | |
| 486 for (var i = 0; i < this.graphList_.length; i++) { | |
| 487 if (this.graphList_[i].name == graphName) { | |
| 488 this.selectedGraph_ = this.graphList_[i]; | |
| 489 break; | |
| 490 } | |
| 491 } | |
| 492 | |
| 493 this.fetchSummary_(); | |
| 494 } | |
| 495 | |
| 496 /** | |
| 497 * On plot clicked, display detail view. | |
| 498 */ | |
| 499 Graph.prototype.onPlotClicked = function(prev_cl, cl) { | |
| 500 if (!this.options_.showDetail) | |
| 501 return; | |
| 502 this.addDetailView_(); | |
| 503 | |
| 504 var getChildByName = function(div, name) { | |
| 505 var children = div.childNodes; | |
| 506 for (var i = 0; i < children.length; i++) | |
| 507 if (children[i].getAttribute('name') == name) | |
| 508 return children[i]; | |
| 509 } | |
| 510 // Define sources for detail tabs | |
| 511 if ('view-change' in Config.detailTabs) { | |
| 512 // If the changeLinkPrefix has {PREV_CL}/{CL} markers, replace them. | |
| 513 // Otherwise, append to the URL. | |
| 514 var url = Config.changeLinkPrefix; | |
| 515 if (url.indexOf('{PREV_CL}') >= 0 || url.indexOf('{CL}') >= 0) { | |
| 516 url = url.replace('{PREV_CL}', prev_cl); | |
| 517 url = url.replace('{CL}', cl); | |
| 518 } else { | |
| 519 url += prev_cl + ':' + cl; | |
| 520 } | |
| 521 getChildByName(this.detailPane, 'view-change').setAttribute('src', url); | |
| 522 } | |
| 523 | |
| 524 if ('view-pages' in Config.detailTabs) { | |
| 525 getChildByName(this.detailPane, 'view-pages'). | |
| 526 setAttribute('src', 'details.html?cl=' + cl + | |
| 527 '&graph=' + this.milestone + '-' + this.selectedGraph_.name + | |
| 528 '&trace=' + this.firstTrace_); | |
| 529 } | |
| 530 if ('view-coverage' in Config.detailTabs) { | |
| 531 getChildByName(this.detailPane, 'view-coverage').setAttribute( | |
| 532 'src',Config.coverageLinkPrefix + cl); | |
| 533 } | |
| 534 | |
| 535 if (!this.didPositionDetail) { | |
| 536 this.positionDetails_(); | |
| 537 this.didPositionDetail = true; | |
| 538 } | |
| 539 } | |
| 540 | |
| 541 /** | |
| 542 * Display detail view. | |
| 543 */ | |
| 544 Graph.prototype.addDetailView_ = function() { | |
| 545 if (this.isDetailViewAdded_) | |
| 546 return; | |
| 547 this.isDetailViewAdded_ = true; | |
| 548 // Add detail page. | |
| 549 this.detailPane = document.createElement('div'); | |
| 550 AppendChild(this.detailPane, this.graphContainer); | |
| 551 | |
| 552 for (var tab in Config.detailTabs) { | |
| 553 var detail = document.createElement('iframe'); | |
| 554 detail.setAttribute('class', 'detail'); | |
| 555 detail.setAttribute('name', tab); | |
| 556 AppendChild(detail, this.detailPane); | |
| 557 } | |
| 558 | |
| 559 this.selectorPane = document.createElement('div'); | |
| 560 this.selectorPane.setAttribute('class', 'selectors'); | |
| 561 this.selectorPane.setAttribute( | |
| 562 'style', 'display: block; 1px solid black; position: absolute;'); | |
| 563 AppendChild(this.selectorPane, this.graphContainer); | |
| 564 | |
| 565 var firstTab = true; | |
| 566 for (var tab in Config.detailTabs) { | |
| 567 var selector = document.createElement('div'); | |
| 568 selector.setAttribute('class', 'selector'); | |
| 569 var obj = this; | |
| 570 selector.onclick = ( | |
| 571 function(){ | |
| 572 var cur = tab; return function() {obj.changeDetailTab(cur)}})(); | |
| 573 if (firstTab) | |
| 574 firstTab = false; | |
| 575 else | |
| 576 selector.setAttribute('style', 'border-top: none'); | |
| 577 selector.innerHTML = Config.detailTabs[tab]; | |
| 578 AppendChild(selector, this.selectorPane); | |
| 579 } | |
| 580 } | |
| 581 | |
| 582 Graph.prototype.positionDetails_ = function() { | |
| 583 var win_height = window.innerHeight; | |
| 584 | |
| 585 var views_width = this.graphContainer.offsetWidth - | |
| 586 this.selectorPane.offsetWidth; | |
| 587 | |
| 588 this.detailPane.style.width = views_width + "px"; | |
| 589 this.detailPane.style.height = ( | |
| 590 win_height - this.graphContainer.offsetHeight - | |
| 591 this.graphContainer.offsetTop - 30) + "px"; | |
| 592 | |
| 593 this.selectorPane.style.left = ( | |
| 594 this.detailPane.offsetLeft + views_width + 1) + "px"; | |
| 595 this.selectorPane.style.top = this.detailPane.offsetTop + "px"; | |
| 596 | |
| 597 // Change to the first detail tab | |
| 598 for (var tab in Config.detailTabs) { | |
| 599 this.changeDetailTab_(tab); | |
| 600 break; | |
| 601 } | |
| 602 } | |
| 603 | |
| 604 Graph.prototype.changeDetailTab_ = function(target) { | |
| 605 var detailArr = this.detailPane.getElementsByTagName('iframe'); | |
| 606 var i = 0; | |
| 607 for (var tab in Config.detailTabs) { | |
| 608 detailArr[i++].style.display = (tab == target ? 'block' : 'none'); | |
| 609 } | |
| 610 } | |
| 611 | |
| 612 Graph.prototype.goTo = function(graph) { | |
| 613 params.graph = graph; | |
| 614 if (params.graph == '') | |
| 615 delete params.graph; | |
| 616 window.location.href = MakeURL(params); | |
| 617 } | |
| 618 | |
| 619 Graph.prototype.getURL = function() { | |
| 620 new_url = window.location.href; | |
| 621 new_url = new_url.replace(/50/, "150"); | |
| 622 new_url = new_url.replace(/\&lookout=1/, ""); | |
| 623 return new_url; | |
| 624 } | |
| 625 | |
| 626 | |
| 627 /** | |
| 628 * Encapsulates a *-summary.dat file. | |
| 629 * @constructor | |
| 630 */ | |
| 631 function Rows(data) { | |
| 632 this.rows_ = (data) ? data.split('\n') : []; | |
| 633 this.length = this.rows_.length; | |
| 634 } | |
| 635 | |
| 636 /** | |
| 637 * Returns the row at the given index. | |
| 638 */ | |
| 639 Rows.prototype.get = function(i) { | |
| 640 if (!this.rows_[i].length) return null; | |
| 641 var row = JsonToJs(this.rows_[i]); | |
| 642 row.revision = isNaN(row['rev']) ? row['rev'] : parseInt(row['rev']); | |
| 643 row.version = row['ver']; | |
| 644 return row; | |
| 645 }; | |
| OLD | NEW |