Formatting GD::Graph x-axis in Perl - perl

I'm producing a graph of prices over time. Because I have dates on the x-axis, I have converted them to days since the epoch. Days since the epoch is not a very clear value to display on the graph so I want to convert them back to human readable dates using the x_number_format option.
But...it doesn't appear to be called as the graph is rendered.
I've created the following test code to demonstrate the problem.
use strict;
use GD::Graph::points;
# Generate some random data!
my #x_data;
my #y_data;
for (1...20) {
push #x_data, $_;
push #y_data, rand(20) + 10;
}
# This is never called - possible bug!
sub x_format {
print "X Formatter!\n";
return " - $_[0] - ";
}
# This gets called for every Y-axis point
sub y_format {
print "Y Formatter!\n";
return " - $_[0] - ";
}
my $graph=GD::Graph::points->new(1000,450);
$graph->set(
y_label => 'Random numbers',
y_number_format => \&y_format,
x_number_format => \&x_format,
x_label => 'Sequential meaningless numbers',
x_labels_vertical => 1,
x_plot_values => 1,
);
my #data=(
[ #x_data ],
[ #y_data ],
);
open PNG, ">temp.png";
binmode PNG;
print PNG $graph->plot(\#data)->png;
close PNG;
system("temp.png");
This test code produces a graph as expected and prints Y Formatter! 6 times. One for each point on the y-axis. However, it doesn't print X Formatter! and doesn't format the x-axis.
I have tried formatting the x-axis values more directly with
x_number_format => sub { " - $_[0] - " },
That doesn't format the x-axis either.
Am I doing something glaring stupid or is this a bug in GD:Graph?
There are no bug reports for this issue in the GD::Graph bug page

By inspecting the source, I can see that you need to set
x_tick_number to a defined value for the x_number_format callback to be called.
So you can try something like this:
$graph->set(
y_label => 'Random numbers',
y_number_format => \&y_format,
x_number_format => \&x_format,
x_tick_number => 6,
x_label => 'Sequential meaningless numbers',
x_labels_vertical => 1,
x_plot_values => 1,
);
According to the documentation:
x_tick_number
If set to 'auto', GD::Graph will attempt to format the X
axis in a nice way, based on the actual X values. If set to a number,
that's the number of ticks you will get. If set to undef, GD::Graph
will treat X data as labels. Default: undef.

Related

Can't get Perl Chart (gnuplot) to label all tics on axis

Using Chart::Gnuplot in perl.
The x axis is to be for date/time.
I'm specifying
timeaxis => "x" (seems to work)
I'm using
timefmt => '%Y-%m-%d_%H:%M:%S'
to read the elements in the array ref passed to xdata in the Chart::Gnuplot::DataSet->new. (that seems to work too)
Using
xtics => {labelfmt => "%m-%d %H", rotate => -90}
to display the labels the way I want them. (that all seems to work too)
And indeed, everything looks good except for the fact that it's only labeling a handful of tics on the x (date/time) axis. I want to label them all (or every other one, or have some control over this)
I found lots of examples of how to do this for numbers (note dates) using... start, incr, end, etc... . And I tried lots of experiments to get this to work. But I think I've exhausted all that I can find on this googling around and I'm still stuck :-(
And so, if there is any advise on how to get this to label all the tics with the date/time, I'd very much appreciate it.
You can use xtics => { labels=>[...] } but you need to respect that
In case of timeseries data, position values must be given as quoted dates or times according to the format timefmt.
from gnuplot documentation.
Assuming the array #x contains the data set's time values in timefmt, x-tick labels can be forced at each of those times like this.
xtics => {
labels=>[map { q(').$_.q(') } #x]
}
There are plenty of ways to add single quotes around each time, but I think the map above is cleanest.
You can of course provide your own labels, just make sure they are quoted properly and the same as timefmt. I think Perl's q() quoting operator is the way to go.
labels=>[ q('2005-6-7_07:04:53') , q('2005-6-7_07:05:10') ]
Full working example
Here is a full working example, modified from the gnuplot tick examples.
#!/usr/bin/perl -w
use strict;
use Chart::Gnuplot;
# Change the date time format of the tic labels
# - the solution is the same as change the number format
# Date array
my #x = qw(
2005-6-7_07:00:00
2005-6-7_07:05:00
2005-6-7_07:10:00
2005-6-7_07:15:00
);
my #y = qw(
3562279127
3710215571
3877469703
3876354871
);
# Create the chart object
my $chart = Chart::Gnuplot->new(
output => 'test.png',
xtics => {
rotate => -90,
labelfmt => "%m-%d %H",
labels=>[map { q(').$_.q(') } #x]
},
timeaxis => "x", # declare that x-axis uses time format
);
# Data set object
my $data = Chart::Gnuplot::DataSet->new(
xdata => \#x,
ydata => \#y,
style => 'linespoints',
timefmt => '%Y-%m-%d_%H:%M:%S',
);
# Plot the graph
$chart->plot2d($data);
Without the labels, you'll get something like this.
With the labels, you'll get something like this.

How to convert PDL image to GdkPixbuf

I'm trying to display a graph generated by PDL (using PLplot) inside a Gtk3 app. When I try the following code, I see two problems:
$pdlImg isn't a GdkPixbuf so new_from_pixbuf() doesn't work.
$pdlImg appears to be empty as because the error message prints out the 10x10x3 array as a string and they're all zeroes.
Code:
#!/usr/bin/perl -w
use strict;
use PDL;
use PDL::Graphics::PLplot;
use Gtk3 -init;
my $pdlImg = zeroes(byte, 10, 10, 3);
my $pl = PDL::Graphics::PLplot->new(DEV => 'mem', MEM => $pdlImg);
my $x = sequence(10);
my $y = $x**2;
$pl->xyplot($x, $y);
$pl->close;
my $win = Gtk3::Window->new;
my $img = Gtk3::Image->new_from_pixbuf($pdlImg);
$win->add($img);
$win->show_all;
Gtk3::main();
To answer your first question, you are having PLplot put the plot into a piddle that is 10 pixels wide and 10 pixels high. I'm not sure if you're just going to get one corner of the normal plot in that case, or if you're getting the whole plot sampled into those 10x10 pixels. But in either case it's no surprise that $pdlImg is entirely zeroes. Try passing in a piddle with larger size (perhaps 1000, 1000, 3), or perhaps even using MEM => $pdlImg=null when you create the PLplot plot object.
I can't help with your second question, I have no experience with Gtk3, sorry.

Convert string date to Perl DateTime

I'm a newbie in Perl, so please be patient with me:
I am writing a log parser and have successfully parsed "Dec 1 17:45:36.185" into it's individual units (month, day, hour, minute, seconds, milliseconds). I want to convert this to Perl's DateTime object.
I'm having trouble with the milliseconds portion: .185.
I hope to use DateTime::Format::Strptime like such:
my $strp = DateTime::Format::Strptime(
pattern => "%b %d %H:%M:%S" # how do I add the milliseconds part?
)
If you want to display milliseconds, use this format %3N:
my $strp = DateTime::Format::Strptime(
pattern => "%b %d %H:%M:%S.%3N" # now we have the milliseconds part
)
The number jut before the N means the number of digits that will be displayed.
The number displayed is truncated, not rounded.
I might be missunderstanding you. But if you want to have an object of this: http://metacpan.org/pod/DateTime and know the individual numbers, why not use the constructor like so:
use DateTime;
$dt = DateTime->new(
year => 1964,
month => 10,
day => 16,
hour => 16,
minute => 12,
second => 47,
nanosecond => 500000000,
time_zone => 'Asia/Taipei',
);
Or do you wonder how to format that information into a string later? In that case, you could just use sprintf and DateTimes get methods to produce any format you want.
edit: I think i understood you now. DataTime does not have ms, only ns. When constructing, that is no problem, as you can just put nanosecond => ($ms*1000000) but i see how that can be a problem when using ::Strptime.
I cannot install DateTime here to test it, but the CPAN does say
%N
Nanoseconds. For other sub-second values use %[number]N.
So when you have a DateTime object with nanoseconds, you could play with that [number] value to see what it does and when you have found a way to tell it that you like ms, it should even work for parsing.

Formatting output with 'printf' in Perl

I'm trying to format my output to look like it's in columns. I'm trying to use the printf function.
Here's what I have:
printf("%s %10s %12s %10s\n", "Qty", "Desc.", "Unit \$", "Total");
for ($he = 0; $he <= 6; $he++) {
if (#quantity[$he] != 0) {
printf("%d %10s %12.2f %10.2f\n", #quantity[$he], #selections[$he], #prices[$he], #prices[$he] * #quantity[$he])
}
}
I'm trying to make it so that the second printf inside of the if statement of the for loop lines up with the "Qty", "Desc.", "Unit \$" and "Total."
You need to use the same numbers in the two formats:
printf("%3s %10s %15s %13s\n", "Qty", "Desc.", "Unit \$", "Total");
and
printf("%3d %10s %12.2f %10.2f\n", #quantity[$he], #selections[$he], #prices[$he], #prices[$he]*#quantity[$he])
Note that 12.2 means (12 digits + 1 point + 2 digits), which is why I wrote 15 in the first format. The same goes for the 13.
Also note that you're accessing array elements incorrectly.
Instead of #quantity[$he] use $quantity[$he]. That is, replace the # with a $.
Long time ago, Perl was mainly used for formatting files. It still has these capabilities although I haven't seen it used in a program since Perl 4.x came out.
Check out the perlform documentation, the format function, and the write function.
I'd give you an example on what the code would look like except I haven't done it in years. Otherwise, use the printf statement. You can limit the size of a text field with a %-10.10s type of format. This says to left justify the string, and pad it out to 10 characters, but not more than 10 characters.
I also suggest you get a book on modern Perl. One that will teach you about references.
I've rewritten your program to use references. Notice that all of the data is now in a single array instead of spread over four separate arrays that you hope you keep the index together.
I can talk about the ENTREE of $item[1] by saying $item[1]->{ENTREE}. It's easier to read and easier to maintain.
Also note that I've changed your for loop. In yours, you had to know that you had seven items. If you added a new item, you'd have to change your loop. In mine, I use $#menu to get the last index of my menu. I then use (0..$#menu) to automatically loop from 0 to the last item in the #menu array.
And, while you're at it:
Notice the printf statement formats.
Notice the use of use strict; and use warnings;. That will catch a lot of errors.
Notice the preferred way I use parentheses and curly braces to mark off blocks of code. This is the preferred method.
And, now the program:
use strict;
use warnings;
my #menu = (
{ ENTREE => "Hamburger", PRICE => 3.49, QUANTITY => 3 },
{ ENTREE => "Frankfurter", PRICE => 2.19, QUANTITY => 0 },
{ ENTREE => "French Fries", PRICE => 1.69, QUANTITY => 0 },
{ ENTREE => "Large Coke", PRICE => 1.79, QUANTITY => 4 },
{ ENTREE => "Medium Coke", PRICE => 1.59, QUANTITY => 0 },
{ ENTREE => "Small Coke", PRICE => 1.39, QUANTITY => 0 },
{ ENTREE => "Onion Rings", PRICE => 1.19, QUANTITY => 8 },
);
printf "%-3.3s %-10.10s %-6.6s %s\n\n", 'Qty', 'Desc.', 'Unit $', 'Total';
# Use $#menu to get the number of items in the array instead of knowing it's 6
foreach my $item (0..$#menu) {
# Dereference $menu[$item] to make $menu_item a hash
# This makes the syntax easier to read.
my %menu_item = %{ $menu[$item] };
if ( $menu_item{QUANTITY} ) {
printf "%3d %-10.10s %9.2f %7.2f\n",
$menu_item{QUANTITY}, $menu_item{ENTREE}, $menu_item{PRICE},
$menu_item{QUANTITY} * $menu_item{PRICE};
}
}
OUTPUT:
Qty Desc. Unit $ Total
3 Hamburger 3.49 10.47
4 Large Coke 1.79 7.16
8 Onion Ring 1.19 9.52

How do I sort an array of strings given an arbitrary ordering of those strings?

I wish to sort an array of strings so that the strings wind up in the following order:
#set = ('oneM', 'twoM', 'threeM', 'sixM', 'oneY', 'twoY', 'oldest');
As you may notice, these represent time periods so oneM is the first month, etc. My problem is that I want to sort by the time period, but with the strings as they are I can't just use 'sort', so I created this hash to express how the strings should be ordered:
my %comparison = (
oneM => 1,
twoM => 2,
threeM => 3,
sixM => 6,
oneY => 12,
twoY => 24,
oldest => 25,
);
This I was hoping would make my life easier where I can do something such as:
foreach my $s (#set) {
foreach my $k (%comparison) {
if ($s eq $k) {
something something something
I'm getting the feeling that this is a long winded way of doing things and I wasn't actually sure how I would actually sort it once I've found the equivalent... I think I'm missing my own plot a bit so any help would be appreciated
As requested the expected output would be like how it is shown in #set above. I should have mentioned that the values in #set will be part of that set, but not necessarily all of them and not in the same order.
You've choose good strategy in precomputing data to form easy to sort. You can calculate this data right inside sorting itself, but then you'd be wasting time for recalculation each time sort needs to compare value, which happens more than once through process. On the other hand, the drawback of cache is, obviously, that you'd need additional memory to store it and it might slow down your sort under low memory condition, despite doing less calculations overall.
With your current set up sorting is as easy as:
my #sorted = sort { $comparison{$a} <=> $comaprison{$b} } #set;
While if you want to save memory at expense of CPU it'd be:
my #sorted = sort { calculate_integer_based_on_input{$a} <=> calculate_integer_based_on_input{$b} } #set;
with separate calculate_integer_based_on_input function that would convert oneY and the like to 12 or other corresponding value on the fly or just inline conversion of input to something suitable for sorting.
You might also want to check out common idioms for sorting with caching computations, like Schwartzian transform and Guttman Rosler Transform.
Giving an example with the input and you expected result would help. I guess that this is what you are looking for:
my #data = ( ... );
my %comparison = (
oneM => 1, twoM => 2, threeM => 3,
sixM => 6, oneY => 12, twoY => 24,
oldest => 25,
);
my #sorted = sort { $comparison{$a} <=> $comaprison{$b} } #data;
There are plenty of examples in the documentation for the sortfunction in the perlfunc manual page. ("perldoc -f sort")