View Raw SPL
/*****************************************************************************
*                                                                            *
*   OVERLAYXSYNC.SPL  Copyright (C) 2021 DSP Development Corporation         *
*                               All Rights Reserved                          *
*                                                                            *
*   Author:      Randy Race                                                  *
*                                                                            *
*   Synopsis:    Simple X axis synchronization of multiple overlays          *
*                                                                            *
*   Revisions:   22 Jan 2021  RRR  Creation                                  *
*                                                                            *
*****************************************************************************/

#if @HELP_OVERLAYXSYNC

    OVERLAYXSYNC

    Purpose: X axis synchronization of one or more overlays.

    Syntax:  OVERLAYXSYNC(win, maxrange)

                   win - Optional. The target window. Defaults to the current
                         window.

              maxrange - Optional. An integer, set the X range of each
                         overlay to fit all series. Defaults to 1.

    Returns: Nothing, the overlays of a window are synchronized based on the
             X axis.

    Example:
             W1: 1..10;settime(w0, "12:00");sethunits("s");setvunits("V")
             W2: 3..0.1..20;settime(w0, "12:00");sethunits("s");setvunits("A")
             W3: w1;setsym(14);overlay(w2);focus(2);scales(13);focus(1);
             W4: w1;setsym(14);overlay(w2);focus(2);scales(13);overlayxsync();

             The series in W1 starts at 1.0 and contains 10 samples.
             The series in W2 starts at 3.0 and contains 171 samples.
             The sample rate of W1 is 1 Hz and the sample rate of W2 is 10 Hz.

             W3 contains a simple overlay of W1 and W2. Because the series
             do not begin at the same time, the data values do not line up
             on the X axis.

             W4 contains an overlay of W1 and W2. The traces are X aligned
             such that data samples with the same X value plot at the
             same X coordinate.

    Example:
             W1: 1..10;settime(w0, "12:00");sethunits("Time");setvunits("V")
             W2: 3..0.1..20;settime(w0, "12:00");sethunits("Time");setvunits("A")
             W3: stripchart(w1, w2);setsym(14, 1);griddot;gridhv
             W4: w1;setsym(14);overlay(w2);focus(2);scales(13);overlayxsync();

             Similar to above, except wall clock time is used as the X axis.

             The series in W1 starts at 12:00:01 and contains 10 samples.
             The series in W2 starts at 12:00:03 and contains 171 samples.
             The sample rate of W1 is 1 Hz and the sample rate of W2 is 10 Hz.

             W3 contains a simple strupchart of W1 and W2. The stripchart
             automatically aligns the data values in each trace on the common
             time axis.

             W4 contains an overlay of W1 and W2. Similar to the stripchart,
             the traces are time aligned such that data samples with the same
             time value plot at the same X coordinate.

    Example:
             W1: 1..10;settime(w0, "12:00");sethunits("Time");setvunits("V")
             W2: 3..0.1..20;settime(w0, "12:00");sethunits("Time");setvunits("A")
             W3: stripchart(w2, w1);setsym(14, 2);griddot;gridhv
             W4: w2;overlay(w1);focus(2);scales(13);setsym(14);overlayxsync();

             Same as above except the input series are swapped. The data
             values of W4 still line up in time, but since the first trace
             has more samples per second, there may not be a corresponding
             value of the second trace for every sample of the first trace.

    Example:
             W1: 1..10;settime(w0, "12:00");sethunits("Time");setvunits("V")
             W2: 3..0.1..20;settime(w0, "12:00");sethunits("Time");setvunits("A")
             W3: (y1, y2) = timesync(w1, w2);y2;overlay(y1);focus(2);scales(13);setsym(14);focus(1);
             W4: (y1, y2) = timesync(w1, w2);y2;overlay(y1);focus(2);scales(13);setsym(14);overlayxsync();

             Same as above except the series are first time synchronized
             by TIMESYNC to have the same sample rate. The data
             values of W4 line up in time, and each value of the first trace
             has a corresponding value in the second trace.

    Remarks:
             OVERLAYXSYNC synchronizes overlays by adjusting the window
             extent so X values align as with an OVERPLOT. The data values are
             not changed.

             If MAXRANGE is 0, the X range of each overlay is set to the
             X range of the root series.

             If MAXRANGE is 1 (default), the X range of each overlay is
             set to the maximum X extent of all the series.

             See TIMESYNC to perform time synchronization of two series
             by resampling and extraction.

    See Also:
             Overlay
             Sync
             Timesync
