Imager-Graph
view release on metacpan or search on metacpan
You can also supply many different parameters which control the way
the graph looks. These are supplied as keyword, value pairs, where
the value can be a hashref containing sub values.
The C<style> parameter will selects a basic color set, and possibly
sets other related parameters. See L</"STYLES">.
my $font = Imager::Font->new(file => 'ImUgly.ttf');
my $img = $chart->draw(
data => \@data,
font => $font,
title => {
text => "Hello, World!",
size => 36,
color => 'FF0000'
}
);
When referring to a single sub-value this documentation will refer to
'title.color' rather than 'the color element of title'.
Returns the graph image on success, or false on failure.
=back
=head1 STYLES
The currently defined styles are:
=over
=item primary
a light grey background with no outlines. Uses primary colors for the
data fills.
=item primary_red
a light red background with no outlines. Uses primary colors for the
data fills.
Graphs drawn using this style should save well as a gif, even though
some graphs may perform a slight blur.
This was the default style, but the red was too loud.
=item mono
designed for monochrome output, such as most laser printers, this uses
hatched fills for the data, and no colors. The returned image is a
one channel image (which can be overridden with the C<channels>
parameter.)
You can also override the colors used by all components for background
or drawing by supplying C<fg> and/or C<bg> parameters. ie. if you
supply C<<fg=>'FF0000', channels=>3>> then the hash fills and anything
else will be drawn in red. Another use might be to set a transparent
background, by supplying C<<bg=>'00000000', channels=>4>>.
This style outlines the legend if present and outlines the hashed fills.
=item fount_lin
designed as a "pretty" style this uses linear fountain fills for the
background and data fills, and adds a drop shadow.
You can override the value used for text and outlines by setting the
C<fg> parameter.
This is the default style.
=item fount_rad
also designed as a "pretty" style this uses radial fountain fills for
the data and a linear blue to green fill for the background.
=back
=head1 Style API
To set or override styles, you can use the following methods:
=over 4
=item set_image_background
=cut
sub set_image_background {
$_[0]->{'custom_style'}->{'back'} = $_[1];
}
=item set_channels
=cut
sub set_channels {
$_[0]->{'custom_style'}->{'channels'} = $_[1];
}
=item set_line_color
=cut
sub set_line_color {
$_[0]->{'custom_style'}->{'line'} = $_[1];
}
=item set_title_font_size
=cut
sub set_title_font_size {
$_[0]->{'custom_style'}->{'title'}->{'size'} = $_[1];
}
=item set_title_font_color
=cut
sub set_title_font_color {
$_[0]->{'custom_style'}->{'title'}->{'color'} = $_[1];
}
=item set_title_horizontal_align
=cut
sub set_title_horizontal_align {
$_[0]->{'custom_style'}->{'title'}->{'halign'} = $_[1];
}
=item set_title_vertical_align
=cut
sub set_title_vertical_align {
$_[0]->{'custom_style'}->{'title'}->{'valign'} = $_[1];
}
=item set_text_font_color
=cut
sub set_text_font_color {
$_[0]->{'custom_style'}->{'text'}->{'color'} = $_[1];
}
=item set_text_font_size
=cut
sub set_text_font_size {
$_[0]->{'custom_style'}->{'text'}->{'size'} = $_[1];
}
=item set_graph_background_color
=cut
sub set_graph_background_color {
$_[0]->{'custom_style'}->{'bg'} = $_[1];
}
=item set_graph_foreground_color
=cut
sub set_graph_foreground_color {
$_[0]->{'custom_style'}->{'fg'} = $_[1];
}
=item set_legend_font_color
=cut
sub set_legend_font_color {
$_[0]->{'custom_style'}->{'legend'}->{'color'} = $_[1];
}
=item set_legend_font
=cut
sub set_legend_font {
$_[0]->{'custom_style'}->{'legend'}->{'font'} = $_[1];
}
=item set_legend_font_size
=cut
sub set_legend_font_size {
$_[0]->{'custom_style'}->{'legend'}->{'size'} = $_[1];
}
=item set_legend_patch_size
=cut
sub set_legend_patch_size {
$_[0]->{'custom_style'}->{'legend'}->{'patchsize'} = $_[1];
}
=item set_legend_patch_gap
=cut
sub set_legend_patch_gap {
$_[0]->{'custom_style'}->{'legend'}->{'patchgap'} = $_[1];
}
=item set_legend_horizontal_align
=cut
sub set_legend_horizontal_align {
$_[0]->{'custom_style'}->{'legend'}->{'halign'} = $_[1];
}
=item set_legend_vertical_align
=cut
sub set_legend_vertical_align {
$_[0]->{'custom_style'}->{'legend'}->{'valign'} = $_[1];
}
=item set_legend_padding
=cut
sub set_legend_padding {
$_[0]->{'custom_style'}->{'legend'}->{'padding'} = $_[1];
}
=item set_legend_outside_padding
=cut
sub set_legend_outside_padding {
$_[0]->{'custom_style'}->{'legend'}->{'outsidepadding'} = $_[1];
}
=item set_legend_fill
=cut
sub set_legend_fill {
$_[0]->{'custom_style'}->{'legend'}->{'fill'} = $_[1];
}
=item set_legend_border
=cut
sub set_legend_border {
$_[0]->{'custom_style'}->{'legend'}->{'border'} = $_[1];
}
=item set_legend_orientation
=cut
sub set_legend_orientation {
$_[0]->{'custom_style'}->{'legend'}->{'orientation'} = $_[1];
}
=item set_callout_font_color
=cut
sub set_callout_font_color {
$_[0]->{'custom_style'}->{'callout'}->{'color'} = $_[1];
}
=item set_callout_font
=cut
sub set_callout_font {
$_[0]->{'custom_style'}->{'callout'}->{'font'} = $_[1];
}
=item set_callout_font_size
=cut
sub set_callout_font_size {
$_[0]->{'custom_style'}->{'callout'}->{'size'} = $_[1];
}
=item set_callout_line_color
=cut
sub set_callout_line_color {
$_[0]->{'custom_style'}->{'callout'}->{'line'} = $_[1];
}
=item set_callout_leader_inside_length
=cut
sub set_callout_leader_inside_length {
$_[0]->{'custom_style'}->{'callout'}->{'inside'} = $_[1];
}
=item set_callout_leader_outside_length
=cut
sub set_callout_leader_outside_length {
$_[0]->{'custom_style'}->{'callout'}->{'outside'} = $_[1];
}
=item set_callout_leader_length
=cut
sub set_callout_leader_length {
$_[0]->{'custom_style'}->{'callout'}->{'leadlen'} = $_[1];
}
=item set_callout_gap
=cut
sub set_drop_shadowXOffset {
$_[0]->{'custom_style'}->{'dropshadow'}->{'offx'} = $_[1];
}
=item set_drop_shadowYOffset
=cut
sub set_drop_shadowYOffset {
$_[0]->{'custom_style'}->{'dropshadow'}->{'offy'} = $_[1];
}
=item set_drop_shadow_filter
=cut
sub set_drop_shadow_filter {
$_[0]->{'custom_style'}->{'dropshadow'}->{'filter'} = $_[1];
}
=item set_outline_color
=cut
sub set_outline_color {
$_[0]->{'custom_style'}->{'outline'}->{'line'} = $_[1];
}
=item set_data_area_fills
=cut
sub set_data_area_fills {
$_[0]->{'custom_style'}->{'fills'} = $_[1];
}
=item set_data_line_colors
=cut
sub set_data_line_colors {
$_[0]->{'custom_style'}->{'colors'} = $_[1];
}
=back
=head1 FEATURES
Each graph type has a number of features. These are used to add
various items that are displayed in the graph area.
Features can be controlled by calling methods on the graph object, or
by passing a C<features> parameter to draw().
Some common features are:
=over
=item show_legend()
Feature: legend
X<legend><features, legend>
adds a box containing boxes filled with the data fills, with
the labels provided to the draw method. The legend will only be
displayed if both the legend feature is enabled and labels are
supplied.
=cut
sub show_legend {
$_[0]->{'custom_style'}->{'features'}->{'legend'} = 1;
}
=item show_outline()
Feature: outline
X<outline>X<features, outline>
If enabled, draw a border around the elements representing data in the
graph, eg. around each pie segments on a pie chart, around each bar on
a bar chart.
=cut
sub show_outline {
$_[0]->{'custom_style'}->{'features'}->{'outline'} = 1;
}
=item show_labels()
Feature: labels
X<labels>X<features, labels>
labels each data fill, usually by including text inside the data fill.
If the text does not fit in the fill, they could be displayed in some
other form, eg. as callouts in a pie graph.
For pie charts there isn't much point in enabling both the C<legend>
and C<labels> features.
For other charts, the labels label the independent variable, while the
legend describes the color used to plot the dependent variables.
=cut
sub show_labels {
$_[0]->{'custom_style'}->{'features'}->{'labels'} = 1;
}
=item show_drop_shadow()
Feature: dropshadow
X<dropshadow>X<features, dropshadow>
a simple drop shadow is shown behind some of the graph elements.
=cut
sub show_drop_shadow {
$_[0]->{'custom_style'}->{'features'}->{'dropshadow'} = 1;
}
=item reset_features()
Unsets all of the features.
Note: this disables all features, even those enabled by default for a
style. They can then be enabled by calling feature methods or by
supplying a C<feature> parameter to the draw() method.
=cut
sub reset_features {
$_[0]->{'custom_style'}->{'features'} = {};
$_[0]->{'custom_style'}->{'features'}->{'reset'} = 1;
}
=back
Additionally, features can be set by passing them into the draw()
method, named as above:
=over
=item *
if supplied as an array reference, then any element C<no>I<featurename> will
disable that feature, while an element I<featurename> will enable it.
=item *
if supplied as a scalar, it is treated as if it were a reference to
an array containing only that scalar.
=item *
if supplied as a hash reference, then a C<reset> key with a true value
will avoid inheriting any default features, a key I<feature> with a
false value will disable that feature and a key I<feature> with a true
value will enable that feature.
=back
The default font used by the graph. Normally you should supply this
if your graph as any text.
=item line
The default line color.
=item text
defaults for drawing text. Other textual graph elements will inherit
or modify these values.
=over
=item color
default text color, defaults to the I<fg> color.
=item size
default text size. Default: 14. This is used to scale many graph
elements, including padding and leader sizes. Other text elements
will either use or scale this value.
=item font
default font object. Inherited from I<font>, which should have been
supplied by the caller.
=back
=item title
If you supply a scalar value for this element, it will be stored in
the I<text> field.
Defines the text, font and layout information for the title.
=over
=item color
The color of the title, inherited from I<text.color>.
=item font
The font object used for the title, inherited from I<text.font>.
=item size
size of the title text. Default: double I<text.size>
=item halign
=item valign
The horizontal and vertical alignment of the title.
=back
=item legend
defines attributes of the graph legend, if present.
=over
=item color
=item font
=item size
text attributes for the labels used in the legend.
=item patchsize
the width and height of the color patch in the legend. Defaults to
90% of the legend text size.
=item patchgap
the minimum gap between patches in pixels. Defaults to 30% of the
patchsize.
=item patchborder
the color of the border drawn around each patch. Inherited from I<line>.
=item halign
=item valign
the horizontal and vertical alignment of the legend within the graph.
Defaults to 'right' and 'top'.
=item padding
the gap between the legend patches and text and the outside of its
box, or to the legend border, if any.
=item outsidepadding
the gap between the border and the outside of the legend's box. This
is only used if the I<legend.border> attribute is defined.
=item fill
the background fill for the legend. Default: none
=item border
the border color of the legend. Default: none (no border is drawn
around the legend.)
=item orientation
The orientation of the legend. If this is C<vertical> the the patches
and labels are stacked on top of each other. If this is C<horizontal>
the patchs and labels are word wrapped across the image. Default:
vertical.
=back
For example to create a horizontal legend with borderless patches,
darker than the background, you might do:
my $im = $chart->draw
(...,
legend =>
{
patchborder => undef,
orientation => 'horizontal',
fill => { solid => Imager::Color->new(0, 0, 0, 32), }
},
...);
=item callout
defines attributes for graph callouts, if any are present. eg. if the
pie graph cannot fit the label into the pie graph segement it will
present it as a callout.
=over
=item color
=item font
=item size
the text attributes of the callout label. Inherited from I<text>.
=item line
the color of the callout lines. Inherited from I<line>
=item inside
=item outside
the length of the leader on the inside and the outside of the fill,
usually at some angle. Both default to the size of the callout text.
=item leadlen
the length of the horizontal portion of the leader. Default:
I<callout.size>.
=item gap
the gap between the callout leader and the callout text. Defaults to
30% of the text callout size.
=back
=item label
defines attributes for labels drawn into the data areas of a graph.
=over
=item color
=item font
=item size
The text attributes of the labels. Inherited from I<text>.
=item offy
the horizontal and vertical offsets of the drop shadow. Both
inherited from I<dropshadow.off>.
=item filter
the filter description passed to Imager's filter method to blur the
drop shadow. Default: an 11 element convolution filter.
=back
=item outline
describes the lines drawn around filled data areas, such as the
segments of a pie chart.
=over
=item line
the line color of the outlines, inherited from I<line>.
=back
=item fills
a reference to an array containing fills for each data item.
You can mix fill types, ie. using a simple color for the first item, a
hatched fill for the second and a fountain fill for the next.
=back
=head1 HOW VALUES WORK
Internally rather than specifying literal color, fill, or font objects
or literal sizes for each element, Imager::Graph uses a number of
special values to inherit or modify values taken from other graph
element names.
=head2 Specifying colors
You can specify colors by either supplying an Imager::Color object, by
supplying lookup of another color, or by supplying a single value that
Imager::Color::new can use as an initializer. The most obvious is
just a 6 or 8 digit hex value representing the red, green, blue and
optionally alpha channels of the image.
You can lookup another color by using the lookup() "function", for
example if you give a color as "lookup(fg)" then Imager::Graph will
look for the fg element in the current style (or as overridden by
you.) This is used internally by Imager::Graph to set up the
relationships between the colors of various elements, for example the
default style information contains:
text=>{
color=>'lookup(fg)',
...
},
legend =>{
color=>'lookup(text.color)',
...
},
So by setting the I<fg> color, you also set the default text color,
since each text element uses lookup(text.color) as its value.
=head2 Specifying fills
Fills can be used for the graph background color, the background color
for the legend block and for the fills used for each data element.
You can specify a fill as a L<color value|Specifying colors> or as a
general fill, see L<Imager::Fill> for details.
You don't need (or usually want) to call Imager::Fill::new yourself,
since the various fill functions will call it for you, and
Imager::Graph provides some hooks to make them more useful.
=over
=item *
with hatched fills, if you don't supply a 'fg' or 'bg' parameter,
Imager::Graph will supply the current graph fg and bg colors.
=item *
with fountain fill, you can supply the xa_ratio, ya_ratio, xb_ratio
and yb_ratio parameters, and they will be scaled in the fill area to
define the fountain fills xa, ya, xb and yb parameters.
=back
As with colors, you can use lookup(name) or lookup(name1.name2) to
have one element to inherit the fill of another.
Imager::Graph defaults the fill combine value to C<'normal'>. This
doesn't apply to simple color fills.
=head2 Specifying numbers
You can specify various numbers, usually representing the size of
something, commonly text, but sometimes the length of a line or the
size of a gap.
You can use the same lookup mechanism as with colors and fills, but
you can also scale values. For example, 'scale(0.5,text.size)' will
return half the size of the normal text size.
As with colors, this is used internally to scale graph elements based
on the base text size. If you change the base text size then other
graph elements will scale as well.
=head2 Specifying other elements
Other elements, such as fonts, or parameters for a filter, can also
use the lookup(name) mechanism.
=head1 INTERNAL METHODS
Only useful if you need to fix bugs, add features or create a new
graph class.
=over
=cut
my %style_defs =
(
back=> 'lookup(bg)',
line=> 'lookup(fg)',
aa => 1,
text=>{
color => 'lookup(fg)',
font => 'lookup(font)',
size => 14,
aa => 'lookup(aa)',
},
title=>{
color => 'lookup(text.color)',
font => 'lookup(text.font)',
halign => 'center',
valign => 'top',
size => 'scale(text.size,2.0)',
aa => 'lookup(text.aa)',
},
legend =>{
color => 'lookup(text.color)',
font => 'lookup(text.font)',
aa => 'lookup(text.aa)',
size => 'lookup(text.size)',
patchsize => 'scale(legend.size,0.9)',
patchgap => 'scale(legend.patchsize,0.3)',
patchborder => 'lookup(line)',
halign => 'right',
valign => 'top',
padding => 'scale(legend.size,0.3)',
outsidepadding => 'scale(legend.padding,0.4)',
},
callout => {
color => 'lookup(text.color)',
font => 'lookup(text.font)',
size => 'lookup(text.size)',
line => 'lookup(line)',
inside => 'lookup(callout.size)',
outside => 'lookup(callout.size)',
leadlen => 'scale(0.8,callout.size)',
gap => 'scale(callout.size,0.3)',
aa => 'lookup(text.aa)',
lineaa => 'lookup(lineaa)',
},
label => {
font => 'lookup(text.font)',
size => 'lookup(text.size)',
color => 'lookup(text.color)',
hpad => 'lookup(label.pad)',
vpad => 'lookup(label.pad)',
pad => 'scale(label.size,0.2)',
pcformat => sub { sprintf "%s (%.0f%%)", $_[0], $_[1] },
pconlyformat => sub { sprintf "%.1f%%", $_[0] },
aa => 'lookup(text.aa)',
lineaa => 'lookup(lineaa)',
},
dropshadow => {
fill => { solid => Imager::Color->new(0, 0, 0, 96) },
off => 'scale(0.4,text.size)',
offx => 'lookup(dropshadow.off)',
offy => 'lookup(dropshadow.off)',
filter => { type=>'conv',
# this needs a fairly heavy blur
coef=>[0.1, 0.2, 0.4, 0.6, 0.7, 0.9, 1.2,
0.9, 0.7, 0.6, 0.4, 0.2, 0.1 ] },
},
# controls the outline of graph elements representing data, eg. pie
# slices, bars or columns
outline => {
line =>'lookup(line)',
lineaa => 'lookup(lineaa)',
},
# controls the outline and background of the data area of the chart
graph =>
{
fill => "lookup(bg)",
outline => "lookup(fg)",
},
size=>256,
width=>'scale(1.5,size)',
height=>'lookup(size)',
# yes, the handling of fill and line AA is inconsistent, lack of
# forethought, unfortunately
fill => {
aa => 'lookup(aa)',
},
lineaa => 'lookup(aa)',
line_markers =>[
{ shape => 'circle', radius => 4 },
{ shape => 'diamond', radius => 4 },
{ shape => 'triangle', radius => 4 },
{ shape => 'x', radius => 4 },
{ shape => 'plus', radius => 4 },
],
);
=item _error($message)
Sets the error field of the object and returns an empty list or undef,
depending on context. Should be used for error handling, since it may
provide some user hooks at some point.
The intended usage is:
some action
or return $self->_error("error description");
You should almost always return the result of _error() or return
immediately afterwards.
=cut
sub _error {
my ($self, $error) = @_;
$self->{_errstr} = $error;
return;
}
=item _style_defs()
Returns the style defaults, such as the relationships between line
color and text color.
Intended to be over-ridden by base classes to provide graph specific
defaults.
=cut
sub _style_defs {
\%style_defs;
}
# Let's make the default something that looks really good, so folks will be interested enough to customize the style.
my $def_style = 'fount_lin';
my %styles =
(
primary =>
{
fills=>
[
qw(FF0000 00FF00 0000FF C0C000 00C0C0 FF00FF)
],
fg=>'000000',
negative_bg=>'EEEEEE',
bg=>'E0E0E0',
legend=>
{
#patchborder=>'000000'
},
},
primary_red =>
{
fills=>
[
qw(FF0000 00FF00 0000FF C0C000 00C0C0 FF00FF)
],
fg=>'000000',
negative_bg=>'EEEEEE',
bg=>'C08080',
legend=>
{
patchborder=>'000000'
},
},
mono =>
{
fills=>
[
{ hatch=>'slash2' },
{ hatch=>'slosh2' },
{ hatch=>'vline2' },
{ hatch=>'hline2' },
{ hatch=>'cross2' },
{ hatch=>'grid2' },
{ hatch=>'stipple3' },
{ hatch=>'stipple2' },
],
channels=>1,
bg=>'FFFFFF',
fg=>'000000',
negative_bg=>'EEEEEE',
features=>{ outline=>1 },
pie =>{
blur=>undef,
},
aa => 0,
line_markers =>
[
{ shape => "x", radius => 4 },
{ shape => "plus", radius => 4 },
{ shape => "open_circle", radius => 4 },
{ shape => "open_diamond", radius => 5 },
{ shape => "open_square", radius => 4 },
{ shape => "open_triangle", radius => 4 },
{ shape => "x", radius => 8 },
{ shape => "plus", radius => 8 },
{ shape => "open_circle", radius => 8 },
{ shape => "open_diamond", radius => 10 },
{ shape => "open_square", radius => 8 },
{ shape => "open_triangle", radius => 8 },
],
},
fount_lin =>
{
fills=>
[
{ fountain=>'linear',
xa_ratio=>0.13, ya_ratio=>0.13, xb_ratio=>0.87, yb_ratio=>0.87,
segments => Imager::Fountain->simple(positions=>[0, 1],
colors=>[ NC('FFC0C0'), NC('FF0000') ]),
},
{ fountain=>'linear',
xa_ratio=>0, ya_ratio=>0, xb_ratio=>1.0, yb_ratio=>1.0,
segments => Imager::Fountain->simple(positions=>[0, 1],
colors=>[ NC('C0FFC0'), NC('00FF00') ]),
},
{ fountain=>'linear',
xa_ratio=>0, ya_ratio=>0, xb_ratio=>1.0, yb_ratio=>1.0,
segments => Imager::Fountain->simple(positions=>[0, 1],
colors=>[ NC('C0C0FF'), NC('0000FF') ]),
$box->[1] = $chart_box->[1];
}
elsif ($valign eq 'bottom') {
$box->[1] = $chart_box->[3] - $box->[3];
}
elsif ($valign eq 'center' || $valign eq 'centre') {
$box->[1] = ($chart_box->[1] + $chart_box->[3] - $box->[3])/2;
}
else {
return $self->_error("invalid valign $valign for $name");
}
$box->[2] += $box->[0];
$box->[3] += $box->[1];
}
sub _remove_box {
my ($self, $chart_box, $object_box) = @_;
my $areax;
my $areay;
if ($object_box->[0] - $chart_box->[0]
< $chart_box->[2] - $object_box->[2]) {
$areax = ($object_box->[2] - $chart_box->[0])
* ($chart_box->[3] - $chart_box->[1]);
}
else {
$areax = ($chart_box->[2] - $object_box->[0])
* ($chart_box->[3] - $chart_box->[1]);
}
if ($object_box->[1] - $chart_box->[1]
< $chart_box->[3] - $object_box->[3]) {
$areay = ($object_box->[3] - $chart_box->[1])
* ($chart_box->[2] - $chart_box->[0]);
}
else {
$areay = ($chart_box->[3] - $object_box->[1])
* ($chart_box->[2] - $chart_box->[0]);
}
if ($areay < $areax) {
if ($object_box->[1] - $chart_box->[1]
< $chart_box->[3] - $object_box->[3]) {
$chart_box->[1] = $object_box->[3];
}
else {
$chart_box->[3] = $object_box->[1];
}
}
else {
if ($object_box->[0] - $chart_box->[0]
< $chart_box->[2] - $object_box->[2]) {
$chart_box->[0] = $object_box->[2];
}
else {
$chart_box->[2] = $object_box->[0];
}
}
}
sub _draw_legend {
my ($self, $img, $labels, $chart_box) = @_;
my $orient = $self->_get_thing('legend.orientation');
defined $orient or $orient = 'vertical';
if ($orient eq 'vertical') {
return $self->_draw_legend_vertical($img, $labels, $chart_box);
}
elsif ($orient eq 'horizontal') {
return $self->_draw_legend_horizontal($img, $labels, $chart_box);
}
else {
return $self->_error("Unknown legend.orientation $orient");
}
}
sub _draw_legend_horizontal {
my ($self, $img, $labels, $chart_box) = @_;
defined(my $padding = $self->_get_integer('legend.padding'))
or return;
my $patchsize = $self->_get_integer('legend.patchsize')
or return;
defined(my $gap = $self->_get_integer('legend.patchgap'))
or return;
my $minrowsize = $patchsize + $gap;
my ($width, $height) = (0,0);
my $row_height = $minrowsize;
my $pos = 0;
my @sizes;
my @offsets;
for my $label (@$labels) {
my @text_box = $self->_text_bbox($label, 'legend')
or return;
push(@sizes, \@text_box);
my $entry_width = $patchsize + $gap + $text_box[2];
if ($pos == 0) {
# never re-wrap the first entry
push @offsets, [ 0, $height ];
}
else {
if ($pos + $gap + $entry_width > $chart_box->[2]) {
$pos = 0;
$height += $row_height;
}
push @offsets, [ $pos, $height ];
}
my $entry_right = $pos + $entry_width;
$pos += $gap + $entry_width;
$entry_right > $width and $width = $entry_right;
if ($text_box[3] > $row_height) {
$row_height = $text_box[3];
}
}
$height += $row_height;
my @box = ( 0, 0, $width + $padding * 2, $height + $padding * 2 );
my $outsidepadding = 0;
if ($self->{_style}{legend}{border}) {
defined($outsidepadding = $self->_get_integer('legend.outsidepadding'))
or return;
$box[2] += 2 * $outsidepadding;
$box[3] += 2 * $outsidepadding;
}
$self->_align_box(\@box, $chart_box, 'legend')
or return;
if ($self->{_style}{legend}{fill}) {
$img->box(xmin=>$box[0]+$outsidepadding,
ymin=>$box[1]+$outsidepadding,
xmax=>$box[2]-$outsidepadding,
ymax=>$box[3]-$outsidepadding,
$self->_get_fill('legend.fill', \@box));
}
$box[0] += $outsidepadding;
$box[1] += $outsidepadding;
$box[2] -= $outsidepadding;
$box[3] -= $outsidepadding;
my %text_info = $self->_text_style('legend')
or return;
my $patchborder;
if ($self->{_style}{legend}{patchborder}) {
$patchborder = $self->_get_color('legend.patchborder')
or return;
}
my $dataindex = 0;
for my $label (@$labels) {
my ($left, $top) = @{$offsets[$dataindex]};
$left += $box[0] + $padding;
$top += $box[1] + $padding;
my $textpos = $left + $patchsize + $gap;
my @patchbox = ( $left, $top,
$left + $patchsize, $top + $patchsize );
my @fill = $self->_data_fill($dataindex, \@patchbox)
or return;
$img->box(xmin=>$left, ymin=>$top, xmax=>$left + $patchsize,
ymax=>$top + $patchsize, @fill);
if ($self->{_style}{legend}{patchborder}) {
$img->box(xmin=>$left, ymin=>$top, xmax=>$left + $patchsize,
ymax=>$top + $patchsize,
color=>$patchborder);
}
$img->string(%text_info, x=>$textpos, 'y'=>$top + $patchsize,
text=>$label);
++$dataindex;
}
if ($self->{_style}{legend}{border}) {
my $border_color = $self->_get_color('legend.border')
or return;
$img->box(xmin=>$box[0], ymin=>$box[1], xmax=>$box[2], ymax=>$box[3],
color=>$border_color);
}
$self->_remove_box($chart_box, \@box);
1;
}
sub _draw_legend_vertical {
my ($self, $img, $labels, $chart_box) = @_;
defined(my $padding = $self->_get_integer('legend.padding'))
or return;
my $patchsize = $self->_get_integer('legend.patchsize')
or return;
defined(my $gap = $self->_get_integer('legend.patchgap'))
or return;
my $minrowsize = $patchsize + $gap;
my ($width, $height) = (0,0);
my @sizes;
for my $label (@$labels) {
my @box = $self->_text_bbox($label, 'legend')
or return;
push(@sizes, \@box);
$width = $box[2] if $box[2] > $width;
if ($minrowsize > $box[3]) {
$height += $minrowsize;
}
else {
$height += $box[3];
}
}
my @box = (0, 0,
$width + $patchsize + $padding * 2 + $gap,
$height + $padding * 2 - $gap);
my $outsidepadding = 0;
if ($self->{_style}{legend}{border}) {
defined($outsidepadding = $self->_get_integer('legend.outsidepadding'))
or return;
$box[2] += 2 * $outsidepadding;
$box[3] += 2 * $outsidepadding;
}
$self->_align_box(\@box, $chart_box, 'legend')
or return;
if ($self->{_style}{legend}{fill}) {
$img->box(xmin=>$box[0]+$outsidepadding,
ymin=>$box[1]+$outsidepadding,
xmax=>$box[2]-$outsidepadding,
ymax=>$box[3]-$outsidepadding,
$self->_get_fill('legend.fill', \@box));
}
$box[0] += $outsidepadding;
$box[1] += $outsidepadding;
$box[2] -= $outsidepadding;
$box[3] -= $outsidepadding;
my $ypos = $box[1] + $padding;
my $patchpos = $box[0]+$padding;
my $textpos = $patchpos + $patchsize + $gap;
my %text_info = $self->_text_style('legend')
or return;
my $patchborder;
if ($self->{_style}{legend}{patchborder}) {
$patchborder = $self->_get_color('legend.patchborder')
or return;
}
my $dataindex = 0;
for my $label (@$labels) {
my @patchbox = ( $patchpos - $patchsize/2, $ypos - $patchsize/2,
$patchpos + $patchsize * 3 / 2, $ypos + $patchsize*3/2 );
my @fill;
if ($self->_draw_flat_legend()) {
@fill = (color => $self->_data_color($dataindex), filled => 1);
}
else {
@fill = $self->_data_fill($dataindex, \@patchbox)
or return;
}
$img->box(xmin=>$patchpos, ymin=>$ypos, xmax=>$patchpos + $patchsize,
ymax=>$ypos + $patchsize, @fill);
if ($self->{_style}{legend}{patchborder}) {
$img->box(xmin=>$patchpos, ymin=>$ypos, xmax=>$patchpos + $patchsize,
ymax=>$ypos + $patchsize,
color=>$patchborder);
}
$img->string(%text_info, x=>$textpos, 'y'=>$ypos + $patchsize,
text=>$label);
my $step = $patchsize + $gap;
if ($minrowsize < $sizes[$dataindex][3]) {
$ypos += $sizes[$dataindex][3];
}
else {
$ypos += $minrowsize;
}
++$dataindex;
}
if ($self->{_style}{legend}{border}) {
my $border_color = $self->_get_color('legend.border')
or return;
$img->box(xmin=>$box[0], ymin=>$box[1], xmax=>$box[2], ymax=>$box[3],
color=>$border_color);
}
$self->_remove_box($chart_box, \@box);
1;
}
sub _draw_title {
my ($self, $img, $chart_box) = @_;
my $title = $self->{_style}{title}{text};
my @box = $self->_text_bbox($title, 'title')
or return;
my $yoff = $box[1];
@box[0,1] = (0,0);
$self->_align_box(\@box, $chart_box, 'title');
my %text_info = $self->_text_style('title')
or return;
$img->string(%text_info, x=>$box[0], 'y'=>$box[3] + $yoff, text=>$title);
$self->_remove_box($chart_box, \@box);
1;
}
sub _small_extent {
my ($self, $box) = @_;
if ($box->[2] - $box->[0] > $box->[3] - $box->[1]) {
return $box->[3] - $box->[1];
}
else {
return $box->[2] - $box->[0];
}
}
sub _draw_flat_legend {
return 0;
}
=item _composite()
Returns a list of style fields that are stored as composites, and
should be merged instead of just being replaced.
=cut
sub _composite {
qw(title legend text label dropshadow outline callout graph);
}
sub _filter_region {
my ($self, $img, $left, $top, $right, $bottom, $filter) = @_;
unless (ref $filter) {
my $name = $filter;
$filter = $self->_get_thing($name)
or return;
$filter->{type}
or return $self->_error("no type for filter $name");
}
$left > 0 or $left = 0;
$top > 0 or $top = 0;
my $masked = $img->masked(left=>$left, top=>$top,
right=>$right, bottom=>$bottom);
$masked->filter(%$filter);
}
=item _line(x1 => $x1, y1 => $y1, ..., style => $style)
Wrapper for line drawing, implements styles Imager doesn't.
Currently styles are limited to horizontal and vertical lines.
=cut
sub _line {
my ($self, %opts) = @_;
my $img = delete $opts{img}
or die "No img supplied to _line()";
my $style = delete $opts{style} || "solid";
if ($style eq "solid" || ($opts{x1} != $opts{x2} && $opts{y1} != $opts{y2})) {
return $img->line(%opts);
}
elsif ($style eq 'dashed' || $style eq 'dotted') {
my ($x1, $y1, $x2, $y2) = delete @opts{qw/x1 y1 x2 y2/};
# the line is vertical or horizontal, so swapping doesn't hurt
$x1 > $x2 and ($x1, $x2) = ($x2, $x1);
$y1 > $y2 and ($y1, $y2) = ($y2, $y1);
my ($stepx, $stepy) = ( 0, 0 );
my $step_size = $style eq "dashed" ? 8 : 2;
my ($counter, $count_end);
if ($x1 == $x2) {
$stepy = $step_size;
($counter, $count_end) = ($y1, $y2);
}
else {
$stepx = $step_size;
($counter, $count_end) = ($x1, $x2);
}
my ($x, $y) = ($x1, $y1);
while ($counter < $count_end) {
if ($style eq "dotted") {
$img->setpixel(x => $x, y => $y, color => $opts{color});
}
( run in 2.172 seconds using v1.01-cache-2.11-cpan-437f7b0c052 )