| OLD | NEW |
| (Empty) |
| 1 {# Copyright 2014 The LUCI Authors. All rights reserved. | |
| 2 Use of this source code is governed under the Apache License, Version 2.0 | |
| 3 that can be found in the LICENSE file. | |
| 4 #} | |
| 5 // Formats a number into IEC 60027-2 A.2 / ISO 80000. | |
| 6 var BINARY_SUFFIXES = [ | |
| 7 'B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB' | |
| 8 ]; | |
| 9 | |
| 10 function formatToBinaryUnit(val) { | |
| 11 // Prefer to display 1023 as 0.99KiB. | |
| 12 for (var n = 0; val >= 1000; n++) { | |
| 13 val /= 1024; | |
| 14 } | |
| 15 // Enforce 2 decimals. | |
| 16 if (n > 0) { | |
| 17 val = val.toFixed(2); | |
| 18 } | |
| 19 return val + BINARY_SUFFIXES[n]; | |
| 20 } | |
| 21 | |
| 22 // Formats data in a chart into 1024 based units. | |
| 23 function formatDataColumnToBinaryUnit(data, column) { | |
| 24 for (var i = 0; i < data.getNumberOfRows(); i++) { | |
| 25 data.setFormattedValue( | |
| 26 i, column, formatToBinaryUnit(data.getValue(i, column))); | |
| 27 } | |
| 28 } | |
| 29 | |
| 30 var ISO_SUFFIXES = ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z']; | |
| 31 | |
| 32 function formatToIsoUnit(val) { | |
| 33 for (var n = 0; val >= 1000; n++) { | |
| 34 val /= 1000; | |
| 35 } | |
| 36 // Enforce 2 decimals. | |
| 37 if (n > 0) { | |
| 38 val = val.toFixed(2); | |
| 39 } | |
| 40 return val + ISO_SUFFIXES[n]; | |
| 41 } | |
| 42 | |
| 43 // Formats data in a chart into 1000 based units. | |
| 44 function formatDataColumnToIsoUnit(data, column) { | |
| 45 for (var i = 0; i < data.getNumberOfRows(); i++) { | |
| 46 data.setFormattedValue( | |
| 47 i, column, formatToIsoUnit(data.getValue(i, column))); | |
| 48 } | |
| 49 } | |
| 50 | |
| 51 // Makes sure custom formatting is removed for a specific column. | |
| 52 function resetFormattedDataColumn(data, column) { | |
| 53 for (var i = 0; i < data.getNumberOfRows(); i++) { | |
| 54 data.setFormattedValue(i, column, null); | |
| 55 } | |
| 56 } | |
| 57 | |
| 58 // Makes sure ALL custom formatting is removed. | |
| 59 function resetFormattedData(data) { | |
| 60 for (var i = 0; i < data.getNumberOfColumns(); i++) { | |
| 61 resetFormattedDataColumn(data, i); | |
| 62 } | |
| 63 } | |
| 64 | |
| 65 // Removes any ticks. It is necessary when the data changes. | |
| 66 function clearCustomTicks(chart) { | |
| 67 // TODO(maruel): Make this automatic when chart.setDataTable() is used. | |
| 68 var options = chart.getOptions(); | |
| 69 if (typeof options.vAxis != "undefined") { | |
| 70 delete options.vAxis.ticks; | |
| 71 } | |
| 72 if (typeof options.vAxes != "undefined") { | |
| 73 for (var i in options.vAxes) { | |
| 74 delete options.vAxes[i].ticks; | |
| 75 } | |
| 76 } | |
| 77 } | |
| 78 | |
| 79 // Resets the axis 0 of the chart to binary prefix (1024). | |
| 80 function setAxisTicksToUnitsOnNextDraw(chart, as_binary, axe_index) { | |
| 81 // TODO(maruel): This code is racy and sometimes does not trigger correctly. | |
| 82 // It can be reproduced with: | |
| 83 // 1. Reload | |
| 84 // 2. click Day | |
| 85 // 3. click Hour | |
| 86 // 4. Repeat 2 and 3 until reproduced. | |
| 87 function callback() { | |
| 88 google.visualization.events.removeListener(runOnce); | |
| 89 var ticks = []; | |
| 90 // Warning: ChartWrapper really wraps the actual Chart, and the proxy fails | |
| 91 // to expose some methods, like .getChartLayoutInterface(). In this case, | |
| 92 // the user must call .getChart() to retrieve the underlying object. | |
| 93 var cli; | |
| 94 if (typeof chart.getChartLayoutInterface == "undefined") { | |
| 95 cli = chart.getChart().getChartLayoutInterface(); | |
| 96 } else { | |
| 97 cli = chart.getChartLayoutInterface(); | |
| 98 } | |
| 99 var power = 1000; | |
| 100 var suffixes = ISO_SUFFIXES; | |
| 101 if (as_binary) { | |
| 102 power = 1024; | |
| 103 suffixes = BINARY_SUFFIXES; | |
| 104 } | |
| 105 var bb; | |
| 106 for (var i = 0; bb = cli.getBoundingBox('vAxis#0#gridline#' + i); i++) { | |
| 107 var val = cli.getVAxisValue(bb.top); | |
| 108 // The axis value may fall 1/2 way though the pixel height of the | |
| 109 // gridline, so add in 1/2 the height. This assumes that all axis values | |
| 110 // will be integers. | |
| 111 if (val != parseInt(val)) { | |
| 112 val = cli.getVAxisValue(bb.top + bb.height / 2, axe_index); | |
| 113 } | |
| 114 // Converts the auto-selected base-10 values to 2^10 'rounded' values if | |
| 115 // necessary. | |
| 116 for (var n = 0; val >= 1000; n++) { | |
| 117 val /= 1000; | |
| 118 } | |
| 119 // Keep 2 decimals. Note that this code assumes the items are all | |
| 120 // integers. Fix accordingly if needed. | |
| 121 var formattedVal = val; | |
| 122 // TODO(maruel): Detect "almost equal". | |
| 123 if (n > 0 || formattedVal != formattedVal.toFixed(0)) { | |
| 124 formattedVal = formattedVal.toFixed(2); | |
| 125 } | |
| 126 val *= Math.pow(power, n); | |
| 127 ticks.push({v: val, f: formattedVal + suffixes[n]}); | |
| 128 } | |
| 129 if (typeof axe_index == "undefined") { | |
| 130 // It's possible the object vAxis is not defined yet. | |
| 131 chart.getOptions().vAxis = chart.getOptions().vAxis || {}; | |
| 132 chart.getOptions().vAxis.ticks = ticks; | |
| 133 } else { | |
| 134 // If axe_indexes is specified, vAxes must be defined. | |
| 135 chart.getOptions().vAxes = chart.getOptions().vAxes || []; | |
| 136 chart.getOptions().vAxes[axe_index] = chart.getOptions( | |
| 137 ).vAxes[axe_index] || {}; | |
| 138 chart.getOptions().vAxes[axe_index].ticks = ticks; | |
| 139 } | |
| 140 // Draw a second time. | |
| 141 // TODO(maruel): Sadly, this second draw is user visible. | |
| 142 chart.draw(); | |
| 143 } | |
| 144 | |
| 145 // TODO(maruel): This codes cause a visible redraw, it'd be nice to figure out | |
| 146 // a way to not cause it. | |
| 147 var runOnce = google.visualization.events.addListener( | |
| 148 chart, 'ready', callback); | |
| 149 } | |
| 150 | |
| 151 // Sends a single query to feed data to two charts. | |
| 152 function sendQuery(url, redrawCharts) { | |
| 153 if (current_query != null) { | |
| 154 current_query.abort(); | |
| 155 } | |
| 156 current_query = new google.visualization.Query(url); | |
| 157 current_query.send( | |
| 158 function(response) { | |
| 159 if (response.isError()) { | |
| 160 alert('Error in query: ' + response.getMessage() + ' ' + | |
| 161 response.getDetailedMessage()); | |
| 162 return; | |
| 163 } | |
| 164 | |
| 165 redrawCharts(response.getDataTable()); | |
| 166 }); | |
| 167 } | |
| 168 | |
| 169 | |
| 170 // The following functions assume a certain HTML layout, current_resolution is | |
| 171 // defined and google.visualization was loaded. | |
| 172 // TODO(maruel): Better refactor this so this code is also shared with isolate | |
| 173 // server front end. | |
| 174 | |
| 175 function get_key_formatter(resolution) { | |
| 176 if (resolution) { | |
| 177 if (resolution == 'days') { | |
| 178 return new google.visualization.DateFormat({pattern: 'yyyy/MM/dd'}); | |
| 179 } else { | |
| 180 return new google.visualization.DateFormat({pattern: 'MM/dd HH:mm'}); | |
| 181 } | |
| 182 } | |
| 183 if (current_resolution == 'days') { | |
| 184 return new google.visualization.DateFormat({pattern: 'yyyy/MM/dd'}); | |
| 185 } else { | |
| 186 return new google.visualization.DateFormat({pattern: 'MM/dd HH:mm'}); | |
| 187 } | |
| 188 } | |
| 189 | |
| 190 function set_resolution_internal(res) { | |
| 191 document.getElementById('resolution_days').checked = false; | |
| 192 document.getElementById('resolution_hours').checked = false; | |
| 193 document.getElementById('resolution_minutes').checked = false; | |
| 194 document.getElementById('resolution_' + res).checked = true; | |
| 195 current_resolution = res; | |
| 196 } | |
| OLD | NEW |