mirror of
https://github.com/blender/blender
synced 2026-09-29 04:37:17 +03:00
Tests: Add log scale and linear with outlier clipping to benchmark charts
Log scale helps visualize results with very different runtime or memory usage in a single chart. However big performance changes do not stand out as much and may be missed. Linear with outliner clipping keeps the linear display but clips the Y axis by detecting outliers. This solves the chart being compressed too much with extreme outliers, but may also hide some lines entirely. Pull Request: https://projects.blender.org/blender/blender/pulls/152794
This commit is contained in:
parent
befac37188
commit
4dd1c4d736
1 changed files with 151 additions and 2 deletions
|
|
@ -16,10 +16,12 @@
|
|||
|
||||
/* Fixed width for header buttons to ensure groups match in size. */
|
||||
.btn-time-range { width: 100px; }
|
||||
.btn-scale-mode { width: 133.33px; /* (100 * 4) / 3 */ }
|
||||
</style>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js" integrity="sha384-jb8JQMbMoBUzgWatfe6COACi2ljcDdZQ2OxczGA3bGNeWe+6DChMTBJemed7ZnvJ" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
let chart_instances = [];
|
||||
let current_scale_mode = 'linear-clipped';
|
||||
let current_time_range = '4m';
|
||||
let latest_timestamp = 0;
|
||||
|
||||
|
|
@ -105,9 +107,136 @@
|
|||
section.style.display = valid_indices.length === 0 ? 'none' : 'block';
|
||||
}
|
||||
|
||||
update_chart_bounds(chart);
|
||||
chart.update('none');
|
||||
}
|
||||
|
||||
/* Change the Y-axis scale (linear, log, etc.) for all visible charts. */
|
||||
function set_scale_mode(mode, button) {
|
||||
current_scale_mode = mode;
|
||||
const scale_type = mode === 'logarithmic' ? 'logarithmic' : 'linear';
|
||||
const clip_outliers = mode === 'linear-clipped';
|
||||
|
||||
/* Update button states. */
|
||||
document.querySelectorAll('.btn-scale-mode').forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
btn.classList.replace('btn-secondary', 'btn-outline-secondary');
|
||||
});
|
||||
if (button) {
|
||||
button.classList.add('active');
|
||||
button.classList.replace('btn-outline-secondary', 'btn-secondary');
|
||||
} else {
|
||||
const target_btn = document.querySelector(`.btn-scale-mode[data-mode='${mode}']`);
|
||||
if (target_btn) {
|
||||
target_btn.classList.add('active');
|
||||
target_btn.classList.replace('btn-outline-secondary', 'btn-secondary');
|
||||
}
|
||||
}
|
||||
|
||||
/* Only update visible charts immediately for performance. */
|
||||
for (const chart of chart_instances) {
|
||||
const canvas = chart.canvas;
|
||||
const is_visible = canvas.offsetParent !== null;
|
||||
|
||||
if (is_visible) {
|
||||
apply_scale_to_chart(chart, scale_type, clip_outliers, true);
|
||||
} else {
|
||||
/* Mark for deferred update. */
|
||||
chart._needs_scale_update = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Configure a specific chart instance with the selected scale settings. */
|
||||
function apply_scale_to_chart(chart, scale_type, clip_outliers, animate) {
|
||||
chart.options.scales.y.type = scale_type;
|
||||
update_chart_bounds(chart);
|
||||
chart.update(animate ? {
|
||||
duration: scale_type === 'logarithmic' ? 800 : 0,
|
||||
easing: 'easeOutQuart'
|
||||
} : 'none');
|
||||
chart._needs_scale_update = false;
|
||||
}
|
||||
|
||||
/* Calculate robust and absolute maximums for a set of values.
|
||||
* The robust maximum is used to clip outliers, calculated as the 75th percentile
|
||||
* plus 5 times the Interquartile Range (IQR). */
|
||||
function calculate_robust_max(values) {
|
||||
if (values.length === 0) {
|
||||
return { absolute_max: 0, robust_max: 0 };
|
||||
}
|
||||
|
||||
const absolute_max = Math.max(...values);
|
||||
let robust_max = absolute_max;
|
||||
|
||||
if (values.length > 4) {
|
||||
/* Create a copy to sort. */
|
||||
const sorted = Float64Array.from(values).sort();
|
||||
const q1 = sorted[Math.floor(sorted.length * 0.25)];
|
||||
const q3 = sorted[Math.floor(sorted.length * 0.75)];
|
||||
const iqr = q3 - q1;
|
||||
robust_max = q3 + 5 * iqr;
|
||||
}
|
||||
|
||||
return { absolute_max, robust_max };
|
||||
}
|
||||
|
||||
/* Recalculate robust and absolute maximums based on currently visible datasets. */
|
||||
function update_chart_bounds(chart) {
|
||||
let absolute_max = 0;
|
||||
let robust_max = 0;
|
||||
|
||||
chart.data.datasets.forEach((ds, i) => {
|
||||
if (!chart.isDatasetVisible(i)) {
|
||||
return;
|
||||
}
|
||||
/* Filter out null/undefined values. */
|
||||
const ds_values = ds.data.filter(v => v !== null && v !== undefined);
|
||||
const ds_stats = calculate_robust_max(ds_values);
|
||||
|
||||
absolute_max = Math.max(absolute_max, ds_stats.absolute_max);
|
||||
robust_max = Math.max(robust_max, ds_stats.robust_max);
|
||||
});
|
||||
|
||||
chart.config._absolute_max = absolute_max;
|
||||
chart.config._robust_max = robust_max;
|
||||
|
||||
const is_clipped = current_scale_mode === 'linear-clipped';
|
||||
update_chart_max(chart, is_clipped);
|
||||
}
|
||||
|
||||
/* Set a fixed maximum for the Y-axis if outlier clipping is active. */
|
||||
function update_chart_max(chart, clip) {
|
||||
const robust_max = chart.config._robust_max;
|
||||
const absolute_max = chart.config._absolute_max;
|
||||
if (clip && chart.options.scales.y.type === 'linear' && robust_max < absolute_max) {
|
||||
chart.options.scales.y.max = robust_max;
|
||||
} else {
|
||||
chart.options.scales.y.max = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/* Show/hide UI controls like time ranges or scale modes based on the active tab's content. */
|
||||
function update_ui_for_active_tab() {
|
||||
let has_time_series = false;
|
||||
for (const chart of chart_instances) {
|
||||
if (chart.canvas.offsetParent !== null && chart.config._is_time_series) {
|
||||
has_time_series = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const clipped_btn = document.getElementById('linear-clipped');
|
||||
if (has_time_series) {
|
||||
clipped_btn.classList.remove('d-none');
|
||||
} else {
|
||||
clipped_btn.classList.add('d-none');
|
||||
if (current_scale_mode === 'linear-clipped') {
|
||||
set_scale_mode('linear');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Format Y-axis tick labels for memory or large numeric values. */
|
||||
function chart_y_axis_tick_callback(value, index, values, is_memory) {
|
||||
if (is_memory) {
|
||||
|
|
@ -336,21 +465,32 @@
|
|||
|
||||
if (is_time_series) {
|
||||
apply_time_range_to_chart(chart, get_min_timestamp(current_time_range));
|
||||
} else {
|
||||
update_chart_bounds(chart);
|
||||
}
|
||||
|
||||
chart_instances.push(chart);
|
||||
}
|
||||
|
||||
/* Add event listeners to resize charts when tabs are shown. */
|
||||
/* Add event listeners to resize and update charts when tabs are shown. */
|
||||
document.querySelectorAll('button[data-bs-toggle="tab"]').forEach(tab_el => {
|
||||
tab_el.addEventListener('shown.bs.tab', (event) => {
|
||||
const scale_type = current_scale_mode === 'logarithmic' ? 'logarithmic' : 'linear';
|
||||
const clip_outliers = current_scale_mode === 'linear-clipped';
|
||||
|
||||
for (const chart of chart_instances) {
|
||||
if (chart.canvas.offsetParent !== null) {
|
||||
if (chart._needs_scale_update) {
|
||||
apply_scale_to_chart(chart, scale_type, clip_outliers, false);
|
||||
}
|
||||
chart.resize('none');
|
||||
}
|
||||
}
|
||||
update_ui_for_active_tab();
|
||||
});
|
||||
});
|
||||
|
||||
update_ui_for_active_tab();
|
||||
}
|
||||
|
||||
/* Initialize when DOM is ready. */
|
||||
|
|
@ -360,6 +500,10 @@
|
|||
btn.addEventListener('click', (e) => set_time_range(e.target.dataset.range, e.target));
|
||||
});
|
||||
|
||||
document.querySelectorAll('.btn-scale-mode').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => set_scale_mode(e.target.dataset.mode, e.target));
|
||||
});
|
||||
|
||||
draw_charts();
|
||||
});
|
||||
</script>
|
||||
|
|
@ -374,6 +518,11 @@
|
|||
<button type="button" class="btn btn-outline-secondary btn-sm shadow-sm btn-time-range" data-range="1y">12 Months</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm shadow-sm btn-time-range" data-range="all">All Time</button>
|
||||
</div>
|
||||
<div class="btn-group" role="group" aria-label="Scale Type">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm shadow-sm btn-scale-mode" data-mode="linear">Linear</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm shadow-sm btn-scale-mode active" id="linear-clipped" data-mode="linear-clipped">Linear (Clipped)</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm shadow-sm btn-scale-mode" data-mode="logarithmic">Logarithmic</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -384,4 +533,4 @@
|
|||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue