| google.charts.load('current', {packages: ['corechart', 'controls', 'table']}); | |
| google.charts.setOnLoadCallback(drawAllChart); | |
| /** | |
| * Get data from /data/*.json and draw charts from it. | |
| */ | |
| function drawAllChart() { | |
| $.get('file_list?v=' + Date.now(), function(data) { | |
| /** @type {!Array<Dashboard>} */ | |
| const dashboard = []; | |
| /** @type {!Array<ControlWrapper>} */ | |
| const control = []; | |
| /** @type {!Array<LineChart>} */ | |
| const chart = []; | |
| const options = []; | |
| /** @type {!Array<DataTable>} */ | |
| const tableData = []; | |
| /** @type {!Array<Column>} */ | |
| const columns = []; | |
| /** @type {!boolean} */ | |
| let chartInit = false; | |
| /** @type {!number} */ | |
| let targetNum = 0; | |
| /** @type {!Array<string>} */ | |
| const filenames = data.trim().split('\n'); | |
| // Make sure the length of the deffered object array is always > 1 | |
| $.when.apply($, [0].concat(filenames.map(function(filename) { | |
| return $.getJSON("data/" + filename); | |
| }))).then(function(){ | |
| /** @type {!Array<Object>} */ | |
| let responses = [].slice.call(arguments, 1); | |
| for (let response of responses) { | |
| let data = response[0]; | |
| targetNum = data.buildTargetResults.length; | |
| if (!chartInit) { | |
| initChartData(data.buildTargetResults, dashboard, control, chart, tableData, options); | |
| chartInit = true; | |
| } | |
| // Add rows for chart (including data) | |
| for (let i = 0; i < targetNum; ++i) { | |
| addRowsFromData(tableData[i], data.buildTargetResults[i].buildEnvResults); | |
| } | |
| } | |
| afterChartData(targetNum, dashboard, control, chart, columns, tableData, options); | |
| }); | |
| }); | |
| } | |
| /** | |
| * Initialize all the chart data (columns, options, divs and chart objects) | |
| * @param {!Array<Object>} buildTargetResults results for all build targets | |
| * @param {!Array<Dashboard>} dashboard all dashboards | |
| * @param {!Array<Control>} control all controls | |
| * @param {!Array<LineChart>} chart all charts | |
| * @param {!Array<DataTable>} tableData data for all charts | |
| * @param {!Array<Object>} options options for all charts | |
| */ | |
| function initChartData (buildTargetResults, dashboard, control, chart, tableData, options) { | |
| for (let i = 0; i < buildTargetResults.length; ++i) { | |
| const buildEnvResults = buildTargetResults[i].buildEnvResults; | |
| // add divs to #content | |
| $('<div id="target' + i + '" style="width: 100%; height: 600px"></div>') | |
| .appendTo('#content'); | |
| $('<div id="control' + i + '" style="width: 100%; height: 100px"></div>') | |
| .appendTo('#content'); | |
| // Dashboard | |
| dashboard[i] = new google.visualization.Dashboard( | |
| document.getElementById('target' + i)); | |
| // Control | |
| control[i] = new google.visualization.ControlWrapper({ | |
| 'controlType': 'ChartRangeFilter', | |
| 'containerId': 'control' + i, | |
| 'options': { | |
| // Filter by the date axis. | |
| 'filterColumnIndex': 1, | |
| 'ui': { | |
| 'chartType': 'LineChart', | |
| 'chartOptions': { | |
| 'chartArea': {'width': '70%'}, | |
| 'hAxis': {'baselineColor': 'none'} | |
| }, | |
| 'chartView': { | |
| 'columns': [0, 2, 6] | |
| } | |
| } | |
| } | |
| }); | |
| // Options for each chart (including title) | |
| options[i] = { | |
| title: buildTargetResults[i].buildTargetConfig.description, | |
| vAxis: { title: 'Elapsed time (s)' }, | |
| hAxis: { title: 'Changes with pushed time' }, | |
| tooltip: { isHtml: true, trigger: 'both' }, | |
| intervals: { style: 'bars' }, | |
| chartArea: { width: '70%' } | |
| }; | |
| // Create data table & add columns(line options) | |
| tableData[i] = new google.visualization.DataTable(); | |
| addColumnsFromBuildEnv(tableData[i], buildEnvResults); | |
| // Create chart objects | |
| chart[i] = new google.visualization.ChartWrapper({ | |
| 'chartType': 'LineChart', | |
| 'containerId': 'target' + i, | |
| 'options': options[i], | |
| 'view': { columns: [0, 2, 3, 4, 5, 6, 7, 8, 9] } | |
| }); | |
| } | |
| } | |
| /** | |
| * Called after getting and filling chart data, draw all charts | |
| * @param {!number} targetNum number of target configs (charts) | |
| * @param {!Array<Dashboard>} dashboard all dashboards | |
| * @param {!Array<Control>} control all controls | |
| * @param {!Array<LineChart>} chart all charts | |
| * @param {!Array<Column>} columns columns of all charts | |
| * @param {!Array<DataTable>} tableData data for all charts | |
| * @param {!Array<Object>} options options for all charts | |
| */ | |
| function afterChartData (targetNum, dashboard, control, chart, columns, tableData, options) { | |
| // final steps to draw charts | |
| for (let i = 0; i < targetNum; ++i) { | |
| dashboard[i].bind(control[i], chart[i]); | |
| dashboard[i].draw(tableData[i]); | |
| // event | |
| columns[i] = []; | |
| for (let j = 0; j < tableData[i].getNumberOfColumns(); j++) { | |
| columns[i].push(j); | |
| } | |
| google.visualization.events.addListener( | |
| chart[i], 'select', (function (x) { | |
| return function () { | |
| hideOrShow(dashboard[x], chart[x], columns[x], tableData[x], options[x]); | |
| }; | |
| })(i)); | |
| } | |
| } | |
| /** | |
| * Add columns for each buildEnvResults/line. | |
| * @param {!LineChart} lineChart | |
| * @param {!Array<Object>} buildEnvResults build results | |
| */ | |
| function addColumnsFromBuildEnv (lineChart, buildEnvResults) { | |
| // Using datetime value as hAxis label makes intervals different, | |
| // so we use number instead. | |
| lineChart.addColumn('string', 'label index'); | |
| lineChart.addColumn('number', 'numeric index'); | |
| for (let buildEnvResult of buildEnvResults) { | |
| lineChart.addColumn( | |
| 'number', buildEnvResult.config.description); | |
| lineChart.addColumn({type:'number', role:'interval'}); | |
| lineChart.addColumn({type:'number', role:'interval'}); | |
| lineChart.addColumn( | |
| {'type': 'string', 'role': 'tooltip', 'p': {'html': true}}); | |
| } | |
| } | |
| /** | |
| * Add rows for each code version. | |
| * @param {!LineChart} lineChart | |
| * @param {!Array<Object>} buildEnvResults build results | |
| */ | |
| function addRowsFromData (lineChart, buildEnvResults) { | |
| const rowNum = lineChart.getNumberOfRows(); | |
| for (let j = 0; j < buildEnvResults[0].results.length; ++j) { | |
| const row = [buildEnvResults[0].results[j].datetime, rowNum + j]; | |
| for (let buildEnvResult of buildEnvResults) { | |
| const singleBuildResult = buildEnvResult.results[j]; | |
| const ave = getAverage(singleBuildResult.results); | |
| const sd = getStandardDeviation(singleBuildResult.results, ave); | |
| row.push(ave); | |
| row.push(ave - sd); | |
| row.push(ave + sd); | |
| row.push( | |
| createCustomHTMLContent( | |
| singleBuildResult.results, singleBuildResult.codeVersion)); | |
| } | |
| lineChart.addRow(row); | |
| } | |
| } | |
| /** | |
| * Get average of an array. | |
| * @param {!Array<number>} arr | |
| * @return {!number} the average | |
| */ | |
| function getAverage(arr) { | |
| let ave = arr.reduce(function(a, b) { return a + b; }); | |
| ave /= arr.length; | |
| return ave; | |
| } | |
| /** | |
| * Get standard deviation of an array. | |
| * @param {!Array<number>} arr | |
| * @param {!number} ave average of the array | |
| * @return {!number} the standard deviation | |
| */ | |
| function getStandardDeviation(arr, ave) { | |
| let sd = 0; | |
| for (let item of arr) { | |
| const diff = ave - item; | |
| sd += diff * diff; | |
| } | |
| sd = Math.sqrt(sd); | |
| return sd; | |
| } | |
| /** | |
| * Create html content as tooltip. | |
| * @param {!Array<number>} arr array of build results | |
| * @param {!string} codeVersion current code version | |
| * @return {!string} the html content | |
| */ | |
| function createCustomHTMLContent(arr, codeVersion) { | |
| let str = '<div style="padding:10px 10px 10px 10px;">'; | |
| for (let i = 0; i < arr.length; ++i) { | |
| str += (i+1) + '-th run: ' + arr[i] + '<br>'; | |
| } | |
| str += '<a href="https://github.com/bazelbuild/bazel/commit/' + codeVersion | |
| + '">commit</a></div>'; | |
| return str; | |
| } | |
| /** | |
| * Hide or show one column/line in a chart. | |
| * @param {!Dashboard} dashboard the dashboard to operate | |
| * @param {!LineChart} chart the chart to operate | |
| * @param {!Column} columns columns of current chart | |
| * @param {!DataTable} tableData data for current chart | |
| * @param {!Object} options options for current chart | |
| */ | |
| function hideOrShow(dashboard, chart, columns, tableData, options) { | |
| const sel = chart.getChart().getSelection(); | |
| // If selection length is 0, we deselected an element | |
| if (sel.length <= 0 || sel[0].row !== null) { | |
| return; | |
| } | |
| // Since real col[1] is hidden (numeric index), | |
| // the col that we are looking for should +1 | |
| const col = sel[0].column + 1; | |
| if (columns[col] == col) { | |
| // Hide the data series | |
| columns[col] = { | |
| label: tableData.getColumnLabel(col), | |
| type: tableData.getColumnType(col), | |
| calc: function () { | |
| return null; | |
| } | |
| }; | |
| } else { | |
| // Show the data series | |
| columns[col] = col; | |
| } | |
| const view = new google.visualization.DataView(tableData); | |
| view.setColumns(columns); | |
| dashboard.draw(view); | |
| } | |
| /** @type {!Array<string>} */ | |
| const chartDivList = ['java', 'cpp']; | |
| /** | |
| * Only show the specific div | |
| * @param {!string} divId the id of the div that needs to be shown | |
| */ | |
| function showChartDiv(divId) { | |
| for (let id of chartDivList) { | |
| if (id === divId) { | |
| $('#' + id).show(); | |
| } else { | |
| $('#' + id).hide(); | |
| } | |
| } | |
| } |