Image-Magick-Chart

 view release on metacpan or  search on metacpan

lib/Image/Magick/Chart.pm  view on Meta::CPAN

package Image::Magick::Chart;

use strict;
use warnings;

use Carp;

use Image::Magick;

use Moo;

require 5.006002;

our $VERSION = '1.07';

use Types::Standard qw/Any ArrayRef Bool Int Str/;

has antialias =>
(
	default  => sub{return 0},
	is       => 'rw',
	isa      => Bool,
	required => 0,
);

has bar_width =>
(
	default  => sub{return 8},
	is       => 'rw',
	isa      => Int,
	required => 0,
);

has bg_color =>
(
	default  => sub{return 'white'},
	is       => 'rw',
	isa      => Str,
	required => 0,
);

has colorspace =>
(
	default  => sub{return 'RGB'},
	is       => 'rw',
	isa      => Str,
	required => 0,
);

has depth =>
(
	default  => sub{return 8},
	is       => 'rw',
	isa      => Int,
	required => 0,
);

has fg_color =>
(
	default  => sub{return 'black'},
	is       => 'rw',
	isa      => Str,
	required => 0,
);

has font =>
(
	default  => sub{return 'Courier'},

lib/Image/Magick/Chart.pm  view on Meta::CPAN

	default  => sub{return []},
	is       => 'rw',
	isa      => ArrayRef,
	required => 1,
);

has y_axis_labels =>
(
	default  => sub{return []},
	is       => 'rw',
	isa      => ArrayRef,
	required => 1,
);

has y_axis_labels_option =>
(
	default  => sub{return 0},
	is       => 'rw',
	isa      => Bool,
	required => 0,
);

has y_axis_labels_x =>
(
	default  => sub{return undef},
	is       => 'rw',
	isa      => Any,
	required => 0,
);

has y_axis_ticks_option =>
(
	default  => sub{return 1},
	is       => 'rw',
	isa      => Int, # Sic.
	required => 0,
);

has y_pixels_per_unit =>
(
	default  => sub{return 20},
	is       => 'rw',
	isa      => Int,
	required => 0,
);

# -----------------------------------------------