#endif


/* X axis synchronize overlays */
overlayxsync(win, maxrange)
{
        local winnum, nfoc, xr, xl, j, xoff, doff, ddx, new_xl, new_xr;

        /* inputs */
        (winnum, maxrange) = overlayxsync_parse_args(win, maxrange);

        win = castwindow(winnum);

        /* number of traces */
        if ((nfoc = numfocus(win)) > 1)
        {
                /* root */
                focus(win, 1);

                /* sync x */
                sync(win, 1);

                /* full x range */
                autoscale(win, -1, -1);

                if (maxrange)
                {
                        /* full X span */
                        (xl, xr) = overlayxsync_xminmax(win, nfoc);
                }
                else
                {
                        /* X coords of root */
                        (xl, xr) = overlayxsync_minmax_x(refseries(win, 1), refseries(win, 1));
                }

                /* set new x range */
                setx(win, xl, xr);
                setxauto(win, xl, xr);
                autoscale(win);

                xoff = overlayxsync_xoffset(win);

                /* set coords of each trace */
                loop (j = 2..nfoc)
                {
                        /* potential date/time offset and scaling */
                        (doff, ddx) = xyconform(refseries(win, j), refseries(win, 1));

                        /* map new x range */
                        new_xl = xl * ddx + doff - xoff;
                        new_xr = xr * ddx + doff - xoff;

                        focus(win, j);

                        /* set new x range */
                        setx(win, new_xl, new_xr);
                        setxauto(win, new_xl, new_xr);
                        autoscale(win);

                        focus(win, 1);
                }
        }
}


/* parse inputs */
overlayxsync_parse_args(win, maxrange)
{
        if (argc < 2)
        {
                /* default to full X range */
                maxrange = 1;

                if (argc < 1)
                {
                        win = refwindow(w0);
                }
                else if (isscalar(win))
                {
                        maxrange = win;
                        win = refwindow(w0);
                }
        }

        return(getwnum(win), maxrange);
}


/* min/max of all overlays */
overlayxsync_xminmax(win, nfoc)
{
        local i, xmin, xmax, smin, smax, f;

        f = getfocus(win);
        focus(win, 0);

        /* initial range */
        (xmin, xmax) = overlayxsync_minmax_x(refseries(win, 1), refseries(win, 1));

        if (nfoc > 1)
        {
                loop (i = 2..nfoc)
                {
                        (smin, smax) = overlayxsync_minmax_x(refseries(win, 1), refseries(win, i));

                        xmin = min(xmin, smin);
                        xmax = max(xmax, smax);
                }
        }

        focus(win, f);
        
        return(xmin, xmax);
}


/* fast lookup of minmax X with possible date/time correction */
overlayxsync_minmax_x(s0, s)
{
        local xmin, xmax, dt_off, dt_dx;

        /* xval min/max */
        (xmin, xmax) = xminmax(s);

        /* offset for date/time */
        (dt_off, dt_dx) = xyconform(s0, s);

        xmin *= dt_dx;
        xmax *= dt_dx;

        xmin += dt_off;
        xmax += dt_off;

        return(xmin, xmax);
}


/* x offset */
overlayxsync_xoffset(s)
{
        local xoff = 0;

        return(xoff); //

        if (isdt(s))
        {
                xoff = isxy(s) ? 0.0 : xoffset(s);
        }

        return(xoff);
}