From 99dd9aa7a227f31c24bca2972528ffe6fbe5017f Mon Sep 17 00:00:00 2001 From: Antoine Gautier Date: Thu, 15 May 2025 11:46:36 +0200 Subject: [PATCH 01/12] Increase timeout, move plot before error message --- buildingspy/development/regressiontest.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/buildingspy/development/regressiontest.py b/buildingspy/development/regressiontest.py index e4a3375a..e0853753 100644 --- a/buildingspy/development/regressiontest.py +++ b/buildingspy/development/regressiontest.py @@ -2237,6 +2237,13 @@ def _compareResults(self, data_idx, oldRefFulFilNam, y_sim, y_tra, refFilNam, an newStatistics = self._check_statistics( old_results, y_tra, stage, newTrajectories, newStatistics, model_name) + if newTrajectories: + if self._comp_tool == 'legacy': + print("(Close plot window to continue.)") + self._legacy_plot(y_sim, t_ref, y_ref, noOldResults, timOfMaxErr, matFilNam) + else: + self._funnel_plot(model_name) + # If the users selected "Y" or "N" (to not accept or reject any new results) in previous tests, # or if the script is run in batch mode, then don't plot the results. # If we found an error, plot the results, and ask the user to accept or @@ -2256,13 +2263,6 @@ def _compareResults(self, data_idx, oldRefFulFilNam, y_sim, y_tra, refFilNam, an print( f" update reference files with new {self._color_BOLD}trajectories{self._color_ERROR}?{self._color_ENDC}") - if newTrajectories: - if self._comp_tool == 'legacy': - print("(Close plot window to continue.)") - self._legacy_plot(y_sim, t_ref, y_ref, noOldResults, timOfMaxErr, matFilNam) - else: - self._funnel_plot(model_name) - while not (ans == "n" or ans == "y" or ans == "Y" or ans == "N"): ans = input(" Enter: y(yes), n(no), Y(yes for all), N(no for all): ") @@ -2311,7 +2311,7 @@ def _funnel_plot(self, model_name, browser=None): server = pyfunnel.MyHTTPServer(('', 0), pyfunnel.CORSRequestHandler, str_html=content, url_html='funnel') # Start the browser instance. - server.browse(list_files, browser=browser) + server.browse(list_files, browser=browser, timeout=60) def _legacy_plot(self, y_sim, t_ref, y_ref, noOldResults, timOfMaxErr, model_name): """Plot comparison results generated by legacy comparison algorithm.""" From 3265537ff277ed115e529b9cbc00b7dd489ba122 Mon Sep 17 00:00:00 2001 From: Antoine Gautier Date: Thu, 15 May 2025 15:15:45 +0200 Subject: [PATCH 02/12] Guard against missing data, refactor with ajax and Promise --- buildingspy/templates/datatable.html | 145 ++++++++++++++++++----- buildingspy/templates/plot.html | 166 ++++++++++++++++++++------- 2 files changed, 240 insertions(+), 71 deletions(-) diff --git a/buildingspy/templates/datatable.html b/buildingspy/templates/datatable.html index 2a3e9f64..bb0fa9e7 100755 --- a/buildingspy/templates/datatable.html +++ b/buildingspy/templates/datatable.html @@ -122,26 +122,62 @@ }, { data: 'simulation.elapsed_time', - render: $.fn.dataTable.render.number(' ', '.', 2) + render: function(data, type, row) { + if (data === undefined || data === null) { + return ''; + } + return $.fn.dataTable.render.number(' ', '.', 2).display(data); + } }, { data: 'simulation.start_time', - render: $.fn.dataTable.render.number(' ', '.', 0) + render: function(data, type, row) { + if (data === undefined || data === null) { + return ''; + } + return $.fn.dataTable.render.number(' ', '.', 0).display(data); + } }, { data: 'simulation.final_time', - render: $.fn.dataTable.render.number(' ', '.', 0) + render: function(data, type, row) { + if (data === undefined || data === null) { + return ''; + } + return $.fn.dataTable.render.number(' ', '.', 0).display(data); + } }, { data: 'simulation.state_events', - render: $.fn.dataTable.render.number(' ', '.', 0) + render: function(data, type, row) { + if (data === undefined || data === null) { + return ''; + } + return $.fn.dataTable.render.number(' ', '.', 0).display(data); + } }, { data: 'simulation.jacobians', - render: $.fn.dataTable.render.number(' ', '.', 0) + render: function(data, type, row) { + if (data === undefined || data === null) { + return ''; + } + return $.fn.dataTable.render.number(' ', '.', 0).display(data); + } + }, + { + data: 'simulation.success', + render: function(data, type, row) { + return data !== undefined ? data : ''; + } + }, + { + data: 'simulation.message', + width: "40%", + render: function(data, type, row) { + return data || ''; + } }, - { data: 'simulation.success' }, - { data: 'simulation.message', width: "40%" }, ], }); if (!/jmodelica/i.test('$SIMULATOR_LOG')) { @@ -180,10 +216,26 @@ }, { data: 'translation.cpu_time', - render: $.fn.dataTable.render.number(' ', '.', 2) + render: function(data, type, row) { + if (data === undefined || data === null) { + return ''; + } + return $.fn.dataTable.render.number(' ', '.', 2).display(data); + } + }, + { + data: 'translation.success', + render: function(data, type, row) { + return data !== undefined ? data : ''; + } + }, + { + data: 'translation.warnings', + width: "40%", + render: function(data, type, row) { + return data || ''; + } }, - { data: 'translation.success' }, - { data: 'translation.warnings', width: "40%" }, ], }); } @@ -248,10 +300,30 @@ return '' + out + '';; } }, - { data: 'comparison.file_name' }, - { data: 'comparison.funnel_dirs' }, - { data: 'comparison.var_groups' }, - { data: 'comparison.warnings' }, + { + data: 'comparison.file_name', + render: function(data, type, row) { + return data || ''; + } + }, + { + data: 'comparison.funnel_dirs', + render: function(data, type, row) { + return data || []; + } + }, + { + data: 'comparison.var_groups', + render: function(data, type, row) { + return data || []; + } + }, + { + data: 'comparison.warnings', + render: function(data, type, row) { + return data || []; + } + }, ], columnDefs: [ { visible: false, searchable: false, targets: [3, 4, 5, 6] }, @@ -283,20 +355,37 @@ var max_plot_per100 = 4; var height = 100 * (1 + Math.max(0, uniq_groups.length - max_plot_per100) / max_plot_per100); var err_plot_height = 0.18 * 100 / height; - $.get('$COMP_DIR/plot.html', function (data) { - if (uniq_groups.length > 0) { - var title = file; - if (navail_vars.length > 0) { title = title.concat('
No available results for: ').concat(navail_vars.join(', ')); } - var new_data = data.replace('$DICT_VAR_INFO', JSON.stringify(dict_var_info)); - new_data = new_data.replace('$PAGE_TITLE', title); - new_data = new_data.replace('$TITLE', model); - new_data = new_data.replace('$HEIGHT', height + '%'); - new_data = new_data.replace('$ERR_PLOT_HEIGHT', err_plot_height); - var strWindowFeatures = "menubar=yes, location=yes, resizable=yes, scrollbars=yes, status=yes"; - var win = window.open('', '_blank', strWindowFeatures); - win.document.write(new_data); - win.document.close(); // necessary for the scripts on the page to be executed - } else { alert(warnings.join('\n')); } + // Use jQuery's AJAX with timeout and error handling + $.ajax({ + url: '$COMP_DIR/plot.html', + timeout: 5000, + success: function (data) { + if (uniq_groups.length > 0) { + var title = file; + if (navail_vars.length > 0) { title = title.concat('
No available results for: ').concat(navail_vars.join(', ')); } + var new_data = data.replace('$DICT_VAR_INFO', JSON.stringify(dict_var_info)); + new_data = new_data.replace('$PAGE_TITLE', title); + new_data = new_data.replace('$TITLE', model); + new_data = new_data.replace('$HEIGHT', height + '%'); + new_data = new_data.replace('$ERR_PLOT_HEIGHT', err_plot_height); + var strWindowFeatures = "menubar=yes, location=yes, resizable=yes, scrollbars=yes, status=yes"; + // Create a Blob containing the HTML content + var blob = new Blob([new_data], {type: 'text/html'}); + // Create a URL for the Blob + var blobUrl = URL.createObjectURL(blob); + // Open a new window with the Blob URL + var win = window.open(blobUrl, '_blank', strWindowFeatures); + // Clean up the Blob URL once the window has loaded + if (win) { + win.addEventListener('load', function() { + URL.revokeObjectURL(blobUrl); + }); + } + } else { alert(warnings.join('\n')); } + }, + error: function(jqXHR, textStatus, errorThrown) { + console.error("Error loading plot.html:", textStatus, errorThrown); + } }); }); }); diff --git a/buildingspy/templates/plot.html b/buildingspy/templates/plot.html index 9a30f66e..eededac9 100644 --- a/buildingspy/templates/plot.html +++ b/buildingspy/templates/plot.html @@ -4,57 +4,136 @@ $PAGE_TITLE -
- - \ No newline at end of file + From 8f8dbca547abed8840bf76b3f0a6cb95634d44d0 Mon Sep 17 00:00:00 2001 From: Antoine Gautier Date: Thu, 15 May 2025 16:11:31 +0200 Subject: [PATCH 03/12] Bump JS dependencies --- buildingspy/templates/datatable.html | 24 +++++------ buildingspy/templates/plot.html | 60 ++++++++++++++-------------- 2 files changed, 43 insertions(+), 41 deletions(-) diff --git a/buildingspy/templates/datatable.html b/buildingspy/templates/datatable.html index bb0fa9e7..bbe569a1 100755 --- a/buildingspy/templates/datatable.html +++ b/buildingspy/templates/datatable.html @@ -3,7 +3,7 @@ + href="https://cdn.datatables.net/v/bs-3.3.7/jq-3.3.1/jszip-2.5.0/dt-1.13.8/b-1.5.4/b-colvis-1.5.4/b-flash-1.5.4/b-html5-1.5.4/datatables.min.css" /> - - - - + .dataTable tbody td { + word-break: break-word; + vertical-align: top; + } - -
-
- -
-
-

-
-
-
-
-
-
-
- - - - - - - - - - -
ModelVariablesSuccessResults Filecomp_dirs
-

Color Legend

-

Translation, simulation or extracting simulation results failed: see message in - alert box.

-

Funnel comparison failed: see message in alert box.

-

Result verification detected error, see plot.

-

Test passed.

-
-
-
+ .table-responsive { + max-width: 90%; + margin-left: auto; + margin-right: auto; + overflow-x: visible; + } + + + + + - - - + var table2 = $("#myTable2").DataTable({ + autoWidth: false, + aLengthMenu: [ + [10, 50, 100, -1], + [10, 50, 100, "All"], + ], + iDisplayLength: -1, + ajax: { + url: "$SIMULATOR_LOG", + dataSrc: "", + }, + columns: [ + { + data: "model", + width: "30%", + render: function (data, type, full) { + return data.replace(/\./g, "."); + }, + }, + { + data: "translation.cpu_time", + render: function (data, type, row) { + if (data === undefined || data === null) { + return ""; + } + return $.fn.dataTable.render.number(" ", ".", 2).display(data); + }, + }, + { + data: "translation.success", + render: function (data, type, row) { + return data !== undefined ? data : ""; + }, + }, + { + data: "translation.warnings", + width: "40%", + render: function (data, type, row) { + return data || ""; + }, + }, + ], + }); + } + var table3 = $("#myTable3").DataTable({ + autoWidth: false, + aLengthMenu: [ + [10, 50, 100, -1], + [10, 50, 100, "All"], + ], + iDisplayLength: -1, + ajax: { + url: "$SIMULATOR_LOG", + dataSrc: "", + }, + columns: [ + { + data: "model", + width: "30%", + render: function (data, type, full) { + return data.replace(/\./g, "."); + }, + }, + { + data: "comparison.variables", + width: "60%", + render: function (data, type, row, meta) { + var out = ""; + if (typeof data !== "undefined") { + out = data.map(function (e, var_idx) { + var warnings = table3.cell(meta.row, 6).data(); + var color; + if (warnings[0] != null && warnings[0].includes("failed")) { + color = "red"; + } else if (warnings[var_idx] != null) { + if (warnings[var_idx].includes("not found in")) { + color = "red"; + } else if (warnings[var_idx].includes("While processing file")) { + color = "brown"; + } else if (warnings[var_idx].includes("Absolute error")) { + color = "orange"; + } + } + if (color != null) { + return " " + e.fontcolor(color); + } else { + return " " + e; + } + }); + } else { + out = "No simulation results available."; + } + return out; + }, + }, + { + data: "comparison.success_rate", + render: function (data, type, full) { + var out = ""; + if (typeof data !== "undefined") { + out = data.toLocaleString("en-US", { style: "percent" }); + } else { + out = "0%"; + } + return '' + out + ""; + }, + }, + { + data: "comparison.file_name", + render: function (data, type, row) { + return data || ""; + }, + }, + { + data: "comparison.funnel_dirs", + render: function (data, type, row) { + return data || []; + }, + }, + { + data: "comparison.var_groups", + render: function (data, type, row) { + return data || []; + }, + }, + { + data: "comparison.warnings", + render: function (data, type, row) { + return data || []; + }, + }, + ], + columnDefs: [{ visible: false, searchable: false, targets: [3, 4, 5, 6] }], + }); + $("#myTable3").on("click", ".myClass", function () { + var tr = $(this).closest("tr"); + var row_idx = table3.row(tr).index(); + var model = table3.cell(row_idx, 0).data(); + var variables = table3.cell(row_idx, 1).data(); + var file = table3.cell(row_idx, 3).data(); + var dirs = table3.cell(row_idx, 4).data(); + var groups = table3.cell(row_idx, 5).data(); + var warnings = table3.cell(row_idx, 6).data(); + var avail_groups = []; + var navail_vars = []; + var dict_var_info = {}; // dict of array of dicts (to handle multiple plots for one variable) + if (variables != null) { + variables.forEach(function (v, idx) { + if (dirs[idx] != null) { + (dict_var_info[v] = dict_var_info[v] || []).push({ group: groups[idx], dir: dirs[idx] }); + avail_groups.push(groups[idx]); + } else { + navail_vars.push(v); + } + }); + } else { + alert("Funnel comparison could not be executed for this file."); + } + var uniq_groups = [...new Set(avail_groups)]; + var max_plot_per100 = 4; + var height = 100 * (1 + Math.max(0, uniq_groups.length - max_plot_per100) / max_plot_per100); + var err_plot_height = (0.18 * 100) / height; + // Use jQuery's AJAX with timeout and error handling + $.ajax({ + url: "$COMP_DIR/plot.html", + timeout: 5000, + success: function (data) { + if (uniq_groups.length > 0) { + var title = file; + if (navail_vars.length > 0) { + title = title.concat("
No available results for: ").concat(navail_vars.join(", ")); + } + var new_data = data.replace("$DICT_VAR_INFO", JSON.stringify(dict_var_info)); + new_data = new_data.replace("$PAGE_TITLE", title); + new_data = new_data.replace("$TITLE", model); + new_data = new_data.replace("$HEIGHT", height + "%"); + new_data = new_data.replace("$ERR_PLOT_HEIGHT", err_plot_height); + var strWindowFeatures = "menubar=yes, location=yes, resizable=yes, scrollbars=yes, status=yes"; + // Create a Blob containing the HTML content + var blob = new Blob([new_data], { type: "text/html" }); + // Create a URL for the Blob + var blobUrl = URL.createObjectURL(blob); + // Open a new window with the Blob URL + var win = window.open(blobUrl, "_blank", strWindowFeatures); + // Clean up the Blob URL once the window has loaded + if (win) { + win.addEventListener("load", function () { + URL.revokeObjectURL(blobUrl); + }); + } + } else { + alert(warnings.join("\n")); + } + }, + error: function (jqXHR, textStatus, errorThrown) { + console.error("Error loading plot.html:", textStatus, errorThrown); + }, + }); + }); + }); + diff --git a/buildingspy/templates/plot.html b/buildingspy/templates/plot.html index 6945883a..2955fa0a 100644 --- a/buildingspy/templates/plot.html +++ b/buildingspy/templates/plot.html @@ -1,364 +1,382 @@ - - + $PAGE_TITLE - + - -
+ +
- + From 7f9277f4fb711c580a0d25da09eba6ffd6f9ec29 Mon Sep 17 00:00:00 2001 From: AntoineGautier Date: Thu, 18 Sep 2025 11:28:20 +0200 Subject: [PATCH 06/12] Check if server is accessible first, fix function name [ci skip] --- buildingspy/templates/plot.html | 46 ++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/buildingspy/templates/plot.html b/buildingspy/templates/plot.html index 2955fa0a..1e0fc515 100644 --- a/buildingspy/templates/plot.html +++ b/buildingspy/templates/plot.html @@ -1,6 +1,7 @@ + $PAGE_TITLE @@ -59,16 +60,29 @@ const fileInfo = []; // To track which variable each promise belongs to // Collect all groups and prepare file loading + // Check if server is accessible first + // Check if server is accessible first + try { + const response = await fetch(srv_url); + if (!response.ok) { + throw new Error(`Server not accessible (status: ${response.status})`); + } + } catch (error) { + throw new Error(`Server connection failed: ${error.message}. Make sure the local server is running on port ${srv_url.split(':').pop()}.`); + } + Object.keys(dict_var_info).forEach((v) => { dict_var_info[v].forEach((el) => { avail_groups.push(el["group"]); }); - // Prepare file loading for each variable const fileTypes = ["reference.csv", "test.csv", "errors.csv"]; fileTypes.forEach((f) => { const file = [srv_url, dict_var_info[v][0]["dir"], f].join("/"); - filePromises.push(loadCSV(file)); + filePromises.push(loadCSV(file).catch(err => { + console.warn(`Failed to load ${file}: ${err.message}`); + return null; // Return null for failed loads + })); fileInfo.push({ variable: v, type: f }); }); }); @@ -97,8 +111,9 @@ const testIndex = fileInfo.findIndex((info) => info.variable === v && info.type === "test.csv"); const errIndex = fileInfo.findIndex((info) => info.variable === v && info.type === "errors.csv"); - // Process the data if all files were loaded - if (refIndex !== -1 && testIndex !== -1 && errIndex !== -1) { + // Process the data if all files were loaded successfully + if (refIndex !== -1 && testIndex !== -1 && errIndex !== -1 && + csvDataSets[refIndex] && csvDataSets[testIndex] && csvDataSets[errIndex]) { dict_data[v] = { ref: processData(csvDataSets[refIndex]), test: processData(csvDataSets[testIndex]), @@ -106,6 +121,7 @@ }; } else { console.warn(`Missing data files for variable ${v}`); + // Don't add this variable to dict_data } }); @@ -114,9 +130,20 @@ plot(dict_data, dict_var_info, list_group_var); } catch (error) { console.error("Error loading or processing data:", error); + let errorMessage = error.message; + let suggestions = ""; + + if (error.message.includes("Server connection failed") || error.message.includes("Failed to fetch")) { + suggestions = `

Possible solutions:
    +
  • Check if the funnel server is running
  • +
  • Try running: python -c "import buildingspy.development.regressiontest as t; t.Tester().report()"
  • +
  • Ensure all required CSV data files exist in the funnel_comp directory
  • +
`; + } + document.getElementById("myDiv").innerHTML = `
- Error loading data: ${error.message}
- + Error loading data: ${errorMessage}${suggestions}

+
`; } } @@ -247,6 +274,13 @@ i++; var data = dict_data[v]; var axis_data_idx = group + 2; // 1 is for error + + // Check if data exists and has required properties + if (!data || !data.ref || !data.test || !data.err) { + console.warn(`Skipping variable ${v} due to missing data`); + return; // Skip this variable + } + traces.push({ x: data["ref"].x, y: data["ref"].y, From 4c51b4fe035bca6c28558add42c7473fdf1fe3af Mon Sep 17 00:00:00 2001 From: AntoineGautier Date: Thu, 18 Sep 2025 12:14:36 +0200 Subject: [PATCH 07/12] Remove controller w/ timeout --- buildingspy/templates/plot.html | 457 ++++++++++++++------------------ 1 file changed, 199 insertions(+), 258 deletions(-) diff --git a/buildingspy/templates/plot.html b/buildingspy/templates/plot.html index 1e0fc515..42d52cd2 100644 --- a/buildingspy/templates/plot.html +++ b/buildingspy/templates/plot.html @@ -9,131 +9,103 @@
- + \ No newline at end of file From 60426f3372f0870accfddc089bb534d73a68fff1 Mon Sep 17 00:00:00 2001 From: AntoineGautier Date: Fri, 19 Sep 2025 10:03:06 +0200 Subject: [PATCH 08/12] Remove retry button, add dummy favicon --- buildingspy/templates/datatable.html | 1 + buildingspy/templates/plot.html | 57 ++++++++++------------------ 2 files changed, 20 insertions(+), 38 deletions(-) diff --git a/buildingspy/templates/datatable.html b/buildingspy/templates/datatable.html index 3477a9aa..053a0c7b 100755 --- a/buildingspy/templates/datatable.html +++ b/buildingspy/templates/datatable.html @@ -1,6 +1,7 @@ + { avail_groups.push(el["group"]); @@ -67,9 +67,9 @@ // Load files for this variable sequentially const fileDir = dict_var_info[v][0]["dir"]; const baseUrl = [srv_url, fileDir].join("/"); - + console.log(`Loading data for variable: ${v} from ${baseUrl}`); - + const [refData, testData, errData] = await Promise.all([ loadCSV(`${baseUrl}/reference.csv`), loadCSV(`${baseUrl}/test.csv`), @@ -89,7 +89,7 @@ } console.log("DATA LOADING COMPLETED"); - + // Check if we have any data to plot if (Object.keys(dict_data).length === 0) { throw new Error("No data could be loaded for any variables"); @@ -97,26 +97,9 @@ // Plot the data plot(dict_data, dict_var_info, avail_groups); - + } catch (error) { console.error("Error loading or processing data:", error); - let errorMessage = error.message; - let suggestions = ""; - - if (error.message.includes("Server not accessible") || - error.message.includes("Failed to fetch") || - error.message.includes("ERR_CONNECTION_REFUSED")) { - suggestions = `

Possible solutions:
    -
  • Check if the funnel server is running
  • -
  • Try running: python -c "import buildingspy.development.regressiontest as t; t.Tester().report()"
  • -
  • Ensure all required CSV data files exist in the funnel_comp directory
  • -
`; - } - - document.getElementById("myDiv").innerHTML = `
- Error loading data: ${errorMessage}${suggestions}

- -
`; } } @@ -139,7 +122,7 @@ Object.keys(dict_var_info).forEach((v) => { // Only process variables that have data if (!dict_data[v]) return; - + dict_var_info[v].forEach((el) => { let group_reset = flag_groups.indexOf(el["group"]); if (group_reset == -1) { @@ -150,7 +133,7 @@ }); var nb_groups = list_group_var.filter(group => group && group.length > 0).length; - + // Define color palette var colorPalette = [ "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", @@ -221,14 +204,14 @@ var i = -1; list_group_var.forEach(function (el, group) { if (!el || el.length === 0) return; - + el.forEach(function (v, idv) { var data = dict_data[v]; if (!data || !data.ref || !data.test || !data.err) { console.warn(`Skipping variable ${v} due to missing data`); return; } - + i++; var axis_data_idx = group + 2; // 1 is for error @@ -239,7 +222,7 @@ line: { dash: "dot", color: getColor(i) }, xaxis: "x" + axis_data_idx, yaxis: "y" + axis_data_idx, }); - + traces.push({ x: data["test"].x, y: data["test"].y, legendgroup: v + group, name: v, @@ -247,7 +230,7 @@ line: { color: getColor(i) }, xaxis: "x" + axis_data_idx, yaxis: "y" + axis_data_idx, }); - + traces.push({ x: data["err"].x, y: data["err"].y, legendgroup: v + group, showlegend: false, @@ -261,7 +244,7 @@ ticks: "inside", mirror: "ticks", showline: true, zeroline: false, title: gen_y_axis_title, tickformatstops: tickformatstops, }; - + layout["xaxis" + axis_data_idx] = { ticks: "inside", showline: true, showticklabels: false, mirror: "ticks", zeroline: false, anchor: "y" + axis_data_idx, @@ -289,13 +272,11 @@ Plotly.newPlot("myDiv", traces, layout, { responsive: true }) .then(function() { console.log("Plot rendered successfully"); + // Force a resize to ensure plot is visible + window.dispatchEvent(new Event("resize")); }) .catch(function(error) { console.error("Error rendering plot:", error); - document.getElementById("myDiv").innerHTML = `
- Error rendering plot: ${error.message}
- -
`; }); } @@ -327,21 +308,21 @@ // Reposition legend when scrolling let last_known_scroll_position = 0; let ticking = false; - + function repositionLegend(scroll_pos) { var scrollMaxY = Math.max( document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight, ) - window.innerHeight; - + var update = { legend: { x: 1.02, y: (scrollMaxY - scroll_pos) / scrollMaxY }, }; - + Plotly.relayout("myDiv", update); } - + window.addEventListener("scroll", function (e) { last_known_scroll_position = window.scrollY; if (!ticking) { @@ -354,4 +335,4 @@ }); - \ No newline at end of file + From 86da620daba2b29d651dd8440a022964ac178ed5 Mon Sep 17 00:00:00 2001 From: AntoineGautier Date: Wed, 1 Oct 2025 11:02:08 +0200 Subject: [PATCH 09/12] Propagate changes to plot template from pyfunnel --- buildingspy/templates/plot.html | 86 ++++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 27 deletions(-) diff --git a/buildingspy/templates/plot.html b/buildingspy/templates/plot.html index a333682e..bcfea1eb 100644 --- a/buildingspy/templates/plot.html +++ b/buildingspy/templates/plot.html @@ -4,12 +4,45 @@ $PAGE_TITLE + -
+
- + \ No newline at end of file From 77da92bd7a11b1e5fd9d46fc916286776955b7f8 Mon Sep 17 00:00:00 2001 From: AntoineGautier Date: Wed, 1 Oct 2025 11:10:54 +0200 Subject: [PATCH 10/12] Update dependencies and changelog, clean imports --- buildingspy/CHANGES.txt | 5 ++-- buildingspy/development/regressiontest.py | 31 ++++++++++++----------- requirements.txt | 5 ++-- setup.py | 7 +++-- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/buildingspy/CHANGES.txt b/buildingspy/CHANGES.txt index a5723c76..9b48d7c6 100644 --- a/buildingspy/CHANGES.txt +++ b/buildingspy/CHANGES.txt @@ -4,8 +4,9 @@ BuildingsPy Changelog Version 5.2.1, ... ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- Updated pyfunnel to version 1.0.3 to fix plotting issues. - ([#599](https://github.com/lbl-srg/BuildingsPy/issues/599)) +- Updated pyfunnel to version 2.0.1 to fix plotting issues. + With this change, Python 2 is no longer supported. + (https://github.com/lbl-srg/BuildingsPy/issues/599) Version 5.2.0, March 14, 2025 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/buildingspy/development/regressiontest.py b/buildingspy/development/regressiontest.py index e0853753..33b7fd29 100644 --- a/buildingspy/development/regressiontest.py +++ b/buildingspy/development/regressiontest.py @@ -7,10 +7,6 @@ # MWetter@lbl.gov 2011-02-23 ####################################################### # -from collections import defaultdict -from contextlib import contextmanager -import difflib -import fnmatch import functools import glob import io @@ -24,19 +20,24 @@ import sys import tempfile import time -import webbrowser +from collections import defaultdict +from contextlib import contextmanager + # Third-party module or package imports. import numpy as np -import simplejson + # Code repository sub-package imports. import pyfunnel -from buildingspy.development import error_dictionary_openmodelica -from buildingspy.development import error_dictionary_optimica -from buildingspy.development import error_dictionary_dymola -from buildingspy.io.outputfile import Reader -from buildingspy.io.postprocess import Plotter + import buildingspy.io.outputfile as of import buildingspy.io.reporter as rep +from buildingspy.development import ( + error_dictionary_dymola, + error_dictionary_openmodelica, + error_dictionary_optimica, +) +from buildingspy.io.outputfile import Reader +from buildingspy.io.postprocess import Plotter def runSimulation(worDir, cmd): @@ -3880,7 +3881,7 @@ def _get(model, key, data): return 0 with open(self._statistics_log, 'r') as f: - staVal = simplejson.loads(f.read()) + staVal = json.loads(f.read()) data = [] for case in staVal['testCase']: if 'translate' in case: @@ -3902,7 +3903,7 @@ def _get(model, key, data): temp['simulation']['state_events'] = case['simulate']['state_events'] if 'state_events' in case['simulate'] else 0 temp['simulation']['success'] = case['simulate']['result'] data.append(temp) - dataJson = simplejson.dumps(data) + dataJson = json.dumps(data) return dataJson def run(self): @@ -4135,7 +4136,7 @@ def run(self): # For Dymola: store available simulation info into # self._comp_info used for reporting. val = self._run_simulation_info() - self._comp_info = simplejson.loads(val) + self._comp_info = json.loads(val) r = self._checkReferencePoints(ans) if r != 0: # In case of comparison error. Comparison warnings are handled @@ -4154,7 +4155,7 @@ def run(self): # For OpenModelica and OPTIMICA: store available translation and simulation info # into self._comp_info used for reporting or for rewriting the configuration file. with open(self._simulator_log_file, 'r') as f: - self._comp_info = simplejson.loads(f.read()) + self._comp_info = json.loads(f.read()) if not self._skip_verification: r = self._checkReferencePoints(ans='N') diff --git a/requirements.txt b/requirements.txt index b62aec0d..bf1aab9d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,9 +5,8 @@ gitpython>=3.1.33; python_version>='3.8' --only-binary numpy==1.21.4; python_version>='3.8' --only-binary scipy==1.7.3; python_version>='3.8' pytidylib==0.3.2 -simplejson==3.16.0 -six==1.14.0 -pyfunnel==1.0.3 + +pyfunnel==2.0.1 PyYAML==6.0.1 cerberus==1.3.4 diff --git a/setup.py b/setup.py index 871d0d43..5601ce6f 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ import io -import platform import os + from setuptools import setup # Python setup file. @@ -39,9 +39,8 @@ 'numpy>=1.21', 'scipy>=1.7', 'pytidylib>=0.3.2', - 'simplejson>=3.16', - 'six>=1.14', - 'pyfunnel>=1.0.3', + + 'pyfunnel>=2.0.1', 'PyYAML>=6.0.1', 'cerberus>=1.3.4', ], From ee9cf06fe806e364ed371d3828eb17f98aee7129 Mon Sep 17 00:00:00 2001 From: AntoineGautier Date: Wed, 1 Oct 2025 14:23:14 +0200 Subject: [PATCH 11/12] Specify build tools in setup --- requirements.txt | 2 -- setup.py | 5 ++++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index bf1aab9d..8c0b092c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,4 @@ PyYAML==6.0.1 cerberus==1.3.4 # For documentation and testing. -setuptools==70.0.0; python_version>='3.8' -wheel==0.38.1 autopep8==1.6 diff --git a/setup.py b/setup.py index 5601ce6f..c76cd5d0 100644 --- a/setup.py +++ b/setup.py @@ -32,6 +32,10 @@ keywords="modelica dymola openmodelica mat", url="http://simulationresearch.lbl.gov/modelica/", python_requires='>=3.8', + setup_requires=[ + 'setuptools>=70.0.0', + 'wheel>=0.37.0', + ], install_requires=[ 'gitpython>=3.1.33', 'jinja2>=3.1.4', @@ -39,7 +43,6 @@ 'numpy>=1.21', 'scipy>=1.7', 'pytidylib>=0.3.2', - 'pyfunnel>=2.0.1', 'PyYAML>=6.0.1', 'cerberus>=1.3.4', From 4696af29c2a39106fa080583c8d8eb262011ac32 Mon Sep 17 00:00:00 2001 From: AntoineGautier Date: Sun, 5 Oct 2025 15:33:11 +0200 Subject: [PATCH 12/12] Specify encoding to avoid JSONDecodeError: Unexpected UTF-8 BOM --- buildingspy/development/regressiontest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildingspy/development/regressiontest.py b/buildingspy/development/regressiontest.py index 33b7fd29..6d42a587 100644 --- a/buildingspy/development/regressiontest.py +++ b/buildingspy/development/regressiontest.py @@ -4154,7 +4154,7 @@ def run(self): if not self._skip_verification or self._rewrite_configuration_file: # For OpenModelica and OPTIMICA: store available translation and simulation info # into self._comp_info used for reporting or for rewriting the configuration file. - with open(self._simulator_log_file, 'r') as f: + with open(self._simulator_log_file, 'r', encoding='utf-8-sig') as f: self._comp_info = json.loads(f.read()) if not self._skip_verification: