The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

PDL::Graphics::Prima::Internals - a space to put documentation on the internals

This is very disorganized at the moment. My apologies.

OVERVIEW

Here is an overview of the plotting infrastructure to help keep your head straight. The data types are indicated after the datatype and information that is only meant to be used internally is in parentheses

At the moment, it is not quite accurate and needs updating. I'm Sory. :-(

 Plotting Widget
  |- title string
  |- backColor colorValue
  |- replotDuration float in milliseconds
  |- x and y axes
    |- min float
    |- max float
    |- lowerEdge integer
    |- upperEdge integer
    |- scaling, a class name or an object
      |- $self->compute_ticks($min, $max)
      |- $self->transform($min, $max, $data)
      |- $self->inv_transform($min, $max, $data)
      |- $self->sample_evently($min, $max, $N_values)
      |- $self->is_valid_extremum($value)
    |- (minValue float)
    |- (minAuto boolean)
    |- (maxValue float)
    |- (maxAuto boolean)
#   |- (pixel_extent int)
#   |- $self->pixel_extent([$new_extent])
?   |- $self->recompute_min_auto()
?   |- $self->recompute_max_auto()
    |- $self->update_edges()
?   |- $self->minmax_with_padding($data)
    |- $self->reals_to_relatives($data)
    |- $self->relatives_to_reals($data)
    |- $self->pixels_to_relatives($data)
    |- $self->relatives_to_pixels($data)
    |- $self->reals_to_pixels($data)
    |- $self->pixels_to_reals($data)
  |- dataSets (name => data)
    |- xs (floats)
    |- ys (floats)
    |- plotType
      |- type-specific data
      |- $self->xmin($dataset, $widget)
      |- $self->xmax($dataset, $widget)
      |- $self->ymin($dataset, $widget)
      |- $self->ymax($dataset, $widget)
      |- $self->draw($dataset, $widget)
    |- $self->get_data_as_pixels($widget)
    |- $self->extremum($nane, $comperator, $widget)
  |- $self->compute_min_max_for($axis_name)
  |- $self->get_image
  |- $self->copy_to_clipboard
  |- $self->save_to_file

Work with arrays, in which case the index itself is equal to the needed padding. Build a doubly-linked list with a structure patterned after

 padding => number    # padding of interest
 data => float        # min/max for this padding
 curr_value => float  # computed extent
 next => pointer      # next (smaller) padding

Also, keep track of the tail, the current min, and the current max.

The linked list is initially assembled in order of decreasing padding (largest padding on top). Here's something that's important, which you will need to get your head around, and which I will illustrate with an example. Suppose we have two paddings, 10 pixels and 5 pixels, and we're trying to compute the minimum. If the minimum data value with a pixel padding of 5 is 2.2 and the minimum data value with a pixel padding of 10 is 2.1, we know that the pixel padding of 10 must lead to a smaller minimum than the pixel padding of 5. As such, we can remove the pixel padding of 5 from the list. I call this weeding out the values. The result is that as we go through the list in order of decreasing padding, the data values will become more extreme, like a pyramid.

The argument I just made about the paddings for 5 and 10 pixels only took their data values and the sort order of the padding into account. It did not take the actual values of the paddings into account. In the next stage, which is iterative, I will begin to account for the effect of the different padding values.

With the pyramid in hand, examine the tail values for both the min and the max. Each of these will have a padding associated with them. Estimate the min and max by assuming that the tail values, together with their padding, represent the most extreme values of the data set, which is a conservative estimate. With this estimate in hand, run through the list and compute the min or max associated with each list element, taking the padding and current scaling into account, and storing the result in curr_value. Then weed out the list using curr_value and iterate the procedure of this last paragraph until the tail of both the min and the max lists does not change.

An important feature of this algorithm is that the min/max values begin with very conservative estimates and become more extreme with each round.

Furthermore, the pyramid data structure is arranged so that with each round the width of the top end of the pyramid grows more than the width of the bottom end.

