import Plotly from 'plotly.js-dist'; import { DCCRealListQuantity } from '../dccQuantity.js'; const palette = [ '#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf' ]; const lightPalette = [ '#c6e2ff', '#ffddbf', '#c9e6c8', '#f4c2c2', '#dcd0ff', '#e0cda9', '#ffccff', '#d3d3d3', '#e6e6a9', '#b3ffff' ]; const conformityColors = { pass: '#2ca02c', fail: '#d62728', conditionalpass: '#8bc34a', conditionalfail: '#ff9800', nopass: '#9e9e9e', nofail: '#9e9e9e' }; export function renderMeasurementResults(measurementResults, language) { console.debug('renderMeasurementResults called with:', measurementResults); const container = document.getElementById('measurementResults'); container.innerHTML = ''; if (!measurementResults) { console.error('No measurementResults provided.'); return; } let results = measurementResults['dcc:measurementResult']; if (!results) { console.error("Missing 'dcc:measurementResult' in measurementResults"); return; } if (!Array.isArray(results)) { results = [results]; } const measurementResult = results[0]; 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]; } 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; } } const tabTitle = document.createElement('h2'); tabTitle.textContent = resultName; container.appendChild(tabTitle); 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']; let quantityJSONs = []; if (listData['dcc:quantity']) { quantityJSONs = Array.isArray(listData['dcc:quantity']) ? listData['dcc:quantity'] : [listData['dcc:quantity']]; } 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]; } quantityJSONs = quantityJSONs.concat(qs); } }); } const indexQuantities = []; const dataQuantities = []; const extraInfo = []; quantityJSONs.forEach(q => { if (q.$ && q.$.refType && q.$.refType.match(/basic_tableIndex/)) { indexQuantities.push(new DCCRealListQuantity(q)); } else { const quantity = new DCCRealListQuantity(q); dataQuantities.push(quantity); let uncertainty = quantity.getUncertainty(); let comment = ''; let conformity = null; if (q['dcc:measurementMetaData'] && q['dcc:measurementMetaData']['dcc:metaData']) { let md = q['dcc:measurementMetaData']['dcc:metaData']; if (!Array.isArray(md)) { md = [md]; } md.forEach(item => { if (item.$ && item.$.refType && item.$.refType.includes('basic_tableRowComment')) { if (item['dcc:description'] && item['dcc:description']['dcc:content']) { let desc = item['dcc:description']['dcc:content']; if (Array.isArray(desc)) { const match = desc.find(d => d.$ && d.$.lang === language) || desc[0]; comment = match._ || match; } else { comment = desc._ || desc; } } } if (item.$ && item.$.refType && item.$.refType.includes('basic_conformity')) { if (item['dcc:conformityXMLList']) { conformity = item['dcc:conformityXMLList'].trim().split(/\s+/); } } }); } extraInfo.push({ uncertainty, comment, conformity }); } }); const dataHeaders = dataQuantities.map((q, idx) => { let header = q.getName(language); let unit = q.getUnit(); if (!header.toLowerCase().includes(" in ")) { header = header + " in " + unit; } return header; }); const scalingContainer = document.createElement('div'); scalingContainer.innerHTML = '<strong>Scaling:</strong> '; const logXToggle = document.createElement('input'); logXToggle.type = 'checkbox'; logXToggle.id = 'logXToggle'; const logXLabel = document.createElement('label'); logXLabel.htmlFor = 'logXToggle'; logXLabel.textContent = 'Log X'; scalingContainer.appendChild(logXToggle); scalingContainer.appendChild(logXLabel); const logYToggle = document.createElement('input'); logYToggle.type = 'checkbox'; logYToggle.id = 'logYToggle'; const logYLabel = document.createElement('label'); logYLabel.htmlFor = 'logYToggle'; logYLabel.textContent = 'Log Y'; scalingContainer.appendChild(logYToggle); scalingContainer.appendChild(logYLabel); container.appendChild(scalingContainer); const xAxisContainer = document.createElement('div'); xAxisContainer.innerHTML = '<strong>Select X-Axis:</strong> '; indexQuantities.forEach((q, idx) => { let nameStr = q.getName(language) || ('Index ' + idx); 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); 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); const subplotsContainer = document.createElement('div'); subplotsContainer.id = 'subplotsContainer'; container.appendChild(subplotsContainer); const tableContainer = document.createElement('div'); tableContainer.id = 'tableContainer'; container.appendChild(tableContainer); 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]; const xValues = xQuantity.getValues(); const xUnit = xQuantity.getUnit(); console.debug('Selected X-Axis values:', xValues); console.debug('X-Axis unit:', xUnit); const headers = []; headers.push('X-Axis (selected) (' + xUnit + ')'); const dataValues = []; const uncertaintiesArray = []; const conformityArray = []; extraInfo.forEach((info, idx) => { headers.push(dataHeaders[idx]); if (info.conformity) { headers.push('Conformity'); } dataValues.push(dataQuantities[idx].getValues()); uncertaintiesArray.push(info.uncertainty || []); conformityArray.push(info.conformity || []); }); headers.push('Comments'); const tableData = [headers]; for (let i = 0; i < xValues.length; i++) { const row = []; row.push(xValues[i]); extraInfo.forEach((info, idx) => { let cellValue = (dataValues[idx][i] !== undefined) ? dataValues[idx][i] : ''; if (uncertaintiesArray[idx] && uncertaintiesArray[idx][i] !== undefined) { cellValue = cellValue + ' ± ' + uncertaintiesArray[idx][i]; } row.push(cellValue); if (info.conformity) { row.push(info.conformity[i] || ''); } }); // Comments column remains uncolored row.push(extraInfo.map(info => info.comment).filter(c => c).join(' ; ')); tableData.push(row); } renderTable(tableData, headers); const unitGroups = {}; dataQuantities.forEach((q, idx) => { const unit = q.getUnit(); if (!unitGroups[unit]) { unitGroups[unit] = []; } const header = dataHeaders[idx]; let values = q.getValues(); const uncertainty = uncertaintiesArray[idx]; const conformity = conformityArray[idx]; const defaultColor = palette[idx % palette.length]; const markerColorArray = values.map((val, i) => { if (conformity && conformity[i] && conformity[i].toLowerCase().includes('fail')) { return conformityColors.fail; } return defaultColor; }); unitGroups[unit].push({ name: header, y: values, uncertainty, conformity, defaultColor, markerColor: markerColorArray, index: idx }); }); subplotsContainer.innerHTML = ''; const unitKeys = Object.keys(unitGroups); const plotDivs = []; unitKeys.forEach((unit, groupIdx) => { const group = unitGroups[unit]; const graphDiv = document.createElement('div'); graphDiv.style.width = '100%'; graphDiv.style.height = '300px'; subplotsContainer.appendChild(graphDiv); plotDivs.push(graphDiv); const groupTraces = group.map(trace => { let tooltip = 'X: %{x} ' + xUnit + ' | ' + trace.name + ': %{y}'; if (trace.conformity && trace.conformity.length > 0) { tooltip += ' | Conformity: %{customdata}'; } tooltip += '<extra></extra>'; return { x: xValues, y: trace.y, error_y: { type: 'data', array: (trace.uncertainty && trace.uncertainty.length === xValues.length) ? trace.uncertainty : new Array(xValues.length).fill(0), visible: true, color: trace.defaultColor }, type: 'scatter', mode: 'lines+markers', name: trace.name, marker: { color: trace.markerColor }, line: { color: trace.defaultColor }, hovertemplate: tooltip, customdata: (trace.conformity && trace.conformity.length === xValues.length) ? trace.conformity : new Array(xValues.length).fill('') }; }); let xaxisTitle = ''; if (groupIdx === unitKeys.length - 1) { let xLabel = 'X: ' + xQuantity.getName(language); xaxisTitle = '<b>' + xLabel + ' in ' + xUnit + '</b>'; } const logX = document.getElementById('logXToggle').checked; const logY = document.getElementById('logYToggle').checked; const layout = { xaxis: { title: { text: xaxisTitle, font: { size: 36, family: 'Arial', color: 'black' } }, tickfont: { family: 'Arial', size: 14, color: 'black' }, type: logX ? 'log' : 'linear' }, yaxis: { title: { text: unit, font: { size: 18, family: 'Arial', color: 'black' } }, tickfont: { family: 'Arial', size: 14, color: 'black' }, type: logY ? 'log' : 'linear' }, hovermode: 'closest', margin: { t: 20, b: 40 } }; Plotly.newPlot(graphDiv, groupTraces, layout).then(() => { const caption = document.createElement('div'); caption.innerHTML = '<b>' + group[0].name + '</b>'; caption.style.textAlign = 'center'; caption.style.marginBottom = '5px'; graphDiv.parentNode.insertBefore(caption, graphDiv); }); graphDiv.on('plotly_hover', function(data) { if (data.points && data.points.length > 0) { const pointIndex = data.points[0].pointIndex + 1; highlightTableRow(pointIndex); } }); graphDiv.on('plotly_unhover', function() { clearTableRowHighlights(); }); graphDiv.on('plotly_relayout', function(eventData) { plotDivs.forEach(div => { if (div !== graphDiv && eventData['xaxis.range[0]'] && eventData['xaxis.range[1]']) { Plotly.relayout(div, { 'xaxis.range': [eventData['xaxis.range[0]'], eventData['xaxis.range[1]']] }); } }); }); }); } function renderTable(tableData, headers) { const tableContainer = document.getElementById('tableContainer'); tableContainer.innerHTML = ''; const table = document.createElement('table'); tableData.forEach((rowData, rowIndex) => { const tr = document.createElement('tr'); rowData.forEach((cellData, cellIndex) => { const cell = document.createElement(rowIndex === 0 ? 'th' : 'td'); if (rowIndex === 0) { cell.innerHTML = cellData; if (cellData !== 'Comments' && cellIndex > 0) { const qtyIndex = Math.floor((cellIndex - 1) / 2); cell.style.backgroundColor = lightPalette[qtyIndex % lightPalette.length]; } } else { 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); } function highlightTableRow(rowIndex) { const rows = document.getElementById('tableContainer').querySelectorAll('tr'); if (rows[rowIndex]) { rows[rowIndex].style.backgroundColor = '#fee'; } } function clearTableRowHighlights() { const rows = document.getElementById('tableContainer').querySelectorAll('tr'); rows.forEach(row => row.style.backgroundColor = ''); } updateVisualization(); const radios = document.querySelectorAll('input[name="xAxisSelect"]'); radios.forEach(radio => { radio.addEventListener('change', updateVisualization); }); document.getElementById('logXToggle').addEventListener('change', updateVisualization); document.getElementById('logYToggle').addEventListener('change', updateVisualization); toleranceToggle.addEventListener('change', () => { console.log('Tolerance toggle:', toleranceToggle.checked); }); }