// Generate unique looking color for a given bar index. function generateBarColor(index) { var builtin_colors = [[255, 99, 132], [255, 159, 64], [255, 205, 86], [75, 192, 192], [54, 162, 235], [153, 102, 255], [201, 203, 207], [48, 103, 204], [220, 56, 18], [254, 155, 0], [15, 147, 25], [54, 76, 121], [118, 153, 249], [148, 50, 123], [85, 172, 6], [219, 200, 187], [255, 134, 72], [186, 247, 71], [252, 200, 13]]; var color = [0, 0, 0]; if (index >= 0 && index < builtin_colors.length) { color = builtin_colors[index]; } return "rgb(" + color[0].toString() + ", " + color[1].toString() + ", " + color[2].toString() + ")"; } // Correct device name, ensuring it has clear vendor indication, // and has no trademarks or other things. function correctDeviceName(name) { if (name.startsWith("TITAN") || name.startsWith("Quadro") || name.startsWith("GeForce")) { return "Nvidia " + name; } if (name.startsWith("Radeon")) { return "AMD " + name; } return name; } function clone(object) { return jQuery.extend(true, {}, object); } function padValue(value, size) { var s = String(value); while (s.length < (size || 2)) { s = "0" + s; } return s; } function secondsToHumanReadable(seconds) { var h = Math.floor((seconds %= 86400) / 3600); var m = Math.floor((seconds %= 3600) / 60); var s = Math.floor(seconds % 60); var msec = Math.floor((seconds % 60) % 1 * 100); var result = ""; if (h != 0) { result += h + ":"; } result += padValue(m, 2) + ":"; result += padValue(s, 2) + "."; result += padValue(msec, 2); return result; } function setDatasetColor(data, is_bar) { var index = 0; for (dataset of data.datasets) { var color = Chart.helpers.color(generateBarColor(index)); dataset.backgroundColor = color.alpha(0.5).rgbString(); dataset.borderColor = color.alpha(1.0).rgbString(); if (is_bar) { dataset.borderWidth = 1; } else { dataset.fill = false; } index++; } } function setCorrectDatasetLabels(data) { for (dataset of data.datasets) { dataset.label = correctDeviceName(dataset.label); } } function findDatasetMaxValue(data) { var max_value = 0; for (dataset of data.datasets) { for (value of dataset.data) { if (value === null) { continue; } if (typeof value !== "number") { value = value.y; } max_value = Math.max(max_value, value); } } return max_value; } function buildChart(ctx, bare_data) { var data = clone(bare_data); setCorrectDatasetLabels(data); setDatasetColor(data, true); var my_chart = new Chart( ctx, { type: 'bar', data: data, options: { responsive: true, legend: {position: 'top'}, title: {display: true, text: 'Benchmark Results'}, scales: {xAxes: [{display: true, scaleLabel: {display: true, labelString: 'Scene'}}], yAxes: [{display: true, scaleLabel: {display: true, labelString: 'Render time (sec)'}, ticks: {min: 0}}]}, tooltips: {// mode: 'index', callbacks: { footer: function(tooltipItems, data) { var human_time = ""; tooltipItems.forEach( function(tooltipItem) { var render_time = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; human_time = secondsToHumanReadable(render_time); } ); if (human_time != "") { return "Render Time: " + human_time; } return ""; }, }, filter: function(item, data) { var data = data.datasets[item.datasetIndex].data[item.index]; return !isNaN(data) && data !== null; }, footerFontStyle: 'normal'}, } }); // TODO(sergey): How can we avoid storing chart in some obscure global variable? window.my_chart = my_chart; } function updateEnabledScenesCallback(bare_data) { /* Collect list of enabled scenes. */ var enabled_scenes = []; var disabled_scenes = []; var scene_index = 0; for (scene_name of bare_data.labels) { var id = "scene_" + scene_name + "_id"; var checkbox = document.getElementById(id); if (checkbox.checked) { enabled_scenes.push({"index": scene_index, "name": scene_name}); } else { disabled_scenes.push({"index": scene_index, "name": scene_name}); } ++scene_index; } /* Construct new data by making a copy of original one and removing * disabeld scenes. * * Note: we reverse te list so indices for removal are alweays staying * the same. */ disabled_scenes.reverse(); var updated_data = clone(bare_data); setCorrectDatasetLabels(updated_data); setDatasetColor(updated_data, true); for (scene of disabled_scenes) { // Remove label first. updated_data.labels.splice(scene.index, 1); updated_data.datasets.forEach(function(dataset, dataset_index) { dataset.data.splice(scene.index, 1); }); } /* Update the actual chart. */ var my_chart = window.my_chart; my_chart.data = updated_data; my_chart.update(); } function buildScenesSelector(ctx, bare_data) { var div = document.createElement("div"); div.className = "scene_selector_container"; for (scene_name of data.labels) { var span = document.createElement("span"); var checkbox = document.createElement('input'); checkbox.type = "checkbox"; checkbox.name = "scene_" + scene_name; checkbox.checked = true; checkbox.id = "scene_" + scene_name + "_id"; checkbox.onclick = function() { updateEnabledScenesCallback(bare_data); }; span.appendChild(checkbox); var label = document.createElement('label'); label.htmlFor = "scene_" + scene_name + "_id"; label.appendChild(document.createTextNode(scene_name)); span.appendChild(label); div.appendChild(span); } ctx.appendChild(div); } function historyChartGetSceneStats(bare_data, scene_name) { var datasets = []; for (dataset of bare_data.datasets) { if (dataset.scene_name != scene_name) { continue; } datasets.push({"data": dataset.data, "label": dataset.device_name}); } return {"datasets": datasets}; } function buildHistoryChart(ctx, bare_data, scene_name) { var data = historyChartGetSceneStats(bare_data, scene_name); setCorrectDatasetLabels(data); setDatasetColor(data, false); new Chart( ctx, { type: 'line', data: data, options: { responsive: true, legend: {position: 'top'}, title: {display: true, text: scene_name + ' benchmark results'}, scales: {xAxes: [{type: "time", time: {format: 'DD/MM/YYYY HH:mm', tooltipFormat: 'll HH:mm'}, scaleLabel: {display: true, labelString: 'Date'}},], yAxes: [{display: true, scaleLabel: {display: true, labelString: 'Render time (sec)'}, ticks: {min: 0}}]}, elements: { line: { tension: 0.000001 } }, } }); } function buildSpreadsheet(ctx, data) { // Generate columns for header. var columns = [{label: "", name: "", width: 300}]; for (label of data.labels) { columns.push({label: label, name: label}); } // Generate actual data. var rows = []; for (dataset of data.datasets) { var row = [correctDeviceName(dataset.label)]; for (value of dataset.data) { if (value == null) { row.push(""); } else { row.push(secondsToHumanReadable(value)); } } rows.push(row); } // Create actual table. $(ctx).jqGrid({ data: rows, localReader: {repeatitems: true}, datatype: "local", styleUI : 'Bootstrap', colModel: columns, caption: "Spreadsheet", height: 300, }); }