To actually implement this scheme, I will require that datasets monitor their own data and report a data/padding list upon request. How the datasets monitor their data is entirely up to them. (I am considering using an on_change slice, which I secretly insert over the user's piddle by modifying the @_ argument array, to efficiently monitor changes.)

In order to properly handle bad values, I need to write a function that can look for bad values over many piddles, tens of piddles. I believe I can achieve this by writing a function that takes, say, 20 piddles, and wrapping it in Perl code that supplies null piddles when you only need to call the function for 10 piddles. The funcion would be called collate_min_max_for_many and the calling convention for it would look like this:

 ($min, $max) = collate_min_max_for_many(N_to_return, N_buckets, $index, $p1, $p2, ...)

This is best illustrated with the blobs plot type, since it would use a nontrivial value for index. If I wanted to compute the collated min and max for the x-data, taking potential bad values for y, xradii, yradii, and colors into account, I would call the function like so:

 my ($blob_x_min, $blob_x_max)
     = collate_min_max_for_many(
         1,              # return min/max for $x
         $widget->width, # only need the number of pixels corresponding to the widget
         $xRadii,        # the index
         $x,             # x-data for which to find min/max
         $y,             # \
         $yRadii,        #  |- ignore x-values if any of these are bad
         $colors,        # /
     )

In this case

where N <= M, M < 20, and the return dimensions are N x whatever.

RESPONSIVE PP CODE

If you create a long-running piece of code and you want your GUI to remain responsive, you have a couple of options. Here's how you would call Prima's yield function from within your C code:

        Coce => q{
                /* Need this to keep the app responsive */
                SV * Prima_app = get_sv("::application", 0);

                ...

                threadloop %{

                        ...

                        /* Keep the system responsive */
                        if (Prima_app != NULL) {
                                dSP;
                                ENTER;
                                SAVETMPS;

                                PUSHMARK(SP);
                                XPUSHs(sv_2mortal(newSVpv("Prima::Application", 18)));
                                PUTBACK;
                                call_pv("Prima::Application::yield", G_DISCARD|G_NOARGS);
                                FREETMPS;
                                LEAVE;
                        }
                %}
        }

Alternatively, you could explicitly require a callback method from your user, in which case you would have an OtherPars section. In this example, I pass the current progress as the first argument of the function call, and I actually take the return value of the callback into account: if the callback returned zero, it quits the PDL function immediately:

        OtherPars => 'SV * code_ref',
        Code => q{
                int k;
                int total_count;
                int total_expected;

                ...

                threadloop %{

                        ...

                        calculate total_expected and track total_count

                        ...

                        /* execute the callback */
                        if (Prima_app != NULL) {
                                dSP;
                                ENTER;
                                SAVETMPS;

                                PUSHMARK(SP);
                                XPUSHs(sv_2mortal(newSVnv((double) total_count / (double) total_expected)));
                                PUTBACK;

                                k = call_sv($COMP(code_ref), G_SCALAR);

                                SPAGAIN;

                                if (k != 1) croak ("How did I get more than one arg returned?");

                                k = POPi;
                                if (k == 0) goto QUIT;

                                PUTBACK;
                                FREETMPS;
                                LEAVE;
                        }

                %}

                QUIT: k++;
        }, # close the Code block

Here's a use of that function that lets Prima call its update functioin and which quits the function if it takes more than five seconds:

use Time::HiRes qw(gettimeofday tv_internal);

my $start_time = [gettimeofday];

my_pp_func(... args ..., sub { # Update the UI: $::application->yield;

        # Croak if it's taking too long
        return 0 if tv_internal($start_time, [gettimeofday]) > 5;
        return 1;
});

This is a general callback that takes note of your response. If your callback returns zero, it quits immediately (and, since you told the function to quit, it's your job to ignore the return result). The callback function itself would

AUTHOR

David Mertens (dcmertens.perl@gmail.com)

SEE ALSO

This is a component of PDL::Graphics::Prima. This library is composed of many modules, including:

PDL::Graphics::Prima

Defines the Plot widget for use in Prima applications

PDL::Graphics::Prima::Axis

Specifies the behavior of axes (but not the scaling)

PDL::Graphics::Prima::DataSet

Specifies the behavior of DataSets

PDL::Graphics::Prima::Internals

A dumping ground for my partial documentation of some of the more complicated stuff. It's not organized, so you probably shouldn't read it.

PDL::Graphics::Prima::Limits

Defines the lm:: namespace

PDL::Graphics::Prima::Palette

Specifies a collection of different color palettes

PDL::Graphics::Prima::PlotType

Defines the different ways to visualize your data

PDL::Graphics::Prima::ReadLine

Encapsulates all interaction with the Term::ReadLine family of modules.

PDL::Graphics::Prima::Scaling

Specifies different kinds of scaling, including linear and logarithmic

PDL::Graphics::Prima::Simple

Defines a number of useful functions for generating simple and not-so-simple plots

LICENSE AND COPYRIGHT

Copyright (c) 2011-2012 David Mertens.

All rights reserved.

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.