Skip to content
Snippets Groups Projects
Commit 09fe8d45 authored by Benedikt's avatar Benedikt
Browse files

fixed sirealListXMLList uncer parsing

parent be57effb
Branches
No related tags found
No related merge requests found
......@@ -8,6 +8,7 @@
"name": "dcc-viewer",
"version": "1.0.0",
"dependencies": {
"dsiunits-js": "^0.9.1",
"events": "^3.3.0",
"jsoneditor": "^9.5.6",
"plotly.js-dist": "^2.18.2",
......@@ -419,6 +420,11 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/dsiunits-js": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/dsiunits-js/-/dsiunits-js-0.9.1.tgz",
"integrity": "sha512-4xrSwUks86EteWgQkm38Kb9iC2BlslMxp8tUld304B7GnFoQOBNV7qtaSSKWDR0vXramHNpueq00YO155LXttQ=="
},
"node_modules/esbuild": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
......
......@@ -8,10 +8,11 @@
"preview": "vite preview"
},
"dependencies": {
"xml2js": "^0.4.23",
"dsiunits-js": "^0.9.1",
"events": "^3.3.0",
"jsoneditor": "^9.5.6",
"plotly.js-dist": "^2.18.2",
"events": "^3.3.0"
"xml2js": "^0.4.23"
},
"devDependencies": {
"vite": "^4.0.0"
......
// src/dccQuantity.js
import { DSIUnit } from "dsiunits-js/src/dsiUnit.js";
// Import the unit input component if needed:
// import "dsi-units-js-lib/src/dsiUnitInput.js";
/**
* Base class for a DCC quantity.
* Holds a pointer to the original JSON data.
*/
export class DCCQuantity {
constructor(jsonData) {
this.jsonData = jsonData;
}
/**
* Returns the refType attribute of the quantity.
*/
getRefType() {
return (this.jsonData.$ && this.jsonData.$.refType) ? this.jsonData.$.refType : '';
}
/**
* Returns the quantity name in the specified language.
* @param {string} language - e.g., 'en', 'de'
*/
getName(language) {
const nameData = this.jsonData['dcc:name'];
if (!nameData) return '';
const content = nameData['dcc:content'];
if (Array.isArray(content)) {
const match = content.find(item => item.$ && item.$.lang === language) || content[0];
return match._ || match;
} else {
return content._ || content;
}
}
}
/**
* Class for quantities represented by <si:realListXMLList>.
*/
export class DCCRealListQuantity extends DCCQuantity {
constructor(jsonData) {
super(jsonData);
}
/**
* Extracts and returns an array of numeric values.
*/
getValues() {
const realList = this.jsonData['si:realListXMLList'];
if (realList && realList['si:valueXMLList']) {
return realList['si:valueXMLList']
.trim()
.split(/\s+/)
.map(v => parseFloat(v));
}
return [];
}
/**
* Returns the unit string rendered as HTML using the dsi-units-js library.
*/
getUnit() {
const realList = this.jsonData['si:realListXMLList'];
if (realList && realList['si:unitXMLList']) {
const rawUnit = realList['si:unitXMLList'].trim();
const unit = new DSIUnit(rawUnit);
// Render the unit in a one-line format
return unit.toHTML({ oneLine: true });
}
return '';
}
/**
* Extracts and returns an array of uncertainty values.
*/
getUncertainty() {
const realList = this.jsonData['si:realListXMLList'];
if (
realList['si:measurementUncertaintyUnivariateXMLList'] &&
realList['si:measurementUncertaintyUnivariateXMLList']['si:expandedMUXMLList'] &&
realList['si:measurementUncertaintyUnivariateXMLList']['si:expandedMUXMLList']['si:valueExpandedMUXMLList']
) {
const errStr = realList['si:measurementUncertaintyUnivariateXMLList']['si:expandedMUXMLList']['si:valueExpandedMUXMLList'];
return errStr.trim().split(/\s+/).map(v => parseFloat(v));
}
return [];
}
}
/**
* Class for quantities represented by a single <si:real>.
* (This class is provided for completeness and may be expanded as needed.)
*/
export class DCCRealQuantity extends DCCQuantity {
constructor(jsonData) {
super(jsonData);
}
/**
* Returns the numeric value from the <si:real> element.
*/
getValue() {
const realData = this.jsonData['si:real'];
if (realData && realData['si:value']) {
return parseFloat(realData['si:value']);
}
return null;
}
/**
* Returns the unit rendered as HTML.
*/
getUnit() {
const realData = this.jsonData['si:real'];
if (realData && realData['si:unit']) {
const rawUnit = realData['si:unit'].trim();
const unit = new DSIUnit(rawUnit);
return unit.toHTML({ oneLine: true });
}
return '';
}
}
\ No newline at end of file
import Plotly from 'plotly.js-dist';
import { DCCRealListQuantity } from '../dccQuantity.js';
// Dummy unit converter: currently returns the raw unit (to be enhanced later)
function convertUnit(rawUnit) {
return rawUnit;
}
// Define Tab10 palette and lighter shades for table backgrounds
const palette = [
'#1f77b4',
'#ff7f0e',
......@@ -44,12 +39,10 @@ export function renderMeasurementResults(measurementResults, language) {
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];
}
......@@ -58,7 +51,6 @@ export function renderMeasurementResults(measurementResults, language) {
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);
......@@ -69,7 +61,6 @@ export function renderMeasurementResults(measurementResults, language) {
}
console.debug('Using result object:', resultObj);
// Get measurement result name without language tag
let resultName = 'Measurement Result';
if (measurementResult['dcc:name'] && measurementResult['dcc:name']['dcc:content']) {
let content = measurementResult['dcc:name']['dcc:content'];
......@@ -81,7 +72,6 @@ export function renderMeasurementResults(measurementResults, language) {
}
}
console.debug('Result name:', resultName);
const tabTitle = document.createElement('h2');
tabTitle.textContent = resultName;
container.appendChild(tabTitle);
......@@ -93,14 +83,12 @@ export function renderMeasurementResults(measurementResults, language) {
const listData = resultObj['dcc:data']['dcc:list'];
console.debug('List data:', listData);
// Flatten quantities from the list
let quantities = [];
let quantityJSONs = [];
if (listData['dcc:quantity']) {
quantities = Array.isArray(listData['dcc:quantity']) ? listData['dcc:quantity'] : [listData['dcc:quantity']];
quantityJSONs = 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];
......@@ -108,30 +96,22 @@ export function renderMeasurementResults(measurementResults, language) {
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);
quantityJSONs = quantityJSONs.concat(qs);
}
});
}
console.debug('Combined quantities:', quantities);
console.debug('Combined quantity JSONs:', quantityJSONs);
// Separate index quantities and data quantities; extract extra info (uncertainty, comment, conformity)
const indexQuantities = [];
const dataQuantities = [];
const extraInfo = [];
quantities.forEach(q => {
quantityJSONs.forEach(q => {
if (q.$ && q.$.refType && q.$.refType.match(/basic_tableIndex/)) {
indexQuantities.push(q);
indexQuantities.push(new DCCRealListQuantity(q));
} else {
dataQuantities.push(q);
let uncertainty = null;
if (q['si:measurementUncertaintyUnivariateXMLList'] &&
q['si:measurementUncertaintyUnivariateXMLList']['si:expandedMUXMLList'] &&
q['si:measurementUncertaintyUnivariateXMLList']['si:expandedMUXMLList']['si:valueExpandedMUXMLList']) {
const errStr = q['si:measurementUncertaintyUnivariateXMLList']['si:expandedMUXMLList']['si:valueExpandedMUXMLList'];
uncertainty = errStr.trim().split(/\s+/).map(v => parseFloat(v));
}
dataQuantities.push(new DCCRealListQuantity(q));
let uncertainty = (new DCCRealListQuantity(q)).getUncertainty();
let comment = '';
let validArray = [];
let conformity = null;
if (q['dcc:measurementMetaData'] && q['dcc:measurementMetaData']['dcc:metaData']) {
let md = q['dcc:measurementMetaData']['dcc:metaData'];
......@@ -147,9 +127,6 @@ export function renderMeasurementResults(measurementResults, language) {
comment = desc._ || desc;
}
}
if (item['dcc:validXMLList']) {
validArray = item['dcc:validXMLList'].trim().split(/\s+/);
}
}
if (item.$ && item.$.refType && item.$.refType.includes('basic_conformity')) {
if (item['dcc:conformityXMLList']) {
......@@ -158,27 +135,17 @@ export function renderMeasurementResults(measurementResults, language) {
}
});
}
extraInfo.push({ uncertainty, comment, valid: validArray, conformity });
extraInfo.push({ uncertainty, comment, conformity });
}
});
console.debug('Index Quantities:', indexQuantities);
console.debug('Data Quantities:', dataQuantities);
console.debug('Index Quantities (objects):', indexQuantities);
console.debug('Data Quantities (objects):', dataQuantities);
console.debug('Extra info for data quantities:', extraInfo);
// Create radio buttons for X-axis selection
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;
}
}
let nameStr = q.getName(language) || ('Index ' + idx);
const radio = document.createElement('input');
radio.type = 'radio';
radio.name = 'xAxisSelect';
......@@ -192,7 +159,6 @@ export function renderMeasurementResults(measurementResults, language) {
});
container.appendChild(xAxisContainer);
// Tolerance toggle (placeholder)
const toleranceToggle = document.createElement('input');
toleranceToggle.type = 'checkbox';
toleranceToggle.id = 'toleranceToggle';
......@@ -204,17 +170,13 @@ export function renderMeasurementResults(measurementResults, language) {
tolContainer.appendChild(tolLabel);
container.appendChild(tolContainer);
// Create container for subplots
const subplotsContainer = document.createElement('div');
subplotsContainer.id = 'subplotsContainer';
container.appendChild(subplotsContainer);
// Create container for table
const tableContainer = document.createElement('div');
tableContainer.id = 'tableContainer';
container.appendChild(tableContainer);
// Function to update visualization
function updateVisualization() {
const selectedRadio = document.querySelector('input[name="xAxisSelect"]:checked');
if (!selectedRadio) {
......@@ -223,22 +185,13 @@ export function renderMeasurementResults(measurementResults, language) {
}
const selectedIndex = selectedRadio.value;
const xQuantity = indexQuantities[selectedIndex];
let xValues = [];
let xUnit = '';
if (xQuantity && xQuantity['si:realListXMLList']) {
if (xQuantity['si:realListXMLList']['si:valueXMLList']) {
xValues = xQuantity['si:realListXMLList']['si:valueXMLList'].trim().split(/\s+/).map(v => parseFloat(v));
}
if (xQuantity['si:realListXMLList']['si:unitXMLList']) {
xUnit = xQuantity['si:realListXMLList']['si:unitXMLList'].trim();
}
}
const xValues = xQuantity.getValues();
const xUnit = xQuantity.getUnit();
console.debug('Selected X-Axis values:', xValues);
console.debug('X-Axis unit:', xUnit);
// Build table headers and arrays for values, comments, conformity, uncertainties
const headers = [];
let xHeader = 'X-Axis (selected) (' + convertUnit(xUnit) + ')';
const xHeader = 'X-Axis (selected) (' + xUnit + ')';
headers.push(xHeader);
const dataValues = [];
......@@ -246,54 +199,24 @@ export function renderMeasurementResults(measurementResults, language) {
const conformityArray = [];
const uncertaintiesArray = [];
dataQuantities.forEach((q, idx) => {
let header = 'Data';
let unit = '';
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 {
header = content._ || content;
}
}
if (q['si:realListXMLList'] && q['si:realListXMLList']['si:unitXMLList']) {
unit = q['si:realListXMLList']['si:unitXMLList'].trim();
let header = q.getName(language);
let unit = q.getUnit();
if (!header.toLowerCase().includes(" in ")) {
header = header + " in " + unit;
}
headers.push(header + ' in ' + convertUnit(unit));
headers.push(header);
headers.push('Comments');
// Add conformity header only if data exists
if (extraInfo[idx] && extraInfo[idx].conformity) {
headers.push('Conformity');
} else {
headers.push('');
}
let values = [];
if (q['si:realListXMLList'] && q['si:realListXMLList']['si:valueXMLList']) {
values = q['si:realListXMLList']['si:valueXMLList'].trim().split(/\s+/).map(v => parseFloat(v));
}
headers.push(extraInfo[idx] && extraInfo[idx].conformity ? 'Conformity' : '');
let values = q.getValues();
if (values.length === 1 && xValues.length > 1) {
values = new Array(xValues.length).fill(values[0]);
}
dataValues.push(values);
let uncertainty = null;
if (extraInfo[idx] && extraInfo[idx].uncertainty) {
uncertainty = extraInfo[idx].uncertainty;
}
uncertaintiesArray.push(uncertainty);
let comment = extraInfo[idx] ? extraInfo[idx].comment : '';
commentsArray.push(comment);
let conformity = [];
if (extraInfo[idx] && extraInfo[idx].conformity) {
conformity = extraInfo[idx].conformity;
}
conformityArray.push(conformity);
uncertaintiesArray.push(extraInfo[idx] ? extraInfo[idx].uncertainty : null);
commentsArray.push(extraInfo[idx] ? extraInfo[idx].comment : '');
conformityArray.push(extraInfo[idx] ? extraInfo[idx].conformity : []);
});
console.debug('Table data arrays:', { dataValues, commentsArray, conformityArray, uncertaintiesArray });
const tableData = [headers];
for (let i = 0; i < xValues.length; i++) {
const row = [];
......@@ -305,61 +228,42 @@ export function renderMeasurementResults(measurementResults, language) {
}
row.push(cellValue);
row.push(commentsArray[idx] || '');
if (conformityArray[idx] && conformityArray[idx][i]) {
row.push(conformityArray[idx][i]);
} else {
row.push('');
}
row.push(conformityArray[idx] && conformityArray[idx][i] ? conformityArray[idx][i] : '');
});
tableData.push(row);
}
console.debug('Table data:', tableData);
console.debug('Final table data:', tableData);
renderTable(tableData);
// Group data quantities by unit for plotting and assign colors
const unitGroups = {};
dataQuantities.forEach((q, idx) => {
let unit = '';
if (q['si:realListXMLList'] && q['si:realListXMLList']['si:unitXMLList']) {
unit = q['si:realListXMLList']['si:unitXMLList'].trim();
}
const unit = q.getUnit();
if (!unitGroups[unit]) {
unitGroups[unit] = [];
}
let header = headers[idx * 3 + 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));
}
let values = q.getValues();
if (values.length === 1 && xValues.length > 1) {
values = new Array(xValues.length).fill(values[0]);
}
let uncertainty = uncertaintiesArray[idx];
let conformity = [];
if (conformityArray[idx]) {
conformity = conformityArray[idx];
}
// Use Tab10 palette; assign a color for this data quantity
let traceColor = palette[idx % palette.length];
unitGroups[unit].push({ name: header, y: values, uncertainty: uncertainty, conformity: conformity, color: traceColor, index: idx });
const uncertainty = uncertaintiesArray[idx];
const conformity = conformityArray[idx];
const traceColor = palette[idx % palette.length];
unitGroups[unit].push({ name: header, y: values, uncertainty, conformity, color: traceColor, index: idx });
});
console.debug('Unit groups for plots:', unitGroups);
// Build plots: one subplot per unit group, sharing the same X-axis
const plotsContainer = document.getElementById('subplotsContainer');
plotsContainer.innerHTML = '';
subplotsContainer.innerHTML = '';
const unitKeys = Object.keys(unitGroups);
unitKeys.forEach((unit, groupIdx) => {
const group = unitGroups[unit];
const graphDiv = document.createElement('div');
graphDiv.style.width = '100%';
graphDiv.style.height = '300px';
plotsContainer.appendChild(graphDiv);
subplotsContainer.appendChild(graphDiv);
// Build traces for this group
const groupTraces = group.map(trace => {
// Prepare tooltip with short format
const hovertemplate = 'X: %{x} ' + convertUnit(xUnit) + ' | ' + trace.name + ': %{y} ± %{error_y.array} ' + convertUnit(unit) + ' | Conformity: %{customdata}<extra></extra>';
const hovertemplate = 'X: %{x} ' + xUnit + ' | ' + trace.name + ': %{y} ± %{customdata_unc} ' + unit + ' | Conformity: %{customdata}<extra></extra>';
return {
x: xValues,
y: trace.y,
......@@ -369,38 +273,27 @@ export function renderMeasurementResults(measurementResults, language) {
name: trace.name,
marker: { color: trace.color },
hovertemplate: hovertemplate,
customdata: (trace.conformity && trace.conformity.length === xValues.length) ? trace.conformity : new Array(xValues.length).fill('')
customdata: (trace.conformity && trace.conformity.length === xValues.length) ? trace.conformity : new Array(xValues.length).fill(''),
customdata_unc: (trace.uncertainty && trace.uncertainty.length === xValues.length) ? trace.uncertainty : new Array(xValues.length).fill('0')
};
});
// Only the last subplot gets an X-axis label
let xaxisTitle = '';
if (groupIdx === unitKeys.length - 1) {
let xLabel = 'X: ';
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];
xLabel += match._ || match;
} else {
xLabel += content._ || content;
}
}
xaxisTitle = xLabel + ' in ' + convertUnit(xUnit);
let xLabel = 'X: ' + xQuantity.getName(language);
xaxisTitle = xLabel + ' in ' + xUnit;
}
const layout = {
xaxis: { title: { text: xaxisTitle, font: { size: 18, family: 'Arial', color: 'black' } }, tickfont: { family: 'Arial', size: 14, color: 'black' } },
yaxis: { title: { text: convertUnit(unit), font: { size: 18, family: 'Arial', color: 'black' } }, tickfont: { family: 'Arial', size: 14, color: 'black' } },
yaxis: { title: { text: unit, font: { size: 18, family: 'Arial', color: 'black' } }, tickfont: { family: 'Arial', size: 14, color: 'black' } },
hovermode: 'closest',
margin: { t: 20, b: 40 }
};
Plotly.newPlot(graphDiv, groupTraces, layout).then(() => {
console.debug('Plot rendered for unit:', unit);
// Bold caption above the plot: "QuantityName in Unit"; remove duplicate unit if any
const caption = document.createElement('div');
caption.innerHTML = '<b>' + group[0].name + ' in ' + convertUnit(unit) + '</b>';
caption.innerHTML = '<b>' + group[0].name +'</b>';
caption.style.textAlign = 'center';
caption.style.marginBottom = '5px';
graphDiv.parentNode.insertBefore(caption, graphDiv);
......@@ -408,7 +301,7 @@ export function renderMeasurementResults(measurementResults, language) {
graphDiv.on('plotly_hover', function(data) {
if (data.points && data.points.length > 0) {
const pointIndex = data.points[0].pointIndex + 1; // offset for header row
const pointIndex = data.points[0].pointIndex + 1;
highlightTableRow(pointIndex);
}
});
......@@ -418,7 +311,6 @@ export function renderMeasurementResults(measurementResults, language) {
});
}
// Render table from a 2D array with colored backgrounds for data columns
function renderTable(tableData) {
const tableContainer = document.getElementById('tableContainer');
tableContainer.innerHTML = '';
......@@ -427,7 +319,12 @@ export function renderMeasurementResults(measurementResults, language) {
const tr = document.createElement('tr');
rowData.forEach((cellData, cellIndex) => {
const cell = document.createElement(rowIndex === 0 ? 'th' : 'td');
cell.textContent = cellData;
// For headers, allow HTML rendering (dsiUnits output) by setting innerHTML.
if (rowIndex === 0) {
cell.innerHTML = cellData;
} else {
cell.textContent = cellData;
}
cell.style.padding = '4px';
cell.style.border = '1px solid #ccc';
if (rowIndex === 0 && cellIndex > 0) {
......@@ -456,14 +353,11 @@ export function renderMeasurementResults(measurementResults, language) {
}
updateVisualization();
const radios = document.querySelectorAll('input[name="xAxisSelect"]');
radios.forEach(radio => {
radio.addEventListener('change', updateVisualization);
});
toleranceToggle.addEventListener('change', () => {
console.log('Tolerance toggle:', toleranceToggle.checked);
// Future: update plot/table for tolerance markings and color coding
});
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment