package PDL::Graphics::Prima::Simple; use strict; use warnings; use Carp 'croak'; our $VERSION = 0.17; # update with update-version.pl use PDL::Graphics::Prima; =head1 NAME PDL::Graphics::Prima::Simple - a very simple plotting interface for PDL::Graphics::Prima =head1 SYNOPSIS use PDL::Graphics::Prima::Simple; use PDL; # --( Super simple line and symbol plots )-- # Generate some data - a sine curve my $x = sequence(100) / 20; my $y = sin($x); # Draw x/y pairs. Default x-value are sequential: line_plot($y); line_plot($x, $y); circle_plot($y); circle_plot($x, $y); triangle_plot($y); triangle_plot($x, $y); square_plot($y); square_plot($x, $y); diamond_plot($y); diamond_plot($x, $y); X_plot($y); X_plot($x, $y); cross_plot($y); cross_plot($x, $y); asterisk_plot($y); asterisk_plot($x, $y); # Sketch the sine function for x initially from 0 to 10: func_plot(0 => 10, \&PDL::sin); # --( Super simple histogram )-- # Plot a distribution hist_plot($distribution); # Tweak the binning hist_plot($distribution, bt::Linear(normalize => 0)); # --( Super simple matrix plots )-- # Generate some data - a wavy pattern my $image = sin(sequence(100)/10) + sin(sequence(100)/20)->transpose; # Generate a grayscale image: matrix_plot($image); # smallest is white imag_plot($image); # smallest is black # Set the x and y coordinates for the image boundaries # left, right, bottom, top matrix_plot([ 0, 1 ], [ 0, 2 ], $image); imag_plot( [ 0, 1 ], [ 0, 2 ], $image); # --( More complex plots )-- # Use the more general 'plot' function for # multiple DataSets and more plotting features: my $colors = pal::Rainbow()->apply($x); plot( -lines => ds::Pair($x, $y , plotType => ppair::Lines ), -color_squares => ds::Pair($x, $y + 1 , colors => $colors, , plotType => ppair::Squares(filled => 1) ), x => { label => 'Time' }, y => { label => 'Sine' }, ); # --( Managing window interaction )-- # In scripts and older Term::ReadLine, if you press # the letter 'q' when viewing a plot, you can # re-invoke interaction with: twiddle; # query auto-twiddling my $is_auto_twiddline = auto_twiddle; # Turn auto-twiddling on or off auto_twiddle(1); auto_twiddle(0); =head1 DESCRIPTION One of Perl's mottos is to "make easy things easy, and hard things possible." The bulk of the modules provided by the L distribution focus on the latter half, making hard but very powerful things possible. This module tackles the other half: making easy things easy. This module provides a number of simple functions for quick data plotting so that you can easily get a first look at your data. You can think of this as providing a function-based interface to plotting, in contrast to the GUI/OO interface of the rest of L. Or you can look at this as providing a handy collection of plot (and window) constructors for the standard use cases. Either perspective is basically correct. In addition to making easy plots easy, this module and its documentation are meant to serve as an introduction to L. If you are new to the libarary, this is where you should start. This module is built on L and L. You do not need to know much about L to use this tutorial, but you should know L. When you use this library, you get a handful of functions for quick data plotting that include =over =item lines L takes one or two Ls, one for y and optionally one for x, and makes a L with them; if you don't give an x L, it uses L. =item symbols Various symbol plots, like L and L, take one or two Ls, one for y and optionally one for x, and and plots the L at the given points; if you don't give an x L, it uses L. =item functions L takes an I plot min/max and a L and makes a L of the L; it can optionally take the number of points to evaluate on each drawing operation; see L =item histograms L takes a L of data to be plotted and an optional L and plots a L. If no bin type is supplied, L is used. =item images Both L and L take one or three arguments, the first two of which specify the x-bounds and the y-bounds, the third (or only) of which is the matrix to plot =item plot (...) L is a much more powerful routine that takes the same arguments you would pass to the constructor of a L, and packs the resulting plot widget in a L. =back And with that, let's look at how to I these to view data! =head1 TUTORIAL To begin, let's assume you have some x/y data that you want to plot with lines connecting them. My canonical example is a sine wave. Here's a complete script to get us started: use PDL; use PDL::Graphics::Prima::Simple; my $x = sequence(100)/10; line_plot($x, $x->sin); print "All done!\n"; The C line L and, more importantly, imports the L|PDL::Basic/sequence> function (among many others) into the current package. On the third line we use that function to generate a bunch of x-data with a spacing of 1/10. In the last line we plot the sine of that sequence of points. When you run the above example script, it will block at the C command and display a L with your plot. (On the other hand, if you are in the L, the plot will be displayed but it will not block your shell if you have a recent version of L.) We will return to the blocking behavior, and how to call this in a non-blocking fashion, in a little bit. =head2 Interactive Features For now, turn your attention to the plot. This is a highly interactive plot, as are all plots made with L. In particular, your plot responds to the following user interactions: =over =item right-click zooming Clicking and dragging your right mouse button will zoom into a specific region. You will see a zoom rectangle on the plot until you release the mouse, at which point the plot will be zoomed-in to the region that you selected. =item scroll-wheel zooming You can zoom-in and zoom-out using your scroll wheel. The zooming is designed to keep the data under the mouse at the same location as you zoom in and out. (This is not a 100% guarantee, but for most uses it works about right.) =item dragging/panning Once you have zoomed into a region, you can examine nearby data by clicking and dragging with your left mouse button, much like an interactive map. =item context menu Right-clicking on the plot will bring up a L with options including restoring auto-scaling, L* (to paste directly into, say, Microsoft's PowerPoint or LibreOffice's Impress), and saving the current plot image to a L or L file. Postscript output is L L, but the supported raster output file formats depend on the L, so are system- and machine-dependent. For additional information on raster images, see L. * For reasons not clear to me, copying the plot to the clipboard does not seem to work on Mac and appear to be due to limitations with the X-window bindings. =item resizable When packed into a resizable window (as is the case in this example), the plot can be resized and it will be updated and redrawn smoothly. =back The library lets you L L, but if you do not specify bounds, the axis bounds will be calculated to tightly fit the data. In fact, the library is designed to automatically choose axis boundaries that fit your data and symbols exactly. (And if you wanted a bit of padding included in that auto-fitting... well... it's on my todo list. :-) =head2 Soapbox Having played around with the plot widget, you probably want to know how to modify it programatically, by adding a L or L, perhaps. "What sort of options," you ask, "does L accept for me to specify these things?" Well, you can't specify those in your call to L. You either add them to the object after L builds something for you, or you use the more powerful but verbose L. "But WHY?" you ask. "WHY can't I just specify a plot title in L and be done with it?" The reason is simple. The underlying library is built on a very clean and well-thought-out object and I would rather not waste my time creating or your time learning some intermediate API. A means for specifying the plot title in L is the first step down the road of confused mental models. So, it's really in your best interst. Honest. :-) "But WHY?" you ask again. OK, OK, I'll tell you how to essentially get what you want. =head2 Adding axis labels and titles via methods First, you can use the C command to build a L and return them to you I This will allow you to modify the properties of the L before it gets displayed. For example, I can L and L like so: use PDL; use PDL::Graphics::Prima::Simple; # Non-blocking auto_twiddle(0); # Build the plot my $x = sequence(100)/10; my $plot = line_plot($x, $x->sin); # Add a title $plot->title('The sine wave'); # Display the plot twiddle(); You next may ask how you modify the L, such as L or L. The L are sub-objects of the L, accessed with L: $plot->x. The properties of the axes that you can modify include the L, L, L, and L, and are discussed in greater detail L. Let's see how to set the L L: use PDL; use PDL::Graphics::Prima::Simple; # Build the plot auto_twiddle(0); my $x = sequence(100)/10; my $plot = line_plot($x, $x->sin); # Add a title and axis labels $plot->title('The Harmonic Oscillator'); $plot->x->label('time (s)'); $plot->y->label('displacement (cm)'); # Display the plot twiddle(); =head2 Working with plot objects in the PDL shell NOTE: The current state of readline integration, and its work-around, are in a state of flux. These docs are not entirely accurate. But they're close enough. Expect this to get fixed soon. NOTE: L is a pure-perl implementation of L and it is very nice. However, it does not play nicely with the Prima readline integration for reasons I do not yet fully understand. In particular, the displayed text and cursor position are always displayed one step "behind" what you last indicated with the navigation keys (but they are always up-to-date when you type normal letters). If you have any ideas for how to remedy this, please let me know! Thanks! You can do the same sorts of manipulations from the console and see the updates as soon as you press enter. The equivalent commands as the ones shown above are: pdl> use PDL::Graphics::Prima::Simple pdl> auto_twiddle(0) pdl> $x = sequence(100)/10 # After the next commant, the line plot window will pop up pdl> $plot = line_plot($x, $x->sin) pdl> $plot->title('The Harmonic Oscillator') pdl> $plot->x->label('time (s)') pdl> $plot->y->label('displacement (cm)') Each method call to the plot command will cause the plot to get updated with the new element or feature. =head2 Axis minima and maxima If you want to set the axis bounds, you can use the L, L and L functions. For example, continuing from the PDL shell, this sets the x-minimum to zero: pdl> $plot->x->min(0) If you drag the plot around with your mouse and want to see the current value of the min, you could say: pdl> p $plot->x->min This will print two numbers, actually, the second being a boolean flag indicating whether or not autoscaling is on. To just get the minimum value, call the C method in scalar context: pdl> p scalar($plot->x->min) If you want to re-enable autoscaling, you pass in a special value for the min or max (or both) denoted by the constant L|PDL::Graphics::Prima::Limits/lm::Auto> as in # Set the x-axis to autoscaling pdl> $plot->x->minmax(lm::Auto, lm::Auto); # Set the y-minimum to autoscaling pdl> $plot->y->min(lm::Auto); =head2 Histograms and images I've only spoken so far about connecting x/y data with lines, but there are other ways to visualize x/y data, and even other forms of data you may wish to represent. For example, if you want to visualize a distribution of data, you would want to use a histogram: use PDL; use PDL::Graphics::Prima::Simple; # Generate 100 samples with mean 0 and standard deviation 1 my $distribution = grandom(100); hist_plot($distribution); Or, you may have an image of data that you want to view. use PDL; use PDL::Graphics::Prima::Simple; # Generate 100 samples with mean 0 and standard deviation 1 my $image = rvals(100, 100) matrix_plot($image); And you won't be surprised to learn there are many other ways to display x/y data, as well. But let's keep moving. =head2 Adding DataSets What if you wanted to plot additional data along with the current data? You do this by creating a new L. Let's start by adding a L with the cosine of a slightly different x-range: use PDL; use PDL::Graphics::Prima::Simple; # Build the plot my $x = sequence(100)/10; my ($window, $plot) = line_plot($x, $x->sin); # Add a new dataset my $x2 = sequence(100)/10 + 1; $plot->dataSets{'cosine'} = ds::Pair($x2, $x2->cos); # Display the plot $window->execute; The important line in this example is the part that includes L|PDL::Graphics::Prima::DataSet/ds::Pair>. Like L|PDL::Graphics::Prima::Limits/lm::Auto>, L|PDL::Graphics::Prima::DataSet/ds::Pair> is a function that uses a short prefix so that it's fast and easy to type. This is a L (though in the toolkit it is used exclusively for specifying useful constants). L|PDL::Graphics::Prima::DataSet/ds::Pair> is just a short-hand for a constructor with an enormous name: # Short form my $ds = ds::Pair($x, $y); # Long form my $ds = PDL::Graphics::Prima::DataSet::Pair->new(x => $x, y => $y); If you actually run that example, you will notice that the sine cureve is plotted as a line, just as before, but the cosine curve is plotted with a collection of unconnected diamonds. If you want to plot the cosine curve with a line, you need to specify the line plot type: ... # Add a new dataset $plot->dataSets{'cosine'} = ds::Pair($x, $x->cos, plotType => ppair::Lines ); ... Perl is supposed to Do What I Mean, and you could argue that this is a case of the plotting library I doing what you mean: the cosine curve "should" be plotted with L, because that's how the sine curve was plotted. However, I believe that such behavior violates the Principle of Least Surprise. The default plot type for pairwise data is L, so if you didn't specify a plot type, you actually meant to plot diamonds. That brings us to the important distinction between a L and a L. A L contains the x/y data, or the distribution, or the image matrix that you want shown, along with one or more means of visualizing that data. The means for visualizing that data are what are called Ls, and different Ls use distinct Ls. =head2 Adding axis labels and titles at construction Although interacting with the plot object after creation is fun, it is also nice to be able to specify all of these settings when the plot is initially created. I have already explained how limited the super-simple interface is, and L. In light of that, I now show you how to specify all of these properties and more with a single C command. Let's begin by examining the L command. The documentation below states that the function call with args C<($x, $y)> is equivalent to this line: plot(-data => ds::Pair($x, $y, plotType => ppair::Lines)); Let's expand that into a full example and include the title, x-label, and y-label: use PDL; use PDL::Graphics::Prima::Simple; # Build the plot my $x = sequence(100)/10; plot( # Create the DataSet with the Lines pairwise plotType -data => ds::Pair($x, $x->sin, plotType => ppair::Lines ), # Set the title and axis labels title => 'The Harmonic Oscillator', x => { label => 'time (s)' }, y => { label => 'displacement (cm)' }, ); Notice that all arguments to L are key/value pairs. There is a clean, heierarchical structure to the function call, and it is clear simply by examining the punctuation which settings go with which piece. For example, the L argument clearly belongs to the L. In this case, we use data structures that Perl provides to help convey the structure of the plot we are trying to create. =head2 Blocking behavior Precisely what happens when you call these functions depends on your environment. These functions always create a stand-alone window with the plot, but they may or may not pause your script or shell while you interact with the plot. In regular Perl scripts, the code will block at these function calls until you close the window or press 'q' in one of the plot windows. For some folks, when using the PDL shell the functions return immediately, letting them peform more calculations or create new plots while keeping other plot windows open. For other folks, it's possible to go back and forth between an active plot and an inactive PDL shell, or an active shell and an inactive plot. From the shell, the C function makes the plot active; from the plot, pressing 'q' returns focus to the shell. The main drawback of using the Simple interface instead of the full-blown widget interface is that it differs from the normal Prima GUI application interface. I hope that this makes it easier for you to get started with this plotting library, but I hope that you also take the time to learn how to write Prima applications, with the Plot widget as just one component for user interaction. If you need any substantial amount of user interaction or real-time behavior, I suggest you work with the full Prima toolkit. You may find yourself working on a script and wanting to create multiple plot windows at once, without blocking until all of them are ready. You do this by explicitly turning off autotwiddling. Specifically, you would write something like this: my $was_auto_twiddling = auto_twiddle; auto_twiddle(0); # ... plotting operations ... # All done; restore previous autotwiddling # state and let the use interact: auto_twiddle($was_auto_twiddling); twiddle(); Although you can plot multiple Ls in the same plot window, and you can simultaneously build multiple plot windows, a limitation of this Simple interface is that you cannot create multiple independent plots in the same window. This is achieved using the full GUI toolkit by creating two plot widgets packed into a larger container widget. A tutorial for this sort of thing is in the works but hasn't made it into the distribution yet. Stay tuned! Having covered that introductory material, let's cover things a little more systematically. =head1 SIMPLEST FUNCTIONS These functions are bare-bones means for visualizing your data that are no more than simple wrappers around the more powerful L function. If you just want to have a quick look at your data, you should start with these. These functions can return the plot widget itself, allowing you to modify it, but see the L if want more control over your plot, such as plotting multiple data sets, using variable or advanced symbols, using multiple plot types, controlling the axis scaling, using colors, or setting the title or axis labels. In all of these plots, bad values in x and y are simply omitted. =over =item line_plot ([$x], $y) The C function takes either one or two arguments. In the one-argument form, the argument is a piddle with your y data. In the two argument form the arguments are a piddle with your x data and a piddle with your y data. The function plots them by drawing black lines on a white background from one point to the next. Usually C<$x> and C<$y> will have the same dimensions, but you can use any data that are PDL-thread compatible. For example, here's a way to compare three sets of data that have the exact same x-values: my $x = sequence(100)/10; my $y = sequence(3)->transpose + sin($x); # Add mild linear trends to the first and second: use PDL::NiceSlice; $y(:, 0) += $x/5; $y(:, 1) -= $x/6; line_plot($x, $y); The x-values do not need to be sorted. For example, this plots a sine wave sine wave oscillating horizontally: my $y = sequence(100)/10; my $x = sin($y); line_plot($x, $y); For the truly lazy, you can simply supply the y-values: my $y = sin(sequence(100)/10); line_plot($y); Bad values in your data, if they exist, will simply be skipped, inserting a gap into the line. For the two-argument form, to generate the same plot using the L command, you would type this: plot(-data => ds::Pair($x, $y, plotType => ppair::Lines)); For the one-argument form, you would type this: plot(-data => ds::Pair($y->xvals, $y, plotType => ppair::Lines)); =cut sub _get_pairwise_data { # If one arg, and it's a piddle, return a sequence and that piddle as x/y if (@_ == 1 and eval { $_[0]->isa('PDL') } ) { return (PDL->sequence($_[0]->dim(0)), $_[0]); } # If two args, and both are piddles, return them as x/y elsif (@_ == 2 and eval{$_[0]->isa('PDL') and $_[1]->isa('PDL')} ) { return @_; } # Neither of the above conditions applied: determine the function name, croak my $function_name = (caller(1))[3]; $function_name =~ s/.*:://; croak("$function_name expects either one piddle (y-data) or two piddles (x- and y-data)"); } sub line_plot { @_ = (-data => ds::Pair(_get_pairwise_data(@_), plotType => ppair::Lines)); goto &plot; } =item circle_plot ([$x], $y) Plots filled circles at (x, y). (See L for a more detailed description.) Equivalent L commands for the two-argument forms include: plot(-data => ds::Pair($x, $y, plotType => ppair::Blobs)); plot(-data => ds::Pair( $x, $y, plotType => ppair::Symbol( filled => 'yes', N_points => 0, ), )); =cut sub circle_plot { @_ = (-data => ds::Pair(_get_pairwise_data(@_), plotType => ppair::Blobs)); goto &plot; } =item triangle_plot ([$x], $y) Plots filled upright triangles at (x, y). (See L for a more detailed description.) Equivalent L commands for the two-argument form include: plot(-data => ds::Pair( $x, $y, plotType => ppair::Triangles(filled => 1) )); plot(-data => ds::Pair( $x, $y, plotType => ppair::Symbol( filled => 'yes', N_points => 3, orientation => 'up', ), )); =cut sub triangle_plot { @_ = (-data => ds::Pair(_get_pairwise_data(@_) , plotType => ppair::Triangles(filled => 1))); goto &plot; } =item square_plot ([$x], $y) Plots filled squares at (x, y). (See L for a more detailed description.) Equivalent L commands for the two-argument form include: plot(-data => ds::Pair( $x, $y, plotType => ppair::Squares(filled => 1) )); plot(-data => ds::Pair( $x, $y, plotType => ppair::Symbol( filled => 'yes', N_points => 4, orientation => 45, ), )); =cut sub square_plot { @_ = (-data => ds::Pair(_get_pairwise_data(@_) , plotType => ppair::Squares(filled => 1))); goto &plot; } =item diamond_plot ([$x], $y) Plots filled diamonds at (x, y). (See L for a more detailed description.) Equivalent L commands for the two-argument form include: plot(-data => ds::Pair($x, $y)); plot(-data => ds::Pair( $x, $y, plotType => ppair::Diamonds(filled => 1) )); plot(-data => ds::Pair( $x, $y, plotType => ppair::Symbol( filled => 'yes', N_points => 4, ), )); =cut sub diamond_plot { @_ = (-data => ds::Pair(_get_pairwise_data(@_))); goto &plot; } =item cross_plot ([$x], $y) Plots crosses (i.e. plus symbols) at (x, y). (See L for a more detailed description.) Equivalent L commands for the two-argument form include: plot(-data => ds::Pair($x, $y, plotType => ppair::Crosses)); plot(-data => ds::Pair( $x, $y, plotType => ppair::Symbol( N_points => 4, skip => 0, ), )); =cut sub cross_plot { @_ = (-data => ds::Pair(_get_pairwise_data(@_), plotType => ppair::Crosses)); goto &plot; } =item X_plot ([$x], $y) Plots X symbols at (x, y). (See L for a more detailed description.) Equivalent L commands for the two-argument form include: plot(-data => ds::Pair($x, $y, plotType => ppair::Xs)); plot(-data => ds::Pair( $x, $y, plotType => ppair::Symbol( N_points => 4, skip => 0, orientation => 45, ), )); =cut sub X_plot { @_ = (-data => ds::Pair(_get_pairwise_data(@_), plotType => ppair::Xs)); goto &plot; } =item asterisk_plot ([$x], $y) Plots five-pointed asterisks at (x, y). (See L for a more detailed description.) Equivalent L commands for the two-argument form include: plot(-data => ds::Pair( $x, $y, plotType => ppair::Asterisks(N_points => 5) )); plot(-data => ds::Pair( $x, $y, plotType => ppair::Symbol( N_points => 5, skip => 0, orientation => 'up', ), )); =cut sub asterisk_plot { @_ = (-data => ds::Pair(_get_pairwise_data(@_) , plotType => ppair::Asterisks(N_points => 5))); goto &plot; } =item func_plot ($x_min, $x_max, $func_ref, [$N_points]) The C function takes three or four arguments and plots a function. The first two arguments are the initial x-bounds (min and max); the third argument is the function that you want to plot; the optional fourth argument is the number of points that you want to use in generating the plot. The resulting figure will have a black line drawn against a white background. The function itself will be called whenever the plot needs to be redrawn. It will be passed a single argument: a piddle with sequential x-points at which the function should be evaluated. The function should return a piddle of y-points to be plotted. Here are some examples: # Plot PDL's exponential function: func_plot (1, 5, \&PDL::exp); # this time with higher resolution: func_plot (1, 5, \&PDL::exp, 1000); # Plot a rescaled tangent function: func_plot (1, 5, sub { my $xs = shift; return 5 * ($xs / 4)->tan }); # or equivalently, if you "use PDL": func_plot (1, 5, sub { my $xs = shift; return 5 * tan($xs / 4); }); Your function can return bad values, in which case they will not be drawn. For example, here is a function that plots a decaying exponential, but only for values of x greater than or equal to zero. It starts with an initial view of x running from 0 to 4: func_plot (0, 4, sub { my $xs = shift; my $ys = exp(-$xs); return $ys->setbadif($xs < 0); }); The return value must have thread-compatible dimensions, but that means that they do not need to be identical to the input x. For example, a scalar is thread-compatible with a vector, so the following will create a line at a y-value of 1: func_plot (0, 4, sub { 1 }); Or, you can return a piddle with more dimensions than the input: func_plot (0, 4, sub { my $xs = shift; my $first_ys = sin($xs); my $second_ys = cos($xs) + 3; return $first_ys->cat($second_ys); }); If you do not specify the number of points to draw, the equivalent L command is this: plot( -data => ds::Func($func_ref), x => { min => $xmin, max => $xmax }, ); If you do specify the number of points to draw, the equivalent L command is this: plot( -data => ds::Func($func_ref, N_points => $N_points), x => { min => $xmin, max => $xmax }, ); =cut sub func_plot { croak("func_plot expects three or four arguments: the x min and max, the function reference,\n" . " and, optionally, the number of points to plot") unless @_ == 3 or @_ == 4; my ($xmin, $xmax, $func_ref, $N_points) = @_; @_ = ( -data => ds::Func($func_ref, ($N_points ? (N_points => $N_points) : ())), x => { min => $xmin, max => $xmax, }, ); goto &plot; } =item hist_plot ($distribution, [$bin_type]) The C function takes a distribution of data that you want to visualize and an optional binning type. This is a fairly simple wrapper around the L for the L using the L. It plots the histogram as black-outlined rectangles against a white background. The equivalent L commands is: plot(-data => ds::Dist($distribution)); A lot of defaults go into that, so it might be useful to see some of those expanded a bit: plot(-data => ds::Dist($distribution, plotType => ppair::Histogram, binning => bt::Linear ) ); You could tweak the binning, for example by using a L, which throws an exception if your distribution includes negative data: hist_plot($distribution, bt::StrictLog); or you could specify that you want 15 bins: hist_plot($distribution, bt::Linear(nbins => 15)); There is quite a bit more to explore here, and it will be covered below. =cut sub hist_plot { my ($piddle, $subref) = @_; croak("hist_plot expects a piddle as the first argument") unless eval{$piddle->isa('PDL')}; my @bin_args; if (defined $subref) { croak("hist_plot expects a optional bin type or subref") if ref($subref) ne ref(sub{}); @bin_args = (binning => $subref); } @_ = (-data => ds::Dist($piddle, @bin_args)); goto &plot; } =item matrix_plot ([$x_edges, $y_edges,] $matrix) The C function plots a grayscale rendering of a matrix with the largest values closest to black, the smallest values closest to white. (For the opposite color sense, see L.) This function takes either one or three arguments. The first designates the x min and max of the plot; the second designates the y min and max of the plot, and the third (or only) specifies a matrix that you want to have plotted in grayscale. The x-edges and y-edges arguments should be two-element array references, such as: matrix_plot ([0 => 5], [1 => 10], $matrix); Bad values, if your matrix has any, are skipped. This means that you will have a white spot in its place (since the background is white), which is not great. Future versions may use a different color to specify bad values. Not specifying edges looks like this: matrix_plot ($matrix); and its equivalent L commands are: plot(-image => ds::Grid( $matrix, x_bounds => [0, 1], y_bounds => [0, 1], )); plot(-image => ds::Grid( $matrix, x_bounds => [0, 1], y_bounds => [0, 1], plotType => pgrid::Matrix, )); Specifying edges looks like this: matrix_plot ([0 => 5], [1 => 10], $matrix); and the quivalent is: plot(-image => ds::Grid( $matrix, x_bounds => [0, 5], y_bounds => [1, 10], plotType => pgrid::Matrix, )); =cut sub matrix_plot { my ($x, $y, $matrix); if (@_ == 1) { $matrix = $_[0]; my @dims = $matrix->dims; $x = [0, $dims[0]]; $y = [0, $dims[1]]; ($matrix) = @_; } elsif (@_ == 3) { ($x, $y, $matrix) = @_; } else { croak("matrix_plot expects an image, optionally preceeded by its x- and y-limits:\n" . "matrix_plot ([x0, xf], [y0, yf], \$image)") } @_ = (-image => ds::Grid( $matrix, x_bounds => $x, y_bounds => $y, )); goto &plot; } =item imag_plot ([$x_edges, $y_edges,] $matrix) The C function is identical to L except that the color scaling runs from black (lowest) to white (highest). This form is especially useful for plotting black and white images, in which case the brightest points should be white. Omitting edges, the command looks like this: imag_plot($matrix); which has the equivalent L command: plot(-image => ds::Grid( $matrix, x_bounds => [0, 1], y_bounds => [0, 1], plotType => pgrid::Matrix( palette => pal::BlackToWhite ), )); Specifying edges looks like this: imag_plot ([0 => 5], [1 => 10], $matrix); and the quivalent is: plot(-image => ds::Grid( $matrix, x_bounds => [0, 5], y_bounds => [1, 10], plotType => pgrid::Matrix( palette => pal::BlackToWhite ), )); =cut sub imag_plot { my ($x, $y, $matrix); if (@_ == 1) { $matrix = $_[0]; my @dims = $matrix->dims; $x = [0, $dims[0]]; $y = [0, $dims[1]]; ($matrix) = @_; } elsif (@_ == 3) { ($x, $y, $matrix) = @_; } else { croak("imag_plot expects an image, optionally preceeded by its x- and y-limits:\n" . "imag_plot ([x0, xf], [y0, yf], \$image)") } @_ = (-image => ds::Grid( $matrix, x_bounds => $x, y_bounds => $y, plotType => pgrid::Matrix( palette => pal::BlackToWhite ), )); goto &plot; } =back =head1 PLOT FUNCTION The C function is the real workhorse of this module. Not only does it provide the functionality behind all of the above simple functions, but it also lets you plot multiple Ls, specify axis labels and a plot title, direct the axis scaling (linear or logarithmic), and set many other properties of the plot. Arguments that you pass to this function are almost identical to the arguments that you would use to create a Plot widget, so it serves as an excellent sandbox for playing with the widget's constructor. Also, once you understand how to use this function, using the actual widget in an interactive GUI script is simply a matter of understanding how to structure a GUI program. The C function takes options and L specifications as key/value pairs. The basic usage of C looks like this: plot( -dataset1 => ds::Pair($x1, $y1, ...options...), x => { axis => options, }, -dataset2 => ds::Pair($x2, $y2, ...options...), y => { axis => options, }, -mydist => ds::Set($data, ...options...), title => 'Title!', titleSpace => 60, -the_image => ds::Grid($image, ...options...), ... Prima Drawable options ... ); Notice that some of the keys begin with a dash while others do not. Any key that begins with a dash should be followed by a L object (created using the C constructors). You can use any name that you wish for your Ls, the only requirement is that the name begins with a dash. (The dash is optional when it comes time to retrieve the L later.) The keys that do not begin with a dash are Plot options. The Plot widget has a handful of Plot-specific properties, but you can also specify any property of a L object. In the world of C, the fundamental object is the Plot. Each plot can hold one or more Ls, and each L is visualized using one or more Ls. This makes the plotType the simplest element to discuss, so I'll start there. =head2 Plot Types Each L can have one or more plotTypes. If you only want to specify a single plotType, you can do so by specifying it after the plotType key for your L: -data => ds::Pair( ... plotType => ppair::Squares, ... ) You can specify multiple plotTypes by passing them in an anonymous array: -data => ds::Pair( ... plotTypes => [ppair::Triangles, ppair::Lines], ... ) (Note that the singular and plural keys C and C are interchangeable. Use whichever is appropriate.) All the plotTypes take key/value paired arguments. You can specify various L properties like L or L; you can pass plotType-specific options like symbol size (for L and its derivatives) using the C key or the baseline height for L using the C key; and some of the plotTypes have required arguments, such as at least one error bar specification with L. To create L L, you would use something like this: ppair::Blobs(color => cl::LightRed) To create L of all different colors, you would use the plural C key and specify a piddle with Color values. (That's discussed below in an example.) To specify a 5-pixel line width for a Lines plotType, you would say ppair::Lines(lineWidth => 5) When a L gets drawn, it draws the different plotTypes in the order specified. For example, suppose you specify C filled triangles and C lines. If the triangles are specified first, they will have red lines drawn through them, and if the triangles are second, the triangles will be drawn over the red lines. Each L has a default plot type. For Ls, it is L. For L, it is L. For L, it is L. If you want to use a different plotType, you need to specify it as illustrated by the translations given in the super-simple examples above. The plotTypes are discussed thoroughly in L, and are summarized below: Pair plotTypes ============== ppair::Lines - lines from point to point ppair::TrendLines - a linear fit to the data ppair::Blobs - blobs (filled ellipses) with specifiable x- and y- radii ppair::Symbols - open or filled regular geometric shapes with many options: size, orientation, number of points, skip pattern, and fill ppair::Triangles - open or filled triangles with specifiable orientations and sizes ppair::Squares - open or filled squares with specifiable sizes ppair::Diamonds - open or filled diamonds with specifiable sizes ppair::Stars - open or filled star shapes with specifiable sizes, orientations, and number of points ppair::Asterisks - asterisk shapes with specifiable size, orientation, and number of points ppair::Xs - four-point asterisks that look like xs, with specifiable sizes ppair::Crosses - four-point asterisks that look like + signs, with specifiable sizes ppair::Spikes - spikes to (x,y) from a specified vertical or horizontal baseline ppair::Histogram - histograms with specifiable baseline and top padding ppair::ErrorBars - error bars with specified x/y errors and cap sizes Grid plotTypes ============== pgrid::Matrix - colored rectangles, i.e. images More plot types are planned, but the ones listed above are the currently implemented ones. The plotTypes are the simplest unit in L. The next largest unit is the L, which not only holds data of various kinds, but also holds the plotTypes that are to be applied to the given data. =head2 DataSets You can plot one or more sets of data on a given Plot. You do this by specifying the L's name with a dash, followed by a L constructor. The L constructor specifies the properties of your L, including the data itself along with the plotType or plotTypes. In addition to the two types that I have already alluded to, namely L and L, there are a collection of derived L. These include the L and the L. L targets x/y paired data and lets you visualize trends and correlations between two collections of data. The L takes two arguments (the x-data and the y-data) and then key/value pairs that indicate your plotTypes and specify precisely how you want the data visualized: -scatter => ds::Pair($student_weights, $student_heights, ...options...) Typical x/y plots are plotted in such a way that the x-data is sorted in increasing order, but this is not required. This means that it is just as easy to draw a sine function as it is to draw a spiral or a scatter plot. L take a single piddle of unordered data and visually represents it with aggregated plots like histograms, cumulative distributions, or curves to distribution fits. The L takes a single piddle argument that represents that data to plot, and then key/value pairs that let you tweak how the data is visualized. The most important key is the binning key, which dictates how the distribution should be mapped to pairwise data. -distribution => ds::Dist($student_heights, binning => bt::Log(min => 2), ...options... ) Any pairwise plot type will work with the distribution plot types, but some make more (or less) sense than others. For example, L are better visualized with lines whereas L are sometimes better visualized with histograms. L let you specify a function to plot, rather than forcing you to evaluate a specific function at fixed values of x. They inherit from L (in an OO sense) and differ in that the constructor expects a single function reference rather than two piddles of data: -model => ds::Func(\&my_model, ...options...) Because L inherits from L, any L will also work with a L. The Grid DataSet is what you would use to visualize matrices or images. It takes a single piddle which represents a matrix of data, followed by key/value pairs the specify how you want the data plotted. In particular, there are many ways to specify the grid centers or boundaries. -terrain => ds::Grid($landscape, ...options...) The data that you specify for the Set, Pair, and Grid DataSets do not need to be piddles: anything that can be converted to a piddle, including scalar numbers and anonymous arrays of values, can be specified. That means that the following are valid DataSet specifications: -data => ds::Pair(sequence(10), sequence(10)->sin) -data => ds::Pair([1, 2, 3], [1, 4, 9]) -data => ds::Pair(sequence(100), 5) Once you have specified the data or function that you want to plot, you can specify other options with key/value pairs. I discussed the plotType key already, but you can also specify any property in L. When you specify properties from L, these become the default parameters for all the plotTypes that belong to this DataSet. For example, you can specify a default color as C, and then the lines, blobs, and error bars will be drawn in red unless they override the colors themselves. Function-based DataSets also recognize the C key, which indicates the number of points to use in evaluating the function. To get an idea of how this works, suppose I have some data that I want to compare with a model. In this case, I would have two DataSets, the data (plotted using error bars) and the model (plotted using a line). I would plot all of this with code like so: plot( # The experimental data -data => ds::Pair( $x, $y, # I want error bars along with squares: plotTypes => [ ppair::ErrorBars(y_err => $y_errors), ppair::Squares(filled => 1), ], ), # The model: -model => ds::Func( \&my_model, # Default plotType is diamonds, but I want lines: plotType => ppair::Lines, lineStyle => lp::ShortDash, ), ); The part C<< -data => ds::Pair(...) >> specifies the details for how you want to plot the experimental data and the part C<< -model >> specifies the details for how you want to plot the model. The DataSets are plotted in ASCIIbetical order, which means that in the example above, the model will be drawn over the error bars and squares. If you want the data plotted over the model curve, you should choose different names so that they sort the way you want. For example, using C<-curve> instead of C<-model> might work. So would changing the names from C<-data> and C<-model> to C<-b_data> and C<-a_model>, respectively. =head2 Plot Options Finally we come to setting plot-wide properties. As already discussed, you can disperse DataSets among your other Plot properties. Plot-wide properties include the title and the amount of room you want for the title (called titleSpace), the axis specifications, and any default L properties that you want applied to your plot. The text for your plot's title should be a simple Perl string. UTF8 characters are allowed, which means you can insert Greek or other symbols as you need them. However, L, and therefore L, does not support fancy typesetting like subscripts or superscripts. (If you want that in the Plot library, you should probably consider petitioning for and helping add that functionality to Prima. Open-source is great like that!) The amount of space allocated for the title is currently set at 80 pixels. You can specify a different size if you prefer. (It should probably be calculated based on the current font-size---Prima makes it relatively easy to do that---but that's not yet implemented.) Space is allocated for the title only when you specify one; if none is specified, you have more room for your plot. Axis labels have similar restrictions and capabilities as the title string, but are properties of the axes themselves, which additionally have specifiable bounds (min and max) and scaling type. At the moment, the only two scaling types are C and C. The bounds can be set to a specific numeric value, or to C if you want the bounds automatically computed based on your data and plotTypes. Finally, this is essentially a widget constructor, and as such you can specify any L properties that you like. These include all the properties in L. For example, the default background color is white, because I like a white background on my plots. If you disagree, you can change the widget's background and foreground colors by specifying them. The DataSets and their plotTypes will inherit these properties (most importantly, the foreground color) and use them unless you override those properties seperately. =head2 Examples This first example is a simple line plot with triangles at each point. There's only one DataSet, and it has only two plotTypes: use strict; use warnings; use PDL::Graphics::Prima::Simple; use PDL; my $x = sequence(100)/10; my $y = sin($x); plot( -data => ds::Pair( $x, $y, plotTypes => [ ppair::Triangles, ppair::Lines, ], ), ); Now for something more fun. This figure uses bright colors and random circle radii. Notice that the lineWidth of 3 obscures many of the circles since their radii are between 1 and 5. This has only one DataSet and two plotTypes like the previous example. In contrast to the previous example, it specifies a number of properties for the plotTypes: use strict; use warnings; use PDL::Graphics::Prima::Simple; use PDL; my $x = sequence(100)/10; my $y = sin($x); my $colors = pal::Rainbow->apply($y); plot( -data => ds::Pair( $x, $y, plotTypes => [ ppair::Blobs ( radius => 1 + $x->random*4, colors => $colors, ), ppair::Lines ( lineWidths => 3, ), ], ), ); Here I use a black background and white foreground, and plot the circles B the line instead of under it. I achieve this by changing the order of the plotTypes---Lines then Blobs. use strict; use warnings; use PDL::Graphics::Prima::Simple; use PDL; my $x = sequence(100)/10; my $y = sin($x); my $colors = pal::Rainbow->apply($y); my $radius = 1 + $x->random*4; plot( -data => ds::Pair( $x, $y, plotTypes => [ ppair::Lines ( lineWidths => 3, ), ppair::Blobs ( radius => $radius, colors => $colors, ), ], ), backColor => cl::Black, color => cl::White, ); I find the smaller points very difficult to see, so here's a version in which I 'wrap' the points with a white radius. I also use the Symbols plotType instead of the Blobs plotType because it's a bit more flexible: use strict; use warnings; use PDL::Graphics::Prima::Simple; use PDL; my $x = sequence(100)/10; my $y = sin($x); my $colors = pal::Rainbow->apply($y); my $radius = 1 + $x->random*4; plot( -data => ds::Pair( $x, $y, plotTypes => [ ppair::Lines ( lineWidths => 3, ), ppair::Symbols( size => 1 + $radius, filled => 'no', N_points => 0, ), ppair::Symbols ( size => $radius, colors => $colors, filled => 'yes', N_points => 0, ), ], ), backColor => cl::Black, color => cl::White, ); Here I use PDL threading to achieve the same ends as the previous example, but only using one Blobs plotType instead of two. use strict; use warnings; use PDL::Graphics::Prima::Simple; use PDL; my $x = sequence(100)/10; my $y = sin($x); my $rainbow_colors = pal::Rainbow->apply($y); my $whites = $y->ones * cl::White; my $colors = cat($whites, $rainbow_colors); my $inner_radius = 1 + $x->random*4; my $radius = cat($inner_radius + 1, $inner_radius); plot( -data => ds::Pair( $x, $y, plotTypes => [ ppair::Lines ( lineWidths => 3, ), ppair::Blobs( radius => $radius, colors => $colors, ), ], ), backColor => cl::Black, color => cl::White, ); Here I generate some linear data with noise and perform a least-squares fit to it. In this case I perform the least-squares fit by hand, since not everybody will have Slatec installed. The important part is the C line and beyond. This example demonstrates the use of multiple data sets, the use of the ErrorBars plotType and the use of function-based data sets. use strict; use warnings; use PDL; my $x = sequence(100)/10; my $y = $x/2 - 3 + $x->grandom*3; my $y_err = 2*$x->grandom->abs + 1; # Calculate the slope and intercept: my $S = sum(1/$y_err); my $S_x = sum($x/$y_err); my $S_y = sum($y/$y_err); my $S_xx = sum($x*$x/$y_err); my $S_xy = sum($x*$y/$y_err); my $slope = ($S_xy * $S - $S_x * $S_y) / ($S_xx * $S - $S_x * $S_x); my $y0 = ($S_xy - $slope * $S_xx) / $S_x; use PDL::Graphics::Prima::Simple; plot( -data => ds::Pair( $x, $y, plotTypes => [ ppair::Diamonds(filled => 'yes'), ppair::ErrorBars(y_err => $y_err), ], ), -func => ds::Func( sub { $y0 + $slope * $_[0] }, lineWidth => 2, color => cl::LightRed, ), ); That example used a function-based DataSet, but we could just as easily have used C to compute the fit for us. The only difference between the last example and the one below is that the trendline for this next example does not extend out to infinity in the x-direction but terminates at the end of the data. use strict; use warnings; use PDL; my $x = sequence(100)/10; my $y = $x/2 - 3 + $x->grandom*3; my $y_err = 2*$x->grandom->abs + 1; use PDL::Graphics::Prima::Simple; plot( -data => ds::Pair( $x, $y, plotTypes => [ ppair::Diamonds(filled => 'yes'), ppair::ErrorBars(y_err => $y_err), ppair::TrendLines( weights => $y_err, lineWidths => 2, colors => cl::LightRed, ), ], ), ); You can extend the Simple interface with GUI methods. The next example gives you a first glimps into GUI-flavored programming by overriding the onMouseMove method for this Prima widget. There are many ways of setting, overriding, or adding callbacks in relation to all sorts of GUI events, but when using the C command, your only option is to specify it as OnEventName. In this example, I add (that's B, not override, becaus Prima rocks) a method that prints out the mouse position to the console. I also use logarithmic scaling in the x direction (and specify x- and y-axes labels) to make things interesting: use strict; use warnings; use PDL::Graphics::Prima::Simple; use PDL; # Turn on autoflush: $|++; my $x = sequence(100)/10 + 1; my $y = sin($x); plot( -data => ds::Pair( $x, $y, plotTypes => [ ppair::Xs, ppair::Lines, ], ), onMouseMove => sub { # Get the widget and the coordinates: my ($self, $button_and_modifier, $x_pixel, $y_pixel) = @_; # Convert the pixel coordinates to real values: my $x = $self->x->pixels_to_reals($x_pixel); my $y = $self->y->pixels_to_reals($y_pixel); # Print the results: print "\r($x, $y) "; }, x => { label => 'time (s)', scaling => sc::Log, }, y => { label => 'displacement (m)', } ); This kind of immediate feedback can be very useful. It is even possible to capture keyboard events and respond to user interaction this way. But B, don't do that. If you need any substantial amount of user interaction, you would do much better to learn to create a Prima application with buttons, lists, and input lines, along with the Plot widget. For that, see the (soon to be written) L. =cut # A function that allows for quick one-off plots by packing a plot widget # into a window. In void context and no readline support, it builds and # executes the window. In void context and readline support it returns # immediately, letting the readline handle yield()ing. A scalar context # return is only allowed when readline support is enabled. (App::Prima::REPL # allows for scalar return context by overriding this plot command, and other # libraries that want to support the simple interface should do the same.) # In list context, it returns both the window and the plot object. # A function that allows for quick one-off plots: *plot = \&default_plot; our $auto_twiddling = 1; our $is_twiddling = 0; our $N_windows = 0; sub default_plot { # Make sure they sent key/value pairs: croak("Arguments to plot must be in key => value pairs") unless @_ % 2 == 0; my %args = @_; # Create a new window and pack the plot into said window unless (defined $::application) { require Prima::Application; Prima::Application->import; } my $window = Prima::Window->create( text => $args{title} || 'PDL::Graphics::Prima', size => $args{size} || [our @default_sizes], # Add a stop-twiddling listener onKeyDown => sub { my (undef, $key) = @_; $is_twiddling = 0 if chr($key) =~ /q/i; }, # Pair of functions to note when windows are created and destroyed # for purposes of quitting twiddling when the last window is closed. onCreate => sub { $N_windows++; }, onDestroy => sub { $N_windows--; $is_twiddling = 0 if $N_windows == 0; }, ); my $plot = $window->insert('Plot', pack => { fill => 'both', expand => 1}, %args ); $plot->onKeyDown(sub { my (undef, $key) = @_; $is_twiddling = 0 if chr($key) =~ /q/i; }); # make sure it shows up on top. $window->bring_to_front; # Twiddle, then return. Note that twiddling (defined below) may be a # no-op for configurations where the application loop is already running. twiddle() if $auto_twiddling; return $plot; } sub auto_twiddle { $auto_twiddling = $_[0] if @_; return $auto_twiddling; } # Make twiddling a no-op if we're in the perldl shell and have event_loop # support: use Scalar::Util qw(refaddr); if (PDL::Graphics::Prima::ReadLine->is_setup) { *twiddle = sub {}; } # Otherwise, make it run the event loop: else { *twiddle = sub { # twiddling should be a no-op if the plot function is not default_plot, # which is the case for App::Prima::REPL return if refaddr(\&plot) != refaddr(\&default_plot); # No event looping if we don't have any open windows. Otherwise, # they won't be able to exit the loop! print "No open plots\n" and return if $N_windows == 0; # Print a notice explaining what's going on: print "Twiddling plot; close window or press q or Q to resume\n"; # We will use Prima's nice exception handling to exit the go() # method. In order to prevent undue error propogation, localize # the error message: local $@; $is_twiddling = 1; # Start the timer that will check for the exit condition. There # are a number of ways in which the loop will want to exit; placing # the exit excption here centralizes the exception handling. Prima::Timer->create( onTick => sub { if (not $is_twiddling) { $_[0]->stop; # die in order to exit the application loop die 'done with event loop'; } }, timeout => 500, # milliseconds )->start; # Run go() in an eval so we catch the exit exception eval { $::application->go }; # Rethrow the error unless it was the exit exception die unless $@ =~ /^done with event loop/; }; } # Add the twiddle method to the Prima window *Prima::Window::twiddle = \&twiddle; *Prima::Plot::twiddle = \&twiddle; =head1 IMPORTED METHODS There are a couple of ways to call this module. The first is just a simple use statement: use PDL::Graphics::Prima::Simple; This will import the above mentioned functions into your local package's symbol table, which is generally what you want. If you do not want any functions imported, you can call it with an empty string: use PDL::Graphics::Prima::Simple ''; or you can name the functions that you want imported: use PDL::Graphics::Prima::Simple qw(plot); In addition, you can specify the default window plot size by passing a two element anonymous array: # default to 800 x 800 window: use PDL::Graphics::Prima::Simple [800, 800]; # default to 800 wide by 600 tall, import nothing: use PDL::Graphics::Prima::Simple [800, 600], ''; # default to 300 wide by 450 tall, import 'plot' function: use PDL::Graphics::Prima::Simple [300, 450], 'plot'; As an experimental feature, you can provide a subroutine reference in the import method. This subroutine reference will get invoked by the plotting commands instead of the default plotting mechanism. This is of marginal utility, and might be better achieved with a simple glob assignment of C<*PDL::Graphics::Prima::Simple::plot> to your desired function. The glob assignment has the advantage that it can be Cized. Note: I am considering adding a '-hold' option, which would cause all void-context plot commands in scripts to not block, and cause the Prima event loop to be run at the end of the script. However, I haven't settled on either the behavior or the name. Input is welcome! =cut # import/export stuff: require Exporter; our @ISA = qw(Exporter); our @EXPORT = qw(plot line_plot circle_plot triangle_plot square_plot diamond_plot cross_plot X_plot asterisk_plot hist_plot matrix_plot imag_plot func_plot twiddle auto_twiddle ); our @EXPORT_OK = (@EXPORT); our @default_sizes = (400, 400); # Override the import method to handle a few user-specifiable arguments: sub import { my $package = shift; my @args; # Run through all the arguments and pull out anything special: foreach my $arg (@_) { # See if they passed in an array reference if(ref ($arg) and ref($arg) eq 'ARRAY') { # Make sure it is the correct size: croak("Array references passed to PDL::Graphics::Prima::Simple indicate the\n" . "desired plot window size and must contain two elements") unless @$arg == 2; # Apparently we're good to go so save the sizes: @default_sizes = @$arg; } #elsif ($arg eq '-hold') { # # Add code here for holding after each plot #} elsif (ref ($arg) and ref($arg) eq 'CODE') { # a CODE ref; assign plot() to it no warnings 'redefine'; *plot = $arg; } else { push @args, $arg; } } $package->export_to_level(1, $package, @args); } 1; __END__ =head1 LIMITATIONS AND BUGS I am sure there are bugs in this software. If you find them, you can report them in one of two ways: =over =item PDL Mailing List You can (and should!) join the PDL mailing list, and you should feel free to post questions about this or any other PDL module on that list. For details on how to sign-up, see L. =item Github The best place to report problems that you are sure are problems is at L. =back =head1 SEE ALSO For an introduction to L see L. =head1 AUTHOR David Mertens (dcmertens.perl@gmail.com) =head1 ADDITIONAL MODULES Here is the full list of modules in this distribution: =over =item L Defines the Plot widget for use in Prima applications =item L Specifies the behavior of axes (but not the scaling) =item L Specifies the behavior of DataSets =item L Defines the lm:: namespace =item L Specifies a collection of different color palettes =item L Defines the different ways to visualize your data =item L Encapsulates all interaction with the L family of modules. =item L Specifies different kinds of scaling, including linear and logarithmic =item L Defines a number of useful functions for generating simple and not-so-simple plots =back =head1 LICENSE AND COPYRIGHT Unless otherwise stated, all contributions in code and documentation are copyright (c) their respective authors, all rights reserved. Portions of this module's code are copyright (c) 2011 The Board of Trustees at the University of Illinois. Portions of this module's code are copyright (c) 2011-2013 Northwestern University. Portions of this module's code are copyright (c) 2013-2014 Dickinson College. This module's documentation is copyright (c) 2011-2014 David Mertens. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut