From 8651a94f43ec8c9a5b51bd06ab6a867655663cc6 Mon Sep 17 00:00:00 2001 From: Michael Huth Date: Mon, 9 Feb 2026 16:31:55 +0100 Subject: [PATCH 01/15] SF: Add structure for SF plotting data to reduce number of func args - add structure initialization function SF_InitPlotterGraphStruct - add function to fill plot meta data structure: SF_FillPlotMetaData - move filling of formulaResults from SF_GatherFormulaResults to own function SF_FillFormulasResults - Use new structure in function SF_CreateTraceNames - Use new structure in function SF_CreateTracesForResultsImpl - Use new structure in function SF_CreateTracesForResults - Use new structure in SF_FormulaPlotter main plot loop --- Packages/MIES/MIES_SweepFormula.ipf | 344 +++++++++++++++++----------- 1 file changed, 211 insertions(+), 133 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula.ipf b/Packages/MIES/MIES_SweepFormula.ipf index e6318a42c1..01776426ad 100644 --- a/Packages/MIES/MIES_SweepFormula.ipf +++ b/Packages/MIES/MIES_SweepFormula.ipf @@ -35,6 +35,42 @@ static Constant SF_SWEEPFORMULA_AXIS_Y = 1 static StrConstant SF_UDATA_TABLEFORMULAS = "formulas" +static Structure SF_PlotterGraphStruct + + /// Name of the sweep formula graph this struct is associated with + string graph + /// Name of the window in which the plot/table is displayed + string win + /// Number of traces currently plotted + variable traceCnt + /// flag to call PSX plot creation when evaluated formulas contained psx operation + variable postPlotPSX + /// Non-zero if the legend should be shown for this plot + variable showLegend + /// Counter tracking how many formulas have been processed + variable formulaCounter + /// Wave holding x-axis labels used for the plotted data + WAVE xAxisLabels + /// Wave holding y-axis labels used for the plotted data + WAVE yAxisLabels + /// Text wave storing annotations to be displayed + WAVE/T wAnnotations + /// Text wave describing the argument setup for each formula + WAVE/T formulaArgSetup + /// Text wave containing the formulas for data displayed in tables + WAVE/T tableFormulas + /// Wave of waves storing collected plot formatting data for each formula + WAVE/WAVE collPlotFormData + /// Wave tracking which panels (graph/table) have been created + WAVE panelsCreated + /// Wave of waves with the evaluation results of a formula + WAVE/WAVE formulaResults + /// Text wave holding meta data about the plotted formulas and traces + WAVE/T plotMetaData + /// Wave assigning color groups to plotted traces + WAVE colorGroups +EndStructure + Menu "GraphPopup" "Bring browser to front", /Q, SF_BringBrowserToFront() End @@ -112,6 +148,19 @@ Function/S SF_EscapeJsonPath(string str) return ReplaceString("/", str, "~1") End +/// @brief Retrieves the plot meta data from the JSON wave note or other sources and stores it in the plotMetaData wave +static Function/WAVE SF_FillPlotMetaData(WAVE wvYRef, variable useXLabel, string dataUnits) + + WAVE/T plotMetaData = GetSFPlotMetaData() + plotMetaData[%DATATYPE] = JWN_GetStringFromWaveNote(wvYRef, SF_META_DATATYPE) + plotMetaData[%OPSTACK] = JWN_GetStringFromWaveNote(wvYRef, SF_META_OPSTACK) + plotMetaData[%ARGSETUPSTACK] = JWN_GetStringFromWaveNote(wvYRef, SF_META_ARGSETUPSTACK) + plotMetaData[%XAXISLABEL] = SelectString(useXLabel, SF_XLABEL_USER, JWN_GetStringFromWaveNote(wvYRef, SF_META_XAXISLABEL)) + plotMetaData[%YAXISLABEL] = JWN_GetStringFromWaveNote(wvYRef, SF_META_YAXISLABEL) + dataUnits + + return plotMetaData +End + /// @brief transfer the wave scaling from one wave to another /// /// Note: wave scale transfer requires wave units for the first wave or second wave @@ -159,22 +208,14 @@ Function SF_FormulaWaveScaleTransfer(WAVE source, WAVE dest, variable dimSource, endswitch End -static Function [WAVE/WAVE formulaResults, WAVE/T plotMetaData] SF_GatherFormulaResults(string xFormula, string yFormula, string graph, variable lineNr, variable offset) +static Function [WAVE/WAVE formulaResults, WAVE/T plotMetaData] SF_FillFormulaResults(WAVE/Z/WAVE wvYRef, WAVE/Z/WAVE wvXRef, string yFormula) variable i, numResultsY, numResultsX variable useXLabel, addDataUnitsInAnnotation string dataUnits, dataUnitCheck - WAVE/WAVE formulaResults = GetFormulaGatherWave() - WAVE/T plotMetaData = GetSFPlotMetaData() - - WAVE/Z/WAVE wvXRef = $"" - if(!IsEmpty(xFormula)) - WAVE/WAVE wvXRef = SFE_ExecuteFormula(xFormula, graph, useVariables = 0, line = lineNr, offset = offset) - SFH_ASSERT(WaveExists(wvXRef), "x part of formula returned no result.") - endif - WAVE/WAVE wvYRef = SFE_ExecuteFormula(yFormula, graph, useVariables = 0, line = lineNr, offset = 0) SFH_ASSERT(WaveExists(wvYRef), "y part of formula returned no result.") + numResultsY = DimSize(wvYRef, ROWS) if(WaveExists(wvXRef)) numResultsX = DimSize(wvXRef, ROWS) @@ -183,6 +224,7 @@ static Function [WAVE/WAVE formulaResults, WAVE/T plotMetaData] SF_GatherFormula useXLabel = 1 addDataUnitsInAnnotation = 1 + WAVE/WAVE formulaResults = GetFormulaGatherWave() Redimension/N=(numResultsY, -1) formulaResults if(DimSize(wvYRef, ROWS) > 0 && DimSize(formulaResults, ROWS) > 0) @@ -230,11 +272,21 @@ static Function [WAVE/WAVE formulaResults, WAVE/T plotMetaData] SF_GatherFormula dataUnits = SelectString(addDataUnitsInAnnotation && !IsEmpty(dataUnitCheck), "", SF_FormatUnit(dataUnitCheck)) endif - plotMetaData[%DATATYPE] = JWN_GetStringFromWaveNote(wvYRef, SF_META_DATATYPE) - plotMetaData[%OPSTACK] = JWN_GetStringFromWaveNote(wvYRef, SF_META_OPSTACK) - plotMetaData[%ARGSETUPSTACK] = JWN_GetStringFromWaveNote(wvYRef, SF_META_ARGSETUPSTACK) - plotMetaData[%XAXISLABEL] = SelectString(useXLabel, SF_XLABEL_USER, JWN_GetStringFromWaveNote(wvYRef, SF_META_XAXISLABEL)) - plotMetaData[%YAXISLABEL] = JWN_GetStringFromWaveNote(wvYRef, SF_META_YAXISLABEL) + dataUnits + WAVE/T plotMetaData = SF_FillPlotMetaData(wvyRef, useXLabel, dataUnits) + + return [formulaResults, plotMetaData] +End + +static Function [WAVE/WAVE formulaResults, WAVE/T plotMetaData] SF_GatherFormulaResults(string xFormula, string yFormula, string graph, variable lineNr, variable offset) + + WAVE/Z/WAVE wvXRef = $"" + if(!IsEmpty(xFormula)) + WAVE/WAVE wvXRef = SFE_ExecuteFormula(xFormula, graph, useVariables = 0, line = lineNr, offset = offset) + SFH_ASSERT(WaveExists(wvXRef), "x part of formula returned no result.") + endif + WAVE/WAVE wvYRef = SFE_ExecuteFormula(yFormula, graph, useVariables = 0, line = lineNr, offset = 0) + + [WAVE/WAVE formulaResults, WAVE/T plotMetaData] = SF_FillFormulaResults(wvYRef, wvXRef, yFormula) return [formulaResults, plotMetaData] End @@ -488,12 +540,12 @@ End /// /// @retval traces generated trace names /// @retval traceCnt total count of all traces (input *and* output) -static Function [WAVE/T traces, variable traceCnt] SF_CreateTraceNames(variable numTraces, variable dataNum, WAVE/T plotMetaData, WAVE data) +static Function [WAVE/T traces, STRUCT SF_PlotterGraphStruct pg] SF_CreateTraceNames(variable numTraces, variable dataNum, WAVE/T plotMetaData, WAVE data) string traceAnnotation if(!numTraces) - return [$"", traceCnt] + return [$"", pg] endif traceAnnotation = SF_GetTraceAnnotationText(plotMetaData, data) @@ -502,9 +554,10 @@ static Function [WAVE/T traces, variable traceCnt] SF_CreateTraceNames(variable Make/T/N=(numTraces)/FREE traces - traces[] = GetTraceNamePrefix(traceCnt + p) + "d" + num2istr(dataNum) + "_" + traceAnnotation + traces[] = GetTraceNamePrefix(pg.traceCnt + p) + "d" + num2istr(dataNum) + "_" + traceAnnotation + pg.traceCnt += numTraces - return [traces, traceCnt + numTraces] + return [traces, pg] End /// Reduces a multi line legend to a single line if only the sweep number changes. @@ -895,7 +948,7 @@ static Function SF_IsDataForTableDisplay(WAVE wvY) return IsNaN(useTable) ? 0 : !!useTable End -static Function [variable dataCnt, variable traceCnt, variable gdIndex, string annotation, variable formulaAddedOncePerDataset, variable showLegend] SF_CreateTracesForResultsImpl(string graph, WAVE wvResultY, WAVE/Z wvResultX, WAVE/T plotMetaData, variable dataNum, WAVE/Z colorGroups, variable showInTable, string win, WAVE/T tableFormulas, WAVE plotFormData) +static Function [variable dataCnt, variable gdIndex, string annotation, variable formulaAddedOncePerDataset] SF_CreateTracesForResultsImpl(STRUCT SF_PlotterGraphStruct &pg, WAVE wvResultY, WAVE/Z wvResultX, variable dataNum, variable showInTable, WAVE plotFormData) STRUCT RGBColor color variable numTraces, yPoints, xPoints, yMxN, xMxN, idx, splitTraces @@ -906,11 +959,11 @@ static Function [variable dataCnt, variable traceCnt, variable gdIndex, string a SFH_ASSERT(!(IsTextWave(wvResultY) && WaveDims(wvResultY) > 1), "Plotter got 2d+ text wave as y data.") - DFREF dfr = SF_GetBrowserDF(graph) + DFREF dfr = SF_GetBrowserDF(pg.graph) - [color] = SF_GetTraceColor(graph, plotMetaData[%OPSTACK], wvResultY, colorGroups) + [color] = SF_GetTraceColor(pg.graph, pg.plotMetaData[%OPSTACK], wvResultY, pg.colorGroups) - if(!WaveExists(wvResultX) && !IsEmpty(plotMetaData[%XAXISLABEL])) + if(!WaveExists(wvResultX) && !IsEmpty(pg.plotMetaData[%XAXISLABEL])) WAVE/Z wvResultX = JWN_GetNumericWaveFromWaveNote(wvResultY, SF_META_XVALUES) if(!WaveExists(wvResultX)) @@ -933,26 +986,26 @@ static Function [variable dataCnt, variable traceCnt, variable gdIndex, string a if(showInTable) if(HasDimLabels(wvY, ROWS) || HasDimLabels(wvY, COLS)) - AppendToTable/W=$win wvY.ld + AppendToTable/W=$pg.win wvY.ld else - AppendToTable/W=$win wvY.d + AppendToTable/W=$pg.win wvY.d endif if(!formulaAddedOncePerDataset) - idx = GetNumberFromWaveNote(tableFormulas, NOTE_INDEX) - EnsureLargeEnoughWave(tableFormulas, indexShouldExist = idx) - tableFormulas[idx] = JWN_GetStringFromWaveNote(wvY, SF_META_FORMULA) - SetNumberInWaveNote(tableFormulas, NOTE_INDEX, idx + 1) + idx = GetNumberFromWaveNote(pg.tableFormulas, NOTE_INDEX) + EnsureLargeEnoughWave(pg.tableFormulas, indexShouldExist = idx) + pg.tableFormulas[idx] = JWN_GetStringFromWaveNote(wvY, SF_META_FORMULA) + SetNumberInWaveNote(pg.tableFormulas, NOTE_INDEX, idx + 1) formulaAddedOncePerDataset = 1 endif dataCnt += 1 - return [dataCnt, traceCnt, gdIndex, annotation, formulaAddedOncePerDataset, showLegend] + return [dataCnt, gdIndex, annotation, formulaAddedOncePerDataset] endif if(IsTextWave(wvY)) SFH_ASSERT(WaveExists(wvX), "Cannot plot a single text wave") - ModifyGraph/W=$win swapXY=1 + ModifyGraph/W=$pg.win swapXY=1 WAVE dummy = wvY WAVE wvY = wvX WAVE wvX = dummy @@ -960,48 +1013,48 @@ static Function [variable dataCnt, variable traceCnt, variable gdIndex, string a if(!WaveExists(wvX)) numTraces = yMxN - SF_CheckNumTraces(graph, numTraces) - [WAVE/T traces, traceCnt] = SF_CreateTraceNames(numTraces, dataNum, plotMetaData, wvResultY) + SF_CheckNumTraces(pg.graph, numTraces) + [WAVE/T traces, pg] = SF_CreateTraceNames(numTraces, dataNum, pg.plotMetaData, wvResultY) for(i = 0; i < numTraces; i += 1) SF_CollectTraceData(gdIndex, plotFormData, traces[i], wvX, wvY) - AppendTograph/W=$win/C=(color.red, color.green, color.blue) wvY[][i]/TN=$traces[i] - annotation += SF_GetMetaDataAnnotationText(plotMetaData, wvResultY, traces[i]) + AppendTograph/W=$pg.win/C=(color.red, color.green, color.blue) wvY[][i]/TN=$traces[i] + annotation += SF_GetMetaDataAnnotationText(pg.plotMetaData, wvResultY, traces[i]) endfor elseif((xMxN == 1) && (yMxN == 1)) // 1D if(yPoints == 1) // 0D vs 1D numTraces = xPoints - SF_CheckNumTraces(graph, numTraces) - [WAVE/T traces, traceCnt] = SF_CreateTraceNames(numTraces, dataNum, plotMetaData, wvResultY) + SF_CheckNumTraces(pg.graph, numTraces) + [WAVE/T traces, pg] = SF_CreateTraceNames(numTraces, dataNum, pg.plotMetaData, wvResultY) for(i = 0; i < numTraces; i += 1) SF_CollectTraceData(gdIndex, plotFormData, traces[i], wvX, wvY) - AppendTograph/W=$win/C=(color.red, color.green, color.blue) wvY[][0]/TN=$traces[i] vs wvX[i][] - annotation += SF_GetMetaDataAnnotationText(plotMetaData, wvResultY, traces[i]) + AppendTograph/W=$pg.win/C=(color.red, color.green, color.blue) wvY[][0]/TN=$traces[i] vs wvX[i][] + annotation += SF_GetMetaDataAnnotationText(pg.plotMetaData, wvResultY, traces[i]) endfor elseif(xPoints == 1) // 1D vs 0D numTraces = yPoints - SF_CheckNumTraces(graph, numTraces) - [WAVE/T traces, traceCnt] = SF_CreateTraceNames(numTraces, dataNum, plotMetaData, wvResultY) + SF_CheckNumTraces(pg.graph, numTraces) + [WAVE/T traces, pg] = SF_CreateTraceNames(numTraces, dataNum, pg.plotMetaData, wvResultY) for(i = 0; i < numTraces; i += 1) SF_CollectTraceData(gdIndex, plotFormData, traces[i], wvX, wvY) - AppendTograph/W=$win/C=(color.red, color.green, color.blue) wvY[i][]/TN=$traces[i] vs wvX[][0] - annotation += SF_GetMetaDataAnnotationText(plotMetaData, wvResultY, traces[i]) + AppendTograph/W=$pg.win/C=(color.red, color.green, color.blue) wvY[i][]/TN=$traces[i] vs wvX[][0] + annotation += SF_GetMetaDataAnnotationText(pg.plotMetaData, wvResultY, traces[i]) endfor else // 1D vs 1D splitTraces = min(yPoints, xPoints) numTraces = floor(max(yPoints, xPoints) / splitTraces) - SF_CheckNumTraces(graph, numTraces) - [WAVE/T traces, traceCnt] = SF_CreateTraceNames(numTraces, dataNum, plotMetaData, wvResultY) + SF_CheckNumTraces(pg.graph, numTraces) + [WAVE/T traces, pg] = SF_CreateTraceNames(numTraces, dataNum, pg.plotMetaData, wvResultY) if(mod(max(yPoints, xPoints), splitTraces) == 0) DebugPrint("Unmatched Data Alignment in ROWS.") endif for(i = 0; i < numTraces; i += 1) - if(WindowExists(win) && WhichListItem("bottom", AxisList(win)) >= 0) - info = AxisInfo(win, "bottom") + if(WindowExists(pg.win) && WhichListItem("bottom", AxisList(pg.win)) >= 0) + info = AxisInfo(pg.win, "bottom") isCategoryAxis = NumberByKey("ISCAT", info) == 1 if(isCategoryAxis) @@ -1020,19 +1073,19 @@ static Function [variable dataCnt, variable traceCnt, variable gdIndex, string a SF_CollectTraceData(gdIndex, plotFormData, traces[i], wvX, wvY) splitY = SF_SplitPlotting(wvY, ROWS, i, splitTraces) splitX = SF_SplitPlotting(wvX, ROWS, i, splitTraces) - AppendTograph/W=$win/C=(color.red, color.green, color.blue) wvY[splitY, splitY + splitTraces - 1][0]/TN=$traces[i] vs wvX[splitX, splitX + splitTraces - 1][0] - annotation += SF_GetMetaDataAnnotationText(plotMetaData, wvResultY, traces[i]) + AppendTograph/W=$pg.win/C=(color.red, color.green, color.blue) wvY[splitY, splitY + splitTraces - 1][0]/TN=$traces[i] vs wvX[splitX, splitX + splitTraces - 1][0] + annotation += SF_GetMetaDataAnnotationText(pg.plotMetaData, wvResultY, traces[i]) endfor endif elseif(yMxN == 1) // 1D vs 2D numTraces = xMxN - SF_CheckNumTraces(graph, numTraces) - [WAVE/T traces, traceCnt] = SF_CreateTraceNames(numTraces, dataNum, plotMetaData, wvResultY) + SF_CheckNumTraces(pg.graph, numTraces) + [WAVE/T traces, pg] = SF_CreateTraceNames(numTraces, dataNum, pg.plotMetaData, wvResultY) for(i = 0; i < numTraces; i += 1) SF_CollectTraceData(gdIndex, plotFormData, traces[i], wvX, wvY) - AppendTograph/W=$win/C=(color.red, color.green, color.blue) wvY[][0]/TN=$traces[i] vs wvX[][i] - annotation += SF_GetMetaDataAnnotationText(plotMetaData, wvResultY, traces[i]) + AppendTograph/W=$pg.win/C=(color.red, color.green, color.blue) wvY[][0]/TN=$traces[i] vs wvX[][i] + annotation += SF_GetMetaDataAnnotationText(pg.plotMetaData, wvResultY, traces[i]) endfor elseif(xMxN == 1) // 2D vs 1D or 0D if(xPoints == 1) // 2D vs 0D -> extend X to 1D with constant value @@ -1041,18 +1094,18 @@ static Function [variable dataCnt, variable traceCnt, variable gdIndex, string a wvX = wvX[0] endif numTraces = yMxN - SF_CheckNumTraces(graph, numTraces) - [WAVE/T traces, traceCnt] = SF_CreateTraceNames(numTraces, dataNum, plotMetaData, wvResultY) + SF_CheckNumTraces(pg.graph, numTraces) + [WAVE/T traces, pg] = SF_CreateTraceNames(numTraces, dataNum, pg.plotMetaData, wvResultY) for(i = 0; i < numTraces; i += 1) SF_CollectTraceData(gdIndex, plotFormData, traces[i], wvX, wvY) - AppendTograph/W=$win/C=(color.red, color.green, color.blue) wvY[][i]/TN=$traces[i] vs wvX - annotation += SF_GetMetaDataAnnotationText(plotMetaData, wvResultY, traces[i]) + AppendTograph/W=$pg.win/C=(color.red, color.green, color.blue) wvY[][i]/TN=$traces[i] vs wvX + annotation += SF_GetMetaDataAnnotationText(pg.plotMetaData, wvResultY, traces[i]) endfor else // 2D vs 2D numTraces = WaveExists(wvX) ? max(1, max(yMxN, xMxN)) : max(1, yMxN) - SF_CheckNumTraces(graph, numTraces) - [WAVE/T traces, traceCnt] = SF_CreateTraceNames(numTraces, dataNum, plotMetaData, wvResultY) + SF_CheckNumTraces(pg.graph, numTraces) + [WAVE/T traces, pg] = SF_CreateTraceNames(numTraces, dataNum, pg.plotMetaData, wvResultY) if(yPoints != xPoints) DebugPrint("Size mismatch in data rows for plotting waves.") @@ -1063,22 +1116,22 @@ static Function [variable dataCnt, variable traceCnt, variable gdIndex, string a for(i = 0; i < numTraces; i += 1) SF_CollectTraceData(gdIndex, plotFormData, traces[i], wvX, wvY) if(WaveExists(wvX)) - AppendTograph/W=$win/C=(color.red, color.green, color.blue) wvY[][min(yMxN - 1, i)]/TN=$traces[i] vs wvX[][min(xMxN - 1, i)] + AppendTograph/W=$pg.win/C=(color.red, color.green, color.blue) wvY[][min(yMxN - 1, i)]/TN=$traces[i] vs wvX[][min(xMxN - 1, i)] else - AppendTograph/W=$win/C=(color.red, color.green, color.blue) wvY[][i]/TN=$traces[i] + AppendTograph/W=$pg.win/C=(color.red, color.green, color.blue) wvY[][i]/TN=$traces[i] endif - annotation += SF_GetMetaDataAnnotationText(plotMetaData, wvResultY, traces[i]) + annotation += SF_GetMetaDataAnnotationText(pg.plotMetaData, wvResultY, traces[i]) endfor endif - showLegend = showLegend && SF_GetShowLegend(wvY) + pg.showLegend = pg.showLegend && SF_GetShowLegend(wvY) dataCnt += 1 - return [dataCnt, traceCnt, gdIndex, annotation, formulaAddedOncePerDataset, showLegend] + return [dataCnt, gdIndex, annotation, formulaAddedOncePerDataset] End -static Function [variable dataCnt, variable traceCnt, WAVE/Z colorGroups, variable showLegend] SF_CreateTracesForResults(string graph, WAVE/WAVE formulaResults, variable formulaCounter, WAVE/T plotMetaData, string win, WAVE/T tableFormulas, WAVE/T wAnnotations, WAVE/T formulaArgSetup, WAVE/WAVE collPlotFormData) +static Function [variable dataCnt] SF_CreateTracesForResults(STRUCT SF_PlotterGraphStruct &pg) variable i, idx, showInTable, numData, formulaAddedOncePerDataset variable gdIndex // indexes in tracesInGraph wave and dataInGraph wave in SF_CollectTraceData(), both waves are stored in plotformData @@ -1086,21 +1139,21 @@ static Function [variable dataCnt, variable traceCnt, WAVE/Z colorGroups, variab WAVE/WAVE plotFormData = SF_CreatePlotFormulaDataWave() - SF_FormulaPlotterExtendResultsIfCompatible(formulaResults) + SF_FormulaPlotterExtendResultsIfCompatible(pg.formulaResults) - if(WaveExists(colorGroups)) - Duplicate/FREE colorGroups, previousColorGroups + if(WaveExists(pg.colorGroups)) + Duplicate/FREE pg.colorGroups, previousColorGroups else WAVE/ZZ previousColorGroups endif - WAVE/Z colorGroups = SF_GetColorGroups(formulaResults, previousColorGroups) - showInTable = SF_IsDataForTableDisplay(formulaResults) + WAVE/Z pg.colorGroups = SF_GetColorGroups(pg.formulaResults, previousColorGroups) + showInTable = SF_IsDataForTableDisplay(pg.formulaResults) - numData = DimSize(formulaResults, ROWS) + numData = DimSize(pg.formulaResults, ROWS) for(i = 0; i < numData; i += 1) - WAVE/Z wvResultX = formulaResults[i][%FORMULAX] - WAVE/Z wvResultY = formulaResults[i][%FORMULAY] + WAVE/Z wvResultX = pg.formulaResults[i][%FORMULAX] + WAVE/Z wvResultY = pg.formulaResults[i][%FORMULAY] if(!WaveExists(wvResultY)) continue endif @@ -1108,26 +1161,28 @@ static Function [variable dataCnt, variable traceCnt, WAVE/Z colorGroups, variab continue endif - [dataCnt, traceCnt, gdIndex, annotation, formulaAddedOncePerDataset, showLegend] = SF_CreateTracesForResultsImpl(graph, wvResultY, wvResultX, plotMetaData, i, colorGroups, showInTable, win, tableFormulas, plotFormData) + [dataCnt, gdIndex, annotation, formulaAddedOncePerDataset] = SF_CreateTracesForResultsImpl(pg, wvResultY, wvResultX, i, showInTable, plotFormData) endfor if(!IsEmpty(annotation)) - idx = GetNumberFromWaveNote(wAnnotations, NOTE_INDEX) - EnsureLargeEnoughWave(wAnnotations, indexShouldExist = idx) - wAnnotations[idx] = annotation - SetNumberInWaveNote(wAnnotations, NOTE_INDEX, idx + 1) + idx = GetNumberFromWaveNote(pg.wAnnotations, NOTE_INDEX) + EnsureLargeEnoughWave(pg.wAnnotations, indexShouldExist = idx) + pg.wAnnotations[idx] = annotation + SetNumberInWaveNote(pg.wAnnotations, NOTE_INDEX, idx + 1) - idx = GetNumberFromWaveNote(formulaArgSetup, NOTE_INDEX) - EnsureLargeEnoughWave(formulaArgSetup, indexShouldExist = idx) - formulaArgSetup[idx] = plotMetaData[%ARGSETUPSTACK] - SetNumberInWaveNote(formulaArgSetup, NOTE_INDEX, idx + 1) + idx = GetNumberFromWaveNote(pg.formulaArgSetup, NOTE_INDEX) + EnsureLargeEnoughWave(pg.formulaArgSetup, indexShouldExist = idx) + pg.formulaArgSetup[idx] = pg.plotMetaData[%ARGSETUPSTACK] + SetNumberInWaveNote(pg.formulaArgSetup, NOTE_INDEX, idx + 1) endif - EnsureLargeEnoughWave(collPlotFormData, indexShouldExist = formulaCounter) + EnsureLargeEnoughWave(pg.collPlotFormData, indexShouldExist = pg.formulaCounter) WAVE/T tracesInGraph = plotFormData[0] WAVE/WAVE dataInGraph = plotFormData[1] Redimension/N=(gdIndex, -1) tracesInGraph, dataInGraph - collPlotFormData[formulaCounter] = plotFormData + pg.collPlotFormData[pg.formulaCounter] = plotFormData + + return [dataCnt] End static Function SF_RestorePlotProperties(WAVE/WAVE prevPlotProperties) @@ -1256,6 +1311,43 @@ static Function/S SF_CreateDataDisplayWindow(string graph, WAVE/WAVE formulaResu return win End +static Function [STRUCT SF_PlotterGraphStruct pg] SF_ResetPlotterGraphStruct(string graph) + + pg.graph = graph + pg.win = "" + + WAVE/Z pg.colorGroups = $"" + pg.traceCnt = 0 + pg.postPlotPSX = 0 + pg.showLegend = 1 + pg.formulaCounter = 0 + + Make/FREE/T/N=0 xAxisLabels, yAxisLabels + WAVE pg.xAxisLabels = xAxisLabels + WAVE pg.yAxisLabels = yAxisLabels + + Make/FREE=1/T/N=(MINIMUM_WAVE_SIZE) wAnnotations, formulaArgSetup, tableFormulas + SetNumberInWaveNote(wAnnotations, NOTE_INDEX, 0) + SetNumberInWaveNote(formulaArgSetup, NOTE_INDEX, 0) + SetNumberInWaveNote(tableFormulas, NOTE_INDEX, 0) + WAVE/T pg.wAnnotations = wAnnotations + WAVE/T pg.formulaArgSetup = formulaArgSetup + WAVE/T pg.tableFormulas = tableFormulas + + Make/FREE=1/WAVE/N=(MINIMUM_WAVE_SIZE) collPlotFormData + WAVE pg.collPlotFormData = collPlotFormData + + Make/FREE=1/D/N=2 panelsCreated + SetDimLabel ROWS, 0, GRAPH, panelsCreated + SetDimLabel ROWS, 1, TABLE, panelsCreated + WAVE pg.panelsCreated = panelsCreated + + WAVE/Z/WAVE pg.formulaResults = $"" + WAVE/Z/T pg.plotMetaData = $"" + + return [pg] +End + /// @brief Plot the formula using the data from graph /// /// @param graph graph to pass to SF_FormulaExecutor @@ -1269,8 +1361,9 @@ static Function SF_FormulaPlotter(string graph, string formula, [variable dmMode variable keepUserSelection, formulasAreDifferent, postPlotPSX variable formulaCounter, xFormulaOffset variable numTableFormulas, formulaAddedOncePerDataset, showInTable - string win, wList, xAxis + string wList string formulasRemain, moreFormulas, yAndXFormula, xFormula, yFormula, winHook + STRUCT SF_PlotterGraphStruct pg winDisplayMode = ParamIsDefault(dmMode) ? SF_DM_SUBWINDOWS : dmMode lineVars = ParamIsDefault(lineVars) ? NaN : lineVars @@ -1291,30 +1384,12 @@ static Function SF_FormulaPlotter(string graph, string formula, [variable dmMode WAVE/T winGraphs = outputWindows[%GRAPH] WAVE/WAVE prevPlotProperties = GetSFPlotProperties() - Make/FREE/D/N=2 panelsCreated - SetDimLabel ROWS, 0, GRAPH, panelsCreated - SetDimLabel ROWS, 1, TABLE, panelsCreated - for(i = 0; i < numGraphs; i += 1) - traceCnt = 0 - postPlotPSX = 0 - showLegend = 1 - formulaCounter = 0 - formulasAreDifferent = 0 - WAVE/Z colorGroups = $"" - FastOp panelsCreated = 0 - - Make/FREE/T/N=0 xAxisLabels, yAxisLabels - formulasRemain = graphCode[i][%GRAPHCODE] lineGraph = str2num(graphCode[i][%LINE]) - Make/FREE=1/T/N=(MINIMUM_WAVE_SIZE) wAnnotations, formulaArgSetup, tableFormulas - SetNumberInWaveNote(wAnnotations, NOTE_INDEX, 0) - SetNumberInWaveNote(formulaArgSetup, NOTE_INDEX, 0) - SetNumberInWaveNote(tableFormulas, NOTE_INDEX, 0) - Make/FREE=1/WAVE/N=(MINIMUM_WAVE_SIZE) collPlotFormData + [pg] = SF_ResetPlotterGraphStruct(graph) do @@ -1330,73 +1405,76 @@ static Function SF_FormulaPlotter(string graph, string formula, [variable dmMode SFH_ASSERT(!IsEmpty(yFormula), "Could not determine y [vs x] formula pair.") try - [WAVE/WAVE formulaResults, WAVE/T plotMetaData] = SF_GatherFormulaResults(xFormula, yFormula, graph, line, xFormulaOffset) + [WAVE/WAVE formulaResults, WAVE/T plotMetaData] = SF_GatherFormulaResults(xFormula, yFormula, pg.graph, line, xFormulaOffset) + + WAVE/WAVE pg.formulaResults = formulaResults + WAVE/T pg.plotMetaData = plotMetaData catch SF_KillEmptyDataWindows(winGraphs) SF_KillEmptyDataWindows(winTables) Abort endtry - SF_GatherAxisLabels(formulaResults, plotMetaData[%XAXISLABEL], "FORMULAX", xAxisLabels) - SF_GatherAxisLabels(formulaResults, plotMetaData[%YAXISLABEL], "FORMULAY", yAxisLabels) + SF_GatherAxisLabels(pg.formulaResults, pg.plotMetaData[%XAXISLABEL], "FORMULAX", pg.xAxisLabels) + SF_GatherAxisLabels(pg.formulaResults, pg.plotMetaData[%YAXISLABEL], "FORMULAY", pg.yAxisLabels) - showInTable = SF_IsDataForTableDisplay(formulaResults) - if(!panelsCreated[%GRAPH] && !showInTable) - win = SF_CreateDataDisplayWindow(graph, formulaResults, outputWindows, winDisplayMode, prevPlotProperties) - panelsCreated[%GRAPH] = 1 + showInTable = SF_IsDataForTableDisplay(pg.formulaResults) + if(!pg.panelsCreated[%GRAPH] && !showInTable) + pg.win = SF_CreateDataDisplayWindow(pg.graph, pg.formulaResults, outputWindows, winDisplayMode, prevPlotProperties) + pg.panelsCreated[%GRAPH] = 1 if(winDisplaymode == SF_DM_NORMAL) - wList = AddListItem(win, wList) + wList = AddListItem(pg.win, wList) endif - elseif(!panelsCreated[%TABLE] && showInTable) - win = SF_CreateDataDisplayWindow(graph, formulaResults, outputWindows, winDisplayMode, prevPlotProperties) - panelsCreated[%TABLE] = 1 + elseif(!pg.panelsCreated[%TABLE] && showInTable) + pg.win = SF_CreateDataDisplayWindow(pg.graph, pg.formulaResults, outputWindows, winDisplayMode, prevPlotProperties) + pg.panelsCreated[%TABLE] = 1 if(winDisplaymode == SF_DM_NORMAL) - wList = AddListItem(win, wList) + wList = AddListItem(pg.win, wList) endif elseif(!showInTable) - win = winGraphs[GetNumberFromWaveNote(winGraphs, NOTE_INDEX) - 1] + pg.win = winGraphs[GetNumberFromWaveNote(winGraphs, NOTE_INDEX) - 1] else - win = winTables[GetNumberFromWaveNote(winTables, NOTE_INDEX) - 1] + pg.win = winTables[GetNumberFromWaveNote(winTables, NOTE_INDEX) - 1] endif if(!cmpstr(plotMetaData[%DATATYPE], SF_DATATYPE_PSX)) - PSX_Plot(win, graph, formulaResults, plotMetaData) + PSX_Plot(pg.win, pg.graph, pg.formulaResults, pg.plotMetaData) postPlotPSX = 1 continue endif - [dataCnt, traceCnt, colorGroups, showLegend] = SF_CreateTracesForResults(graph, formulaResults, formulaCounter, plotMetaData, win, tableFormulas, wAnnotations, formulaArgSetup, collPlotFormData) - formulaCounter += 1 + [dataCnt] = SF_CreateTracesForResults(pg) + formulaCounter += 1 while(1) - numTableFormulas = GetNumberFromWaveNote(tableFormulas, NOTE_INDEX) + numTableFormulas = GetNumberFromWaveNote(pg.tableFormulas, NOTE_INDEX) if(numTableFormulas) - Redimension/N=(numTableFormulas) tableFormulas - SetWindow $win, userdata($SF_UDATA_TABLEFORMULAS)=WaveToJSON(tableFormulas) + Redimension/N=(numTableFormulas) pg.tableFormulas + SetWindow $pg.win, userdata($SF_UDATA_TABLEFORMULAS)=WaveToJSON(pg.tableFormulas) endif - if(panelsCreated[%GRAPH]) - win = winGraphs[GetNumberFromWaveNote(winGraphs, NOTE_INDEX) - 1] + if(pg.panelsCreated[%GRAPH]) + pg.win = winGraphs[GetNumberFromWaveNote(winGraphs, NOTE_INDEX) - 1] if(showLegend) - formulasAreDifferent = SF_AddPlotLegend(win, wAnnotations, formulaArgSetup, formulaResults) + formulasAreDifferent = SF_AddPlotLegend(pg.win, pg.wAnnotations, pg.formulaArgSetup, pg.formulaResults) endif - SF_AddPlotTicks(graph, win, formulaResults) + SF_AddPlotTicks(pg.graph, pg.win, pg.formulaResults) winHook = JWN_GetStringFromWaveNote(formulaResults, SF_META_WINDOW_HOOK) if(!IsEmpty(winHook)) - SetWindow $win, tooltipHook(SweepFormulaTraceValue)=$winHook + SetWindow $pg.win, tooltipHook(SweepFormulaTraceValue)=$winHook endif - SF_AddPlotTraceStyle(graph, win, formulaCounter, collPlotFormData, formulasAreDifferent) + SF_AddPlotTraceStyle(pg.graph, pg.win, formulaCounter, pg.collPlotFormData, formulasAreDifferent) if(traceCnt > 0) - SF_AddPlotLabels(win, xAxisLabels, yAxisLabels) + SF_AddPlotLabels(pg.win, pg.xAxisLabels, pg.yAxisLabels) endif endif if(postPlotPSX) - PSX_PostPlot(win) + PSX_PostPlot(pg.win) endif endfor @@ -1411,7 +1489,7 @@ static Function SF_FormulaPlotter(string graph, string formula, [variable dmMode SF_KillEmptyDataWindows(winGraphs) SF_KillEmptyDataWindows(winTables) - SF_KillOldDataDisplayWindows(graph, winDisplayMode, wList, outputWindows) + SF_KillOldDataDisplayWindows(pg.graph, winDisplayMode, wList, outputWindows) End static Function SF_AddPlotTraceStyle(string graph, string win, variable formulaCounter, WAVE/WAVE collPlotFormData, variable formulasAreDifferent) From ae14ed4ccce2d1899a5817befdcd1a953204f394 Mon Sep 17 00:00:00 2001 From: Michael Huth Date: Mon, 9 Feb 2026 16:47:53 +0100 Subject: [PATCH 02/15] SF: Factor out code that modifies the plot window in the main loop To own func: SF_FinishPlotWindow and SF_AddPlotTraceStyle that sets trace styles --- Packages/MIES/MIES_SweepFormula.ipf | 86 ++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 25 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula.ipf b/Packages/MIES/MIES_SweepFormula.ipf index 01776426ad..0d283118c7 100644 --- a/Packages/MIES/MIES_SweepFormula.ipf +++ b/Packages/MIES/MIES_SweepFormula.ipf @@ -1456,7 +1456,7 @@ static Function SF_FormulaPlotter(string graph, string formula, [variable dmMode if(pg.panelsCreated[%GRAPH]) pg.win = winGraphs[GetNumberFromWaveNote(winGraphs, NOTE_INDEX) - 1] if(showLegend) - formulasAreDifferent = SF_AddPlotLegend(pg.win, pg.wAnnotations, pg.formulaArgSetup, pg.formulaResults) + formulasAreDifferent = SF_AddPlotLegend(pg) endif SF_AddPlotTicks(pg.graph, pg.win, pg.formulaResults) @@ -1466,7 +1466,7 @@ static Function SF_FormulaPlotter(string graph, string formula, [variable dmMode SetWindow $pg.win, tooltipHook(SweepFormulaTraceValue)=$winHook endif - SF_AddPlotTraceStyle(pg.graph, pg.win, formulaCounter, pg.collPlotFormData, formulasAreDifferent) + SF_AddPlotTraceStyle(pg, formulasAreDifferent) if(traceCnt > 0) SF_AddPlotLabels(pg.win, pg.xAxisLabels, pg.yAxisLabels) @@ -1492,13 +1492,49 @@ static Function SF_FormulaPlotter(string graph, string formula, [variable dmMode SF_KillOldDataDisplayWindows(pg.graph, winDisplayMode, wList, outputWindows) End -static Function SF_AddPlotTraceStyle(string graph, string win, variable formulaCounter, WAVE/WAVE collPlotFormData, variable formulasAreDifferent) +static Function SF_FinishPlotWindow(STRUCT SF_PlotterGraphStruct &pg, WAVE/T winGraphs) + + variable formulasAreDifferent, numTableFormulas + string winHook + + numTableFormulas = GetNumberFromWaveNote(pg.tableFormulas, NOTE_INDEX) + if(numTableFormulas) + Redimension/N=(numTableFormulas) pg.tableFormulas + SetWindow $pg.win, userdata($SF_UDATA_TABLEFORMULAS)=WaveToJSON(pg.tableFormulas) + endif + + if(pg.panelsCreated[%GRAPH]) + pg.win = winGraphs[GetNumberFromWaveNote(winGraphs, NOTE_INDEX) - 1] + if(pg.showLegend) + formulasAreDifferent = SF_AddPlotLegend(pg) + endif + + SF_AddPlotTicks(pg.graph, pg.win, pg.formulaResults) + + winHook = JWN_GetStringFromWaveNote(pg.formulaResults, SF_META_WINDOW_HOOK) + if(!IsEmpty(winHook)) + SetWindow $pg.win, tooltipHook(SweepFormulaTraceValue)=$winHook + endif + + SF_AddPlotTraceStyle(pg, formulasAreDifferent) + + if(pg.traceCnt > 0) + SF_AddPlotLabels(pg.win, pg.xAxisLabels, pg.yAxisLabels) + endif + endif + + if(pg.postPlotPSX) + PSX_PostPlot(pg.win) + endif +End + +static Function SF_AddPlotTraceStyle(STRUCT SF_PlotterGraphStruct &pg, variable formulasAreDifferent) variable i, j, numTraces, markerCode, lineCode, isCategoryAxis, tagCounter, lineStyle, overrideMarker, traceToFront string trace, info, tagText, name, wvName - for(i = 0; i < formulaCounter; i += 1) - WAVE/WAVE plotFormData = collPlotFormData[i] + for(i = 0; i < pg.formulaCounter; i += 1) + WAVE/WAVE plotFormData = pg.collPlotFormData[i] WAVE/T tracesInGraph = plotFormData[0] WAVE/WAVE dataInGraph = plotFormData[1] numTraces = DimSize(tracesInGraph, ROWS) @@ -1512,7 +1548,7 @@ static Function SF_AddPlotTraceStyle(string graph, string win, variable formulaC WAVE wvY = dataInGraph[j][%WAVEY] trace = tracesInGraph[j] - info = AxisInfo(win, "left") + info = AxisInfo(pg.win, "left") isCategoryAxis = (NumberByKey("ISCAT", info) == 1) if(isCategoryAxis) @@ -1525,10 +1561,10 @@ static Function SF_AddPlotTraceStyle(string graph, string win, variable formulaC if(WaveExists(traceColor)) switch(DimSize(traceColor, ROWS)) case 3: - ModifyGraph/W=$win rgb($trace)=(traceColor[0], traceColor[1], traceColor[2]) + ModifyGraph/W=$pg.win rgb($trace)=(traceColor[0], traceColor[1], traceColor[2]) break case 4: - ModifyGraph/W=$win rgb($trace)=(traceColor[0], traceColor[1], traceColor[2], traceColor[3]) + ModifyGraph/W=$pg.win rgb($trace)=(traceColor[0], traceColor[1], traceColor[2], traceColor[3]) break default: FATAL_ERROR("Invalid size of trace color wave") @@ -1538,25 +1574,25 @@ static Function SF_AddPlotTraceStyle(string graph, string win, variable formulaC tagText = JWN_GetStringFromWaveNote(wvY, SF_META_TAG_TEXT) if(!IsEmpty(tagText)) name = "tag" + num2str(tagCounter++) - Tag/C/N=$name/W=$win/F=0/L=0/X=0.00/Y=0.00 $trace, 0, tagText + Tag/C/N=$name/W=$pg.win/F=0/L=0/X=0.00/Y=0.00 $trace, 0, tagText endif - ModifyGraph/W=$win mode($trace)=SF_DeriveTraceDisplayMode(wvX, wvY) + ModifyGraph/W=$pg.win mode($trace)=SF_DeriveTraceDisplayMode(wvX, wvY) lineStyle = JWN_GetNumberFromWaveNote(wvY, SF_META_LINESTYLE) if(IsValidTraceLineStyle(lineStyle)) - ModifyGraph/W=$win lStyle($trace)=lineStyle + ModifyGraph/W=$pg.win lStyle($trace)=lineStyle elseif(formulasAreDifferent) - ModifyGraph/W=$win lStyle($trace)=lineCode + ModifyGraph/W=$pg.win lStyle($trace)=lineCode endif WAVE/Z customMarkerAsFree = JWN_GetNumericWaveFromWaveNote(wvY, SF_META_MOD_MARKER) if(WaveExists(customMarkerAsFree)) - DFREF dfrWork = SFH_GetWorkingDF(graph) + DFREF dfrWork = SFH_GetWorkingDF(pg.graph) wvName = "customMarker_" + NameOfWave(wvY) WAVE customMarker = MoveFreeWaveToPermanent(customMarkerAsFree, dfrWork, wvName) ASSERT(DimSize(wvY, ROWS) == DimSize(customMarker, ROWS), "Marker size mismatch") - ModifyGraph/W=$win zmrkNum($trace)={customMarker} + ModifyGraph/W=$pg.win zmrkNum($trace)={customMarker} else overrideMarker = JWN_GetNumberFromWaveNote(wvY, SF_META_MOD_MARKER) @@ -1564,13 +1600,13 @@ static Function SF_AddPlotTraceStyle(string graph, string win, variable formulaC markerCode = overrideMarker endif - ModifyGraph/W=$win marker($trace)=markerCode + ModifyGraph/W=$pg.win marker($trace)=markerCode endif traceToFront = JWN_GetNumberFromWaveNote(wvY, SF_META_TRACETOFRONT) traceToFront = IsNaN(traceToFront) ? 0 : !!traceToFront if(traceToFront) - ReorderTraces/W=$win _front_, {$trace} + ReorderTraces/W=$pg.win _front_, {$trace} endif endfor @@ -1616,27 +1652,27 @@ static Function SF_AddPlotTicks(string graph, string win, WAVE formulaResults) endif End -static Function SF_AddPlotLegend(string win, WAVE/T wAnnotations, WAVE formulaArgSetup, WAVE formulaResults) +static Function SF_AddPlotLegend(STRUCT SF_PlotterGraphStruct &pg) variable numAnnotations, formulasAreDifferent - string customLegend + string customLegend string annotation = "" - numAnnotations = GetNumberFromWaveNote(wAnnotations, NOTE_INDEX) - customLegend = JWN_GetStringFromWaveNote(formulaResults, SF_META_CUSTOM_LEGEND) + numAnnotations = GetNumberFromWaveNote(pg.wAnnotations, NOTE_INDEX) + customLegend = JWN_GetStringFromWaveNote(pg.formulaResults, SF_META_CUSTOM_LEGEND) if(!IsEmpty(customLegend)) annotation = customLegend elseif(numAnnotations > 0) - wAnnotations[0, numAnnotations - 1] = SF_ShrinkLegend(wAnnotations[p]) - Redimension/N=(numAnnotations) wAnnotations, formulaArgSetup - formulasAreDifferent = SFH_EnrichAnnotations(wAnnotations, formulaArgSetup) - annotation = TextWaveToList(wAnnotations, "\r") + pg.wAnnotations[0, numAnnotations - 1] = SF_ShrinkLegend(pg.wAnnotations[p]) + Redimension/N=(numAnnotations) pg.wAnnotations, pg.formulaArgSetup + formulasAreDifferent = SFH_EnrichAnnotations(pg.wAnnotations, pg.formulaArgSetup) + annotation = TextWaveToList(pg.wAnnotations, "\r") annotation = UnPadString(annotation, char2num("\r")) endif if(!IsEmpty(annotation)) - Legend/W=$win/C/N=metadata/F=2 annotation + Legend/W=$pg.win/C/N=metadata/F=2 annotation endif return formulasAreDifferent From d1f6bd510fe9a527877c30b11d79ec6d95b24e65 Mon Sep 17 00:00:00 2001 From: Michael Huth Date: Mon, 9 Feb 2026 16:31:28 +0100 Subject: [PATCH 03/15] SF: Prepare full plotting specification support - Add constant for SF full plotting meta tag The tag indicates that the formula result contains a full plotting specification - Add SF_IsDataForFullPlotting that checks if a given result has the tag set. (Tag must be set an nonzero) --- Packages/MIES/MIES_Constants.ipf | 1 + Packages/MIES/MIES_SweepFormula.ipf | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/Packages/MIES/MIES_Constants.ipf b/Packages/MIES/MIES_Constants.ipf index 2e77837f5e..241a6af47c 100644 --- a/Packages/MIES/MIES_Constants.ipf +++ b/Packages/MIES/MIES_Constants.ipf @@ -2146,6 +2146,7 @@ StrConstant SF_META_TRACETOFRONT = "/TraceToFront" // number, boolean, StrConstant SF_META_DONOTPLOT = "/DoNotPlot" // number, boolean, defaults to false (0) StrConstant SF_META_WINDOW_HOOK = "/WindowHook" // string StrConstant SF_META_FORMULA = "/Formula" // string +StrConstant SF_META_PLOT = "/Plot" // number, boolean, defaults to false (0) /// A color group allows to have matching colors for sweep data with the same channel type/number and sweep. /// It is applied before the matching headstage/average colors in #SF_GetTraceColor(). diff --git a/Packages/MIES/MIES_SweepFormula.ipf b/Packages/MIES/MIES_SweepFormula.ipf index 0d283118c7..9f7bd9ffec 100644 --- a/Packages/MIES/MIES_SweepFormula.ipf +++ b/Packages/MIES/MIES_SweepFormula.ipf @@ -939,6 +939,16 @@ static Function/WAVE SF_PrepareResultWaveForPlotting(DFREF dfr, WAVE wvResult, v return plotWave End +/// @brief Returns 1 if the result is flagged as full plotting specification, 0 otherwise +static Function SF_IsDataForFullPlotting(WAVE wv) + + variable plot + + plot = JWN_GetNumberFromWaveNote(wv, SF_META_PLOT) + + return IsNaN(plot) ? 0 : !!plot +End + static Function SF_IsDataForTableDisplay(WAVE wvY) variable useTable From 2535f9b9f463236d18a4d271852d783cec7b959f Mon Sep 17 00:00:00 2001 From: Michael Huth Date: Mon, 9 Feb 2026 18:05:34 +0100 Subject: [PATCH 04/15] SF: Add support for full plotting specification in the plotter main loop A full plotting specification is a wave reference wave tree that contains formula results in its leafs. The top level iterates over all formulas specified with AND the second level over all formulas specified with WITH. The second level has two columns named FORMULAX and FORMULAY containing the respective formula results. The full plot specification is inserted into the top level plots/tables. If after the operation returning a full plot specification the plot is continued with WITH, it is respected by the plotter. --- Packages/MIES/MIES_SweepFormula.ipf | 118 +++++++++++++--------------- 1 file changed, 55 insertions(+), 63 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula.ipf b/Packages/MIES/MIES_SweepFormula.ipf index 9f7bd9ffec..0582cf0e58 100644 --- a/Packages/MIES/MIES_SweepFormula.ipf +++ b/Packages/MIES/MIES_SweepFormula.ipf @@ -1366,13 +1366,11 @@ End /// @param lineVars [optional, default NaN] number of lines in the SF notebook with variable assignments in front of the formula static Function SF_FormulaPlotter(string graph, string formula, [variable dmMode, variable lineVars]) - variable i, dataCnt, splitTraces, numGraphs, traceCnt - variable winDisplayMode, showLegend, line, lineGraph, lineGraphFormula - variable keepUserSelection, formulasAreDifferent, postPlotPSX - variable formulaCounter, xFormulaOffset - variable numTableFormulas, formulaAddedOncePerDataset, showInTable + variable i, j, k, dataCnt, numGraphs, numPlotAND, numPlotWITH + variable winDisplayMode, line, lineGraph, lineGraphFormula, xFormulaOffset + variable keepUserSelection, showInTable, isFullPlot string wList - string formulasRemain, moreFormulas, yAndXFormula, xFormula, yFormula, winHook + string formulasRemain, moreFormulas, yAndXFormula, xFormula, yFormula STRUCT SF_PlotterGraphStruct pg winDisplayMode = ParamIsDefault(dmMode) ? SF_DM_SUBWINDOWS : dmMode @@ -1415,8 +1413,7 @@ static Function SF_FormulaPlotter(string graph, string formula, [variable dmMode SFH_ASSERT(!IsEmpty(yFormula), "Could not determine y [vs x] formula pair.") try - [WAVE/WAVE formulaResults, WAVE/T plotMetaData] = SF_GatherFormulaResults(xFormula, yFormula, pg.graph, line, xFormulaOffset) - + [WAVE/WAVE formulaResults, WAVE/T plotMetaData] = SF_GatherFormulaResults(xFormula, yFormula, graph, line, xFormulaOffset) WAVE/WAVE pg.formulaResults = formulaResults WAVE/T pg.plotMetaData = plotMetaData catch @@ -1425,67 +1422,62 @@ static Function SF_FormulaPlotter(string graph, string formula, [variable dmMode Abort endtry - SF_GatherAxisLabels(pg.formulaResults, pg.plotMetaData[%XAXISLABEL], "FORMULAX", pg.xAxisLabels) - SF_GatherAxisLabels(pg.formulaResults, pg.plotMetaData[%YAXISLABEL], "FORMULAY", pg.yAxisLabels) - - showInTable = SF_IsDataForTableDisplay(pg.formulaResults) - if(!pg.panelsCreated[%GRAPH] && !showInTable) - pg.win = SF_CreateDataDisplayWindow(pg.graph, pg.formulaResults, outputWindows, winDisplayMode, prevPlotProperties) - pg.panelsCreated[%GRAPH] = 1 - if(winDisplaymode == SF_DM_NORMAL) - wList = AddListItem(pg.win, wList) + isFullPlot = SF_IsDataForFullPlotting(formulaResults) + numPlotAND = isFullPlot ? DimSize(formulaResults, ROWS) : 1 + for(j = 0; j < numPlotAND; j += 1) + if(isFullPlot) + WAVE/WAVE plotsWITH = formulaResults[j][%FORMULAY] endif - elseif(!pg.panelsCreated[%TABLE] && showInTable) - pg.win = SF_CreateDataDisplayWindow(pg.graph, pg.formulaResults, outputWindows, winDisplayMode, prevPlotProperties) - pg.panelsCreated[%TABLE] = 1 - if(winDisplaymode == SF_DM_NORMAL) - wList = AddListItem(pg.win, wList) - endif - elseif(!showInTable) - pg.win = winGraphs[GetNumberFromWaveNote(winGraphs, NOTE_INDEX) - 1] - else - pg.win = winTables[GetNumberFromWaveNote(winTables, NOTE_INDEX) - 1] - endif - - if(!cmpstr(plotMetaData[%DATATYPE], SF_DATATYPE_PSX)) - PSX_Plot(pg.win, pg.graph, pg.formulaResults, pg.plotMetaData) - postPlotPSX = 1 - continue - endif - - [dataCnt] = SF_CreateTracesForResults(pg) - formulaCounter += 1 - while(1) - - numTableFormulas = GetNumberFromWaveNote(pg.tableFormulas, NOTE_INDEX) - if(numTableFormulas) - Redimension/N=(numTableFormulas) pg.tableFormulas - SetWindow $pg.win, userdata($SF_UDATA_TABLEFORMULAS)=WaveToJSON(pg.tableFormulas) - endif + numPlotWITH = isFullPlot ? DimSize(plotsWITH, ROWS) : 1 + for(k = 0; k < numPlotWITH; k += 1) + if(isFullPlot) + WAVE/Z/WAVE wvYRef = plotsWITH[k][%FORMULAY] + WAVE/Z/WAVE wvXRef = plotsWITH[k][%FORMULAX] + [WAVE/WAVE formulaResultsInner, WAVE/T plotMetaDataInner] = SF_FillFormulaResults(wvYRef, wvXRef, yFormula) + WAVE/WAVE pg.formulaResults = formulaResultsInner + WAVE/T pg.plotMetaData = plotMetaDataInner + endif - if(pg.panelsCreated[%GRAPH]) - pg.win = winGraphs[GetNumberFromWaveNote(winGraphs, NOTE_INDEX) - 1] - if(showLegend) - formulasAreDifferent = SF_AddPlotLegend(pg) - endif + SF_GatherAxisLabels(pg.formulaResults, pg.plotMetaData[%XAXISLABEL], "FORMULAX", pg.xAxisLabels) + SF_GatherAxisLabels(pg.formulaResults, pg.plotMetaData[%YAXISLABEL], "FORMULAY", pg.yAxisLabels) - SF_AddPlotTicks(pg.graph, pg.win, pg.formulaResults) + showInTable = SF_IsDataForTableDisplay(pg.formulaResults) + if(!pg.panelsCreated[%GRAPH] && !showInTable) + pg.win = SF_CreateDataDisplayWindow(pg.graph, pg.formulaResults, outputWindows, winDisplayMode, prevPlotProperties) + pg.panelsCreated[%GRAPH] = 1 + if(winDisplaymode == SF_DM_NORMAL) + wList = AddListItem(pg.win, wList) + endif + elseif(!pg.panelsCreated[%TABLE] && showInTable) + pg.win = SF_CreateDataDisplayWindow(pg.graph, pg.formulaResults, outputWindows, winDisplayMode, prevPlotProperties) + pg.panelsCreated[%TABLE] = 1 + if(winDisplaymode == SF_DM_NORMAL) + wList = AddListItem(pg.win, wList) + endif + elseif(!showInTable) + pg.win = winGraphs[GetNumberFromWaveNote(winGraphs, NOTE_INDEX) - 1] + else + pg.win = winTables[GetNumberFromWaveNote(winTables, NOTE_INDEX) - 1] + endif - winHook = JWN_GetStringFromWaveNote(formulaResults, SF_META_WINDOW_HOOK) - if(!IsEmpty(winHook)) - SetWindow $pg.win, tooltipHook(SweepFormulaTraceValue)=$winHook - endif + if(!cmpstr(pg.plotMetaData[%DATATYPE], SF_DATATYPE_PSX)) + PSX_Plot(pg.win, pg.graph, pg.formulaResults, pg.plotMetaData) + pg.postPlotPSX = 1 + break + endif - SF_AddPlotTraceStyle(pg, formulasAreDifferent) + [dataCnt] = SF_CreateTracesForResults(pg) + pg.formulaCounter += 1 + endfor - if(traceCnt > 0) - SF_AddPlotLabels(pg.win, pg.xAxisLabels, pg.yAxisLabels) - endif - endif + if(j < (numPlotAND - 1)) + SF_FinishPlotWindow(pg, winGraphs) + [pg] = SF_ResetPlotterGraphStruct(graph) + endif + endfor + while(1) - if(postPlotPSX) - PSX_PostPlot(pg.win) - endif + SF_FinishPlotWindow(pg, winGraphs) endfor @@ -1499,7 +1491,7 @@ static Function SF_FormulaPlotter(string graph, string formula, [variable dmMode SF_KillEmptyDataWindows(winGraphs) SF_KillEmptyDataWindows(winTables) - SF_KillOldDataDisplayWindows(pg.graph, winDisplayMode, wList, outputWindows) + SF_KillOldDataDisplayWindows(graph, winDisplayMode, wList, outputWindows) End static Function SF_FinishPlotWindow(STRUCT SF_PlotterGraphStruct &pg, WAVE/T winGraphs) From c044778a048a733c209d245da616d2756153b5ab Mon Sep 17 00:00:00 2001 From: Michael Huth Date: Thu, 12 Feb 2026 16:06:31 +0100 Subject: [PATCH 05/15] SF: Add testop support for testing An operation "testop" is added that is available when AUTOMATED_TESTING is defined. The operation calls a function that can be setup in a testcase for a specific sweepbrowser. The function can be set like SVAR funcName = $GetSFTestopName(graph) funcName = "MyTestOp" this function must have the same signature as a regular SF operation: Function/WAVE MyTestOp(STRUCT SF_ExecutionData &exd) This SF extension allows to implement specific operation behavior in a test that is not present/exposed to the version of MIES in normal execution. --- Packages/MIES/MIES_Constants.ipf | 4 ++++ .../MIES_GlobalStringAndVariableAccess.ipf | 11 ++++++++++ Packages/MIES/MIES_SweepFormula.ipf | 4 ++++ Packages/MIES/MIES_SweepFormula_Executor.ipf | 5 +++++ .../MIES/MIES_SweepFormula_Operations.ipf | 21 +++++++++++++++++++ Packages/tests/UTF_DataGenerators.ipf | 4 ++++ 6 files changed, 49 insertions(+) diff --git a/Packages/MIES/MIES_Constants.ipf b/Packages/MIES/MIES_Constants.ipf index 241a6af47c..33016e6606 100644 --- a/Packages/MIES/MIES_Constants.ipf +++ b/Packages/MIES/MIES_Constants.ipf @@ -2549,6 +2549,10 @@ StrConstant SF_OP_TPINST = "tpinst" StrConstant SF_OP_TPBASE = "tpbase" StrConstant SF_OP_TPFIT = "tpfit" StrConstant SF_OP_EXTRACT = "extract" + +#ifdef AUTOMATED_TESTING +StrConstant SF_OP_TESTOP = "testop" +#endif // AUTOMATED_TESTING ///@} StrConstant SF_PROPERTY_TABLE = "Table" diff --git a/Packages/MIES/MIES_GlobalStringAndVariableAccess.ipf b/Packages/MIES/MIES_GlobalStringAndVariableAccess.ipf index 65add9a172..86beb49252 100644 --- a/Packages/MIES/MIES_GlobalStringAndVariableAccess.ipf +++ b/Packages/MIES/MIES_GlobalStringAndVariableAccess.ipf @@ -824,3 +824,14 @@ Function/S GetSweepFormulaLastRightClickedDisplayWindow() return GetSVARAsString(GetSweepFormulaPath(), "LastRightClickedDisplayWindow", initialValue = "") End + +#ifdef AUTOMATED_TESTING +/// @brief Returns the full path to the global with the name of the function that is declared in a test +/// that is called when testop is executed in a formula, used in testing +Function/S GetSFTestopName(string graph) + + DFREF dfr = SF_GetBrowserDF(graph) + + return GetSVARAsString(dfr, "TestopFunctionName", initialValue = "") +End +#endif // AUTOMATED_TESTING diff --git a/Packages/MIES/MIES_SweepFormula.ipf b/Packages/MIES/MIES_SweepFormula.ipf index 0582cf0e58..642456920d 100644 --- a/Packages/MIES/MIES_SweepFormula.ipf +++ b/Packages/MIES/MIES_SweepFormula.ipf @@ -130,6 +130,10 @@ Function/WAVE SF_GetNamedOperations() SF_OP_SELECTIVSCCSWEEPQC, SF_OP_SELECTIVSCCSETQC, SF_OP_SELECTRANGE, SF_OP_SELECTEXP, SF_OP_SELECTDEV, \ SF_OP_SELECTEXPANDSCI, SF_OP_SELECTEXPANDRAC, SF_OP_SELECTSETCYCLECOUNT, SF_OP_SELECTSETSWEEPCOUNT, \ SF_OP_SELECTSCIINDEX, SF_OP_SELECTRACINDEX, SF_OP_ANAFUNCPARAM, SF_OP_CONCAT, SF_OP_TABLE, SF_OP_EXTRACT} +#ifdef AUTOMATED_TESTING + Make/FREE/T wtTest = {SF_OP_TESTOP} + Concatenate/NP/T {wtTest}, wt +#endif // AUTOMATED_TESTING return wt End diff --git a/Packages/MIES/MIES_SweepFormula_Executor.ipf b/Packages/MIES/MIES_SweepFormula_Executor.ipf index a4dce70cb5..c18a0917d1 100644 --- a/Packages/MIES/MIES_SweepFormula_Executor.ipf +++ b/Packages/MIES/MIES_SweepFormula_Executor.ipf @@ -532,6 +532,11 @@ Function/WAVE SFE_FormulaExecutor(STRUCT SF_ExecutionData &exd, [variable srcLoc case SF_OP_TABLE: WAVE out = SFO_OperationTable(exdop) break +#ifdef AUTOMATED_TESTING + case SF_OP_TESTOP: + WAVE out = SFO_OperationTestop(exdop) + break +#endif // AUTOMATED_TESTING default: SFH_FATAL_ERROR("Undefined Operation", jsonId = exdop.jsonId) endswitch diff --git a/Packages/MIES/MIES_SweepFormula_Operations.ipf b/Packages/MIES/MIES_SweepFormula_Operations.ipf index 301cb47a02..e4dfdf22ff 100644 --- a/Packages/MIES/MIES_SweepFormula_Operations.ipf +++ b/Packages/MIES/MIES_SweepFormula_Operations.ipf @@ -2481,3 +2481,24 @@ Function/WAVE SFO_OperationTable(STRUCT SF_ExecutionData &exd) return SFH_GetOutputForExecutor(output, exd.graph, SF_OP_TABLE) End + +#ifdef AUTOMATED_TESTING +Function/WAVE SFO_OperationTestop_PROTO(STRUCT SF_ExecutionData &exd) + + INFO("TestOp PROTO function was called. A proper implementation function for testop must be registered by setting the SVAR from GetSFTestopName") + FAIL() +End + +/// testop(...) +Function/WAVE SFO_OperationTestop(STRUCT SF_ExecutionData &exd) + + string funcName = ROStr(GetSFTestopName(exd.graph)) + + REQUIRE_PROPER_STR(funcName) + + FUNCREF SFO_OperationTestop_PROTO func = $funcName + WAVE wv = func(exd) + + return wv +End +#endif // AUTOMATED_TESTING diff --git a/Packages/tests/UTF_DataGenerators.ipf b/Packages/tests/UTF_DataGenerators.ipf index d417b0ee32..dde442d934 100644 --- a/Packages/tests/UTF_DataGenerators.ipf +++ b/Packages/tests/UTF_DataGenerators.ipf @@ -576,6 +576,10 @@ static Function/WAVE TestHelpNotebookGetter_IGNORE() SetDimensionLabels(wt, TextWaveToList(wt, ";"), ROWS) +#ifdef AUTOMATED_TESTING + RemoveTextWaveEntry1D(wt, SF_OP_TESTOP) +#endif // AUTOMATED_TESTING + return wt End From a0b443049c9e1598d51ae7939771eb0a21feadf5 Mon Sep 17 00:00:00 2001 From: Michael Huth Date: Thu, 12 Feb 2026 17:05:22 +0100 Subject: [PATCH 06/15] SFE: Add option to ignore empty code in SFE_ExecuteVariableAssignments With the introduction of operation returning a full plotting specification there are use cases where only variables need to be evaluated without a specific formula. For that an optional argument allowEmptyCode was added to disable the check for empty formula code. --- Packages/MIES/MIES_SweepFormula_Executor.ipf | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_Executor.ipf b/Packages/MIES/MIES_SweepFormula_Executor.ipf index c18a0917d1..79ecb8a382 100644 --- a/Packages/MIES/MIES_SweepFormula_Executor.ipf +++ b/Packages/MIES/MIES_SweepFormula_Executor.ipf @@ -61,12 +61,20 @@ Function/WAVE SFE_ExecuteFormula(string formula, string graph, [variable singleR return out End -Function/S SFE_ExecuteVariableAssignments(string graph, string preProcCode) +/// @brief Executes each variable assignment expression and stores the result in the variable storage +/// +/// @param graph SweepBrowser graph +/// @param preProcCode preprocessed sweep formula notebook text +/// @param allowEmptyCode [optional, default 0] when set then the check for empty formula code is disabled, such that +/// input that contains only variable expressions can be evaluated +Function/S SFE_ExecuteVariableAssignments(string graph, string preProcCode, [variable allowEmptyCode]) STRUCT SF_ExecutionData exd variable i, numAssignments, jsonId, srcLocId, line, offset string code, sfWin, nbText + allowEmptyCode = ParamisDefault(allowEmptyCode) ? 0 : !!allowEmptyCode + exd.graph = graph WAVE/WAVE varStorage = GetSFVarStorage(graph) @@ -96,7 +104,7 @@ Function/S SFE_ExecuteVariableAssignments(string graph, string preProcCode) JSON_Release(srcLocId) endfor - if(IsEmpty(code)) + if(!allowEmptyCode && IsEmpty(code)) if(!StringEndsWith(preProcCode, SF_CHAR_CR)) sfWin = BSP_GetSFFormula(graph) nbText = GetNotebookText(sfWin, mode = 2) From d14b310f00d2a6048e97be033da24b4df0892f55 Mon Sep 17 00:00:00 2001 From: Michael Huth Date: Thu, 12 Feb 2026 17:08:06 +0100 Subject: [PATCH 07/15] Getters: Add wave getters for waves to build up a full plotting specification The full plotting specification is a tree of waveref waves where the first level are AND waves and the second level are WITH waves. Each element of the AND wave a WITH wave must be assigned. --- Packages/MIES/MIES_WaveDataFolderGetters.ipf | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Packages/MIES/MIES_WaveDataFolderGetters.ipf b/Packages/MIES/MIES_WaveDataFolderGetters.ipf index 490d8c3e9d..a656b51a1d 100644 --- a/Packages/MIES/MIES_WaveDataFolderGetters.ipf +++ b/Packages/MIES/MIES_WaveDataFolderGetters.ipf @@ -9382,3 +9382,26 @@ Function/WAVE GetSFPlotProperties() return wv End + +/// @brief Returns a permanent SF wave reference wave for the AND branch of a full plotting specification. +/// The elements of this wave must be filled with WITH branches from GetFullPlottingWITH. +/// At the end of the operation code this wave can be returned with +/// return SFH_GetOutputForExecutor(plotAND, exd.graph, opShort) +Function/WAVE GetFullPlottingAND(string graph, string opShort, variable size) + + WAVE/WAVE plotAND = SFH_CreateSFRefWave(graph, opShort, size) + JWN_SetNumberInWaveNote(plotAND, SF_META_PLOT, 1) + + return plotAND +End + +/// @brief Returns a free wave for the WITH branch of a full plotting specification +/// At least the FORMULAY element must be filled by the caller +Function/WAVE GetFullPlottingWITH(variable size) + + Make/FREE/WAVE/N=(size, 2) plotWITH + SetDimlabel COLS, 0, FORMULAX, plotWITH + SetDimlabel COLS, 1, FORMULAY, plotWITH + + return plotWITH +End From f0f72670b0a7e65e049c2243b7857576f416cdeb Mon Sep 17 00:00:00 2001 From: Michael Huth Date: Thu, 12 Feb 2026 17:10:18 +0100 Subject: [PATCH 08/15] SFH: Add option to SFE_ExecuteFormula to execute a formula unpreprocessed This means: - The formula string is not preprocessed - Variable definitions are not allowed - The variable storage is kept as is and can be used in the formula evaluation - The error location for SFH_ASSERT is kept, such that for the case of an error the SF notebook location can be marked The preProcess option allows to execute a formula "internally" without changing the variable storage or error information related to the SF notebook. --- Packages/MIES/MIES_SweepFormula_Executor.ipf | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_Executor.ipf b/Packages/MIES/MIES_SweepFormula_Executor.ipf index 79ecb8a382..241aad4c5f 100644 --- a/Packages/MIES/MIES_SweepFormula_Executor.ipf +++ b/Packages/MIES/MIES_SweepFormula_Executor.ipf @@ -25,7 +25,11 @@ static Constant SFE_VARIABLE_PREFIX = 36 /// @param useVariables [optional, default 1], when not set, hint the function that the formula string contains only an expression and no variable definitions /// @param line [optional, default NaN], line number of formula in SF notebook, when set, stores the information for the case of an SFH_ASSERT /// @param offset [optional, default NaN], offset of a formula in SF notebook in characters from the start of the line (x-formulas), when set, stores the information for the case of an SFH_ASSERT -Function/WAVE SFE_ExecuteFormula(string formula, string graph, [variable singleResult, variable checkExist, variable useVariables, variable line, variable offset]) +/// @param preProcess [optional, default 1], when set to 0 then the formula is not in any way preprocessed and must not contain any variable definitions. +/// Also the current error information for SFH_ASSERT is kept as is. The current variable storage is used. +/// This allows to internally execute a formula where a triggered SFH_ASSERT should result in the marking +/// of the "outer" formula in the current SF notebook. +Function/WAVE SFE_ExecuteFormula(string formula, string graph, [variable singleResult, variable checkExist, variable useVariables, variable line, variable offset, variable preProcess]) STRUCT SF_ExecutionData exd variable jsonId, srcLocId @@ -37,12 +41,15 @@ Function/WAVE SFE_ExecuteFormula(string formula, string graph, [variable singleR useVariables = ParamIsDefault(useVariables) ? 1 : !!useVariables line = ParamIsDefault(line) ? NaN : line offset = ParamIsDefault(offset) ? NaN : offset + preProcess = ParamIsDefault(preProcess) ? 1 : !!preProcess - formula = SF_PreprocessInput(formula) - if(useVariables) - formula = SFE_ExecuteVariableAssignments(graph, formula) + if(preProcess) + formula = SF_PreprocessInput(formula) + if(useVariables) + formula = SFE_ExecuteVariableAssignments(graph, formula) + endif + SFH_StoreAssertInfoParser(line, offset) endif - SFH_StoreAssertInfoParser(line, offset) [jsonId, srcLocId] = SFP_ParseFormulaToJSON(formula) exd.jsonId = jsonId WAVE/Z result = SFE_FormulaExecutor(exd, srcLocId = srcLocId) From 782945b335a99d29ec2cfaacaa06c02b0af1f412 Mon Sep 17 00:00:00 2001 From: Michael Huth Date: Thu, 12 Feb 2026 19:21:25 +0100 Subject: [PATCH 09/15] SF: Use StrConstant for name of legend box in output plot --- Packages/MIES/MIES_Constants.ipf | 2 ++ Packages/MIES/MIES_SweepFormula.ipf | 2 +- Packages/tests/HardwareBasic/UTF_SweepFormulaHardware.ipf | 6 +++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Packages/MIES/MIES_Constants.ipf b/Packages/MIES/MIES_Constants.ipf index 33016e6606..49d2603f87 100644 --- a/Packages/MIES/MIES_Constants.ipf +++ b/Packages/MIES/MIES_Constants.ipf @@ -2208,6 +2208,8 @@ StrConstant SF_DATATYPE_ANAFUNCPARAM = "AnaFunc" StrConstant SF_WREF_MARKER = "\"WREF@\":" StrConstant SF_VARIABLE_MARKER = "/SF_IsVariable" // numeric + +StrConstant SF_ANNOTATION_NAME = "metadata" ///@} /// @name Constants for SweepFormula Clampmode codes returned by operation selcm() diff --git a/Packages/MIES/MIES_SweepFormula.ipf b/Packages/MIES/MIES_SweepFormula.ipf index 642456920d..e33966db36 100644 --- a/Packages/MIES/MIES_SweepFormula.ipf +++ b/Packages/MIES/MIES_SweepFormula.ipf @@ -1678,7 +1678,7 @@ static Function SF_AddPlotLegend(STRUCT SF_PlotterGraphStruct &pg) endif if(!IsEmpty(annotation)) - Legend/W=$pg.win/C/N=metadata/F=2 annotation + Legend/W=$pg.win/C/N=$SF_ANNOTATION_NAME/F=2 annotation endif return formulasAreDifferent diff --git a/Packages/tests/HardwareBasic/UTF_SweepFormulaHardware.ipf b/Packages/tests/HardwareBasic/UTF_SweepFormulaHardware.ipf index 18a3c63a2b..22d9c15986 100644 --- a/Packages/tests/HardwareBasic/UTF_SweepFormulaHardware.ipf +++ b/Packages/tests/HardwareBasic/UTF_SweepFormulaHardware.ipf @@ -72,7 +72,7 @@ static Function TestSweepFormulaAnnotations(string device) formula = "data(select(selrange(cursors(A,B)),selchannels(AD),selsweeps(),selvis(all)))" SF_SetFormula(dbPanel, formula) PGC_SetAndActivateControl(dbPanel, "button_sweepFormula_display", val = 1) - annoInfo = AnnotationInfo(plotWin, "metadata", 1) + annoInfo = AnnotationInfo(plotWin, SF_ANNOTATION_NAME, 1) typeRef = "Legend" flagsRef = "/N=metadata/J/I=0/V=1/D=1/LS=0/O=0/F=2/S=0/H=0/Q/Z=0/G=(0,0,0)/B=(65535,65535,65535)/T=36/A=RT/X=5.00/Y=5.00" textRef = "\\s(T000000d0_Sweep_0_AD1) Sweep 0 AD1\r\\s(T000001d1_Sweep_0_AD2) Sweep 0 AD2\r\\s(T000002d2_Sweep_1_AD1) Sweep 1 AD1\r\\s(T000003d3_Sweep_1_AD2) Sweep 1 AD2\r\\s(T000004d4_Sweep_2_AD1) Sweep 2 AD1\r\\s(T000005d5_Sweep_2_AD2) Sweep 2 AD2" @@ -86,7 +86,7 @@ static Function TestSweepFormulaAnnotations(string device) formula = "avg(data(select(selrange(cursors(A,B)),selchannels(AD),selsweeps(),selvis(all))))" SF_SetFormula(dbPanel, formula) PGC_SetAndActivateControl(dbPanel, "button_sweepFormula_display", val = 1) - annoInfo = AnnotationInfo(plotWin, "metadata", 1) + annoInfo = AnnotationInfo(plotWin, SF_ANNOTATION_NAME, 1) typeRef = "Legend" flagsRef = "/N=metadata/J/I=0/V=1/D=1/LS=0/O=0/F=2/S=0/H=0/Q/Z=0/G=(0,0,0)/B=(65535,65535,65535)/T=36/A=RT/X=5.00/Y=5.00" textRef = "\\s(T000000d0_avg_data_Sweep_0_AD1) avg data Sweep 0 AD1\r\\s(T000001d1_avg_data_Sweep_0_AD2) avg data Sweep 0 AD2\r\\s(T000002d2_avg_data_Sweep_1_AD1) avg data Sweep 1 AD1\r\\s(T000003d3_avg_data_Sweep_1_AD2) avg data Sweep 1 AD2\r\\s(T000004d4_avg_data_Sweep_2_AD1) avg data Sweep 2 AD1\r\\s(T000005d5_avg_data_Sweep_2_AD2) avg data Sweep 2 AD2" @@ -100,7 +100,7 @@ static Function TestSweepFormulaAnnotations(string device) formula = "avg(avg(data(select(selrange(cursors(A,B)),selchannels(AD),selsweeps(),selvis(all)))))" SF_SetFormula(dbPanel, formula) PGC_SetAndActivateControl(dbPanel, "button_sweepFormula_display", val = 1) - annoInfo = AnnotationInfo(plotWin, "metadata", 1) + annoInfo = AnnotationInfo(plotWin, SF_ANNOTATION_NAME, 1) typeRef = "Legend" flagsRef = "/N=metadata/J/I=0/V=1/D=1/LS=0/O=0/F=2/S=0/H=0/Q/Z=0/G=(0,0,0)/B=(65535,65535,65535)/T=36/A=RT/X=5.00/Y=5.00" textRef = "\\s(T000000d0_avg_avg_data_Sweep_0_AD1) avg avg data Sweep 0 AD1\r\\s(T000001d1_avg_avg_data_Sweep_0_AD2) avg avg data Sweep 0 AD2\r\\s(T000002d2_avg_avg_data_Sweep_1_AD1) avg avg data Sweep 1 AD1\r\\s(T000003d3_avg_avg_data_Sweep_1_AD2) avg avg data Sweep 1 AD2\r\\s(T000004d4_avg_avg_data_Sweep_2_AD1) avg avg data Sweep 2 AD1\r\\s(T000005d5_avg_avg_data_Sweep_2_AD2) avg avg data Sweep 2 AD2" From bdf0f5e9aea1c55e5c36f2fec731160d62d85d67 Mon Sep 17 00:00:00 2001 From: Michael Huth Date: Thu, 12 Feb 2026 19:22:44 +0100 Subject: [PATCH 10/15] SF: In SF_GetTraceAnnotationText include legendPreFix by default in annotation Changed SF_GetTraceAnnotationText such that a set legendPrefix is always included in the returned text. --- Packages/MIES/MIES_SweepFormula.ipf | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula.ipf b/Packages/MIES/MIES_SweepFormula.ipf index e33966db36..df5de8a144 100644 --- a/Packages/MIES/MIES_SweepFormula.ipf +++ b/Packages/MIES/MIES_SweepFormula.ipf @@ -324,7 +324,8 @@ static Function/S SF_GetTraceAnnotationText(WAVE/T plotMetaData, WAVE data) string channelId, prefix, legendPrefix string traceAnnotation, annotationPrefix - prefix = RemoveEnding(ReplaceString(";", plotMetaData[%OPSTACK], " "), " ") + prefix = RemoveEnding(ReplaceString(";", plotMetaData[%OPSTACK], " "), " ") + legendPrefix = JWN_GetStringFromWaveNote(data, SF_META_LEGEND_LINE_PREFIX) strswitch(plotMetaData[%DATATYPE]) case SF_DATATYPE_EPOCHS: // fallthrough @@ -332,8 +333,7 @@ static Function/S SF_GetTraceAnnotationText(WAVE/T plotMetaData, WAVE data) case SF_DATATYPE_LABNOTEBOOK: // fallthrough case SF_DATATYPE_ANAFUNCPARAM: // fallthrough case SF_DATATYPE_TP: - sweepNo = JWN_GetNumberFromWaveNote(data, SF_META_SWEEPNO) - legendPrefix = JWN_GetStringFromWaveNote(data, SF_META_LEGEND_LINE_PREFIX) + sweepNo = JWN_GetNumberFromWaveNote(data, SF_META_SWEEPNO) if(!IsEmpty(legendPrefix)) legendPrefix = " " + legendPrefix + " " @@ -352,7 +352,8 @@ static Function/S SF_GetTraceAnnotationText(WAVE/T plotMetaData, WAVE data) break default: if(WhichListItem(SF_OP_DATA, plotMetaData[%OPSTACK]) == -1) - sprintf traceAnnotation, "%s", prefix + sprintf traceAnnotation, "%s %s", prefix, legendPrefix + traceAnnotation = TrimString(traceAnnotation) else channelNumber = JWN_GetNumberFromWaveNote(data, SF_META_CHANNELNUMBER) channelType = JWN_GetNumberFromWaveNote(data, SF_META_CHANNELTYPE) From 2a46845b478adef3763077e053d568a1b77eb136 Mon Sep 17 00:00:00 2001 From: Michael Huth Date: Fri, 20 Feb 2026 20:45:06 +0100 Subject: [PATCH 11/15] Util: Allow empty separators in TextWaveToList This is useful for serializing a text wave --- Packages/MIES/MIES_Utilities_Conversions.ipf | 7 ---- .../tests/Basic/UTF_Utils_Conversions.ipf | 34 +++---------------- 2 files changed, 5 insertions(+), 36 deletions(-) diff --git a/Packages/MIES/MIES_Utilities_Conversions.ipf b/Packages/MIES/MIES_Utilities_Conversions.ipf index fe35c257e8..65c433bc81 100644 --- a/Packages/MIES/MIES_Utilities_Conversions.ipf +++ b/Packages/MIES/MIES_Utilities_Conversions.ipf @@ -54,24 +54,17 @@ threadsafe Function/S TextWaveToList(WAVE/Z/T txtWave, string rowSep, [string co endif ASSERT_TS(IsTextWave(txtWave), "Expected a text wave") - ASSERT_TS(!IsEmpty(rowSep), "Expected a non-empty row list separator") if(ParamIsDefault(colSep)) colSep = "," - else - ASSERT_TS(!IsEmpty(colSep), "Expected a non-empty column list separator") endif if(ParamIsDefault(layerSep)) layerSep = ":" - else - ASSERT_TS(!IsEmpty(layerSep), "Expected a non-empty layer list separator") endif if(ParamIsDefault(chunkSep)) chunkSep = "/" - else - ASSERT_TS(!IsEmpty(chunkSep), "Expected a non-empty chunk list separator") endif if(ParamIsDefault(maxElements)) diff --git a/Packages/tests/Basic/UTF_Utils_Conversions.ipf b/Packages/tests/Basic/UTF_Utils_Conversions.ipf index 39f3fa6ef4..6c10090bb2 100644 --- a/Packages/tests/Basic/UTF_Utils_Conversions.ipf +++ b/Packages/tests/Basic/UTF_Utils_Conversions.ipf @@ -44,35 +44,6 @@ Function TWTLChecksParams() PASS() endtry - // empty separators - try - list = TextWaveToList(w, "") - FAIL() - catch - PASS() - endtry - - try - list = TextWaveToList(w, ";", colSep = "") - FAIL() - catch - PASS() - endtry - - try - list = TextWaveToList(w, ";", layerSep = "") - FAIL() - catch - PASS() - endtry - - try - list = TextWaveToList(w, ";", chunkSep = "") - FAIL() - catch - PASS() - endtry - // invalid max elements try list = TextWaveToList(w, ";", maxElements = -1) @@ -94,6 +65,11 @@ Function TWTLChecksParams() catch PASS() endtry + + // empty separators + Make/FREE/T wt = {{{{"a"}, {"e"}}, {{"c"}, {"g"}}}, {{{"b"}, {"f"}}, {{"d"}, {"h"}}}} + list = TextWaveToList(wt, "", colSep = "", layerSep = "", chunkSep = "") + CHECK_EQUAL_STR(list, "abcdefgh") End // UTF_TD_GENERATOR DataGenerators#TrailSepOptions From f668607735a60e8f4b598c9ad47770ef30c4f505 Mon Sep 17 00:00:00 2001 From: Michael Huth Date: Thu, 12 Feb 2026 19:25:24 +0100 Subject: [PATCH 12/15] SF: Remove the double CR in annotation when multiple traces are in the same plot The conversion from textwave to list caused an addition of another CR. This is solved with a wave conversion that simply serializes the text wave. --- Packages/MIES/MIES_SweepFormula.ipf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula.ipf b/Packages/MIES/MIES_SweepFormula.ipf index df5de8a144..b64ed6be3e 100644 --- a/Packages/MIES/MIES_SweepFormula.ipf +++ b/Packages/MIES/MIES_SweepFormula.ipf @@ -1662,7 +1662,7 @@ End static Function SF_AddPlotLegend(STRUCT SF_PlotterGraphStruct &pg) variable numAnnotations, formulasAreDifferent - string customLegend + string customLegend string annotation = "" numAnnotations = GetNumberFromWaveNote(pg.wAnnotations, NOTE_INDEX) @@ -1674,8 +1674,8 @@ static Function SF_AddPlotLegend(STRUCT SF_PlotterGraphStruct &pg) pg.wAnnotations[0, numAnnotations - 1] = SF_ShrinkLegend(pg.wAnnotations[p]) Redimension/N=(numAnnotations) pg.wAnnotations, pg.formulaArgSetup formulasAreDifferent = SFH_EnrichAnnotations(pg.wAnnotations, pg.formulaArgSetup) - annotation = TextWaveToList(pg.wAnnotations, "\r") - annotation = UnPadString(annotation, char2num("\r")) + annotation = TextWaveToList(pg.wAnnotations, "") + annotation = TrimString(annotation) endif if(!IsEmpty(annotation)) From e40c3eea705351f80ca78461e1d2480551c8986d Mon Sep 17 00:00:00 2001 From: Michael Huth Date: Thu, 12 Feb 2026 19:30:58 +0100 Subject: [PATCH 13/15] SF: Add tests for full plotting specification plotter support The test defines a testop() that creates a specific full plotting specification. Then the operation is positioned in the outer SF code connected with AND and WITH before and after to the surrounding code. Also meta data transfer from the dataset and data wave is checked through SF_META_YAXISLABEL and SF_META_LEGEND_LINE_PREFIX --- Packages/tests/Basic/UTF_SweepFormula.ipf | 172 ++++++++++++++++++++++ 1 file changed, 172 insertions(+) diff --git a/Packages/tests/Basic/UTF_SweepFormula.ipf b/Packages/tests/Basic/UTF_SweepFormula.ipf index 94c07802f9..5e27ac7c89 100644 --- a/Packages/tests/Basic/UTF_SweepFormula.ipf +++ b/Packages/tests/Basic/UTF_SweepFormula.ipf @@ -2692,3 +2692,175 @@ static Function TestSourceLocationContent([WAVE/WAVE wv]) Redimension/N=(size, -1) srcLocs CHECK_EQUAL_WAVES(srcLocs, wSrcLocs, mode = WAVE_DATA) End + +Function/WAVE TestFullPlottingOp(STRUCT SF_ExecutionData &exd) + + string opShort = SF_OP_TESTOP + string formula + + formula = "var = 1" + + WAVE/WAVE varStorage = GetSFVarStorage(exd.graph) + Duplicate/FREE varStorage, varBackup + SFE_ExecuteVariableAssignments(exd.graph, formula, allowEmptyCode = 1) + + WAVE/WAVE plotAND = GetFullPlottingAND(exd.graph, opShort, 2) + // build the following construct for the full plot specification, $var is created independently in the code above beforehand + // $var vs 2 + // with + // 3 vs 4 + // and + // 5 vs 6 + // with + // 7 vs 8 + + WAVE/WAVE plotWITH = GetFullPlottingWITH(2) + formula = "$var" + WAVE/WAVE wvY = SFE_ExecuteFormula(formula, exd.graph, preProcess = 0) + JWN_SetStringInWaveNote(wvY[0], SF_META_LEGEND_LINE_PREFIX, "legend") + JWN_SetStringInWaveNote(wvY, SF_META_YAXISLABEL, "yaxislabel") + plotWITH[0][%FORMULAY] = wvY + formula = "2" + WAVE/WAVE wvX = SFE_ExecuteFormula(formula, exd.graph, preProcess = 0) + plotWITH[0][%FORMULAX] = wvX + + formula = "3" + WAVE/WAVE wvY = SFE_ExecuteFormula(formula, exd.graph, preProcess = 0) + plotWITH[1][%FORMULAY] = wvY + formula = "4" + WAVE/WAVE wvX = SFE_ExecuteFormula(formula, exd.graph, preProcess = 0) + plotWITH[1][%FORMULAX] = wvX + + plotAND[0] = plotWITH + + WAVE/WAVE plotWITH = GetFullPlottingWITH(2) + formula = "5" + WAVE/WAVE wvY = SFE_ExecuteFormula(formula, exd.graph, preProcess = 0) + plotWITH[0][%FORMULAY] = wvY + formula = "6" + WAVE/WAVE wvX = SFE_ExecuteFormula(formula, exd.graph, preProcess = 0) + plotWITH[0][%FORMULAX] = wvX + + formula = "7" + WAVE/WAVE wvY = SFE_ExecuteFormula(formula, exd.graph, preProcess = 0) + plotWITH[1][%FORMULAY] = wvY + formula = "8" + WAVE/WAVE wvX = SFE_ExecuteFormula(formula, exd.graph, preProcess = 0) + plotWITH[1][%FORMULAX] = wvX + + plotAND[1] = plotWITH + + Duplicate/O varBackup, varStorage + + return SFH_GetOutputForExecutor(plotAND, exd.graph, opShort) +End + +static Function TestFullPlottingSpecificationCheckTrace(string win, variable traceIndex, variable yRef, variable xRef) + + string traces = TraceNameList(win, ";", 0x1) + WAVE wvY = TraceNameToWaveRef(win, StringFromList(traceIndex, traces)) + WAVE wvX = XWaveRefFromTrace(win, StringFromList(traceIndex, traces)) + Make/FREE/D wvRef = {{yRef}} + CHECK_EQUAL_WAVES(wvY, wvRef, mode = WAVE_DATA) + wvRef[] = xRef + CHECK_EQUAL_WAVES(wvX, wvRef, mode = WAVE_DATA) +End + +static Function TestFullPlottingSpecification() + + string win, traces, lbl, annoText + string graph, winResultBase + + graph = CreateFakeSweepBrowser_IGNORE() + DFREF dfr = BSP_GetFolder(graph, MIES_BSP_PANEL_FOLDER) + winResultBase = BSP_GetFormulaGraph(graph) + + SVAR funcName = $GetSFTestopName(graph) + funcName = "TestFullPlottingOp" + + MIES_SF#SF_FormulaPlotter(graph, "testop()") + win = winResultBase + "_" + SF_WINNAME_SUFFIX_GRAPH + "#" + SF_WINNAME_SUFFIX_GRAPH + "0" + REQUIRE_EQUAL_VAR(WindowExists(win), 1) + traces = TraceNameList(win, ";", 0x1) + CHECK_EQUAL_VAR(ItemsInList(traces), 2) + TestFullPlottingSpecificationCheckTrace(win, 0, 1, 2) + TestFullPlottingSpecificationCheckTrace(win, 1, 3, 4) + lbl = AxisLabel(win, "left") + CHECK_EQUAL_STR(lbl, "yaxislabel") + annoText = StringByKey("TEXT", AnnotationInfo(win, SF_ANNOTATION_NAME)) + CHECK_GT_VAR(strsearch(annoText, " legend", 0), 0) + win = winResultBase + "_" + SF_WINNAME_SUFFIX_GRAPH + "#" + SF_WINNAME_SUFFIX_GRAPH + "1" + REQUIRE_EQUAL_VAR(WindowExists(win), 1) + traces = TraceNameList(win, ";", 0x1) + CHECK_EQUAL_VAR(ItemsInList(traces), 2) + TestFullPlottingSpecificationCheckTrace(win, 0, 5, 6) + TestFullPlottingSpecificationCheckTrace(win, 1, 7, 8) + + MIES_SF#SF_FormulaPlotter(graph, "9 vs 9\rand\rtestop()") + win = winResultBase + "_" + SF_WINNAME_SUFFIX_GRAPH + "#" + SF_WINNAME_SUFFIX_GRAPH + "0" + REQUIRE_EQUAL_VAR(WindowExists(win), 1) + traces = TraceNameList(win, ";", 0x1) + CHECK_EQUAL_VAR(ItemsInList(traces), 1) + TestFullPlottingSpecificationCheckTrace(win, 0, 9, 9) + win = winResultBase + "_" + SF_WINNAME_SUFFIX_GRAPH + "#" + SF_WINNAME_SUFFIX_GRAPH + "1" + REQUIRE_EQUAL_VAR(WindowExists(win), 1) + traces = TraceNameList(win, ";", 0x1) + CHECK_EQUAL_VAR(ItemsInList(traces), 2) + TestFullPlottingSpecificationCheckTrace(win, 0, 1, 2) + TestFullPlottingSpecificationCheckTrace(win, 1, 3, 4) + win = winResultBase + "_" + SF_WINNAME_SUFFIX_GRAPH + "#" + SF_WINNAME_SUFFIX_GRAPH + "2" + REQUIRE_EQUAL_VAR(WindowExists(win), 1) + traces = TraceNameList(win, ";", 0x1) + CHECK_EQUAL_VAR(ItemsInList(traces), 2) + TestFullPlottingSpecificationCheckTrace(win, 0, 5, 6) + TestFullPlottingSpecificationCheckTrace(win, 1, 7, 8) + + MIES_SF#SF_FormulaPlotter(graph, "9 vs 9\rwith\rtestop()") + win = winResultBase + "_" + SF_WINNAME_SUFFIX_GRAPH + "#" + SF_WINNAME_SUFFIX_GRAPH + "0" + REQUIRE_EQUAL_VAR(WindowExists(win), 1) + traces = TraceNameList(win, ";", 0x1) + CHECK_EQUAL_VAR(ItemsInList(traces), 3) + TestFullPlottingSpecificationCheckTrace(win, 0, 9, 9) + TestFullPlottingSpecificationCheckTrace(win, 1, 1, 2) + TestFullPlottingSpecificationCheckTrace(win, 2, 3, 4) + win = winResultBase + "_" + SF_WINNAME_SUFFIX_GRAPH + "#" + SF_WINNAME_SUFFIX_GRAPH + "1" + REQUIRE_EQUAL_VAR(WindowExists(win), 1) + traces = TraceNameList(win, ";", 0x1) + CHECK_EQUAL_VAR(ItemsInList(traces), 2) + TestFullPlottingSpecificationCheckTrace(win, 0, 5, 6) + TestFullPlottingSpecificationCheckTrace(win, 1, 7, 8) + + MIES_SF#SF_FormulaPlotter(graph, "testop()\rand\r9 vs 9") + win = winResultBase + "_" + SF_WINNAME_SUFFIX_GRAPH + "#" + SF_WINNAME_SUFFIX_GRAPH + "0" + REQUIRE_EQUAL_VAR(WindowExists(win), 1) + traces = TraceNameList(win, ";", 0x1) + CHECK_EQUAL_VAR(ItemsInList(traces), 2) + TestFullPlottingSpecificationCheckTrace(win, 0, 1, 2) + TestFullPlottingSpecificationCheckTrace(win, 1, 3, 4) + win = winResultBase + "_" + SF_WINNAME_SUFFIX_GRAPH + "#" + SF_WINNAME_SUFFIX_GRAPH + "1" + REQUIRE_EQUAL_VAR(WindowExists(win), 1) + traces = TraceNameList(win, ";", 0x1) + CHECK_EQUAL_VAR(ItemsInList(traces), 2) + TestFullPlottingSpecificationCheckTrace(win, 0, 5, 6) + TestFullPlottingSpecificationCheckTrace(win, 1, 7, 8) + win = winResultBase + "_" + SF_WINNAME_SUFFIX_GRAPH + "#" + SF_WINNAME_SUFFIX_GRAPH + "2" + REQUIRE_EQUAL_VAR(WindowExists(win), 1) + traces = TraceNameList(win, ";", 0x1) + CHECK_EQUAL_VAR(ItemsInList(traces), 1) + TestFullPlottingSpecificationCheckTrace(win, 0, 9, 9) + + MIES_SF#SF_FormulaPlotter(graph, "testop()\rwith\r9 vs 9") + win = winResultBase + "_" + SF_WINNAME_SUFFIX_GRAPH + "#" + SF_WINNAME_SUFFIX_GRAPH + "0" + REQUIRE_EQUAL_VAR(WindowExists(win), 1) + traces = TraceNameList(win, ";", 0x1) + CHECK_EQUAL_VAR(ItemsInList(traces), 2) + TestFullPlottingSpecificationCheckTrace(win, 0, 1, 2) + TestFullPlottingSpecificationCheckTrace(win, 1, 3, 4) + win = winResultBase + "_" + SF_WINNAME_SUFFIX_GRAPH + "#" + SF_WINNAME_SUFFIX_GRAPH + "1" + REQUIRE_EQUAL_VAR(WindowExists(win), 1) + traces = TraceNameList(win, ";", 0x1) + CHECK_EQUAL_VAR(ItemsInList(traces), 3) + TestFullPlottingSpecificationCheckTrace(win, 0, 5, 6) + TestFullPlottingSpecificationCheckTrace(win, 1, 7, 8) + TestFullPlottingSpecificationCheckTrace(win, 2, 9, 9) +End From dce5526d658b7aa901ed5fced85f2b7728fe2ab8 Mon Sep 17 00:00:00 2001 From: Michael Huth Date: Fri, 13 Feb 2026 13:57:08 +0100 Subject: [PATCH 14/15] SFH: Add utility function SFH_AddVariableToStorage This function allows to add a result wave to the variable storage. If the specified entry already exists it is overwritten. --- Packages/MIES/MIES_Constants.ipf | 1 + Packages/MIES/MIES_SweepFormula.ipf | 2 +- Packages/MIES/MIES_SweepFormula_Helpers.ipf | 43 ++++++++++++++++ Packages/tests/Basic/UTF_SweepFormula.ipf | 54 +++++++++++++++++++++ 4 files changed, 99 insertions(+), 1 deletion(-) diff --git a/Packages/MIES/MIES_Constants.ipf b/Packages/MIES/MIES_Constants.ipf index 49d2603f87..5cee83a1eb 100644 --- a/Packages/MIES/MIES_Constants.ipf +++ b/Packages/MIES/MIES_Constants.ipf @@ -2210,6 +2210,7 @@ StrConstant SF_WREF_MARKER = "\"WREF@\":" StrConstant SF_VARIABLE_MARKER = "/SF_IsVariable" // numeric StrConstant SF_ANNOTATION_NAME = "metadata" +StrConstant SF_VARNAME_REGEXP = "[A-Z]{1}[A-Z0-9_]*" ///@} /// @name Constants for SweepFormula Clampmode codes returned by operation selcm() diff --git a/Packages/MIES/MIES_SweepFormula.ipf b/Packages/MIES/MIES_SweepFormula.ipf index b64ed6be3e..c6c8ed7408 100644 --- a/Packages/MIES/MIES_SweepFormula.ipf +++ b/Packages/MIES/MIES_SweepFormula.ipf @@ -2486,7 +2486,7 @@ End static Function [string varName, string formula] SF_SplitVariableAssignment(string line) - string regex = "^(?i)\\s*([A-Z]{1}[A-Z0-9_]*)\\s*=(.+)$" + string regex = "^(?i)\\s*(" + SF_VARNAME_REGEXP + ")\\s*=(.+)$" SplitString/E=regex line, varName, formula if(V_flag != 2) diff --git a/Packages/MIES/MIES_SweepFormula_Helpers.ipf b/Packages/MIES/MIES_SweepFormula_Helpers.ipf index 5f59e84f2c..846dcc62e0 100644 --- a/Packages/MIES/MIES_SweepFormula_Helpers.ipf +++ b/Packages/MIES/MIES_SweepFormula_Helpers.ipf @@ -2177,3 +2177,46 @@ Function/WAVE SFH_GetDatasetArrayAsResolvedWaverefs(STRUCT SF_ExecutionData &exd return dataFromEachGroup End + +/// @brief Executes a formula from within an operation with low overhead +/// - the currently active variable storage is used +/// - the formula string is not preprocessed +Function/WAVE SFH_ExecuteFormulaInternal(string graph, string formula) + + STRUCT SF_ExecutionData exd + variable jsonId, srcLocId + + exd.graph = graph + [jsonId, srcLocId] = SFP_ParseFormulaToJSON(formula) + exd.jsonId = jsonId + WAVE dataRef = SFE_FormulaExecutor(exd, srcLocId = srcLocId) + + JSON_Release(exd.jsonId) + JSON_Release(srcLocId) + + WAVE resolved = SF_ResolveDataset(dataRef) + + return resolved +End + +/// @brief Adds a variable to the variable storage. If the variable already exists it is overwritten. +Function SFH_AddVariableToStorage(string graph, string name, WAVE result) + + variable size, idx + string varName + string regex = "^(?i)(" + SF_VARNAME_REGEXP + ")$" + + SplitString/E=regex name, varName + ASSERT(V_flag == 1, "Invalid SF variable name") + + WAVE/WAVE varStorage = GetSFVarStorage(graph) + idx = FindDimLabel(varStorage, ROWS, varName) + if(idx == -2) + size = DimSize(varStorage, ROWS) + Redimension/N=(size + 1) varStorage + idx = size + SetDimLabel ROWS, size, $varName, varStorage + endif + JWN_SetNumberInWaveNote(result, SF_VARIABLE_MARKER, 1) + varStorage[idx] = result +End diff --git a/Packages/tests/Basic/UTF_SweepFormula.ipf b/Packages/tests/Basic/UTF_SweepFormula.ipf index 5e27ac7c89..08518470ed 100644 --- a/Packages/tests/Basic/UTF_SweepFormula.ipf +++ b/Packages/tests/Basic/UTF_SweepFormula.ipf @@ -2864,3 +2864,57 @@ static Function TestFullPlottingSpecification() TestFullPlottingSpecificationCheckTrace(win, 1, 7, 8) TestFullPlottingSpecificationCheckTrace(win, 2, 9, 9) End + +static Function TestAddVariableToStorage() + + string graph + + graph = CreateFakeSweepBrowser_IGNORE() + + WAVE/WAVE varStorage = GetSFVarStorage(graph) + Make/FREE result1, result2 + + try + SFH_AddVariableToStorage(graph, "", result1) + FAIL() + catch + PASS() + endtry + + try + SFH_AddVariableToStorage(graph, "0VAR", result1) + FAIL() + catch + PASS() + endtry + + try + SFH_AddVariableToStorage(graph, " VAR", result1) + FAIL() + catch + PASS() + endtry + + try + SFH_AddVariableToStorage(graph, "VAR ", result1) + FAIL() + catch + PASS() + endtry + + result2[] = 2 + SFH_AddVariableToStorage(graph, "result1", result1) + CHECK_EQUAL_VAR(FindDimLabel(varStorage, ROWS, "result1"), 0) + WAVE wv = varStorage[%result1] + CHECK_EQUAL_WAVES(result1, wv) + + SFH_AddVariableToStorage(graph, "result2", result2) + CHECK_EQUAL_VAR(FindDimLabel(varStorage, ROWS, "result2"), 1) + WAVE wv = varStorage[%result2] + CHECK_EQUAL_WAVES(result2, wv) + + SFH_AddVariableToStorage(graph, "result1", result2) + CHECK_EQUAL_VAR(FindDimLabel(varStorage, ROWS, "result1"), 0) + WAVE wv = varStorage[%result1] + CHECK_EQUAL_WAVES(result2, wv) +End From 388d1401446a2fbc937f419f502a42e6df91b29b Mon Sep 17 00:00:00 2001 From: Michael Huth Date: Fri, 13 Feb 2026 15:03:21 +0100 Subject: [PATCH 15/15] SF: Add documentation for full plotting specification and using local variable storages and adding variables in an operation document testop --- Packages/doc/SweepFormula.rst | 188 ++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) diff --git a/Packages/doc/SweepFormula.rst b/Packages/doc/SweepFormula.rst index 2e33fff0f4..5f8c91df21 100644 --- a/Packages/doc/SweepFormula.rst +++ b/Packages/doc/SweepFormula.rst @@ -2423,6 +2423,180 @@ Thus, `SF_GetArgument` sees with the resolved `wave` operation `[17]`, whereas ` More complex operation such as `data` build the output wave reference wave dynamically. See `SF_GetSweepsForFormula` how the output wave is build depending on selectData and the found sweeps. +Operations returning a Full Plotting Specification +"""""""""""""""""""""""""""""""""""""""""""""""""" + +Sweep formula operations can return a result wave that is tagged as full plotting specification with the `SF_META_PLOT` tag. A full plotting specification +is a wave reference wave that stores formula results as if they would have been specified with the AND / WITH keyword syntax in a sweepformula notebook. +This represents an evaluated formula specification for plotting that is not yet plotted. An operation can return such full plotting specification as Y-formula +and the formula plotter will insert this in the output panel with the plots as if the formulas would have been in the sweepformula notebook. + +The full plotting specification is a wave reference wave tree with two levels. The topmost level indexes over evaluated formula results as if +specified through the AND keyword. The second level stores evaluated formulas as if specified through the WITH keyword. For each level a wave getter is +implemented. + +A simple operation that creates and returns a full plotting specification looks like this: + +.. code-block:: igorpro + + Function/WAVE FullPlottingOp(STRUCT SF_ExecutionData &exd) + + string opShort = SF_OP_MYOP + string formula + + WAVE/WAVE plotAND = GetFullPlottingAND(exd.graph, opShort, 1) + WAVE/WAVE plotWITH = GetFullPlottingWITH(1) + + formula = "[1, 2, 3]" + WAVE/WAVE wvY = SFE_ExecuteFormula(formula, exd.graph, preProcess=0) + plotWITH[0][%FORMULAY] = wvY + formula = "[10, 20, 30]" + WAVE/WAVE wvX = SFE_ExecuteFormula(formula, exd.graph, preProcess=0) + plotWITH[0][%FORMULAX] = wvX + plotAND[0] = plotWITH + + return SFH_GetOutputForExecutor(plotAND, exd.graph, opShort) + End + +This is equivalent to a formula of `[1, 2, 3] vs [10, 20, 30]`. Note that when plotAND is created the `SF_META_PLOT` tag is automatically set. + +Multiple plots with AND (for simplicity no optional X-formula set here): + +.. code-block:: igorpro + + WAVE/WAVE plotAND = GetFullPlottingAND(exd.graph, opShort, 2) + + WAVE/WAVE plotWITH = GetFullPlottingWITH(1) + formula = "[1, 2, 3]" + WAVE/WAVE wvY = SFE_ExecuteFormula(formula, exd.graph, preProcess=0) + plotWITH[0][%FORMULAY] = wvY + plotAND[0] = plotWITH + + WAVE/WAVE plotWITH = GetFullPlottingWITH(1) + formula = "[4, 5, 6]" + WAVE/WAVE wvY = SFE_ExecuteFormula(formula, exd.graph, preProcess=0) + plotWITH[0][%FORMULAY] = wvY + plotAND[1] = plotWITH + +This is equivalent to a formula of + +.. code-block:: igorpro + + [1, 2, 3] + and + [4, 5, 6] + +Multiple traces with WITH (for simplicity no optional X-formula set here): + +.. code-block:: igorpro + + WAVE/WAVE plotAND = GetFullPlottingAND(exd.graph, opShort, 1) + + WAVE/WAVE plotWITH = GetFullPlottingWITH(2) + formula = "[1, 2, 3]" + WAVE/WAVE wvY = SFE_ExecuteFormula(formula, exd.graph, preProcess=0) + plotWITH[0][%FORMULAY] = wvY + formula = "[4, 5, 6]" + WAVE/WAVE wvY = SFE_ExecuteFormula(formula, exd.graph, preProcess=0) + plotWITH[1][%FORMULAY] = wvY + plotAND[0] = plotWITH + +This is equivalent to a formula of + +.. code-block:: igorpro + + [1, 2, 3] + with + [4, 5, 6] + +The evaluated formula result of the full plotting specification will be inserted in the formula specification of the sweep formula notebook. +For example if there is a `FullPlottingOp()` defined that returns `[1, 2, 3]` then the following sweepformula notebook code results in three plots with one trace each: + +.. code-block:: igorpro + + [4, 5, 6] + and + FullPlottingOp() + and + [4, 5, 6] + +If the connection is done through `with` then there will be only two plots where the trace from the full plotting specification is either added to the first or +second plot, depending on if `with` is before or after `FullPlottingOp()`. + +.. code-block:: igorpro + + # two plots, first plot has two traces, second plot one trace + [4, 5, 6] + with + FullPlottingOp() + and + [4, 5, 6] + + # upper code is equivalent to + [4, 5, 6] + with + [1, 2, 3] + and + [4, 5, 6] + +.. code-block:: igorpro + + # two plots, first plot has one trace, second plot two traces + [4, 5, 6] + and + FullPlottingOp() + with + [4, 5, 6] + + # upper code is equivalent to + [4, 5, 6] + and + [1, 2, 3] + with + [4, 5, 6] + +Note: If `FullPlottingOp()` would return more than one entry in the plotAND wave then it would itself create more plots and an `with` keyword +in the sweepformula notebook would only connect to the first/last plot. + +To keep the construction of the full plotting specification simple it is recommended to put most of the formula evaluation in variables. +The evaluation call to `SFE_ExecuteFormula` with `preProcess=0` uses the current variable storage. This allows to provide already evaluated variables. +The following code snippet illustrates this: + +.. code-block:: igorpro + + // save previous variables + WAVE/WAVE varStorage = GetSFVarStorage(exd.graph) + Duplicate/FREE varStorage, varBackup + + // evaluate own variables + formula = "var1 = 1\rvar2 = $var1 * 2" + SFE_ExecuteVariableAssignments(exd.graph, formula, allowEmptyCode = 1) + + // create plotting specification, use own variables + WAVE/WAVE plotAND = GetFullPlottingAND(exd.graph, opShort, 1) + WAVE/WAVE plotWITH = GetFullPlottingWITH(1) + formula = "$var2" + WAVE/WAVE wvY = SFE_ExecuteFormula(formula, exd.graph, preProcess=0) + plotWITH[0][%FORMULAY] = wvY + plotAND[0] = plotWITH + + // restore previous variables + Duplicate/O varBackup, varStorage + +Return Operation Results in Variable +"""""""""""""""""""""""""""""""""""" + +In some situations it is useful that the operation returns results in a variable. +This is done with the utility function `SFH_AddVariableToStorage`. + +.. code-block:: igorpro + + SFH_AddVariableToStorage(graph, "varName", result) + +`varName` is the name of the new variable. If the variable is already present then it is replaced with the new result. +`result` is a wave from an evaluated formula. The utility function works on the current variable storage. +The variable content is available for all later formula evaluations. + Meta Data Handling """""""""""""""""" @@ -2517,3 +2691,17 @@ need to be shown with a different marker or line style. It also adapts the legen apfrequency(data(select(selrange(ST), selchannels(AD), selvis(all))), 3, 100, freq, normoversweepsavg, count) with apfrequency(data(select(selrange(ST), selchannels(AD), selvis(all))), 3, 100, time, norminsweepsavg, count) + +Dynamic Operation for Testing +""""""""""""""""""""""""""""" + +When `AUTOMATED_TESTING` is defined then the additional operation `testop` is available. The operation function content can be defined specifically for a test case. +The test case has to implement a non-static function with the same function signature as a sweepformula operation. Then the test case code can set +the implemented function as body for `testop` by updating a SVAR global: + +.. code-block:: igorpro + + SVAR funcName = $GetSFTestopName(graph) + funcName = "MySpecialTestCaseOperation" + +Formulas that use `testop` will then execute `MySpecialTestCaseOperation` in that case. The global is set per SweepBrowser.