Renaming tracenames?

I just discovered the /TN flag in Display and AppendToGraph, and it makes some issues much much easier for me.  My question is, is it possible to rename a tracename?  When a wave is renamed, its associated tracenames are renamed automatically in every graph according to the new wavename, provided they are the default "#" style.  But if I specify the tracename myself using the /TN flag with Display or AppendToGraph, how can I modify the tracename?

This arises because I am writing an interface for users to trace structures on images.  The interface shows multiple images on one graph, and the user has the option to show or hide different colour channels, as well as the tracings they have already done.  The same tracing can therefore appear on multiple images.  I have implemented the tracings as x-y waves (e.g. "cell1_y vs cell1_x"), with the tracename being the y wave with a suffix (e.g. "cell1_y_1" if it is displayed on top of image #1).  This method allows me to specifically modify traces that occupy known locations.

The problem is that the user needs to be able to name and rename the tracings.  When the x-y waves are renamed, the tracenames do not change (they do with the default #-style tracenames).  Is there a way to force a name change?  ReplaceWave doesn't address this (side note: ReplaceWave acts a little strangely with these non-default tracenames).  Right now, it seems I have to RemoveFromGraph the old tracename and then AppendToGraph with the new tracename.  Is that my only option?  BTW, I'm using Igor 7.  Maybe this works differently in Igor 8.

I'm grateful for anyone's insights into this.

-Matthew

To my knowledge, trace renaming is not available (unfortunately).

One small piece of advice is that you might reduce the number of waves that have to be dealt with by combining the x-y waves into one wave. (You might already be doing this -- but if so it wasn't totally clear from your post.) Instead of a 1D wave cell1_y of length n rows (for n traced locations) and a second 1D wave cell1_x of length n rows, you would have a single wave, called say cell1, with two columns, one column for x locations and one column for y locations (and n rows). For example:

make/o/n=(100,2) cell1
cell1=nan
setdimlabel 1,0,xLocations,cell1
setdimlabel 1,1,yLocations,cell1
cell1[0][%xLocations]=5 //store location (5,10) in row 0
cell1[0][%yLocations]=10
cell1[1][%xLocations]=10 //store location (10,15) in row 1
cell1[1][%yLocations]=15
//...and so on

display/k=1
appendtograph cell1[][%yLocations]/tn=cell1_1 vs cell1[][%xLocations]

 

You are correct- at present there is no way to rename a user-named trace. As you say, if you rename the wave that provides a trace name in the old way, the trace name changes because the trace name comes from the wave's name.

The missing feature (how about ModifyGraph rename(something)=somethingelse) has been noticed here, we just haven't tackled it yet. It would have to be for a future version, maybe 9 but no promises.

>  Right now, it seems I have to RemoveFromGraph the old tracename and then AppendToGraph with the new tracename.  Is that my only option?  BTW, I'm using Igor 7.  Maybe this works differently in Igor 8.

This is the only way to hack a rename of a trace.

Perhaps an alternative of use to you is to set the old trace wave to NaN and rename it to "..."_NaN, allow the empty traces to accumulate, and then offer the user a "Save This Set" button. When clicked, have a code to remove all "..."_NaN waves in one step. The advantage here may be that you can offer a "Restore Previous Set" button to re-invoke something.

No real need to set to NaNs if all you want is to hide a trace. These days, you can do ModifyGraph hideTrace(tracename)=1

Oh. Yes. Excellent! Better to just hide the trace and then reshow a new one.

Then, use the two strings below to find all traces and all displayed traces.

string alltraces = TraceNameList("",";",1)
string onlydisplayedtraces = TraceNameList("",";",4)

It seems here that an iteration loop is needed to find hidden traces. Perhaps a request ... Allow TraceNameList(...) with Bit 5 to return only hidden traces. Alternatively, perhaps also have TraceInfo(...) return a setting for whether a trace is or is not hidden.

 

TraceInfo returns the hideTrace setting in the RECREATION portion of the result:

make/O jack=x;display jack
ModifyGraph hideTrace=1
print traceInfo("","",0)
  XWAVE:;YAXIS:left;XAXIS:bottom;AXISFLAGS:;AXISZ:NaN;XWAVEDF:;YRANGE:[*];XRANGE:;TYPE:0;ERRORBARS:;RECREATION:zColor(x)=0;zColorMax(x)=0;zColorMin(x)=0;zmrkSize(x)=0;zmrkNum(x)=0;zpatNum(x)=0;
textMarker(x)=0;arrowMarker(x)=0;mask(x)=0;patBkgColor(x)=0;useNegPat(x)=0;hBarNegFill(x)=0;usePlusRGB(x)=0;plusRGB(x)=(65535,0,0);useNegRGB(x)=0;negRGB(x)=(65535,0,0);toMode(x)=0;mode(x)=0;
marker(x)=0;lSize(x)=0.5;lStyle(x)=0;lSmooth(x)=0;rgb(x)=(65535,0,0);msize(x)=0;mrkThick(x)=0.5;opaque(x)=0;mstandoff(x)=0;hbFill(x)=0;gaps(x)=1;live(x)=0;quickdrag(x)=0;mskip(x)=0;cmplxMode(x)=0;
hideTrace(x)=1;useMrkStrokeRGB(x)=0;mrkStrokeRGB(x)=(0,0,0);useBarStrokeRGB(x)=0;barStrokeRGB(x)=(0,0,0);offset(x)={0,0};muloffset(x)={0,0};

See that "hideTrace(x)=1;" part?

Thanks Jim. While we can get the required information this way, it would be useful to be able to get it without needing to resort to a loop and an extraction of recreation macro content. So, the feature request for the equivalent of a BIT 5 list option to TraceNameList(...) remains.

From the TraceNameList help:

// Generate a list of hidden traces
Make/O jack,jill,joy;Display jack,jill,joy
ModifyGraph hideTrace(joy)=1        // hide joy
// (hidden + visible) - visible = hidden
String visibleTraces=TraceNameList("",";",1+4)  // only visible normal traces
String allNormalTraces=TraceNameList("",";",1)  // hidden + visible normal traces
String hiddenTraces= RemoveFromList(visibleTraces,allNormalTraces)
Print hiddenTraces
// Prints: joy;

 

Jim: I would swear that I posted exactly that suggestion just a few days ago. Thanks.

Today I implemented ModifyGraph userName(tracename)=newname so that you can change a user-defined trace name to something else, or if you set the name to $"" you can revert to the default based on the wave's name.

Coming to an Igor Pro 9 near you, on some unspecified future date.

In reply to by johnweeks

johnweeks wrote:

Today I implemented ModifyGraph userName(tracename)=newname so that you can change a user-defined trace name to something else, or if you set the name to $"" you can revert to the default based on the wave's name.

Coming to an Igor Pro 9 near you, on some unspecified future date.

That sounds great.

It would also be very nice for the new ModifyGraph to create a string containing what the new name was set to. Presumably #[number] would be applied in the case of a conflict with the requested new name. I am imagining something analogous to the S_name string that is created after a display command. 

I have always wished that such a string was created after use of the /TN flag with display or appendtograph, and it would be great to have it added for that. 

I suppose one issue is that multiple traces could be displayed/appended or renamed at once. Perhaps the created string (called something like S_traceNames) could be a keyed list e.g.,  "[requestedTraceName0]:[finalTraceName0];[requestedTraceName1]:[finalTraceName1];...". In the absence of the /TN flag, requested trace names would be the name of the wave, I suppose, and the order of the list would matter.

(Even more) peripherally related, I think it would be helpful if traceInfo reported whether a trace has a user-defined tracename (from the display/appendToGraph /TN flag) or an automatic trace name. Unless there is another way to tell? It would be helpful for making a user-defined graph duplication function that switches out the waves.

And later today I changed the keyword from userName to traceName.

I can see the utility in what you are suggesting; unfortunately it doesn't fit will into the mammoth reach of ModifyGraph. It might be that you could get what you need using TraceNameList. After an AppendToGraph command that adds a single wave, the newly added trace will be the last one in the list.

You are also correct that renaming a trace where there are multiple traces with the same name will change things. If you have three waves called "wave0" in your graph, then you have traces called "wave0", "wave0#1" and "wave0#2". Now if you execute ModifyGraph traceName(wave0#1)=betterName, then your list of traces will be wave0, betterName, wave0#1. So that third instance of wave0 has changed from wave0#2 to wave0#1.

One of the benefits of /TN is that you can avoid trace names with instance numbers!

In reply to by xufriedman

An approach like this might be useful in the interim. It seems to work for the cases I generally need to use it for. 

//cursors are not transferred to the new trace; likely doesn't work for box, violin, contour, or image traces.
function/S renameTrace(winN,oldName,newName)
    String winN,oldName,newName
     
    if (strlen(winN) < 1)
        winN = winname(0,1)
    endif
   
    String traces = tracenamelist(winN,";",2^0+2^1+2^2+2^3+2^4)
    Variable oldPosition = whichlistitem(oldName,traces)
   
    if (oldPosition < 0)
        print "renameTrace() in winN",winN,"oldName",oldName,"not found, aborting"
        return ""
    endif
   
    //make sure new name does not conflict with anything except old name
    if (stringmatch(newName,oldName))       //I guess could be useful if a difference in case is sought
        print "renameTrace() newName requested matches oldName. Aborting"
        return ""
    endif
   
    newName = uniquetraceName(winN,newName)
   
    make/o/t/free axFlags = {"/L=","/B=","/T=","/R="}
    setdimlabel 0,0,left,axFlags
    setdimlabel 0,1,bottom,axFlags
    setdimlabel 0,2,top,axFlags
    setdimlabel 0,3,right,axFlags
   
    String winFlag = "/W="+winN
    String info = traceinfo(winN,oldName,0)
    String xwave = Stringbykey("xwave",info)
    Variable hasxwave = strlen(xwave) > 0
    WAVE oldWv = $oldName
    String ywave = nameofwave(tracenametowaveref(winN,oldName))
    String xaxis = Stringbykey("xaxis",info)
    String yaxis = Stringbykey("yaxis",info)
    String xaxInfo = axisinfo(winN,xaxis)
    String xaxType = stringbykey("axtype",xaxInfo)
    String yaxInfo = axisinfo(winN,yaxis)
    String yaxType = stringbykey("axtype",yaxInfo)
    String yRange = Stringbykey("yRange",info)
    String xRange = Stringbykey("xRange",info)
    String errBars = Stringbykey("errorbars",info)
    //trace info not handled:
    //TYPE -- contour, box, violin may need different commands
    //XWAVEDF -- problematic if X wave has a data folder that is not stored in xwave (same true of ywave perhaps, but there is no key for that in traceInfo)
   
    String appendCmd = "appendtograph"+winFlag+axFlags[%$yaxType]+yaxis+axFlags[%$xaxType]+xaxis+" "+ywave+yRange+"/TN="+newName
    if (hasxwave)
        appendCmd += " vs " + xwave+xRange
    endif
   
    //before removing original, append this trace name (removing the original first would result in deletion of its axes if no other waves are plotted on them)
    execute/q appendCmd
   
    if (strlen(errBars) > 0)
        WAVE/T errText = listtotextwave(errBars," ")
        errText[0] = errText[0] + winFlag   //held beginning of the command, ErrorBars + flags. Append /W=winN
        errText[1] = newName            //held original trace name, needs the new trace name
        String errBarsCmd
        wfprintf errBarsCmd,"%s ",errText
       
        execute/q errBarsCmd
    endif
   
    //now remove original trace
    removefromgraph/W=$winN $oldName
       
    //now apply style settings 
    String easyInfo = "dummy~" + replacestring("RECREATION:",info,"|RECREATION~",1) + "|"
    String recreation = Stringbykey("RECREATION",easyInfo,"~","|",1)        //recreation is the last key, so put in a nonsense sepStr to get the entirety of the string from RECREATION: on
    String styleStr
    Variable i,numCmds = itemsinlist(recreation)
    for (i=0;i<numCmds;i+=1)
        styleStr = stringfromlist(i,recreation)
        styleStr = replacestring("(x)",styleStr,"("+newName+")")
        execute/Q "modifygraph/w="+winN+" " + styleStr
    endfor
   
    //now fix trace ordering
    Variable numTraces = itemsinlist(traces)
    if (oldPosition == 0)       //bottom-most
        reordertraces _back_, {$newName}
    elseif (oldPosition < numTraces - 1)        //if oldPosition == numTraces - 1, it was top-most and is still
        Variable followingTracePos = oldPosition + 1
        reordertraces $stringfromlist(followingTracePos,traces),{$newName}      //put trace just before following trace, where it was originally
    endif
   
    return newName
end

function/S uniqueTraceName(winN,traceName)  //(it would be nice if this were a built in function too, like uniqueName but for traces)
    STring winN,traceName
   
    Variable i=0,attemptLimit = 10^5
    String traces = tracenamelist(winN,";",2^0+2^1+2^2+2^3+2^4)
    String outName = traceName,numStr
    do
        if (whichListItem(outName,traces) < 0)
            return outName
        endif
       
        sprintf numStr,"%i",i
        outName = traceName + "#"+ numStr
        i+=1
    while (i < attemptLimit)
   
    String justInCase =     traceName + "_Unique_" + num2str(floor(10*(0.5+enoise(0.5)))) + num2str(floor(10*(0.5+enoise(0.5)))) + num2str(floor(10*(0.5+enoise(0.5)))) + num2str(floor(10*(0.5+enoise(0.5)))) + num2str(floor(10*(0.5+enoise(0.5))))
    return uniqueTraceName(winN,justInCase)
end

In reply to by johnweeks

johnweeks wrote:

Today I implemented ModifyGraph userName(tracename)=newname so that you can change a user-defined trace name to something else, or if you set the name to $"" you can revert to the default based on the wave's name.

Coming to an Igor Pro 9 near you, on some unspecified future date.

...

And later today I changed the keyword from userName to traceName.

It would also be very nice if the Replace Wave dialog box provided a GUI-based way to use the new renameTrace option in Igor 9.

Replace Wave seems like the appropriate GUI dialog to use to me, since this dialog already changes the automatically assigned trace name in cases where /TN wasn't originally used.

I've attached the current ReplaceWave dialog box, along with the way I think it would look and work with the new renameTrace option.

It probably goes without saying, but the rename trace field I propose would ideally also be usable through the dialog even in the absence of a replace wave command.

The fact that the Replace Wave dialog changes the automatic trace name is, of course, just a side effect of changing the displayed wave. I'm inclined to think it belongs in the Modify Trace Appearance dialog. That's where the ModifyGraph keywords are handled, so it already has a lot of the infrastructure needed. It's surprisingly complicated to do modify any dialog, and both the Replace Wave dialog and the Modify Trace dialog are especially difficult.

I'll see if I have time before Igor 9 ships.

In reply to by johnweeks

johnweeks wrote:

Today I implemented ModifyGraph userName(tracename)=newname  .... Coming to an Igor Pro 9 near you, on some unspecified future date.

Would it be possible to add an equivalent for renaming axes? This would be especially useful because of the special status of axes with the name "left" "bottom" "top" "right".

(This status is annoying to me because such axes behave differently (e.g., no freepos setting). At present, it is fairly laborious to recreate the graph with the axis under a different name.)

So you want to not just rename the axis, you want to convert it from Standard (left, right, bottom, top) to Free?

In reply to by johnweeks

Yes that would be great. Many times it's frustrating that the Standard axes can't be manipulated like Free axes. Freepos is the behavior I miss most often, but there might be others that I am forgetting.

Is there presently any work around for converting from a standard to free axis? The only way I know of is to remake the axis under a non-standard name (which means removing and then re-appending all the waves)

 

----

As a bit of an aside, I would personally be in favor of abolishing the differences between Standard and Free axis behaviors -- I don't see any benefit and they cause trouble and confusion. Why not just have left,right,bottom,top be default names for axes that all work the same?

The differences between them that I find continually troublesome:  For standard axes, it's the lack of freepos. For free axes, it's that the default freepos and label pos are weird (-50 and 0) and I have to adjust them to make them look like standard axes. In my ideal world, all axes would default to looking like standard axes, but they would have all the functionality of free axes.

I presume this particular change is not an option on the grounds of backwards compatibility alone -- and probably also other people's preferences that differ from mine.

One approach to converting a graph with standard axes into a graph with free axes would be to save a recreation macro (Windows->Window Control->Window Control, or Ctrl-Y). Then edit the macro, changing all instances of Left to, say, FreeLeft, and so forth. Then run the macro to create a new graph with free axes. Because of the freePos thing, some repair will be required :)

The reason for the differences between standard axes and free axes has to do with history and, as you suspect, backward compatibility. I'll have to ask around here to see if I can find out why it would be bad to make the default for a free action position be Fraction of Plot Area, which defaults to zero, which makes it like a standard axis.