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} */ const dashboard = []; /** @type {!Array} */ const control = []; /** @type {!Array} */ const chart = []; const options = []; /** @type {!Array} */ const tableData = []; /** @type {!Array} */ const columns = []; /** @type {!boolean} */ let chartInit = false; /** @type {!number} */ let targetNum = 0; /** @type {!Array} */ 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} */ 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} buildTargetResults results for all build targets * @param {!Array} dashboard all dashboards * @param {!Array} control all controls * @param {!Array} chart all charts * @param {!Array} tableData data for all charts * @param {!Array} 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 $('
') .appendTo('#content'); $('
') .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 all dashboards * @param {!Array} control all controls * @param {!Array} chart all charts * @param {!Array} columns columns of all charts * @param {!Array} tableData data for all charts * @param {!Array} 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} 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} 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} 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} 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} arr array of build results * @param {!string} codeVersion current code version * @return {!string} the html content */ function createCustomHTMLContent(arr, codeVersion) { let str = '
'; for (let i = 0; i < arr.length; ++i) { str += (i+1) + '-th run: ' + arr[i] + '
'; } str += 'commit
'; 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} */ 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(); } } }