sub BUILD
{
	my($self) = @_;

	if ($self -> image)
	{
		($self -> width, $self -> height) = $self -> image -> Get('width', 'height');
	}
	else
	{
		$self -> width(${$self -> padding}[3] + 1 + ($self -> x_pixels_per_unit * ${$self -> x_axis_data}[$#{$self -> x_axis_data}]) + ${$self -> padding}[1]);
		$self -> height(${$self -> padding}[2] + 1 + ($self -> y_pixels_per_unit * ${$self -> y_axis_data}[$#{$self -> y_axis_data}]) + ${$self -> padding}[0]);
		$self -> image(Image::Magick -> new(size => "$self -> width x $self -> height") );

		$self -> image -> Set(antialias => $self -> antialias) && Carp::croak("Can't set antialias: $self -> antialias");
		$self -> image -> Set(colorspace => $self -> colorspace) && Carp::croak("Can't set colorspace: $self -> colorspace");
		$self -> image -> Set(depth => $self -> depth) && Carp::croak("Can't set depth: $self -> depth");
		$self -> image -> Read('xc:' . $self -> bg_color) && Carp::croak("Can't set bg_color color: $self -> bg_color");
	}

}	# End of BUILD.

# -----------------------------------------------

sub draw_frame
{
	my($self)  = @_;
	my($x_max) = $self -> x_pixels_per_unit * ${$self -> x_axis_data}[$#{$self -> x_axis_data}];

	$self -> image -> Draw
	(
		fill      => 'none',
		primitive => 'polyline',
		stroke    => $self -> frame_color,
		points    => sprintf
		(
			"%i,%i %i,%i %i,%i %i,%i %i,%i",
			${$self -> padding}[3], ${$self -> padding}[0],
			${$self -> padding}[3] + $x_max, ${$self -> padding}[0],
			${$self -> padding}[3] + $x_max, ($self -> height - ${$self -> padding}[2] - 1),
			${$self -> padding}[3], ($self -> height - ${$self -> padding}[2] - 1),
			${$self -> padding}[3], ${$self -> padding}[0]
		),
	) && Carp::croak("Can't draw frame");

}	# End of draw_frame.

# -----------------------------------------------

sub draw_horizontal_bars
{
	my($self)           = @_;
	my($half_bar_width) = int($self -> bar_width / 2);
	my($y_zero)         = $self -> height - ${$self -> padding}[2] - 1;

	my($i, $data, @metric, $x_right, $y_top);

	for $i (0 .. $#{$self -> x_data})
	{
		$data    = ${$self -> x_data}[$i];
		$x_right = ${$self -> padding}[3] + ($self -> x_pixels_per_unit * $data);
		$y_top   = $y_zero - ($self -> y_pixels_per_unit * ${$self -> y_axis_data}[$i]);

		$self -> image -> Draw
		(
			fill      => $self -> fg_color,
			primitive => 'polyline',
			method    => 'floodfill',
			stroke    => $self -> fg_color,
			points    => sprintf
			(
				"%i,%i %i,%i %i,%i %i,%i",
				${$self -> padding}[3], $y_top - $half_bar_width,

lib/Image/Magick/Chart.pm  view on Meta::CPAN

		) && Carp::croak("Can't draw Y-axis labels");
	}

}	# End of draw_y_axis_labels.

# -----------------------------------------------

sub draw_y_axis_ticks
{
	my($self)   = @_;
	my($x_max)  = $self -> x_pixels_per_unit * ${$self -> x_axis_data}[$#{$self -> x_axis_data}];
	my($x_zero) = $self -> y_axis_ticks_option == 1 ? ${$self -> padding}[3] : $x_max + ${$self -> padding}[3];
	my($x_one)  = ${$self -> padding}[3] - $self -> tick_length;
	my($y_zero) = $self -> height - ${$self -> padding}[2] - 1;

	my($i);

	# We use _x_data here and not _y_axis_* so that the number
	# of ticks corresponds to the number of data points, and
	# not to the number of y-axis labels. Remember: The user
	# can - and should - have an empty string as the last
	# label on the y-axis, to make the image pretty.

	for $i (0 .. $#{$self -> x_data})
	{
		$y_zero -= $self -> y_pixels_per_unit;

		$self -> image -> Draw
		(
			primitive => 'line',
			stroke    => $self -> frame_color,
			points    => sprintf
			(
				"%i,%i %i,%i",
				$x_zero, $y_zero,
				$x_one, $y_zero
			),
		) && Carp::croak("Can't draw Y-axis ticks");
	}

}	# End of draw_y_axis_ticks.

# -----------------------------------------------

sub write
{
	my($self) = @_;

	$self -> image -> Write($self -> output_file_name) && Carp::croak("Can't write file");

}	# End of write.

# -----------------------------------------------

1;

__END__

=head1 NAME

Image::Magick::Chart - Use Image::Magick to create charts

=head1 Synopsis

	#!/usr/bin/env perl

	use Image::Magick::Chart::HorizontalBars;

	Image::Magick::Chart::HorizontalBars -> new
	(
		antialias            => 0, # 0 => No antialias; 1 => Antialias.
		bar_width            => 8, # Pixels.
		bg_color             => 'white',
		colorspace           => 'RGB',
		depth                => 8, # Bits per channel.
		fg_color             => 'blue',
		font                 => 'Courier',
		frame_color          => 'black',
		frame_option         => 1, # 0 => None; 1 => Draw it.
		height               => 0,
		image                => '',
		output_file_name     => 'image-1.png',
		padding              => [30, 30, 30, 30], # [12 noon, 3, 6, 9].
		pointsize            => 14, # Points.
		tick_length          => 4,  # Pixels.
		title                => 'Percent (%)',
		width                => 0,
		x_axis_data          => [0, 20, 40, 60, 80, 100],
		x_axis_labels        => [0, 20, 40, 60, 80, 100],
		x_axis_labels_option => 1, # 0 => None; 1 => Draw them.
		x_axis_ticks_option  => 2, # 0 => None; 1 => Below x-axis; 2 => Across frame.
		x_data               => [15, 5, 70, 25, 45, 20, 65],
		x_data_option        => 1,
		x_pixels_per_unit    => 3, # Horizontal width of each data unit.
		y_axis_data          => [1 .. 7, 8], # 7 data points, plus 1 to make image pretty.
		y_axis_labels        => [(map{"($_)"} reverse (1 .. 7) ), ''],
		y_axis_labels_option => 1, # 0 => None; 1 => Draw them.
		y_axis_ticks_option  => 1, # 0 => None; 1 => Left of y-axis; 2 => Across frame.
		y_pixels_per_unit    => 20,
	) -> draw();

This code is part of examples/test-chart.pl.

Note: You do not need to specify all the options above, of course, but only those you
wish to differ from the defaults. I've included all options in examples/test-chart.pl
just to save you the effort of having to type them in.

See Image::Magick's documentation page www/perl.html for the list of values supported by
each Image::Magick option.

=head1 Description

C<Image::Magick::Chart> is a pure Perl module.

This module uses C<Image::Magick> as the base of a set of modules which create simple images
of various types. Only C<Image::Magick::Chart::HorizontalBars> is available at this time.

See examples/image-*.png for sample output, and examples/test-chart.pl for the program
which created those samples.

You control the size of the image by specifying the data values for X and Y, and also by
specifying the scaling factors in the X and Y directions in terms of pixels per unit of data.

Eg: In the above code, the x-axis data ranges up to 100 (sic), and the x-axis scaling factor
is 3 pixels/unit, so the part of the image occupied by the data will be 3 * 100 + 1 pixels wide.
The 1 is for the y-axis.

The 100 comes from max(last value in @$x_axis_data, last value in @$x_data).

=head1 Distributions

This module is available both as a Unix-style distro (*.tgz) and an
ActiveState-style distro (*.ppd). The latter is shipped in a *.zip file.

See http://savage.net.au/Perl-modules.html for details.

See http://savage.net.au/Perl-modules/html/installing-a-module.html for
help on unpacking and installing each type of distro.

=head1 Constructor and initialization

new(...) returns an C<Image::Magick::Chart> object.

This is the class contructor.

Usage: Image::Magick::Chart -> new().

Note: Actually, you do not normally do this.

Instead, you call: Image::Magick::Chart::HorizontalBars -> new(...) -> draw().

This method takes a set of parameters. Only the output_file_name parameter is mandatory.

For each parameter you wish to use, call new as new(param_1 => value_1, ...).

Parameters:

=over 4

=item o antialias

The value, 0 or 1, is passed to Image::Magick, if this module creates the image.

See the 'image' option if you wish to use a pre-existing object of type Image::Magick.

Using a value of 1 will make your output file slightly larger.

The default value is 0.

This parameter is optional.

=item o bar_width

This is the thickness of the bars, in pixels.

The default value is 8 pixels.

This parameter is optional.

=item o bg_color

This is the background color of the image, if this module creates the image.

See the 'image' option if you wish to use a pre-existing object of type Image::Magick.

The default value is 'white'.

This parameter is optional.

=item o colorspace

The value, 'RGB' etc, is passed to Image::Magick, if this module creates the image.

See the 'image' option if you wish to use a pre-existing object of type Image::Magick.

This parameter is optional.

=item o depth

This is the number of bits per color channel.

The default value is 8.

This parameter is optional.

=item o fg_color

This is the color of the horizontal bars, when using C<Image::Magick::Chart::HorizontalBars>.

The default value is 'black'.

This parameter is optional.

=item o font

This is the font used for:

=over 4

=item o The x-axis labels

=item o The y-axis labels

=item o The title on top of the image

=item o The labels (values) on top of (to the right of) the horizontal bars

=back

The default value is 'Courier'.

This parameter is optional.

=item o frame_color

This is the color used to draw a frame around the area of the image which is
actually occupied by the data.

The default color is 'black'.

This parameter is optional.

=item o frame_option

The value, 0 or 1, determines whether (1) or not (0) the frame will be drawn.

The default value is 1.

This parameter is optional.

=item o height

This is the calculated height of the image, taking into account the area occupied by
the data (the framed area), and the padding on the 4 sides of the frame.

If you use a pre-existing object of type Image::Magick, this module will get the
values for width and height from that image.

This parameter is optional.

=item o image

This is the object of type Image::Magick used to manage the image.

This module creates this object by default, but you can pass in to the constructor
a pre-existing object of type Image::Magick, and this module will use your object.

If you use you own object, I assume you have set these parameters for your image:

=over 4

=item o antialias

=item o bg_color

=item o colorspace

=item o depth

=back

By which I mean this module will not attempt to set those 4 options when you pass
in a pre-existing object.

This parameter is optional.

=item o output_file_name

This is the path, filename and extension to the file where the image will be written.

There is no default.

This parameter is mandatory.

=item o padding

This is a array ref of values, in pixels, to leave on all 4 sides of the image, between
the edge of the image and the part occupied by data (the framed part).

You must provide an array ref of 4 values for this parameter.

A clockface is used to define the meanings of the 4 values, thus:

=over 4

=item o $$padding[0], the first value, is at the top of the frame

Top corresponds to 12 noon.

=item o $$padding[1], the second value, is at the right of the frame

Right corresponds to 3 pm.

Or, if you prefer 3 am, and you think in Spanish, then it is the hour beloved by facists
and other psychopaths: 'de la madrugada'.

=item o $$padding[2], the third value, is at the bottom of the frame

Bottom corresponds to 6 pm.

=item o $$padding[3], the fourth value, is at the left of the frame

Left corresponds to 9 pm.

=back

The default value is [30, 30, 30, 30].

This parameter is optional.

=item o pointsize

This is the size of the text used wherever the 'font' option is used.

The default value is 14.

This parameter is optional.

=item o tick_length

This is the length, in pixels, of the tick marks on the x and y axes.

The default value is 4 pixels.

This parameter is optional.

=item o title

This is the text written at the top centre of the image.

The default value is '' (the empty string).

This parameter is optional.

=item o width

This is the calculated width of the image, taking into account the area occupied by
the data (the framed area), and the padding on the 4 sides of the frame.

If you use a pre-existing object of type Image::Magick, this module will get the
values for width and height from that image.

This parameter is optional.

=item o x_axis_data

This is an array ref of X values (abscissas) where you want the x-axis labels and x-axis tick marks
to be drawn.

The values in this array ref are multiplied by the value of the x_pixels_per_unit parameter,
to determine where the x-axis labels and x-axis tick marks are drawn.

The default value is [], meaning neither labels nor tick marks will be drawn along the
x-axis.

This parameter is optional.

Warning: Do not confuse this parameter with the x_data parameter.

=item o x_axis_labels

This is an array ref of labels to draw below the x-axis. The X values - around which these
labels are centered - are supplied by the value of the x_axis_data option.

In order to draw the x_axis_data values (abscissas) themselves as x-axis labels, just pass in the same
array ref for both the x_axis_data parameter and the x_axis_labels parameter.

The default value is [], meaning no labels are drawn below the x-axis.

This parameter is optional.

=item o x_axis_labels_option

The value, 0 or 1, determines whether (1) or not (0) the x-axis labels will be drawn.

The default value is 1.

This parameter is optional.

=item o x_axis_ticks_option

Values:

=over 4

=item o 0

Do not draw x-axis tick marks.

The quotes are just to stop the zero disappearing when POD is converted to HTML.

=item o 1

Draw x-axis tick marks below the x-axis.

=item o 2

Draw x-axis tick marks across the frame and below the x-axis.

=back

lib/Image/Magick/Chart.pm  view on Meta::CPAN


The default value is undef.

This parameter is optional.

=item o y_axis_ticks_option

Values:

=over 4

=item o 0

Do not draw y-axis tick marks.

The quotes are just to stop the zero disappearing when POD is converted to HTML.

=item o 1

Draw y-axis tick marks left the y-axis.

=item o 2

Draw y-axis tick marks across the frame and left of the y-axis.

=back

The lengths of the tick marks left of the y-axis is given by the value of the
tick_length parameter.

The default value is 1.

This parameter is optional.

=item o y_pixels_per_unit

This is the scaling factor in the y-axis direction.

The default value is 20, meaning each unit of data in the y-axis direction will
occupy 20 pixels vertically.

This value is used in conjunction with the y_axis_data parameter.

Eg: In the above code, the y-axis data ranges up to 8, and the y-axis scaling factor
is 20 pixels/unit, so the part of the image occupied by the data will be 20 * 8 + 1 pixels high.
The 1 is for the x-axis.

The 8 comes from the last value in @$y_axis_data.

Notice how the label corresponding this value of 8 is '', just to make the image pretty by making
the area occupied by the data (the framed area) a bit higher.

This parameter is optional.

=back

=head1 Methods

=head2 draw_frame()

Called by method C<draw()> in C<Image::Magick::Chart::HorizontalBars>.

These is no need to call this method yourself.

=head2 draw_horizontal_bars()

Called by method C<draw()> in C<Image::Magick::Chart::HorizontalBars>.

These is no need to call this method yourself.

=head2 draw_title()

Called by method C<draw()> in C<Image::Magick::Chart::HorizontalBars>.

These is no need to call this method yourself.

=head2 draw_x_axis_labels()

Called by method C<draw()> in C<Image::Magick::Chart::HorizontalBars>.

These is no need to call this method yourself.

=head2 draw_x_axis_ticks()

Called by method C<draw()> in C<Image::Magick::Chart::HorizontalBars>.

These is no need to call this method yourself.

=head2 draw_y_axis_labels()

Called by method C<draw()> in C<Image::Magick::Chart::HorizontalBars>.

These is no need to call this method yourself.

=head2 draw_y_axis_ticks()

Called by method C<draw()> in C<Image::Magick::Chart::HorizontalBars>.

These is no need to call this method yourself.

=head2 new(...)

Returns a object of type C<Image::Magick::Chart>.

See above, in the section called 'Constructor and initialization' for details.

=head2 write()

Called by method C<draw()> in C<Image::Magick::Chart::HorizontalBars>.

These is no need to call this method yourself.

=head1 Machine-Readable Change Log

The file Changes was converted into Changelog.ini by L<Module::Metadata::Changes>.

=head1 Version Numbers

Version numbers < 1.00 represent development versions. From 1.00 up, they are production versions.

=head1 Repository

L<https://github.com/ronsavage/Image-Magick-Chart>

=head1 Support

Email the author, or log a bug on RT:

L<https://rt.cpan.org/Public/Dist/Display.html?Name=Image-Magick-Chart>.

=head1 Author

C<Image::Magick::Chart> was written by Ron Savage I<E<lt>ron@savage.net.auE<gt>> in 2005.

L<Homepage|http://savage.net.au/>

=head1 Copyright

Australian copyright (c) 2005, Ron Savage.
	All Programs of mine are 'OSI Certified Open Source Software';
	you can redistribute them and/or modify them under the terms of
	The Perl License, a copy of which is available at:
	http://dev.perl.org/licenses/

=cut



( run in 1.658 second using v1.01-cache-2.11-cpan-39bf76dae61 )