Mousewheel Graph Window Zoom

In my web browser I can magnify the window contents by holding down the command/control key and scrolling with mousewheel or trackpad. Now I can do the same in a graph window :)

You can already use the mousewheel/trackpad to change the axis range if you hover over an axis. The code below lets you zoom both axes simultaneously. Holding down the shift key as well as command/ctrl accelerates the zoom factor. Expansion/contraction is centred at the mouse cursor position, so you can position your mouse and command-shift-scroll to zoom in rapidly on a point of interest.

When the 'global' symbol is defined, scroll-to-zoom is active for all newly created graph windows. Otherwise, it can be turned on or off for each graph window.

#pragma rtGlobals=3
#pragma DefaultTab={3,20,4}
#pragma version=1.50
#pragma ModuleName=ScrollToZoom

#define global

// key codes
// 2: shift; 4: option/alt; 8: command/ctrl
// On Windows, alt key is reserved for graph drag.
static constant kZoomKey=8
static constant kFasterKey=2
static constant kZoomSpeed=70
static constant kReverse=0 // reverse the direction of expansion

#ifdef global

static function AfterWindowCreatedHook(string win, variable type)
    if (type == 1)
        SetWindow $win hook(hScrollToZoom)=ScrollToZoom#hookScrollToZoom
    endif
    return 0
end

#else

menu "Graph", dynamic
    ScrollToZoom#zoomMenu(), /Q, ScrollToZoom#toggleZoom()
end

static function /S zoomMenu()
    GetWindow kwTopWin hook(hScrollToZoom)
    return SelectString(strlen(s_value)>0, "", "!" + num2char(18)) + "Scroll-Zoom"
end

#endif

static function toggleZoom()
    GetWindow kwTopWin hook(hScrollToZoom)
    SetWindow kwTopWin hook(hScrollToZoom) = $SelectString(strlen(s_value)>0, "ScrollToZoom#hookScrollToZoom", "")
end

static function hookScrollToZoom(STRUCT WMWinHookStruct &s)
    if (s.eventCode == 22 && s.eventMod&kZoomKey) // mousewheel/touchpad + zoom key

        string strAxes = AxisList(s.WinName), axis = "", type = ""
        int i, numAxes, isHorizontal, isLog
        Make /D/free/N=3 wAx // free wave to hold axis minimum, maximum, axis value for mouse location
        numAxes = ItemsInList(strAxes)
        int rev = 1 - 2*kReverse
        variable expansion = 1 - rev*s.wheelDy * kZoomSpeed/5000
        if (s.eventMod & kFasterKey)
            if (kFasterKey==2 && s.wheelDy==0)
                // this works when shift key also transforms wheel.dY to wheel.dX
                s.wheelDy = s.wheelDx
            endif
            expansion = 1 - rev*s.wheelDy * kZoomSpeed/500
        endif
               
        for (i=0;i<numAxes;i++)
            axis = StringFromList(i, strAxes)
            type = StringByKey("AXTYPE", AxisInfo(s.WinName, axis))
            isHorizontal = (cmpstr(type, "bottom")==0 || cmpstr(type,"top")==0 || cmpstr(type[0,2],"/B=")==0 || cmpstr(type[0,2],"/T=")==0)
            isLog = (NumberByKey("log(x)", AxisInfo(s.WinName, axis),"="))
           
            GetAxis /W=$s.WinName/Q $axis
            wAx = {v_min, v_max, AxisValFromPixel(s.WinName, axis, isHorizontal ? s.mouseLoc.h : s.mouseLoc.v)}
            if (WaveMax(wAx) == wAx[2] || WaveMin(wAx) == wAx[2])
                continue
            endif
                       
            if (isLog)
                wAx = log(wAx)
                wAx = wAx[2] - (wAx[2] - wAx[p]) * expansion
                wAx = alog(wAx)
            else
                wAx = wAx[2] - (wAx[2] - wAx[p]) * expansion
            endif
                   
            WaveStats /Q/M=1 wAx
            if ( (V_numNaNs+V_numInfs) || wAx[1]==wAx[0] )
                continue
            endif
           
            if (wAx[1] > wAx[0])
                SetAxis /W=$s.WinName $axis, wAx[0], wAx[1]
            else
                SetAxis /R/W=$s.WinName $axis, wAx[0], wAx[1]
            endif
        endfor 
    endif
    return 0
end

 

