« Return to Thread: Adding some new transformations to gnuplot (arctan and power laws)

Re: Adding some new transformations to gnuplot (arctan and power laws)

by Ethan Merritt :: Rate this Message:

Reply to Author | View in Thread

On Sunday 31 May 2009, James R. Van Zandt wrote:

>
> A year ago, Ethan wrote:
> >> I figured to leave the existing log-scale code in place, as you
> >> suggested above, while adding a new more general mechanism that worked
> >> on the original stored coordinates.  Once the general mechanism was
> >> in place and working, the old log-scale code could be removed without
> >> ever having to modify it to work  with "last-minute" scaling.
>
> Here's my reply.  Do you think this makes sense?
>
> > I think I see how that would work:
> >
> > Currently:
> > Upon "set log x", all stored x values are transformed and on "unset
> > log x" they are inverse transformed.
> >
> > Phase 1:
> > Add a scaling "newlog".
> >
> > Add to AXIS a pointer to a function that transforms the data.  The
> > function takes two parameters: the datum to be transformed, and a
> > double.  For scaling "linear" or "log" the function is a no-op.  For
> > "newlog", the extra parameter is the base of the logs.  Otherwise the
> > extra parameter is ignored.

I have been thinking a bit about this.  I think that the core requirement
is simply to store two function pointers for each axis: the transform and
its inverse.  The forward transform is used [only?] by the routines that
map plot coordinates to terminal coordinates.  It is conceptually easiest
to show for the routine map_position_double():

%%%%%%%%%%%%%%%%%% current code %%%%%%%%%%%%%%%%%%%%%
/*{{{  map_position_double */
static void
map_position_double(
    struct position *pos,
    double *x, double *y,
    const char *what)
{
    switch (pos->scalex) {
    case first_axes:
        {
            double xx = axis_log_value_checked(FIRST_X_AXIS, pos->x, what);
            *x = AXIS_MAP(FIRST_X_AXIS, xx);
            break;
        }
    ...
%%%%%%%%%%%%%%% proposed code %%%%%%%%%%%%%%%%%%%%%%%%%
/*{{{  map_position_double */
static void
map_position_double(
    struct position *pos,
    double *x, double *y,
    const char *what)
{
    switch (pos->scalex) {
    case first_axes:
        {
            double xx = pos->x;
            if (FIRST_X_AXIS.transform)
                xx = (FIRST_X_AXIS.transform)xx;
            *x = AXIS_MAP(FIRST_X_AXIS, xx);
            break;
        }
    ...
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

The transform is obviously not applied in the case statements handling
screen, character, or graph coordinates.  It only applies to the axial
coordinates.

Note: this edit by itself doesn't actually do much, because the data points
are usually plotted via a direct calls to the macros map_x() and map_y().
I made a very quick hack to test specifically a log transform on axis y2,
and found that for simple plots it was sufficient to modify the macros
AXIS_MAP(axis, variable) and AXIS_SETSCALE(axis, out_low, out_high)
in axis.h.   I'm sure there will turn out to be other places that need
tweaking as well, but this is looking quite do-able.

The inverse transform is obviously needed for mouse feedback. But it
would also be available for guiding the choice of sampling intervals
and tic placement if needed.

I am not following you about needing a second parameter for log scale.
The appearance of the plot is independent of the base of the log.
Choosing a different base would introduce a constant scale multiplier,
which is re-scaled out of existence by conversion to terminal coordinates.
 
> > Always do "last-minute" scaling: When plotting a value, call the
> > function via the supplied pointer to transform it.
> > On "set log x" or "unset prob x" etc., update the function pointer.
> > On "(un)set log x", continue to (un)transform the stored data.
> >
> > Phase 2 (after initial checkout):
> > Rename scaling "log" to "oldlog", and rename "newlog" to "log".

I suggest to introduce a new command
   {un}set transform {x|y|x2|y2|...} func(dummy)

Phase 1 is to start using this command.

Phase 2 is to trap the older command "set log y" and treat it as
"set transform y log(y)".


> > Phase 3 (after a major release):
> > Delete the "oldlog" commands and the code to (un)transform the stored
> > data.
> >
> > The above only implements standard scalings (linear, log, probability,
> > and maybe weibull).  To accommodate a user-defined scaling function,
> > the scaling step needs to be able to point to an action table.  We
> > could use that mechanism to implement the standard scalings too -
> > building the action tables from static strings like "_linear(x,b)=x"
> > or "_log(x,b)=log(x)/log(b)".  However, I'm worried that would be too
> > slow.  The faster method would be to give the scaling function three
> > parameters (datum, extra, and action table), and let the standard
> > scaling functions ignore the third parameter.

While I understand your concern about speed in evaluating the function,
I point out that we have extensive benchmarks on how much it costs.
Every time you put parentheses in a 'using' statement you  incur the cost
of action table evaluation.
     plot 'foo' using ($1):($2)
is slower than
     plot 'foo' using 1:2
And yes, there have been a small number of complaints about the speed hit,
but it is only noticeable for very large datasets.  A while back I showed
that unexpectedly, at least under linux the largest part of this hit comes
from from enabling/disabling FPE trapping around each point.
But that's a digression.

I suggest that if we implement this, and it works, then we can worry later
about further optimizing the speed.  For some common cases, e.g. log(), we
could store a pointer to the C library function rather than to an action
table.


Summary:

- I could make simple plots work using a last-minute log transform by modifying
  only 2 macros in axis.h.   This is looking quite do-able, although I would
  prefer to turn these macros back into real subroutines for readability if
  nothing else.

- Unresolved issues:
  + how to deal with singularities in the transform?  We won't hit them until
    we are in the middle of plotting.
  + how to pass the transform to an external helper (gnuplot_x11) or wrap it
    in the driver output (canvas terminal)?
  + tic placement (although I can report that my quick hack "just worked" for
    placing logscale tics on simple plots with no change to the tic code at all

                        - Ethan

------------------------------------------------------------------------------
Register Now for Creativity and Technology (CaT), June 3rd, NYC. CaT
is a gathering of tech-side developers & brand creativity professionals. Meet
the minds behind Google Creative Lab, Visual Complexity, Processing, &
iPhoneDevCamp as they present alongside digital heavyweights like Barbarian
Group, R/GA, & Big Spaceship. http://p.sf.net/sfu/creativitycat-com 
_______________________________________________
gnuplot-beta mailing list
gnuplot-beta@...
https://lists.sourceforge.net/lists/listinfo/gnuplot-beta

 « Return to Thread: Adding some new transformations to gnuplot (arctan and power laws)