From 53d7f02a1f24990bd9ad02dc7c6180a9c693afe7 Mon Sep 17 00:00:00 2001 From: Benedikt Seeger <benedikt.seeger@ptb.de> Date: Mon, 24 Feb 2025 17:43:33 +0100 Subject: [PATCH] aded first viewer --- .idea/.gitignore | 8 + .idea/dccviewertypescript.iml | 12 + .idea/inspectionProfiles/Project_Default.xml | 15 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + dcc-viewer/.idea/workspace.xml | 91 +++++ project_structure.json | 10 +- src/app.js | 17 +- src/main.js | 2 +- src/renderers/AdminRenderer.js | 4 - src/renderers/MeasurementRenderer.js | 343 ++++++++++++++++--- src/renderers/SelectRenderer.js | 16 +- src/styles.css | 16 +- vite.config.js | 2 - 14 files changed, 468 insertions(+), 82 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/dccviewertypescript.iml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 dcc-viewer/.idea/workspace.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/dccviewertypescript.iml b/.idea/dccviewertypescript.iml new file mode 100644 index 0000000..24643cc --- /dev/null +++ b/.idea/dccviewertypescript.iml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="WEB_MODULE" version="4"> + <component name="NewModuleRootManager"> + <content url="file://$MODULE_DIR$"> + <excludeFolder url="file://$MODULE_DIR$/.tmp" /> + <excludeFolder url="file://$MODULE_DIR$/temp" /> + <excludeFolder url="file://$MODULE_DIR$/tmp" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..798a81f --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,15 @@ +<component name="InspectionProjectProfileManager"> + <profile version="1.0"> + <option name="myName" value="Project Default" /> + <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="ignoredPackages"> + <value> + <list size="1"> + <item index="0" class="java.lang.String" itemvalue="bokeh" /> + </list> + </value> + </option> + </inspection_tool> + </profile> +</component> \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..53f01cf --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectModuleManager"> + <modules> + <module fileurl="file://$PROJECT_DIR$/.idea/dccviewertypescript.iml" filepath="$PROJECT_DIR$/.idea/dccviewertypescript.iml" /> + </modules> + </component> +</project> \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="VcsDirectoryMappings"> + <mapping directory="" vcs="Git" /> + </component> +</project> \ No newline at end of file diff --git a/dcc-viewer/.idea/workspace.xml b/dcc-viewer/.idea/workspace.xml new file mode 100644 index 0000000..cf5d6f0 --- /dev/null +++ b/dcc-viewer/.idea/workspace.xml @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="AutoImportSettings"> + <option name="autoReloadType" value="SELECTIVE" /> + </component> + <component name="ChangeListManager"> + <list default="true" id="c3d759c9-e9f8-41d4-b9b5-ef12db68e70c" name="Changes" comment=""> + <change beforePath="$PROJECT_DIR$/../src/app.js" beforeDir="false" afterPath="$PROJECT_DIR$/../src/app.js" afterDir="false" /> + <change beforePath="$PROJECT_DIR$/../src/main.js" beforeDir="false" afterPath="$PROJECT_DIR$/../src/main.js" afterDir="false" /> + <change beforePath="$PROJECT_DIR$/../src/renderers/AdminRenderer.js" beforeDir="false" afterPath="$PROJECT_DIR$/../src/renderers/AdminRenderer.js" afterDir="false" /> + <change beforePath="$PROJECT_DIR$/../src/renderers/MeasurementRenderer.js" beforeDir="false" afterPath="$PROJECT_DIR$/../src/renderers/MeasurementRenderer.js" afterDir="false" /> + <change beforePath="$PROJECT_DIR$/../src/renderers/SelectRenderer.js" beforeDir="false" afterPath="$PROJECT_DIR$/../src/renderers/SelectRenderer.js" afterDir="false" /> + <change beforePath="$PROJECT_DIR$/../src/styles.css" beforeDir="false" afterPath="$PROJECT_DIR$/../src/styles.css" afterDir="false" /> + <change beforePath="$PROJECT_DIR$/../vite.config.js" beforeDir="false" afterPath="$PROJECT_DIR$/../vite.config.js" afterDir="false" /> + </list> + <option name="SHOW_DIALOG" value="false" /> + <option name="HIGHLIGHT_CONFLICTS" value="true" /> + <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> + <option name="LAST_RESOLUTION" value="IGNORE" /> + </component> + <component name="FileTemplateManagerImpl"> + <option name="RECENT_TEMPLATES"> + <list> + <option value="JavaScript File" /> + </list> + </option> + </component> + <component name="Git.Settings"> + <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." /> + </component> + <component name="ProjectColorInfo">{ + "associatedIndex": 6 +}</component> + <component name="ProjectId" id="2tLWdqlIz5X4SCSwK5EjWvfiZtG" /> + <component name="ProjectViewState"> + <option name="hideEmptyMiddlePackages" value="true" /> + <option name="showLibraryContents" value="true" /> + </component> + <component name="PropertiesComponent"><![CDATA[{ + "keyToString": { + "JavaScript Debug.Debug Vite.executor": "Run", + "JavaScript Debug.dccViewer.executor": "Debug", + "JavaScript Debug.index.html.executor": "Run", + "Node.js.vite.config.js.executor": "Run", + "RunOnceActivity.ShowReadmeOnStart": "true", + "RunOnceActivity.git.unshallow": "true", + "git-widget-placeholder": "main", + "last_opened_file_path": "/home/seeger01/repos/dccviewertypescript", + "node.js.detected.package.eslint": "true", + "node.js.detected.package.tslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "node.js.selected.package.tslint": "(autodetect)", + "nodejs_package_manager_path": "npm", + "settings.editor.selected.configurable": "http.proxy", + "ts.external.directory.path": "/home/seeger01/Downloads/WebStorm-243.24978.60/plugins/javascript-plugin/jsLanguageServicesImpl/external", + "vue.rearranger.settings.migration": "true" + } +}]]></component> + <component name="RunManager"> + <configuration name="dccViewer" type="JavascriptDebugType" uri="http://localhost:5173/"> + <method v="2" /> + </configuration> + </component> + <component name="SharedIndexes"> + <attachedChunks> + <set> + <option value="bundled-js-predefined-d6986cc7102b-76f8388c3a79-JavaScript-WS-243.24978.60" /> + </set> + </attachedChunks> + </component> + <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" /> + <component name="TaskManager"> + <task active="true" id="Default" summary="Default task"> + <changelist id="c3d759c9-e9f8-41d4-b9b5-ef12db68e70c" name="Changes" comment="" /> + <created>1740131925655</created> + <option name="number" value="Default" /> + <option name="presentableId" value="Default" /> + <updated>1740131925655</updated> + <workItem from="1740131927264" duration="102000" /> + <workItem from="1740132052064" duration="10000" /> + <workItem from="1740132146315" duration="24000" /> + <workItem from="1740132175271" duration="89000" /> + <workItem from="1740158794192" duration="1155000" /> + <workItem from="1740380748268" duration="5753000" /> + </task> + <servers /> + </component> + <component name="TypeScriptGeneratedFilesManager"> + <option name="version" value="3" /> + </component> +</project> \ No newline at end of file diff --git a/project_structure.json b/project_structure.json index 746ffdf..55cbd1e 100644 --- a/project_structure.json +++ b/project_structure.json @@ -1,15 +1,7 @@ { - "package.json": "{\n \"name\": \"dcc-viewer\",\n \"version\": \"1.0.0\",\n \"description\": \"Digital Calibration Certificate Viewer\",\n \"scripts\": {\n \"dev\": \"vite\",\n \"build\": \"vite build\",\n \"preview\": \"vite preview\"\n },\n \"dependencies\": {\n \"xml2js\": \"^0.4.23\",\n \"jsoneditor\": \"^9.5.6\",\n \"plotly.js-dist\": \"^2.18.2\",\n \"events\": \"^3.3.0\"\n },\n \"devDependencies\": {\n \"vite\": \"^4.0.0\"\n }\n}\n", - "vite.config.js": "import { defineConfig } from 'vite';\n\nexport default defineConfig({\n resolve: {\n alias: {\n events: 'events'\n }\n },\n optimizeDeps: {\n esbuildOptions: {\n define: { global: 'globalThis' }\n }\n }\n});\n", - "index.html": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>DCC Viewer</title>\n <link rel=\"stylesheet\" href=\"/src/styles.css\">\n</head>\n<body>\n <div id=\"app\"></div>\n <script type=\"module\" src=\"/src/main.js\"></script>\n</body>\n</html>\n", "src": { - "main.js": "import { initApp } from './app.js';\n\n// Initialize after DOM loads\ndocument.addEventListener('DOMContentLoaded', () => {\n initApp();\n});\n", - "app.js": "import { parseString } from 'xml2js';\n\n// Import our interactive measurement renderer\nimport { renderMeasurementResults } from './renderers/MeasurementRenderer.js';\nimport { renderAdminData } from './renderers/AdminRenderer.js';\n\n// Global configuration\nconst selectableLanguages = ['en', 'de', 'fr', 'es'];\nlet selectedLanguage = 'en';\nlet dccData = null;\n\nexport function initApp() {\n const appContainer = document.getElementById('app');\n\n // Create language selection dropdown\n const langSelect = document.createElement('select');\n selectableLanguages.forEach(lang => {\n const option = document.createElement('option');\n option.value = lang;\n option.textContent = lang;\n if (lang === selectedLanguage) option.selected = true;\n langSelect.appendChild(option);\n });\n langSelect.addEventListener('change', (e) => {\n selectedLanguage = e.target.value;\n renderAll();\n });\n appContainer.appendChild(langSelect);\n\n // Create containers for admin data and measurement results\n const adminContainer = document.createElement('div');\n adminContainer.id = 'adminData';\n appContainer.appendChild(adminContainer);\n\n const measContainer = document.createElement('div');\n measContainer.id = 'measurementResults';\n appContainer.appendChild(measContainer);\n\n // Load the XML file from the data folder\n fetch('/data/sin_acceleration_example_dcc_WithExampleConformatyStatment.xml')\n .then(response => response.text())\n .then(xmlText => {\n parseString(xmlText, { explicitArray: false }, (err, result) => {\n if (err) {\n console.error('Error parsing XML:', err);\n return;\n }\n dccData = result;\n renderAll();\n });\n })\n .catch(err => console.error('Error loading XML file:', err));\n}\n\nfunction renderAll() {\n // Clear containers\n document.getElementById('adminData').innerHTML = '';\n document.getElementById('measurementResults').innerHTML = '';\n\n if (dccData && dccData['dcc:digitalCalibrationCertificate']) {\n const cert = dccData['dcc:digitalCalibrationCertificate'];\n if (cert['dcc:administrativeData']) {\n renderAdminData(cert['dcc:administrativeData'], selectedLanguage);\n }\n if (cert['dcc:measurementResults']) {\n renderMeasurementResults(cert['dcc:measurementResults'], selectedLanguage);\n }\n }\n}\n", - "styles.css": "body {\n font-family: Arial, sans-serif;\n margin: 20px;\n}\n\nh2, h3 {\n color: #333;\n}\n\n#adminData, #measurementResults {\n margin-top: 20px;\n border: 1px solid #ccc;\n padding: 10px;\n}\n\ntable {\n width: 100%;\n border-collapse: collapse;\n}\n\ntable, th, td {\n border: 1px solid #ccc;\n}\n\nth, td {\n padding: 4px;\n text-align: center;\n}\n\n.plot-and-table, .plot-table {\n margin: 10px 0;\n padding: 10px;\n border: 1px dashed #999;\n}\n", "renderers": { - "AdminRenderer.js": "import JSONEditor from 'jsoneditor';\nimport 'jsoneditor/dist/jsoneditor.css';\n\nexport function renderAdminData(adminData, language) {\n const container = document.getElementById('adminData');\n const title = document.createElement('h2');\n title.textContent = 'Administrative Data (' + language + ')';\n container.appendChild(title);\n\n const options = {\n mode: 'view',\n mainMenuBar: false,\n navigationBar: false,\n statusBar: false\n };\n const editor = new JSONEditor(container, options);\n editor.set(adminData);\n}\n", - "SelectRenderer.js": "export function selectRenderer(json, language) {\n // This function is not used in the interactive measurement renderer\n const container = document.createElement('div');\n container.textContent = 'Interactive Measurement Renderer in use.';\n return container;\n}\n", - "MeasurementRenderer.js": "import Plotly from 'plotly.js-dist';\n\nexport function renderMeasurementResults(measurementResults, language) {\n const container = document.getElementById('measurementResults');\n container.innerHTML = '';\n\n // Use the first measurementResult for this example\n let result = measurementResults['dcc:measurementResult'];\n if (!Array.isArray(result)) {\n result = [result];\n }\n result = result[0];\n\n // Use dcc:name content for the tab title\n let resultName = 'Measurement Result';\n if (result['dcc:name'] && result['dcc:name']['dcc:content']) {\n let content = result['dcc:name']['dcc:content'];\n if (Array.isArray(content)) {\n const match = content.find(item => item.$ && item.$.lang === language) || content[0];\n resultName = match._ || match;\n } else {\n resultName = content._ || content;\n }\n }\n const tabTitle = document.createElement('h2');\n tabTitle.textContent = resultName + ' (' + language + ')';\n container.appendChild(tabTitle);\n\n // Process the measurement data from dcc:data -> dcc:list\n if (!result['dcc:data'] || !result['dcc:data']['dcc:list']) return;\n const listData = result['dcc:data']['dcc:list'];\n\n // Flatten all quantities from the list\n let quantities = [];\n if (listData['dcc:quantity']) {\n quantities = Array.isArray(listData['dcc:quantity']) ? listData['dcc:quantity'] : [listData['dcc:quantity']];\n }\n // Also add quantities from measurementMetaData\n if (listData['dcc:measurementMetaData'] && listData['dcc:measurementMetaData']['dcc:metaData']) {\n let metaData = listData['dcc:measurementMetaData']['dcc:metaData'];\n if (!Array.isArray(metaData)) metaData = [metaData];\n metaData.forEach(md => {\n if (md['dcc:data'] && md['dcc:data']['dcc:quantity']) {\n let qs = md['dcc:data']['dcc:quantity'];\n if (!Array.isArray(qs)) qs = [qs];\n quantities = quantities.concat(qs);\n }\n });\n }\n\n // Separate index quantities and data quantities\n const indexQuantities = [];\n const dataQuantities = [];\n quantities.forEach(q => {\n if (q.$ && q.$.refType && q.$.refType.match(/basic_tableIndex/)) {\n indexQuantities.push(q);\n } else {\n dataQuantities.push(q);\n }\n });\n\n // Create radio buttons for X-axis selection (only from indexQuantities)\n const xAxisContainer = document.createElement('div');\n xAxisContainer.innerHTML = '<strong>Select X-Axis:</strong> ';\n indexQuantities.forEach((q, idx) => {\n let nameStr = 'Index ' + idx;\n if (q['dcc:name'] && q['dcc:name']['dcc:content']) {\n let content = q['dcc:name']['dcc:content'];\n if (Array.isArray(content)) {\n const match = content.find(item => item.$ && item.$.lang === language) || content[0];\n nameStr = match._ || match;\n } else {\n nameStr = content._ || content;\n }\n }\n const radio = document.createElement('input');\n radio.type = 'radio';\n radio.name = 'xAxisSelect';\n radio.value = idx;\n if (idx === 0) radio.checked = true;\n const label = document.createElement('label');\n label.textContent = nameStr;\n label.style.marginRight = '10px';\n xAxisContainer.appendChild(radio);\n xAxisContainer.appendChild(label);\n });\n container.appendChild(xAxisContainer);\n\n // Optional tolerance toggle (placeholder for future implementation)\n const toleranceToggle = document.createElement('input');\n toleranceToggle.type = 'checkbox';\n toleranceToggle.id = 'toleranceToggle';\n const tolLabel = document.createElement('label');\n tolLabel.htmlFor = 'toleranceToggle';\n tolLabel.textContent = ' Enable tolerance markings';\n const tolContainer = document.createElement('div');\n tolContainer.appendChild(toleranceToggle);\n tolContainer.appendChild(tolLabel);\n container.appendChild(tolContainer);\n\n // Create containers for plots and table\n const plotsContainer = document.createElement('div');\n plotsContainer.id = 'plotsContainer';\n container.appendChild(plotsContainer);\n const tableContainer = document.createElement('div');\n tableContainer.id = 'tableContainer';\n container.appendChild(tableContainer);\n\n // Function to update the visualization\n function updateVisualization() {\n // Determine selected X-axis (default index0)\n const selectedIndex = document.querySelector('input[name=\"xAxisSelect\"]:checked').value;\n const xQuantity = indexQuantities[selectedIndex];\n let xValues = [];\n if (xQuantity && xQuantity['si:realListXMLList'] && xQuantity['si:realListXMLList']['si:valueXMLList']) {\n xValues = xQuantity['si:realListXMLList']['si:valueXMLList'].trim().split(/\\s+/).map(v => parseFloat(v));\n }\n\n // Build table data\n const headers = [];\n // Header for X-axis\n let xHeader = 'X-Axis';\n if (xQuantity['dcc:name'] && xQuantity['dcc:name']['dcc:content']) {\n let content = xQuantity['dcc:name']['dcc:content'];\n if (Array.isArray(content)) {\n const match = content.find(item => item.$ && item.$.lang === language) || content[0];\n xHeader = match._ || match;\n } else {\n xHeader = content._ || content;\n }\n }\n headers.push(xHeader);\n\n const dataValues = [];\n dataQuantities.forEach(q => {\n let header = 'Data';\n if (q['dcc:name'] && q['dcc:name']['dcc:content']) {\n let content = q['dcc:name']['dcc:content'];\n if (Array.isArray(content)) {\n const match = content.find(item => item.$ && item.$.lang === language) || content[0];\n header = match._ || match;\n } else {\n header = content._ || content;\n }\n }\n headers.push(header);\n let values = [];\n if (q['si:realListXMLList'] && q['si:realListXMLList']['si:valueXMLList']) {\n values = q['si:realListXMLList']['si:valueXMLList'].trim().split(/\\s+/).map(v => parseFloat(v));\n }\n if (values.length === 1 && xValues.length > 1) {\n values = new Array(xValues.length).fill(values[0]);\n }\n dataValues.push(values);\n });\n\n // Build table rows\n const tableData = [headers];\n for (let i = 0; i < xValues.length; i++) {\n const row = [];\n row.push(xValues[i]);\n dataValues.forEach(values => {\n row.push(values[i] !== undefined ? values[i] : '');\n });\n tableData.push(row);\n }\n renderTable(tableData);\n\n // Group data quantities by unit for plotting\n const unitGroups = {};\n dataQuantities.forEach((q, idx) => {\n let unit = '';\n if (q['si:realListXMLList'] && q['si:realListXMLList']['si:unitXMLList']) {\n unit = q['si:realListXMLList']['si:unitXMLList'].trim();\n }\n if (!unitGroups[unit]) {\n unitGroups[unit] = [];\n }\n let header = headers[idx + 1];\n let values = [];\n if (q['si:realListXMLList'] && q['si:realListXMLList']['si:valueXMLList']) {\n values = q['si:realListXMLList']['si:valueXMLList'].trim().split(/\\s+/).map(v => parseFloat(v));\n }\n if (values.length === 1 && xValues.length > 1) {\n values = new Array(xValues.length).fill(values[0]);\n }\n unitGroups[unit].push({ name: header, y: values });\n });\n\n // Clear plots container\n plotsContainer.innerHTML = '';\n Object.keys(unitGroups).forEach(unit => {\n const graphDiv = document.createElement('div');\n graphDiv.style.width = '100%';\n graphDiv.style.height = '300px';\n plotsContainer.appendChild(graphDiv);\n\n const traces = unitGroups[unit].map(trace => {\n return {\n x: xValues,\n y: trace.y,\n type: 'scatter',\n mode: 'lines+markers',\n name: trace.name\n };\n });\n const layout = {\n title: 'Plot (' + unit + ')',\n xaxis: { title: xHeader },\n yaxis: { title: unit },\n hovermode: 'closest'\n };\n Plotly.newPlot(graphDiv, traces, layout);\n\n // Coupled mouseover events\n graphDiv.on('plotly_hover', function(data) {\n if (data.points && data.points.length > 0) {\n const pointIndex = data.points[0].pointIndex + 1; // offset for header row\n highlightTableRow(pointIndex);\n }\n });\n graphDiv.on('plotly_unhover', function() {\n clearTableRowHighlights();\n });\n });\n }\n\n // Render table from 2D array\n function renderTable(tableData) {\n tableContainer.innerHTML = '';\n const table = document.createElement('table');\n tableData.forEach((rowData, rowIndex) => {\n const tr = document.createElement('tr');\n rowData.forEach(cellData => {\n const cell = document.createElement(rowIndex === 0 ? 'th' : 'td');\n cell.textContent = cellData;\n cell.style.padding = '4px';\n cell.style.border = '1px solid #ccc';\n tr.appendChild(cell);\n });\n tr.addEventListener('mouseover', () => { tr.style.backgroundColor = '#eef'; });\n tr.addEventListener('mouseout', () => { tr.style.backgroundColor = ''; });\n table.appendChild(tr);\n });\n tableContainer.appendChild(table);\n }\n\n // Placeholder functions for coupled table row highlights\n function highlightTableRow(rowIndex) {\n const rows = tableContainer.querySelectorAll('tr');\n if (rows[rowIndex]) {\n rows[rowIndex].style.backgroundColor = '#fee';\n }\n }\n function clearTableRowHighlights() {\n const rows = tableContainer.querySelectorAll('tr');\n rows.forEach(row => row.style.backgroundColor = '');\n }\n\n // Initial update\n updateVisualization();\n\n // Update visualization on X-axis selection change\n const radios = document.querySelectorAll('input[name=\"xAxisSelect\"]');\n radios.forEach(radio => {\n radio.addEventListener('change', updateVisualization);\n });\n\n // Tolerance toggle event (placeholder)\n toleranceToggle.addEventListener('change', () => {\n console.log('Tolerance toggle:', toleranceToggle.checked);\n // Future implementation: update plot/table for tolerance markings and color coding\n });\n}\n" + "MeasurementRenderer.js": "import Plotly from 'plotly.js-dist';\n\nexport function renderMeasurementResults(measurementResults, language) {\n console.debug('renderMeasurementResults called with:', measurementResults);\n const container = document.getElementById('measurementResults');\n container.innerHTML = '';\n\n if (!measurementResults) {\n console.error('No measurementResults provided.');\n return;\n }\n\n let result = measurementResults['dcc:measurementResult'];\n console.debug('Raw dcc:measurementResult:', result);\n\n if (!result) {\n console.error(\"Missing 'dcc:measurementResult' in measurementResults\");\n return;\n }\n\n if (!Array.isArray(result)) {\n result = [result];\n }\n console.debug('Processed measurement result array:', result);\n\n // Use the first measurement result for rendering\n result = result[0];\n\n // Get the measurement result name from dcc:name\n let resultName = 'Measurement Result';\n if (result['dcc:name'] && result['dcc:name']['dcc:content']) {\n let content = result['dcc:name']['dcc:content'];\n if (Array.isArray(content)) {\n const match = content.find(item => item.$ && item.$.lang === language) || content[0];\n resultName = match._ || match;\n } else {\n resultName = content._ || content;\n }\n }\n console.debug('Result name:', resultName);\n\n const tabTitle = document.createElement('h2');\n tabTitle.textContent = resultName + ' (' + language + ')';\n container.appendChild(tabTitle);\n\n if (!result['dcc:data'] || !result['dcc:data']['dcc:list']) {\n console.error(\"Missing 'dcc:data' or 'dcc:list' in measurement result:\", result);\n return;\n }\n const listData = result['dcc:data']['dcc:list'];\n console.debug('List data:', listData);\n\n // Flatten all quantities from the list\n let quantities = [];\n if (listData['dcc:quantity']) {\n quantities = Array.isArray(listData['dcc:quantity']) ? listData['dcc:quantity'] : [listData['dcc:quantity']];\n }\n console.debug('Quantities from list:', quantities);\n\n // Also add quantities from measurementMetaData\n if (listData['dcc:measurementMetaData'] && listData['dcc:measurementMetaData']['dcc:metaData']) {\n let metaData = listData['dcc:measurementMetaData']['dcc:metaData'];\n if (!Array.isArray(metaData)) metaData = [metaData];\n metaData.forEach(md => {\n if (md['dcc:data'] && md['dcc:data']['dcc:quantity']) {\n let qs = md['dcc:data']['dcc:quantity'];\n if (!Array.isArray(qs)) qs = [qs];\n quantities = quantities.concat(qs);\n }\n });\n }\n console.debug('Combined quantities:', quantities);\n\n // Separate index quantities and data quantities\n const indexQuantities = [];\n const dataQuantities = [];\n quantities.forEach(q => {\n if (q.$ && q.$.refType && q.$.refType.match(/basic_tableIndex/)) {\n indexQuantities.push(q);\n } else {\n dataQuantities.push(q);\n }\n });\n console.debug('Index Quantities:', indexQuantities);\n console.debug('Data Quantities:', dataQuantities);\n\n // Create radio buttons for X-axis selection (from indexQuantities)\n const xAxisContainer = document.createElement('div');\n xAxisContainer.innerHTML = '<strong>Select X-Axis:</strong> ';\n indexQuantities.forEach((q, idx) => {\n let nameStr = 'Index ' + idx;\n if (q['dcc:name'] && q['dcc:name']['dcc:content']) {\n let content = q['dcc:name']['dcc:content'];\n if (Array.isArray(content)) {\n const match = content.find(item => item.$ && item.$.lang === language) || content[0];\n nameStr = match._ || match;\n } else {\n nameStr = content._ || content;\n }\n }\n const radio = document.createElement('input');\n radio.type = 'radio';\n radio.name = 'xAxisSelect';\n radio.value = idx;\n if (idx === 0) radio.checked = true;\n const label = document.createElement('label');\n label.textContent = nameStr;\n label.style.marginRight = '10px';\n xAxisContainer.appendChild(radio);\n xAxisContainer.appendChild(label);\n });\n container.appendChild(xAxisContainer);\n\n // Tolerance toggle (placeholder)\n const toleranceToggle = document.createElement('input');\n toleranceToggle.type = 'checkbox';\n toleranceToggle.id = 'toleranceToggle';\n const tolLabel = document.createElement('label');\n tolLabel.htmlFor = 'toleranceToggle';\n tolLabel.textContent = ' Enable tolerance markings';\n const tolContainer = document.createElement('div');\n tolContainer.appendChild(toleranceToggle);\n tolContainer.appendChild(tolLabel);\n container.appendChild(tolContainer);\n\n // Create containers for plots and table\n const plotsContainer = document.createElement('div');\n plotsContainer.id = 'plotsContainer';\n container.appendChild(plotsContainer);\n const tableContainer = document.createElement('div');\n tableContainer.id = 'tableContainer';\n container.appendChild(tableContainer);\n\n // Function to update the visualization\n function updateVisualization() {\n const selectedRadio = document.querySelector('input[name=\"xAxisSelect\"]:checked');\n if (!selectedRadio) {\n console.error('No X-Axis selection found.');\n return;\n }\n const selectedIndex = selectedRadio.value;\n const xQuantity = indexQuantities[selectedIndex];\n let xValues = [];\n if (xQuantity && xQuantity['si:realListXMLList'] && xQuantity['si:realListXMLList']['si:valueXMLList']) {\n xValues = xQuantity['si:realListXMLList']['si:valueXMLList'].trim().split(/\\s+/).map(v => parseFloat(v));\n }\n console.debug('Selected X-Axis values:', xValues);\n\n // Build table headers and values\n const headers = [];\n let xHeader = 'X-Axis';\n if (xQuantity['dcc:name'] && xQuantity['dcc:name']['dcc:content']) {\n let content = xQuantity['dcc:name']['dcc:content'];\n if (Array.isArray(content)) {\n const match = content.find(item => item.$ && item.$.lang === language) || content[0];\n xHeader = match._ || match;\n } else {\n xHeader = content._ || content;\n }\n }\n headers.push(xHeader);\n\n const dataValues = [];\n dataQuantities.forEach(q => {\n let header = 'Data';\n if (q['dcc:name'] && q['dcc:name']['dcc:content']) {\n let content = q['dcc:name']['dcc:content'];\n if (Array.isArray(content)) {\n const match = content.find(item => item.$ && item.$.lang === language) || content[0];\n header = match._ || match;\n } else {\n header = content._ || content;\n }\n }\n headers.push(header);\n let values = [];\n if (q['si:realListXMLList'] && q['si:realListXMLList']['si:valueXMLList']) {\n values = q['si:realListXMLList']['si:valueXMLList'].trim().split(/\\s+/).map(v => parseFloat(v));\n }\n if (values.length === 1 && xValues.length > 1) {\n values = new Array(xValues.length).fill(values[0]);\n }\n dataValues.push(values);\n });\n\n // Build table rows\n const tableData = [headers];\n for (let i = 0; i < xValues.length; i++) {\n const row = [];\n row.push(xValues[i]);\n dataValues.forEach(values => {\n row.push(values[i] !== undefined ? values[i] : '');\n });\n tableData.push(row);\n }\n console.debug('Table data:', tableData);\n renderTable(tableData);\n\n // Group data quantities by unit for plotting\n const unitGroups = {};\n dataQuantities.forEach((q, idx) => {\n let unit = '';\n if (q['si:realListXMLList'] && q['si:realListXMLList']['si:unitXMLList']) {\n unit = q['si:realListXMLList']['si:unitXMLList'].trim();\n }\n if (!unitGroups[unit]) {\n unitGroups[unit] = [];\n }\n let header = headers[idx + 1];\n let values = [];\n if (q['si:realListXMLList'] && q['si:realListXMLList']['si:valueXMLList']) {\n values = q['si:realListXMLList']['si:valueXMLList'].trim().split(/\\s+/).map(v => parseFloat(v));\n }\n if (values.length === 1 && xValues.length > 1) {\n values = new Array(xValues.length).fill(values[0]);\n }\n unitGroups[unit].push({ name: header, y: values });\n });\n console.debug('Unit groups for plots:', unitGroups);\n\n // Clear and render plots\n plotsContainer.innerHTML = '';\n Object.keys(unitGroups).forEach(unit => {\n const graphDiv = document.createElement('div');\n graphDiv.style.width = '100%';\n graphDiv.style.height = '300px';\n plotsContainer.appendChild(graphDiv);\n\n const traces = unitGroups[unit].map(trace => {\n return {\n x: xValues,\n y: trace.y,\n type: 'scatter',\n mode: 'lines+markers',\n name: trace.name\n };\n });\n const layout = {\n title: 'Plot (' + unit + ')',\n xaxis: { title: xHeader },\n yaxis: { title: unit },\n hovermode: 'closest'\n };\n Plotly.newPlot(graphDiv, traces, layout).then(() => {\n console.debug('Plot rendered for unit:', unit);\n });\n\n // Coupled mouseover events\n graphDiv.on('plotly_hover', function(data) {\n if (data.points && data.points.length > 0) {\n const pointIndex = data.points[0].pointIndex + 1; // offset for header row\n highlightTableRow(pointIndex);\n }\n });\n graphDiv.on('plotly_unhover', function() {\n clearTableRowHighlights();\n });\n });\n }\n\n // Render table from a 2D array\n function renderTable(tableData) {\n tableContainer.innerHTML = '';\n const table = document.createElement('table');\n tableData.forEach((rowData, rowIndex) => {\n const tr = document.createElement('tr');\n rowData.forEach(cellData => {\n const cell = document.createElement(rowIndex === 0 ? 'th' : 'td');\n cell.textContent = cellData;\n cell.style.padding = '4px';\n cell.style.border = '1px solid #ccc';\n tr.appendChild(cell);\n });\n tr.addEventListener('mouseover', () => { tr.style.backgroundColor = '#eef'; });\n tr.addEventListener('mouseout', () => { tr.style.backgroundColor = ''; });\n table.appendChild(tr);\n });\n tableContainer.appendChild(table);\n }\n\n // Functions for coupled table row highlights\n function highlightTableRow(rowIndex) {\n const rows = tableContainer.querySelectorAll('tr');\n if (rows[rowIndex]) {\n rows[rowIndex].style.backgroundColor = '#fee';\n }\n }\n function clearTableRowHighlights() {\n const rows = tableContainer.querySelectorAll('tr');\n rows.forEach(row => row.style.backgroundColor = '');\n }\n\n // Initial update\n updateVisualization();\n\n // Update visualization when X-axis selection changes\n const radios = document.querySelectorAll('input[name=\"xAxisSelect\"]');\n radios.forEach(radio => {\n radio.addEventListener('change', updateVisualization);\n });\n\n // Tolerance toggle event (placeholder)\n toleranceToggle.addEventListener('change', () => {\n console.log('Tolerance toggle:', toleranceToggle.checked);\n // Future: update plot/table for tolerance markings and color coding\n });\n}\n" } } } diff --git a/src/app.js b/src/app.js index 95e25b5..6a2a9c9 100644 --- a/src/app.js +++ b/src/app.js @@ -1,9 +1,8 @@ import { parseString } from 'xml2js'; -import JSONEditor from 'jsoneditor'; -// Import renderer functions -import { renderAdminData } from './renderers/AdminRenderer.js'; +// Import our interactive measurement renderer import { renderMeasurementResults } from './renderers/MeasurementRenderer.js'; +import { renderAdminData } from './renderers/AdminRenderer.js'; // Global configuration const selectableLanguages = ['en', 'de', 'fr', 'es']; @@ -28,7 +27,7 @@ export function initApp() { }); appContainer.appendChild(langSelect); - // Create containers for administrative data and measurement results + // Create containers for admin data and measurement results const adminContainer = document.createElement('div'); adminContainer.id = 'adminData'; appContainer.appendChild(adminContainer); @@ -37,11 +36,10 @@ export function initApp() { measContainer.id = 'measurementResults'; appContainer.appendChild(measContainer); - // Load the embedded XML file (for testing) from the data folder + // Load the XML file from the data folder fetch('/data/sin_acceleration_example_dcc_WithExampleConformatyStatment.xml') .then(response => response.text()) .then(xmlText => { - // Convert XML to JSON using xml2js parseString(xmlText, { explicitArray: false }, (err, result) => { if (err) { console.error('Error parsing XML:', err); @@ -56,12 +54,9 @@ export function initApp() { function renderAll() { // Clear containers - const adminContainer = document.getElementById('adminData'); - adminContainer.innerHTML = ''; - const measContainer = document.getElementById('measurementResults'); - measContainer.innerHTML = ''; + document.getElementById('adminData').innerHTML = ''; + document.getElementById('measurementResults').innerHTML = ''; - // Render the content if XML has been loaded and converted if (dccData && dccData['dcc:digitalCalibrationCertificate']) { const cert = dccData['dcc:digitalCalibrationCertificate']; if (cert['dcc:administrativeData']) { diff --git a/src/main.js b/src/main.js index 122f3f4..06d04af 100644 --- a/src/main.js +++ b/src/main.js @@ -1,6 +1,6 @@ import { initApp } from './app.js'; -// Wait for the DOM to be loaded before initializing +// Initialize after DOM loads document.addEventListener('DOMContentLoaded', () => { initApp(); }); diff --git a/src/renderers/AdminRenderer.js b/src/renderers/AdminRenderer.js index 82ffa9b..83d4e93 100644 --- a/src/renderers/AdminRenderer.js +++ b/src/renderers/AdminRenderer.js @@ -3,20 +3,16 @@ import 'jsoneditor/dist/jsoneditor.css'; export function renderAdminData(adminData, language) { const container = document.getElementById('adminData'); - // Title for admin data const title = document.createElement('h2'); title.textContent = 'Administrative Data (' + language + ')'; container.appendChild(title); - // JSONEditor options (read-only view) const options = { mode: 'view', mainMenuBar: false, navigationBar: false, statusBar: false }; - - // Create a new JSONEditor instance to display the adminData const editor = new JSONEditor(container, options); editor.set(adminData); } diff --git a/src/renderers/MeasurementRenderer.js b/src/renderers/MeasurementRenderer.js index fa583a0..4cf1abc 100644 --- a/src/renderers/MeasurementRenderer.js +++ b/src/renderers/MeasurementRenderer.js @@ -1,57 +1,320 @@ -import { selectRenderer } from './SelectRenderer.js'; +import Plotly from 'plotly.js-dist'; export function renderMeasurementResults(measurementResults, language) { + console.debug('renderMeasurementResults called with:', measurementResults); const container = document.getElementById('measurementResults'); - const title = document.createElement('h2'); - title.textContent = 'Measurement Results (' + language + ')'; - container.appendChild(title); + container.innerHTML = ''; + + if (!measurementResults) { + console.error('No measurementResults provided.'); + return; + } - // measurementResult can be an object or an array let results = measurementResults['dcc:measurementResult']; + console.debug('Raw dcc:measurementResult:', results); + + if (!results) { + console.error("Missing 'dcc:measurementResult' in measurementResults"); + return; + } + if (!Array.isArray(results)) { results = [results]; } + console.debug('Processed measurement result array:', results); - results.forEach(result => { - // Render the measurement result title - const resultTitle = document.createElement('h3'); - if (result['dcc:name'] && result['dcc:name']['dcc:content']) { - let nameContent = result['dcc:name']['dcc:content']; - if (Array.isArray(nameContent)) { - // Find the content matching the selected language or fallback - const match = nameContent.find(item => item.$ && item.$.lang === language) || nameContent[0]; - resultTitle.textContent = match._ || match; - } else { - resultTitle.textContent = nameContent._ || nameContent; + // Use the first measurementResult object + const measurementResult = results[0]; + console.debug('Using measurementResult:', measurementResult); + + // Extract the result object from dcc:results -> dcc:result + let resultObj = measurementResult['dcc:results'] && measurementResult['dcc:results']['dcc:result']; + if (!resultObj) { + console.error("Missing 'dcc:results' or 'dcc:result' in measurementResult:", measurementResult); + return; + } + if (Array.isArray(resultObj)) { + resultObj = resultObj[0]; + } + console.debug('Using result object:', resultObj); + + // Get the measurement result name from dcc:name (from measurementResult) + let resultName = 'Measurement Result'; + if (measurementResult['dcc:name'] && measurementResult['dcc:name']['dcc:content']) { + let content = measurementResult['dcc:name']['dcc:content']; + if (Array.isArray(content)) { + const match = content.find(item => item.$ && item.$.lang === language) || content[0]; + resultName = match._ || match; + } else { + resultName = content._ || content; + } + } + console.debug('Result name:', resultName); + + const tabTitle = document.createElement('h2'); + tabTitle.textContent = resultName + ' (' + language + ')'; + container.appendChild(tabTitle); + + // Now extract dcc:data -> dcc:list from the resultObj + if (!resultObj['dcc:data'] || !resultObj['dcc:data']['dcc:list']) { + console.error("Missing 'dcc:data' or 'dcc:list' in result object:", resultObj); + return; + } + const listData = resultObj['dcc:data']['dcc:list']; + console.debug('List data:', listData); + + // Flatten all quantities from the list + let quantities = []; + if (listData['dcc:quantity']) { + quantities = Array.isArray(listData['dcc:quantity']) ? listData['dcc:quantity'] : [listData['dcc:quantity']]; + } + console.debug('Quantities from list:', quantities); + + // Also add quantities from measurementMetaData if available + if (listData['dcc:measurementMetaData'] && listData['dcc:measurementMetaData']['dcc:metaData']) { + let metaData = listData['dcc:measurementMetaData']['dcc:metaData']; + if (!Array.isArray(metaData)) metaData = [metaData]; + metaData.forEach(md => { + if (md['dcc:data'] && md['dcc:data']['dcc:quantity']) { + let qs = md['dcc:data']['dcc:quantity']; + if (!Array.isArray(qs)) qs = [qs]; + quantities = quantities.concat(qs); } + }); + } + console.debug('Combined quantities:', quantities); + + // Separate index quantities and data quantities + const indexQuantities = []; + const dataQuantities = []; + quantities.forEach(q => { + if (q.$ && q.$.refType && q.$.refType.match(/basic_tableIndex/)) { + indexQuantities.push(q); } else { - resultTitle.textContent = 'Measurement Result'; - } - container.appendChild(resultTitle); - - // Render results using dcc:results -> dcc:result - if (result['dcc:results'] && result['dcc:results']['dcc:result']) { - let resItems = result['dcc:results']['dcc:result']; - if (!Array.isArray(resItems)) { - resItems = [resItems]; - } - resItems.forEach(res => { - // Check if the result contains a dcc:data with a dcc:list that should be rendered as plot/table - if (res['dcc:data'] && res['dcc:data']['dcc:list']) { - const dataContainer = document.createElement('div'); - dataContainer.className = 'plot-table'; - // Use the global selectRenderer to decide how to render this data - const renderedContent = selectRenderer(res['dcc:data']['dcc:list'], language); - dataContainer.appendChild(renderedContent); - container.appendChild(dataContainer); + dataQuantities.push(q); + } + }); + console.debug('Index Quantities:', indexQuantities); + console.debug('Data Quantities:', dataQuantities); + + // Create radio buttons for X-axis selection (from indexQuantities) + const xAxisContainer = document.createElement('div'); + xAxisContainer.innerHTML = '<strong>Select X-Axis:</strong> '; + indexQuantities.forEach((q, idx) => { + let nameStr = 'Index ' + idx; + if (q['dcc:name'] && q['dcc:name']['dcc:content']) { + let content = q['dcc:name']['dcc:content']; + if (Array.isArray(content)) { + const match = content.find(item => item.$ && item.$.lang === language) || content[0]; + nameStr = match._ || match; + } else { + nameStr = content._ || content; + } + } + const radio = document.createElement('input'); + radio.type = 'radio'; + radio.name = 'xAxisSelect'; + radio.value = idx; + if (idx === 0) radio.checked = true; + const label = document.createElement('label'); + label.textContent = nameStr; + label.style.marginRight = '10px'; + xAxisContainer.appendChild(radio); + xAxisContainer.appendChild(label); + }); + container.appendChild(xAxisContainer); + + // Tolerance toggle (placeholder) + const toleranceToggle = document.createElement('input'); + toleranceToggle.type = 'checkbox'; + toleranceToggle.id = 'toleranceToggle'; + const tolLabel = document.createElement('label'); + tolLabel.htmlFor = 'toleranceToggle'; + tolLabel.textContent = ' Enable tolerance markings'; + const tolContainer = document.createElement('div'); + tolContainer.appendChild(toleranceToggle); + tolContainer.appendChild(tolLabel); + container.appendChild(tolContainer); + + // Create containers for plots and table + const plotsContainer = document.createElement('div'); + plotsContainer.id = 'plotsContainer'; + container.appendChild(plotsContainer); + const tableContainer = document.createElement('div'); + tableContainer.id = 'tableContainer'; + container.appendChild(tableContainer); + + // Function to update the visualization + function updateVisualization() { + const selectedRadio = document.querySelector('input[name="xAxisSelect"]:checked'); + if (!selectedRadio) { + console.error('No X-Axis selection found.'); + return; + } + const selectedIndex = selectedRadio.value; + const xQuantity = indexQuantities[selectedIndex]; + let xValues = []; + if (xQuantity && xQuantity['si:realListXMLList'] && xQuantity['si:realListXMLList']['si:valueXMLList']) { + xValues = xQuantity['si:realListXMLList']['si:valueXMLList'].trim().split(/\s+/).map(v => parseFloat(v)); + } + console.debug('Selected X-Axis values:', xValues); + + // Build table headers and values + const headers = []; + let xHeader = 'X-Axis'; + if (xQuantity['dcc:name'] && xQuantity['dcc:name']['dcc:content']) { + let content = xQuantity['dcc:name']['dcc:content']; + if (Array.isArray(content)) { + const match = content.find(item => item.$ && item.$.lang === language) || content[0]; + xHeader = match._ || match; + } else { + xHeader = content._ || content; + } + } + headers.push(xHeader); + + const dataValues = []; + dataQuantities.forEach(q => { + let header = 'Data'; + if (q['dcc:name'] && q['dcc:name']['dcc:content']) { + let content = q['dcc:name']['dcc:content']; + if (Array.isArray(content)) { + const match = content.find(item => item.$ && item.$.lang === language) || content[0]; + header = match._ || match; } else { - // Fallback: simple placeholder - const defaultContainer = document.createElement('div'); - defaultContainer.className = 'default-render'; - defaultContainer.textContent = 'Default rendering for measurement result.'; - container.appendChild(defaultContainer); + header = content._ || content; } + } + headers.push(header); + let values = []; + if (q['si:realListXMLList'] && q['si:realListXMLList']['si:valueXMLList']) { + values = q['si:realListXMLList']['si:valueXMLList'].trim().split(/\s+/).map(v => parseFloat(v)); + } + if (values.length === 1 && xValues.length > 1) { + values = new Array(xValues.length).fill(values[0]); + } + dataValues.push(values); + }); + + // Build table rows + const tableData = [headers]; + for (let i = 0; i < xValues.length; i++) { + const row = []; + row.push(xValues[i]); + dataValues.forEach(values => { + row.push(values[i] !== undefined ? values[i] : ''); }); + tableData.push(row); } + console.debug('Table data:', tableData); + renderTable(tableData); + + // Group data quantities by unit for plotting + const unitGroups = {}; + dataQuantities.forEach((q, idx) => { + let unit = ''; + if (q['si:realListXMLList'] && q['si:realListXMLList']['si:unitXMLList']) { + unit = q['si:realListXMLList']['si:unitXMLList'].trim(); + } + if (!unitGroups[unit]) { + unitGroups[unit] = []; + } + let header = headers[idx + 1]; + let values = []; + if (q['si:realListXMLList'] && q['si:realListXMLList']['si:valueXMLList']) { + values = q['si:realListXMLList']['si:valueXMLList'].trim().split(/\s+/).map(v => parseFloat(v)); + } + if (values.length === 1 && xValues.length > 1) { + values = new Array(xValues.length).fill(values[0]); + } + unitGroups[unit].push({ name: header, y: values }); + }); + console.debug('Unit groups for plots:', unitGroups); + + // Clear and render plots + plotsContainer.innerHTML = ''; + Object.keys(unitGroups).forEach(unit => { + const graphDiv = document.createElement('div'); + graphDiv.style.width = '100%'; + graphDiv.style.height = '300px'; + plotsContainer.appendChild(graphDiv); + + const traces = unitGroups[unit].map(trace => { + return { + x: xValues, + y: trace.y, + type: 'scatter', + mode: 'lines+markers', + name: trace.name + }; + }); + const layout = { + title: 'Plot (' + unit + ')', + xaxis: { title: xHeader }, + yaxis: { title: unit }, + hovermode: 'closest' + }; + Plotly.newPlot(graphDiv, traces, layout).then(() => { + console.debug('Plot rendered for unit:', unit); + }); + + // Coupled mouseover events for table row highlighting + graphDiv.on('plotly_hover', function(data) { + if (data.points && data.points.length > 0) { + const pointIndex = data.points[0].pointIndex + 1; // offset for header row + highlightTableRow(pointIndex); + } + }); + graphDiv.on('plotly_unhover', function() { + clearTableRowHighlights(); + }); + }); + } + + // Render table from a 2D array + function renderTable(tableData) { + tableContainer.innerHTML = ''; + const table = document.createElement('table'); + tableData.forEach((rowData, rowIndex) => { + const tr = document.createElement('tr'); + rowData.forEach(cellData => { + const cell = document.createElement(rowIndex === 0 ? 'th' : 'td'); + cell.textContent = cellData; + cell.style.padding = '4px'; + cell.style.border = '1px solid #ccc'; + tr.appendChild(cell); + }); + tr.addEventListener('mouseover', () => { tr.style.backgroundColor = '#eef'; }); + tr.addEventListener('mouseout', () => { tr.style.backgroundColor = ''; }); + table.appendChild(tr); + }); + tableContainer.appendChild(table); + } + + // Functions for coupled table row highlights + function highlightTableRow(rowIndex) { + const rows = tableContainer.querySelectorAll('tr'); + if (rows[rowIndex]) { + rows[rowIndex].style.backgroundColor = '#fee'; + } + } + function clearTableRowHighlights() { + const rows = tableContainer.querySelectorAll('tr'); + rows.forEach(row => row.style.backgroundColor = ''); + } + + // Initial update + updateVisualization(); + + // Update visualization when X-axis selection changes + const radios = document.querySelectorAll('input[name="xAxisSelect"]'); + radios.forEach(radio => { + radio.addEventListener('change', updateVisualization); + }); + + // Tolerance toggle event (placeholder) + toleranceToggle.addEventListener('change', () => { + console.log('Tolerance toggle:', toleranceToggle.checked); + // Future: update plot/table for tolerance markings and color coding }); } diff --git a/src/renderers/SelectRenderer.js b/src/renderers/SelectRenderer.js index f593395..3927995 100644 --- a/src/renderers/SelectRenderer.js +++ b/src/renderers/SelectRenderer.js @@ -1,18 +1,6 @@ export function selectRenderer(json, language) { - // If the JSON node has a refType matching basic_\dIndexTable, render as plot and table - if (json && json.$ && json.$.refType && /^basic_\dIndexTable/.test(json.$.refType)) { - return renderPlotAndTable(json, language); - } - // Fallback: render a simple JSON string - const container = document.createElement('pre'); - container.textContent = JSON.stringify(json, null, 2); - return container; -} - -function renderPlotAndTable(json, language) { - // Placeholder implementation for plot and table rendering + // This function is not used in the interactive measurement renderer const container = document.createElement('div'); - container.className = 'plot-and-table'; - container.textContent = 'Plot and Table renderer placeholder for language: ' + language; + container.textContent = 'Interactive Measurement Renderer in use.'; return container; } diff --git a/src/styles.css b/src/styles.css index e10ba0b..a5dc683 100644 --- a/src/styles.css +++ b/src/styles.css @@ -13,7 +13,21 @@ h2, h3 { padding: 10px; } -.plot-table, .plot-and-table { +table { + width: 100%; + border-collapse: collapse; +} + +table, th, td { + border: 1px solid #ccc; +} + +th, td { + padding: 4px; + text-align: center; +} + +.plot-and-table, .plot-table { margin: 10px 0; padding: 10px; border: 1px dashed #999; diff --git a/vite.config.js b/vite.config.js index be1e181..527552c 100644 --- a/vite.config.js +++ b/vite.config.js @@ -3,13 +3,11 @@ import { defineConfig } from 'vite'; export default defineConfig({ resolve: { alias: { - // Ensure that Node's events module is polyfilled events: 'events' } }, optimizeDeps: { esbuildOptions: { - // Define global as globalThis to help with some Node packages define: { global: 'globalThis' } } } -- GitLab