From 8b8cf4db442d47b103f12481377e32e6966d88ad Mon Sep 17 00:00:00 2001 From: Herb Schilling Date: Fri, 2 Feb 2024 17:41:26 -0500 Subject: [PATCH 01/12] Set the background color for the tooltip to be a shade of the aviary color --- aviary/visualization/assets/aviary_styles.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/aviary/visualization/assets/aviary_styles.css b/aviary/visualization/assets/aviary_styles.css index 7e65ed3be..b544f554c 100644 --- a/aviary/visualization/assets/aviary_styles.css +++ b/aviary/visualization/assets/aviary_styles.css @@ -19,4 +19,8 @@ nav#header { .bk-header .bk-tab { height: 25px;; +} + +.tabulator-tooltip { + background-color: #6bbecd; } \ No newline at end of file From 6227c4b7df2048d49cb6ebb2ef41f17401084384 Mon Sep 17 00:00:00 2001 From: Herb Schilling Date: Fri, 2 Feb 2024 17:42:50 -0500 Subject: [PATCH 02/12] Added metadata to the data available for the aviary variables table --- aviary/visualization/dashboard.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/aviary/visualization/dashboard.py b/aviary/visualization/dashboard.py index 5810c945b..730799c2c 100644 --- a/aviary/visualization/dashboard.py +++ b/aviary/visualization/dashboard.py @@ -17,6 +17,8 @@ import openmdao.api as om from openmdao.utils.general_utils import env_truthy +import aviary.api as av + pn.extension(sizing_mode="stretch_width") # Constants - # Can't get using CSS to work with frames and the raw_css for the template so going with @@ -180,25 +182,31 @@ def create_aviary_variables_table_data_nested(script_name, recorder_file): for group_name in sorted_group_names: if len(grouped[group_name]) == 1: # a list of one var. var_info = grouped[group_name][0] + prom_name = outputs[var_info]["prom_name"] + aviary_metadata = av.CoreMetaData.get(prom_name) table_data_nested.append( { "abs_name": group_name, - "prom_name": outputs[var_info]["prom_name"], + "prom_name": prom_name, "value": str(outputs[var_info]["val"]), "units": str(outputs[var_info]["units"]), + "metadata": json.dumps(aviary_metadata), } ) else: # create children children_list = [] for children_name in grouped[group_name]: - var_info = outputs[children_name] + # var_info = outputs[children_name] + prom_name = outputs[children_name]["prom_name"] + aviary_metadata = av.CoreMetaData.get(prom_name) children_list.append( { "abs_name": children_name, - "prom_name": outputs[children_name]["prom_name"], + "prom_name": prom_name, "value": str(outputs[children_name]["val"]), "units": str(outputs[children_name]["units"]), + "metadata": json.dumps(aviary_metadata), } ) table_data_nested.append( # not a real var, just a group of vars so no values From 1980ac9f574f75054c5d37bc0682e079448c91d0 Mon Sep 17 00:00:00 2001 From: Herb Schilling Date: Fri, 2 Feb 2024 17:43:36 -0500 Subject: [PATCH 03/12] Fixed up the tooltip to be more readable. Added a button to each cell so user can copy the value and use in their code --- .../assets/aviary_vars/index.html | 14 + .../assets/aviary_vars/script.js | 279 ++++++++++++++---- 2 files changed, 232 insertions(+), 61 deletions(-) diff --git a/aviary/visualization/assets/aviary_vars/index.html b/aviary/visualization/assets/aviary_vars/index.html index 0031ba91d..7d1af2194 100755 --- a/aviary/visualization/assets/aviary_vars/index.html +++ b/aviary/visualization/assets/aviary_vars/index.html @@ -5,11 +5,25 @@ Aviary Dashboard + + + + + + + Show Only Aviary Variables +
diff --git a/aviary/visualization/assets/aviary_vars/script.js b/aviary/visualization/assets/aviary_vars/script.js index 17ed2cbab..84eba9a2d 100755 --- a/aviary/visualization/assets/aviary_vars/script.js +++ b/aviary/visualization/assets/aviary_vars/script.js @@ -1,71 +1,228 @@ $(function () { -// Read in the json file that the dashboard.py script generated -// fetch('/home/aviary_vars.json') -fetch('./aviary_vars.json') + var table; + + var MAX_LINE_LENGTH = 80; // make sure the description field in the tooltip + // does not exceed this length + + // Read in the json file that the dashboard.py script generated + // fetch('/home/aviary_vars.json') + fetch('./aviary_vars.json') .then((response) => response.json()) .then((json) => createTabulator(json)); -function displayValueInToolTip(e, cell, onRendered) { - //e - mouseover event - //cell - cell component - //onRendered - onRendered callback registration function - - var el = document.createElement("div"); - el.style.backgroundColor = "lightgreen"; -// el.innerText = cell.getColumn().getField() + " - " + cell.getValue(); //return cells "field - value"; - el.innerText = cell.getValue(); //return cells "field - value"; - - return el; -}; - -// Given the variable data as a JSON object, create the Tabulator displays on the Web page -function createTabulator(tableData) -{ - var table = new Tabulator("#example-table", { - height:"100%", - data: tableData, - dataTree: true, - dataTreeFilter: false, - headerSort: true, - movableColumns: false, - dataTreeStartExpanded: false, - // fitData - not a big difference maybe because already gave fixed width to the columns - // fitDataFill - not a big difference maybe because already gave fixed width to the columns - // fitDataTable - not a big difference maybe because already gave fixed width to the columns - // fitColumns - not a big difference maybe because already gave fixed width to the columns - // fitDataStretch - uses the full width for the value, which is good - layout:"fitDataStretch", - columns: [ - { - title: "Absolute Name", - field: "abs_name", - headerFilter: "input", - width: 550, - }, - { - title: "Promoted Name", - field: "prom_name", - headerFilter: "input", - width: 300, - tooltip: true, - }, - { - title: "Value", - field: "value", - width: 300, - tooltip: (e, cell, onRendered) => displayValueInToolTip(e, cell, onRendered), - }, - { - title: "Units", - field: "units", - width: 200, - tooltip: (e, cell, onRendered) => displayValueInToolTip(e, cell, onRendered), - }, - ] + // Add event listener to the checkbox for showing only aviary variables + document.getElementById('aviary-vars-filter').addEventListener('change', function () { + if (this.checked) { + applyRegexFilter(); + } else { + removeFilter(); + } }); -} + // Function to apply the regex filter + function applyRegexFilter() { + var regex = new RegExp("^(aircraft:|mission:)"); // Replace with your regex + table.setFilter((data) => { + return regex.test(data.prom_name); // Assuming you are filtering the 'name' column + }); + } + + // Function to remove the filter + function removeFilter() { + table.clearFilter(); + } + + function isDictionary(obj) { + return typeof obj === 'object' && obj !== null && !Array.isArray(obj) && !(obj instanceof Function); + } + + // function to count the initial spaces in a string + function countInitialSpaces(str) { + // This regex matches the initial spaces in the string + const match = str.match(/^\s*/); + // If there's a match, return its length; otherwise, return 0 + return match ? match[0].length : 0; + } + + // split a string so that it isn't too long for the tooltip + function splitStringIntoLines(str, maxLineLength = 80) { + // need to preserve the initial spaces of the string! + lengthOfInitialSpaces = countInitialSpaces(str); + initialWordOfSpaces = " ".repeat(lengthOfInitialSpaces); + const words = str.trim().split(' '); + words.unshift(initialWordOfSpaces); + const lines = []; + let currentLine = ''; + + words.forEach(word => { + // Truncate the word if it's longer than maxLineLength + if (word.length > maxLineLength) { + word = word.substring(0, maxLineLength); + } + + if ((currentLine + word).length > maxLineLength) { + lines.push(currentLine); + currentLine = word; + } else { + currentLine += word + ' '; + } + }); + + if (currentLine) { + lines.push(currentLine); + } + + return lines; + } + + // format each individual string for metadata + function formatIndividualMetadataString(preText, item, maxLineLength = 80) { + indent = preText.length; + resultString = ""; + s = JSON.stringify(item); + lines = splitStringIntoLines(preText + s, maxLineLength = MAX_LINE_LENGTH); + lines.forEach((line, index) => { + if (index == 0) { + resultString += line + "\n"; + } + else { + resultString += " ".repeat(indent) + line + "\n"; + } + }) + return resultString; + } + + function formatMetadataTooltip(cell) { + prom_name = cell.getValue(); + metadata = cell.getData().metadata; + + // Initialize a string to hold the resulting string + let resultString = "prom_name: " + prom_name + "\n"; + + dictObject = JSON.parse(metadata); + + // Iterate over each key-value pair + for (let key in dictObject) { + if (dictObject.hasOwnProperty(key)) { + // Append key and value to the result string + if (isDictionary(dictObject[key])) { + resultString += key + ": " + "\n"; + for (let hnkey in dictObject[key]) { + if (Array.isArray(dictObject[key][hnkey])) { + resultString += " " + hnkey + ": \n"; + for (let item of dictObject[key][hnkey]) { + resultString += formatIndividualMetadataString(" ", item); + } + } + else { + resultString += formatIndividualMetadataString(" " + hnkey + ": ", dictObject[key][hnkey]); + } + } + } + else { + resultString += formatIndividualMetadataString(key + ": ", dictObject[key]); + } + } + } + return resultString; + } + + // The Tabulator formatter function used to include a button to copy the + // value in the cell + function copyButtonFormatter(cell, formatterParams, onRendered) { + var cellContent = document.createElement("span"); + var cellValue = cell.getValue(); + if (cellValue.length === 0) { + return ""; + } + + var button = document.createElement("button"); + // button.textContent = "Copy"; + button.style.marginRight = "5px"; // Add some spacing between the cell content and the button + button.style.padding = "1px"; // Add some spacing between the cell content and the button + button.classList.add("btn", "btn-light", "btn-sm", "fa-xs"); // Add any additional classes for styling + + // Create an icon element using FontAwesome + var icon = document.createElement("i"); + icon.className = "fas fa-copy"; // Use the copy icon from FontAwesome + button.appendChild(icon); // Add the icon to the button + + // Append the button to the cellContent container + cellContent.appendChild(button); + + + var text = document.createElement("value"); + text.textContent = cellValue; + cellContent.appendChild(text); + + + onRendered(function () { + $(button).on('click', (e) => { + e.stopPropagation(); + navigator.clipboard.writeText(cellValue); + }); + } + ); + + return cellContent; + } + + // Given the variable data as a JSON object, create the Tabulator displays on the Web page + function createTabulator(tableData) { + table = new Tabulator("#example-table", { + height: "100%", + data: tableData, + dataTree: true, + dataTreeFilter: false, + headerSort: true, + movableColumns: false, + dataTreeStartExpanded: false, + // fitData - not a big difference maybe because already gave fixed width to the columns + // fitDataFill - not a big difference maybe because already gave fixed width to the columns + // fitDataTable - not a big difference maybe because already gave fixed width to the columns + // fitColumns - not a big difference maybe because already gave fixed width to the columns + // fitDataStretch - uses the full width for the value, which is good + layout: "fitDataStretch", + columns: [ + { + title: "Absolute Name", + field: "abs_name", + headerFilter: "input", + width: 550, + tooltip: function (e, cell) { + return formatMetadataTooltip(cell); + }, + formatter: copyButtonFormatter + }, + { + title: "Promoted Name", + field: "prom_name", + headerFilter: "input", + width: 300, + tooltip: function (e, cell) { + return formatMetadataTooltip(cell); + }, + formatter: copyButtonFormatter + }, + { + title: "Value", + field: "value", + width: 300, + tooltip: function (e, cell) { + return formatValueTooltip(cell); + }, + formatter: copyButtonFormatter + }, + { + title: "Units", + field: "units", + width: 120, + }, + ] + }); + } + }); \ No newline at end of file From 684269b3f1fae0be7a61bfcbb0524be4f4d3b470 Mon Sep 17 00:00:00 2001 From: Herb Schilling Date: Tue, 6 Feb 2024 13:18:22 -0500 Subject: [PATCH 04/12] Working version of code that adds the metadata hover over display to the abs name section, does a better job of formatting the aviary metadata in the hover over window, and adds a copy button to each cell to make it easy for the end user to copy that cell value --- .../assets/aviary_vars/script.js | 121 ++++++++++++++++-- 1 file changed, 113 insertions(+), 8 deletions(-) diff --git a/aviary/visualization/assets/aviary_vars/script.js b/aviary/visualization/assets/aviary_vars/script.js index 84eba9a2d..795889091 100755 --- a/aviary/visualization/assets/aviary_vars/script.js +++ b/aviary/visualization/assets/aviary_vars/script.js @@ -93,6 +93,109 @@ $(function () { return resultString; } + + // d3.format('g'); + + + function formatNumberIntelligently(value, precision = 3) { + // Determine the absolute value to decide on the formatting + const absValue = Math.abs(value); + + // Define thresholds for using scientific notation + const upperThreshold = 1e5; + const lowerThreshold = 1e-3; + + // Use scientific notation for very small or very large numbers + if (absValue >= upperThreshold || (absValue > 0 && absValue < lowerThreshold)) { + return value.toExponential(precision); + } + + // Use fixed-point notation for numbers within a 'normal' range, ensuring the precision is respected + // Adjust the maximumFractionDigits based on the integer part length to maintain overall precision + const integerPartLength = Math.floor(value) === 0 ? 1 : Math.floor(Math.log10(Math.abs(Math.floor(value)))) + 1; + const adjustedPrecision = Math.max(0, precision - integerPartLength); + + return value.toLocaleString('en-US', { + minimumFractionDigits: adjustedPrecision, + maximumFractionDigits: precision, + useGrouping: false, + }); + } + + // // Usage examples: + // console.log(formatNumberIntelligently(1234.5678)); // Outputs: "1234.568" + // console.log(formatNumberIntelligently(0.000123456)); // Outputs: "1.23e-4" + // console.log(formatNumberIntelligently(12345678)); // Outputs: "1.235e+7" + // console.log(formatNumberIntelligently(0.00123456)); // Outputs: "0.001" + // console.log(formatNumberIntelligently(123)); // Outputs: "123" + + + + + /** + * Convert an element to a string that is human readable. + * @param {Object} element The scalar item to convert. + * @returns {String} The string representation of the element. + */ + function elementToString(element) { + if (typeof element === 'number') { + if (Number.isInteger(element)) { return element.toString(); } + // return this.floatFormatter(element); /* float */ + return formatNumberIntelligently(element); /* float */ + } + + if (element === 'nan') { return element; } + return JSON.stringify(element); + } + + + + /** + * Convert a value to a string that can be used in Python code. + * @param {Object} val The value to convert. + * @returns {String} The string of the converted object. + */ + function valToCopyStringOld(val) { + if (!Array.isArray(val)) { return elementToString(val); } + + let valStr = 'array(['; + for (const element of val) { + valStr += valToCopyString(element) + ', '; + } + + if (val.length > 0) { + var qqq = valStr.replace(/^(.+)(, )$/, '$1])') + return valStr.replace(/^(.+)(, )$/, '$1])'); + } +} + +/** + * Convert a value to a string that can be used in Python code. + * @param {Object} val The value to convert. + * @returns {String} The string of the converted object. + */ +function valToCopyString(val, isTopLevel = true) { + if (!Array.isArray(val)) { return elementToString(val); } + let valStr; + + if (isTopLevel){ + valStr = 'array(['; + } else { + valStr = '['; + } + for (const element of val) { + valStr += valToCopyString(element, false) + ', '; + } + + if (val.length > 0) { + if (isTopLevel){ + return valStr.replace(/^(.+)(, )$/, '$1])'); + } else { + return valStr.replace(/^(.+)(, )$/, '$1]'); + } + } +} + function formatMetadataTooltip(cell) { prom_name = cell.getValue(); metadata = cell.getData().metadata; @@ -138,29 +241,30 @@ $(function () { } var button = document.createElement("button"); - // button.textContent = "Copy"; + button.textContent = "Copy"; button.style.marginRight = "5px"; // Add some spacing between the cell content and the button button.style.padding = "1px"; // Add some spacing between the cell content and the button - button.classList.add("btn", "btn-light", "btn-sm", "fa-xs"); // Add any additional classes for styling + // button.classList.add("btn", "btn-light", "btn-sm", "fa-xs"); // Add any additional classes for styling // Create an icon element using FontAwesome - var icon = document.createElement("i"); - icon.className = "fas fa-copy"; // Use the copy icon from FontAwesome - button.appendChild(icon); // Add the icon to the button + // var icon = document.createElement("i"); + // icon.className = "fas fa-copy"; // Use the copy icon from FontAwesome + // button.appendChild(icon); // Add the icon to the button // Append the button to the cellContent container cellContent.appendChild(button); var text = document.createElement("value"); - text.textContent = cellValue; + text.textContent = JSON.stringify(cellValue); cellContent.appendChild(text); onRendered(function () { $(button).on('click', (e) => { e.stopPropagation(); - navigator.clipboard.writeText(cellValue); + copiedCellValue = valToCopyString(cellValue); + navigator.clipboard.writeText(copiedCellValue); }); } ); @@ -210,7 +314,8 @@ $(function () { field: "value", width: 300, tooltip: function (e, cell) { - return formatValueTooltip(cell); + // return formatValueTooltip(cell); + return cell.getValue(); }, formatter: copyButtonFormatter }, From b89b6b6bc984e30edc09e9eeff77fff740e630e0 Mon Sep 17 00:00:00 2001 From: Herb Schilling Date: Wed, 7 Feb 2024 10:05:44 -0500 Subject: [PATCH 05/12] Removed font-awesome since using text for copy button, not icon. Added styling for the copy button --- .../assets/aviary_vars/index.html | 9 +- .../assets/aviary_vars/script.js | 188 +++++++----------- aviary/visualization/dashboard.py | 65 +++++- 3 files changed, 138 insertions(+), 124 deletions(-) diff --git a/aviary/visualization/assets/aviary_vars/index.html b/aviary/visualization/assets/aviary_vars/index.html index 7d1af2194..fe677784f 100755 --- a/aviary/visualization/assets/aviary_vars/index.html +++ b/aviary/visualization/assets/aviary_vars/index.html @@ -5,15 +5,18 @@ Aviary Dashboard - - - diff --git a/aviary/visualization/assets/aviary_vars/script.js b/aviary/visualization/assets/aviary_vars/script.js index 795889091..4650c31a7 100755 --- a/aviary/visualization/assets/aviary_vars/script.js +++ b/aviary/visualization/assets/aviary_vars/script.js @@ -38,6 +38,76 @@ $(function () { } // function to count the initial spaces in a string + // format floating point numbers using decimal or scientific notation + // as needed + function formatNumberIntelligently(value, precision = 3) { + // Determine the absolute value to decide on the formatting + const absValue = Math.abs(value); + + // Define thresholds for using scientific notation + const upperThreshold = 1e5; + const lowerThreshold = 1e-3; + + // Use scientific notation for very small or very large numbers + if (absValue >= upperThreshold || (absValue > 0 && absValue < lowerThreshold)) { + return value.toExponential(precision); + } + + // Use fixed-point notation for numbers within a 'normal' range, ensuring the precision is respected + // Adjust the maximumFractionDigits based on the integer part length to maintain overall precision + const integerPartLength = Math.floor(value) === 0 ? 1 : Math.floor(Math.log10(Math.abs(Math.floor(value)))) + 1; + const adjustedPrecision = Math.max(0, precision - integerPartLength); + + return value.toLocaleString('en-US', { + minimumFractionDigits: adjustedPrecision, + maximumFractionDigits: precision, + useGrouping: false, + }); + } + + /** + * Convert an element to a string that is human readable. + * @param {Object} element The scalar item to convert. + * @returns {String} The string representation of the element. + */ + function elementToString(element) { + if (typeof element === 'number') { + if (Number.isInteger(element)) { return element.toString(); } + // return this.floatFormatter(element); /* float */ + return formatNumberIntelligently(element); /* float */ + } + + if (element === 'nan') { return element; } + return JSON.stringify(element); + } + + /** + * Convert a value to a string that can be used in Python code. + * @param {Object} val The value to convert. + * @returns {String} The string of the converted object. + */ + function valToCopyString(val, isTopLevel = true) { + if (!Array.isArray(val)) { return elementToString(val); } + let valStr; + + if (isTopLevel) { + valStr = 'array(['; + } else { + valStr = '['; + } + for (const element of val) { + valStr += valToCopyString(element, false) + ', '; + } + + if (val.length > 0) { + if (isTopLevel) { + return valStr.replace(/^(.+)(, )$/, '$1])'); + } else { + return valStr.replace(/^(.+)(, )$/, '$1]'); + } + } + } + function countInitialSpaces(str) { // This regex matches the initial spaces in the string const match = str.match(/^\s*/); @@ -93,109 +163,7 @@ $(function () { return resultString; } - - // d3.format('g'); - - - function formatNumberIntelligently(value, precision = 3) { - // Determine the absolute value to decide on the formatting - const absValue = Math.abs(value); - - // Define thresholds for using scientific notation - const upperThreshold = 1e5; - const lowerThreshold = 1e-3; - - // Use scientific notation for very small or very large numbers - if (absValue >= upperThreshold || (absValue > 0 && absValue < lowerThreshold)) { - return value.toExponential(precision); - } - - // Use fixed-point notation for numbers within a 'normal' range, ensuring the precision is respected - // Adjust the maximumFractionDigits based on the integer part length to maintain overall precision - const integerPartLength = Math.floor(value) === 0 ? 1 : Math.floor(Math.log10(Math.abs(Math.floor(value)))) + 1; - const adjustedPrecision = Math.max(0, precision - integerPartLength); - - return value.toLocaleString('en-US', { - minimumFractionDigits: adjustedPrecision, - maximumFractionDigits: precision, - useGrouping: false, - }); - } - - // // Usage examples: - // console.log(formatNumberIntelligently(1234.5678)); // Outputs: "1234.568" - // console.log(formatNumberIntelligently(0.000123456)); // Outputs: "1.23e-4" - // console.log(formatNumberIntelligently(12345678)); // Outputs: "1.235e+7" - // console.log(formatNumberIntelligently(0.00123456)); // Outputs: "0.001" - // console.log(formatNumberIntelligently(123)); // Outputs: "123" - - - - - /** - * Convert an element to a string that is human readable. - * @param {Object} element The scalar item to convert. - * @returns {String} The string representation of the element. - */ - function elementToString(element) { - if (typeof element === 'number') { - if (Number.isInteger(element)) { return element.toString(); } - // return this.floatFormatter(element); /* float */ - return formatNumberIntelligently(element); /* float */ - } - - if (element === 'nan') { return element; } - return JSON.stringify(element); - } - - - - /** - * Convert a value to a string that can be used in Python code. - * @param {Object} val The value to convert. - * @returns {String} The string of the converted object. - */ - function valToCopyStringOld(val) { - if (!Array.isArray(val)) { return elementToString(val); } - - let valStr = 'array(['; - for (const element of val) { - valStr += valToCopyString(element) + ', '; - } - - if (val.length > 0) { - var qqq = valStr.replace(/^(.+)(, )$/, '$1])') - return valStr.replace(/^(.+)(, )$/, '$1])'); - } -} - -/** - * Convert a value to a string that can be used in Python code. - * @param {Object} val The value to convert. - * @returns {String} The string of the converted object. - */ -function valToCopyString(val, isTopLevel = true) { - if (!Array.isArray(val)) { return elementToString(val); } - let valStr; - - if (isTopLevel){ - valStr = 'array(['; - } else { - valStr = '['; - } - for (const element of val) { - valStr += valToCopyString(element, false) + ', '; - } - - if (val.length > 0) { - if (isTopLevel){ - return valStr.replace(/^(.+)(, )$/, '$1])'); - } else { - return valStr.replace(/^(.+)(, )$/, '$1]'); - } - } -} - + // format the entire text for the metadata tooltip function formatMetadataTooltip(cell) { prom_name = cell.getValue(); metadata = cell.getData().metadata; @@ -242,24 +210,15 @@ function valToCopyString(val, isTopLevel = true) { var button = document.createElement("button"); button.textContent = "Copy"; - button.style.marginRight = "5px"; // Add some spacing between the cell content and the button - button.style.padding = "1px"; // Add some spacing between the cell content and the button - // button.classList.add("btn", "btn-light", "btn-sm", "fa-xs"); // Add any additional classes for styling - - // Create an icon element using FontAwesome - // var icon = document.createElement("i"); - // icon.className = "fas fa-copy"; // Use the copy icon from FontAwesome - // button.appendChild(icon); // Add the icon to the button + button.classList.add("copy-button"); // Append the button to the cellContent container cellContent.appendChild(button); - var text = document.createElement("value"); text.textContent = JSON.stringify(cellValue); cellContent.appendChild(text); - onRendered(function () { $(button).on('click', (e) => { e.stopPropagation(); @@ -327,7 +286,4 @@ function valToCopyString(val, isTopLevel = true) { ] }); } - - - }); \ No newline at end of file diff --git a/aviary/visualization/dashboard.py b/aviary/visualization/dashboard.py index 730799c2c..b45d4813d 100644 --- a/aviary/visualization/dashboard.py +++ b/aviary/visualization/dashboard.py @@ -16,6 +16,7 @@ import openmdao.api as om from openmdao.utils.general_utils import env_truthy +from openmdao.utils.array_utils import convert_ndarray_to_support_nans_in_json import aviary.api as av @@ -133,6 +134,61 @@ def create_report_frame(format, text_filepath): return report_pane + +# def _convert_nans_in_nested_list(val_as_list): +# """ +# Given a list, possibly nested, replace any numpy.nan values with the string "nan". + +# This is done since JSON does not handle nan. This code is used to pass variable values +# to the N2 diagram. + +# The modifications to the list values are done in-place to avoid excessive copying of lists. + +# Parameters +# ---------- +# val_as_list : list, possibly nested +# the list whose nan elements need to be converted +# """ +# for i, val in enumerate(val_as_list): +# if isinstance(val, list): +# _convert_nans_in_nested_list(val) +# else: +# if np.isnan(val): +# val_as_list[i] = "nan" +# elif np.isinf(val): +# val_as_list[i] = "infinity" +# else: +# val_as_list[i] = val + +# def _convert_ndarray_to_support_nans_in_json(val): +# """ +# Given numpy array of arbitrary dimensions, return the equivalent nested list with nan replaced. + +# numpy.nan values are replaced with the string "nan". + +# Parameters +# ---------- +# val : ndarray +# the numpy array to be converted + +# Returns +# ------- +# object : list, possibly nested +# The equivalent list with any nan values replaced with the string "nan". +# """ +# val = np.asarray(val) + +# # do a quick check for any nans or infs and if not we can avoid the slow check +# nans = np.where(np.isnan(val)) +# infs = np.where(np.isinf(val)) +# if nans[0].size == 0 and infs[0].size == 0: +# return val.tolist() + +# val_as_list = val.tolist() +# _convert_nans_in_nested_list(val_as_list) +# return val_as_list + + def create_aviary_variables_table_data_nested(script_name, recorder_file): """ Create a JSON file with information about Aviary variables. @@ -188,8 +244,8 @@ def create_aviary_variables_table_data_nested(script_name, recorder_file): { "abs_name": group_name, "prom_name": prom_name, - "value": str(outputs[var_info]["val"]), - "units": str(outputs[var_info]["units"]), + "value": convert_ndarray_to_support_nans_in_json(outputs[var_info]["val"]), + "units": outputs[var_info]["units"], "metadata": json.dumps(aviary_metadata), } ) @@ -197,15 +253,14 @@ def create_aviary_variables_table_data_nested(script_name, recorder_file): # create children children_list = [] for children_name in grouped[group_name]: - # var_info = outputs[children_name] prom_name = outputs[children_name]["prom_name"] aviary_metadata = av.CoreMetaData.get(prom_name) children_list.append( { "abs_name": children_name, "prom_name": prom_name, - "value": str(outputs[children_name]["val"]), - "units": str(outputs[children_name]["units"]), + "value": convert_ndarray_to_support_nans_in_json(outputs[children_name]["val"]), + "units": outputs[children_name]["units"], "metadata": json.dumps(aviary_metadata), } ) From ccc61f37edf8a4b33a01313c8c746d443c7f6b3a Mon Sep 17 00:00:00 2001 From: Herb Schilling Date: Wed, 7 Feb 2024 10:13:48 -0500 Subject: [PATCH 06/12] removed commented out code and unneeded css. --- aviary/visualization/assets/aviary_styles.css | 4 -- aviary/visualization/dashboard.py | 55 ------------------- 2 files changed, 59 deletions(-) diff --git a/aviary/visualization/assets/aviary_styles.css b/aviary/visualization/assets/aviary_styles.css index b544f554c..7e65ed3be 100644 --- a/aviary/visualization/assets/aviary_styles.css +++ b/aviary/visualization/assets/aviary_styles.css @@ -19,8 +19,4 @@ nav#header { .bk-header .bk-tab { height: 25px;; -} - -.tabulator-tooltip { - background-color: #6bbecd; } \ No newline at end of file diff --git a/aviary/visualization/dashboard.py b/aviary/visualization/dashboard.py index b45d4813d..e28e3c452 100644 --- a/aviary/visualization/dashboard.py +++ b/aviary/visualization/dashboard.py @@ -134,61 +134,6 @@ def create_report_frame(format, text_filepath): return report_pane - -# def _convert_nans_in_nested_list(val_as_list): -# """ -# Given a list, possibly nested, replace any numpy.nan values with the string "nan". - -# This is done since JSON does not handle nan. This code is used to pass variable values -# to the N2 diagram. - -# The modifications to the list values are done in-place to avoid excessive copying of lists. - -# Parameters -# ---------- -# val_as_list : list, possibly nested -# the list whose nan elements need to be converted -# """ -# for i, val in enumerate(val_as_list): -# if isinstance(val, list): -# _convert_nans_in_nested_list(val) -# else: -# if np.isnan(val): -# val_as_list[i] = "nan" -# elif np.isinf(val): -# val_as_list[i] = "infinity" -# else: -# val_as_list[i] = val - -# def _convert_ndarray_to_support_nans_in_json(val): -# """ -# Given numpy array of arbitrary dimensions, return the equivalent nested list with nan replaced. - -# numpy.nan values are replaced with the string "nan". - -# Parameters -# ---------- -# val : ndarray -# the numpy array to be converted - -# Returns -# ------- -# object : list, possibly nested -# The equivalent list with any nan values replaced with the string "nan". -# """ -# val = np.asarray(val) - -# # do a quick check for any nans or infs and if not we can avoid the slow check -# nans = np.where(np.isnan(val)) -# infs = np.where(np.isinf(val)) -# if nans[0].size == 0 and infs[0].size == 0: -# return val.tolist() - -# val_as_list = val.tolist() -# _convert_nans_in_nested_list(val_as_list) -# return val_as_list - - def create_aviary_variables_table_data_nested(script_name, recorder_file): """ Create a JSON file with information about Aviary variables. From c848d9fd39f1f9d88c11b099ece62a08cc2d01bb Mon Sep 17 00:00:00 2001 From: Herb Schilling Date: Wed, 7 Feb 2024 14:26:47 -0500 Subject: [PATCH 07/12] Removed commented out code --- aviary/visualization/assets/aviary_vars/script.js | 5 ----- aviary/visualization/dashboard.py | 4 ++++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/aviary/visualization/assets/aviary_vars/script.js b/aviary/visualization/assets/aviary_vars/script.js index 4650c31a7..3efbd54be 100755 --- a/aviary/visualization/assets/aviary_vars/script.js +++ b/aviary/visualization/assets/aviary_vars/script.js @@ -241,11 +241,6 @@ $(function () { headerSort: true, movableColumns: false, dataTreeStartExpanded: false, - // fitData - not a big difference maybe because already gave fixed width to the columns - // fitDataFill - not a big difference maybe because already gave fixed width to the columns - // fitDataTable - not a big difference maybe because already gave fixed width to the columns - // fitColumns - not a big difference maybe because already gave fixed width to the columns - // fitDataStretch - uses the full width for the value, which is good layout: "fitDataStretch", columns: [ { diff --git a/aviary/visualization/dashboard.py b/aviary/visualization/dashboard.py index e28e3c452..350d7cafd 100644 --- a/aviary/visualization/dashboard.py +++ b/aviary/visualization/dashboard.py @@ -158,9 +158,12 @@ def create_aviary_variables_table_data_nested(script_name, recorder_file): """ cr = om.CaseReader(recorder_file) + print(f"r.list_c with {cr=}") + if 'final' not in cr.list_cases(): return None + print(f"cr.get_case with {cr=}") case = cr.get_case('final') outputs = case.list_outputs(explicit=True, implicit=True, val=True, residuals=True, residuals_tol=None, @@ -168,6 +171,7 @@ def create_aviary_variables_table_data_nested(script_name, recorder_file): scaling=False, hierarchical=True, print_arrays=True, out_stream=None, return_format='dict') + sorted_abs_names = sorted(outputs.keys()) grouped = {} From 2cfcc823fc1373447a45c57310b356e0acda529f Mon Sep 17 00:00:00 2001 From: Herb Schilling Date: Wed, 7 Feb 2024 14:47:18 -0500 Subject: [PATCH 08/12] removed unused import and pep8 fix --- aviary/visualization/dashboard.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/aviary/visualization/dashboard.py b/aviary/visualization/dashboard.py index 350d7cafd..7720ca044 100644 --- a/aviary/visualization/dashboard.py +++ b/aviary/visualization/dashboard.py @@ -1,5 +1,4 @@ import argparse -import glob import json import os from pathlib import Path @@ -171,7 +170,6 @@ def create_aviary_variables_table_data_nested(script_name, recorder_file): scaling=False, hierarchical=True, print_arrays=True, out_stream=None, return_format='dict') - sorted_abs_names = sorted(outputs.keys()) grouped = {} From b639a5823fde22fbfe4e7964d8fb90492857f9fe Mon Sep 17 00:00:00 2001 From: Herb Schilling Date: Thu, 8 Feb 2024 10:21:58 -0500 Subject: [PATCH 09/12] setup the imports for convert_ndarray_to_support_nans_in_json so that it can use the openmdao utils version if available, otherwise, use the private function in n2_viewer --- aviary/visualization/dashboard.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/aviary/visualization/dashboard.py b/aviary/visualization/dashboard.py index 7720ca044..cf130dc59 100644 --- a/aviary/visualization/dashboard.py +++ b/aviary/visualization/dashboard.py @@ -15,7 +15,13 @@ import openmdao.api as om from openmdao.utils.general_utils import env_truthy -from openmdao.utils.array_utils import convert_ndarray_to_support_nans_in_json + +# support getting this function from OpenMDAO post movement of the function to utils +# but also support it's old location +try: + from openmdao.utils.array_utils import convert_ndarray_to_support_nans_in_json +except ImportError: + from openmdao.visualization.n2_viewer.n2_viewer import _convert_nans_in_nested_list as convert_nans_in_nested_list import aviary.api as av From 445dc5227d39e0c89eff3c2a2e175a87b99fd67a Mon Sep 17 00:00:00 2001 From: Herb Schilling Date: Mon, 12 Feb 2024 13:03:32 -0500 Subject: [PATCH 10/12] Was importing the wrong function if getting convert_ndarray_to_support_nans_in_json from older version of OpenMDAO --- aviary/visualization/dashboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aviary/visualization/dashboard.py b/aviary/visualization/dashboard.py index cf130dc59..7eac4abee 100644 --- a/aviary/visualization/dashboard.py +++ b/aviary/visualization/dashboard.py @@ -21,7 +21,7 @@ try: from openmdao.utils.array_utils import convert_ndarray_to_support_nans_in_json except ImportError: - from openmdao.visualization.n2_viewer.n2_viewer import _convert_nans_in_nested_list as convert_nans_in_nested_list + from openmdao.visualization.n2_viewer.n2_viewer import _convert_ndarray_to_support_nans_in_json as convert_ndarray_to_support_nans_in_json import aviary.api as av From 246d5f8e883befacea433a43deab380927c570e7 Mon Sep 17 00:00:00 2001 From: Herb Schilling Date: Tue, 13 Feb 2024 10:48:46 -0500 Subject: [PATCH 11/12] Fixes based on reviewers comments. Adding white space between words in metadata tooltip was wrong. Strings were being rendered and copied with quotes around them but now those removed --- .../assets/aviary_vars/script.js | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/aviary/visualization/assets/aviary_vars/script.js b/aviary/visualization/assets/aviary_vars/script.js index 3efbd54be..f0344168a 100755 --- a/aviary/visualization/assets/aviary_vars/script.js +++ b/aviary/visualization/assets/aviary_vars/script.js @@ -65,6 +65,22 @@ $(function () { }); } + /** + * Converts the input element to a string representation intelligently. + * + * @param {any} element - The element to be converted to a string. Can be of any type. + * @returns {string} The string representation of the input element. + */ + function intelligentStringify(element) { + if (typeof element === 'string') { + // Return the string directly without quotes + return element; + } else { + // Use JSON.stringify for other types + return JSON.stringify(element); + } + } + /** * Convert an element to a string that is human readable. * @param {Object} element The scalar item to convert. @@ -77,8 +93,7 @@ $(function () { return formatNumberIntelligently(element); /* float */ } - if (element === 'nan') { return element; } - return JSON.stringify(element); + return intelligentStringify(element); } /** @@ -135,7 +150,7 @@ $(function () { lines.push(currentLine); currentLine = word; } else { - currentLine += word + ' '; + currentLine += ' ' + word; } }); @@ -216,7 +231,8 @@ $(function () { cellContent.appendChild(button); var text = document.createElement("value"); - text.textContent = JSON.stringify(cellValue); + + text.textContent = intelligentStringify(cellValue); cellContent.appendChild(text); onRendered(function () { From 01c874073e8c2fea4cb525efb2534f4a84e44cb9 Mon Sep 17 00:00:00 2001 From: Herb Schilling Date: Tue, 13 Feb 2024 10:50:42 -0500 Subject: [PATCH 12/12] removed some commented out code --- aviary/visualization/assets/aviary_vars/script.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/aviary/visualization/assets/aviary_vars/script.js b/aviary/visualization/assets/aviary_vars/script.js index f0344168a..eab752dea 100755 --- a/aviary/visualization/assets/aviary_vars/script.js +++ b/aviary/visualization/assets/aviary_vars/script.js @@ -6,7 +6,6 @@ $(function () { // does not exceed this length // Read in the json file that the dashboard.py script generated - // fetch('/home/aviary_vars.json') fetch('./aviary_vars.json') .then((response) => response.json()) .then((json) => createTabulator(json)); @@ -89,7 +88,6 @@ $(function () { function elementToString(element) { if (typeof element === 'number') { if (Number.isInteger(element)) { return element.toString(); } - // return this.floatFormatter(element); /* float */ return formatNumberIntelligently(element); /* float */ } @@ -284,7 +282,6 @@ $(function () { field: "value", width: 300, tooltip: function (e, cell) { - // return formatValueTooltip(cell); return cell.getValue(); }, formatter: copyButtonFormatter