Edited to add /R flag for reversed axes. A subsequent autoscale will not switch the axis direction.

I just noticed that forum member Henry S released a project back in 2014 that functions more-or-less identically to this code snippet.

https://www.wavemetrics.com/project/ScrollWheelZoom

I suspect that the 2014 project is not maintained any more. Nevertheless, I wanted to point out that Henry S got there first, and note that it's easy to overlook the resources available in this forum!

Say, Tony- I'm guessing that this adds to the functionality you get currently when the mouse is over an axis and you use the mouse wheel?

Yes, it zooms so that the point under the mouse stays put. The older project used a modifier key to prevent zooming, mine uses a modifier to allow. It feels quite 'natural'.

My version also handles log axes.

What I didn't do is to get a list of all axes and figure out which ones are relevant for the mouse cursor position...

Edit: v1.5 fixes this

In reply to by tony

Hi Tony,

Thank you for posting this code! It works like a charm and it is extremely useful. However, for us, it would be really great if one could add-on additional functionalities such as: (i) correcting a linear slope and (ii) being able to extract (wave)points and the corresponding wavenames by clicking, perhaps while pressing the right mouse button, on a specific graph point. Maybe the former functionality could be implemented to work similarly to how the “alt” key is currently used for dragging. For the second feature the output may be directed to a either a separate, stand-alone window or to a user function. Could you please provide some pointers on how these features could be incorporated in your code? Alternatively, if updating your code is not feasible, would you mind providing some suggestions on how these functions could be developed?

Best,

Sebastian

 

Hi Sebastian,

All the things you describe sound very doable, but I wouldn't attempt to tack any of them onto this code snippet. I suspect that there are some existing examples of code that does very similar things.

I have some code that provides a UI that will allow you change the slope of a data set interactively. It also allows other kinds of manipulations; I can share that with you if you're interested, but I prefer not to make it public.

For investigating wave names and points with mouseover interactivity, I would use a hook function together with the TraceFromPixel() function to identify the trace. Here is an example of code where a trace is labelled with a tag that disappears when the mouse moves off of the trace:

function StdsWindowHook(s)
    STRUCT WMWinHookStruct &s
   
    variable hookResult = 0 // 0 if we do not handle event, 1 if we handle it.
    switch(s.eventCode)
        case 4:                 // mousemoved event
            string foo=TraceFromPixel(s.mouseloc.h,s.mouseloc.v, "WINDOW:StandardsPanel#G0;DELTAX:4;DELTAY:4;")
            if (strlen(foo))
                string tname=StringByKey("TRACE", foo)
                if (cmpstr(tname, "std_betacorr") && cmpstr(tname, "std_Ybcorr"))
                    Tag/K/W=StandardsPanel#G0 /N=idtag
                    break
                endif
                variable HitP=str2num(StringByKey("HITPOINT", foo))
                DFREF LuHfdfr = GetLuHfDFREF()
                wave /SDFR=LuHfdfr/T std_name
                Tag/C/N=idtag/Z=1/B=(65535,65534,49151)/G=(43690,43690,43690)/D={1,20}/A=LT/X=-2.00/Y=-2.00/L=0 /W=StandardsPanel#G0 $tname, HitP,"\\K(0,0,0)\\Z10"+std_name[HitP]
            else // not on point
                Tag/K/W=StandardsPanel#G0 /N=idtag
            endif
            break
    endswitch
    return hookResult   // If non-zero, we handled event and Igor will ignore it.
end

Maybe that provides a pathway to the kind of things you want to do? If you want to provide more details of what you have in mind I'm sure you can get some more specific pointers from the forum about how to get it done.

Here is the code that allows you to 'correct' a linear slope in a completely arbitrary fashion. This is a dangerous activity, IMO. At least the manipulation is recorded in a wavenote. However, for spectroscopic applications at least, I strongly recommend fitting and subtracting a linear baseline rather than simply 'rotating' the data like this.

Edit: version 1.10 works for both waveform and X-Y traces

TiltAndBend110.zip

See also

DisplayHelpTopic "Trace Menus"

for an example of how to extend the trace popup menu to extract a wave name. Invoking the menu also records the mouse position in global variables, so you can use TraceFromPixel to find the hitpoint.

@sstoian

I found a newer version of the tilt&bend code, attached in the post above.

Forum

Support

Gallery

Igor Pro 9

Learn More

Igor XOP Toolkit

Learn More

Igor NIDAQ Tools MX

Learn More