default style information contains:
text=>{
- color=>'lookup(fg)',
+ color=>'lookup(fg)',
...
},
legend =>{
- color=>'lookup(text.color)',
+ color=>'lookup(text.color)',
...
},
back=> 'lookup(bg)',
line=> 'lookup(fg)',
text=>{
- color => 'lookup(fg)',
+ color => 'lookup(fg)',
font => 'lookup(font)',
- size => 14,
- },
+ size => 14,
+ },
title=>{
- color => 'lookup(text.color)',
+ color => 'lookup(text.color)',
font => 'lookup(text.font)',
- halign => 'center',
- valign => 'top',
- size => 'scale(text.size,2.0)',
- },
+ halign => 'center',
+ valign => 'top',
+ size => 'scale(text.size,2.0)',
+ },
legend =>{
- color => 'lookup(text.color)',
+ color => 'lookup(text.color)',
font => 'lookup(text.font)',
- 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)',
- },
+ 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)',
+ 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)',
- },
+ 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)',
+ },
label => {
font => 'lookup(text.font)',
- size => 'lookup(text.size)',
- color => 'lookup(text.color)',
+ 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] },
- },
+ },
dropshadow => {
fill => { solid => Imager::Color->new(0, 0, 0, 96) },
off => 'scale(0.4,text.size)',
{ 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') ]),
+ 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') ]),
+ 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') ]),
+ colors=>[ NC('C0C0FF'), NC('0000FF') ]),
},
{ 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('FFFFC0'), NC('FFFF00') ]),
+ colors=>[ NC('FFFFC0'), NC('FFFF00') ]),
},
{ 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('C0FFFF'), NC('00FFFF') ]),
+ colors=>[ NC('C0FFFF'), NC('00FFFF') ]),
},
{ 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('FFC0FF'), NC('FF00FF') ]),
+ colors=>[ NC('FFC0FF'), NC('FF00FF') ]),
},
],
back=>{ fountain=>'linear',
{ fountain=>'radial',
xa_ratio=>0.5, ya_ratio=>0.5, xb_ratio=>1.0, yb_ratio=>0.5,
segments => Imager::Fountain->simple(positions=>[0, 1],
- colors=>[ NC('FF8080'), NC('FF0000') ]),
+ colors=>[ NC('FF8080'), NC('FF0000') ]),
},
{ fountain=>'radial',
xa_ratio=>0.5, ya_ratio=>0.5, xb_ratio=>1.0, yb_ratio=>0.5,
segments => Imager::Fountain->simple(positions=>[0, 1],
- colors=>[ NC('80FF80'), NC('00FF00') ]),
+ colors=>[ NC('80FF80'), NC('00FF00') ]),
},
{ fountain=>'radial',
xa_ratio=>0.5, ya_ratio=>0.5, xb_ratio=>1.0, yb_ratio=>0.5,
segments => Imager::Fountain->simple(positions=>[0, 1],
- colors=>[ NC('808080FF'), NC('0000FF') ]),
+ colors=>[ NC('808080FF'), NC('0000FF') ]),
},
{ fountain=>'radial',
xa_ratio=>0.5, ya_ratio=>0.5, xb_ratio=>1.0, yb_ratio=>0.5,
segments => Imager::Fountain->simple(positions=>[0, 1],
- colors=>[ NC('FFFF80'), NC('FFFF00') ]),
+ colors=>[ NC('FFFF80'), NC('FFFF00') ]),
},
{ fountain=>'radial',
xa_ratio=>0.5, ya_ratio=>0.5, xb_ratio=>1.0, yb_ratio=>0.5,
segments => Imager::Fountain->simple(positions=>[0, 1],
- colors=>[ NC('80FFFF'), NC('00FFFF') ]),
+ colors=>[ NC('80FFFF'), NC('00FFFF') ]),
},
{ fountain=>'radial',
xa_ratio=>0.5, ya_ratio=>0.5, xb_ratio=>1.0, yb_ratio=>0.5,
segments => Imager::Fountain->simple(positions=>[0, 1],
- colors=>[ NC('FF80FF'), NC('FF00FF') ]),
+ colors=>[ NC('FF80FF'), NC('FF00FF') ]),
},
],
back=>{ fountain=>'linear',
}
}
else {
- $work{$key} = $src->{$key};
+ $work{$key} = $src->{$key};
}
}
}
else {
if ($what =~ /^lookup\(([\w.]+)\)$/) {
@depth < MAX_DEPTH
- or return $self->_error("too many levels of recursion in lookup (@depth)");
+ or return $self->_error("too many levels of recursion in lookup (@depth)");
return $self->_get_number($1, @depth);
}
elsif ($what =~ /^scale\(
- ((?:[a-z][\w.]*)|$NUM_RE)
+ ((?:[a-z][\w.]*)|$NUM_RE)
,
- ((?:[a-z][\w.]*)|$NUM_RE)\)$/x) {
+ ((?:[a-z][\w.]*)|$NUM_RE)\)$/x) {
my ($left, $right) = ($1, $2);
unless ($left =~ /^$NUM_RE$/) {
- @depth < MAX_DEPTH
- or return $self->_error("too many levels of recursion in scale (@depth)");
- $left = $self->_get_number($left, @depth);
+ @depth < MAX_DEPTH
+ or return $self->_error("too many levels of recursion in scale (@depth)");
+ $left = $self->_get_number($left, @depth);
}
unless ($right =~ /^$NUM_RE$/) {
- @depth < MAX_DEPTH
- or return $self->_error("too many levels of recursion in scale (@depth)");
- $right = $self->_get_number($right, @depth);
+ @depth < MAX_DEPTH
+ or return $self->_error("too many levels of recursion in scale (@depth)");
+ $right = $self->_get_number($right, @depth);
}
return $left * $right;
}
unless (ref $what) {
if ($what =~ /^lookup\((\w+(?:\.\w+)?)\)$/) {
@depth < MAX_DEPTH or
- return $self->_error("too many levels of recursion in lookup (@depth)");
+ return $self->_error("too many levels of recursion in lookup (@depth)");
return $self->_get_color($1, @depth);
}
# default to normal combine mode
my %work = ( combine => 'normal', %$what );
if ($what->{hatch}) {
- if (!$work{fg}) {
- $work{fg} = $self->_get_color('fg')
- or return;
- }
- if (!$work{bg}) {
- $work{bg} = $self->_get_color('bg')
- or return;
- }
- return ( fill=>\%work );
+ if (!$work{fg}) {
+ $work{fg} = $self->_get_color('fg')
+ or return;
+ }
+ if (!$work{bg}) {
+ $work{bg} = $self->_get_color('bg')
+ or return;
+ }
+ return ( fill=>\%work );
}
elsif ($what->{fountain}) {
- for my $key (qw(xa ya xb yb)) {
- if (exists $work{"${key}_ratio"}) {
- if ($key =~ /^x/) {
- $work{$key} = $box->[0] + $work{"${key}_ratio"}
- * ($box->[2] - $box->[0]);
- }
- else {
- $work{$key} = $box->[1] + $work{"${key}_ratio"}
- * ($box->[3] - $box->[1]);
- }
- }
- }
- return ( fill=>\%work );
+ for my $key (qw(xa ya xb yb)) {
+ if (exists $work{"${key}_ratio"}) {
+ if ($key =~ /^x/) {
+ $work{$key} = $box->[0] + $work{"${key}_ratio"}
+ * ($box->[2] - $box->[0]);
+ }
+ else {
+ $work{$key} = $box->[1] + $work{"${key}_ratio"}
+ * ($box->[3] - $box->[1]);
+ }
+ }
+ }
+ return ( fill=>\%work );
}
else {
- return ( fill=> \%work );
+ return ( fill=> \%work );
}
}
}
or return;
my @bbox = $text_info{font}->bounding_box(%text_info, string=>$text,
- canon=>1);
+ canon=>1);
return @bbox[0..3];
}
if ($areay < $areax) {
if ($object_box->[1] - $chart_box->[1]
- < $chart_box->[3] - $object_box->[3]) {
+ < $chart_box->[3] - $object_box->[3]) {
$chart_box->[1] = $object_box->[3];
}
else {
}
else {
if ($object_box->[0] - $chart_box->[0]
- < $chart_box->[2] - $object_box->[2]) {
+ < $chart_box->[2] - $object_box->[2]) {
$chart_box->[0] = $object_box->[2];
}
else {
}
else {
if ($pos + $gap + $entry_width > $chart_box->[2]) {
- $pos = 0;
- $height += $row_height;
+ $pos = 0;
+ $height += $row_height;
}
push @offsets, [ $pos, $height ];
}
ymin=>$box[1]+$outsidepadding,
xmax=>$box[2]-$outsidepadding,
ymax=>$box[3]-$outsidepadding,
- $self->_get_fill('legend.fill', \@box));
+ $self->_get_fill('legend.fill', \@box));
}
$box[0] += $outsidepadding;
$box[1] += $outsidepadding;
my @fill = $self->_data_fill($dataindex, \@patchbox)
or return;
$img->box(xmin=>$left, ymin=>$top, xmax=>$left + $patchsize,
- ymax=>$top + $patchsize, @fill);
+ ymax=>$top + $patchsize, @fill);
if ($self->{_style}{legend}{patchborder}) {
$img->box(xmin=>$left, ymin=>$top, xmax=>$left + $patchsize,
- ymax=>$top + $patchsize,
- color=>$patchborder);
+ ymax=>$top + $patchsize,
+ color=>$patchborder);
}
$img->string(%text_info, x=>$textpos, 'y'=>$top + $patchsize,
text=>$label);
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);
+ color=>$border_color);
}
$self->_remove_box($chart_box, \@box);
1;
}
}
my @box = (0, 0,
- $width + $patchsize + $padding * 2 + $gap,
- $height + $padding * 2 - $gap);
+ $width + $patchsize + $padding * 2 + $gap,
+ $height + $padding * 2 - $gap);
my $outsidepadding = 0;
if ($self->{_style}{legend}{border}) {
defined($outsidepadding = $self->_get_integer('legend.outsidepadding'))
ymin=>$box[1]+$outsidepadding,
xmax=>$box[2]-$outsidepadding,
ymax=>$box[3]-$outsidepadding,
- $self->_get_fill('legend.fill', \@box));
+ $self->_get_fill('legend.fill', \@box));
}
$box[0] += $outsidepadding;
$box[1] += $outsidepadding;
my @fill = $self->_data_fill($dataindex, \@patchbox)
or return;
$img->box(xmin=>$patchpos, ymin=>$ypos, xmax=>$patchpos + $patchsize,
- ymax=>$ypos + $patchsize, @fill);
+ ymax=>$ypos + $patchsize, @fill);
if ($self->{_style}{legend}{patchborder}) {
$img->box(xmin=>$patchpos, ymin=>$ypos, xmax=>$patchpos + $patchsize,
- ymax=>$ypos + $patchsize,
- color=>$patchborder);
+ ymax=>$ypos + $patchsize,
+ color=>$patchborder);
}
$img->string(%text_info, x=>$textpos, 'y'=>$ypos + $patchsize,
text=>$label);
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);
+ color=>$border_color);
}
$self->_remove_box($chart_box, \@box);
1;
my $chart = Imager::Graph::Pie->new;
# see Imager::Graph for options
my $img = $chart->draw(
- data => [ $first_amount, $second_amount ],
- size => 350);
+ data => [ $first_amount, $second_amount ],
+ size => 350);
=head1 DESCRIPTION
something a bit prettier:
- # requires Imager > 0.38
$img = $pie->draw(data=>\@data, labels=>\@labels,
style=>'fount_lin', features=>'legend');
suitable for monochrome output:
- # requires Imager > 0.38
$img = $pie->draw(data=>\@data, labels=>\@labels,
style=>'mono', features=>'legend');
$opts{data}
or return $self->_error("No data parameter supplied");
- my @data = @{$opts{data}};
+ my @data = @{$opts{data}}
+ or return $self->_error("No values in the data parameter");
my @labels;
@labels = @{$opts{labels}} if $opts{labels};
+ my $total = 0;
+ {
+ my $index = 0;
+ for my $item (@data) {
+ $item < 0
+ and return $self->_error("Data index $index is less than zero");
+
+ $total += $item;
+
+ ++$index;
+ }
+ }
+ $total == 0
+ and return $self->_error("Sum of all data values is zero");
+
$self->_style_setup(\%opts);
my $style = $self->{_style};
my $img = $self->_make_img()
or return;
- my $total = 0;
- for my $item (@data) {
- $total += $item;
- }
-
my @chart_box = ( 0, 0, $img->getwidth-1, $img->getheight-1 );
if ($style->{title}{text}) {
$self->_draw_title($img, \@chart_box)
elsif ($style->{features}{labels}) {
$item->{label} = 1;
}
- my @lbox = $self->_text_bbox($item->{text}, 'label')
- or return;
- $item->{lbox} = \@lbox;
- if ($item->{label}) {
- unless ($self->_fit_text(0, 0, 'label', $item->{text}, $guessradius,
- $item->{begin}, $item->{end})) {
- $item->{callout} = 1;
+ $item->{callout} = 1 if $style->{features}{allcallouts};
+ if (!$item->{callout}) {
+ my @lbox = $self->_text_bbox($item->{text}, 'label')
+ or return;
+ $item->{lbox} = \@lbox;
+ if ($item->{label}) {
+ unless ($self->_fit_text(0, 0, 'label', $item->{text}, $guessradius,
+ $item->{begin}, $item->{end})) {
+ $item->{callout} = 1;
+ }
}
}
- $item->{callout} = 1 if $style->{features}{allcallouts};
if ($item->{callout}) {
$item->{label} = 0;
- my @cbox = $self->_text_bbox($item->{text}, 'callout')
- or return;
- $item->{cbox} = \@cbox;
- $item->{cangle} = ($item->{begin} + $item->{end}) / 2;
- my $dist = cos($item->{cangle}) * ($guessradius+
+ my @cbox = $self->_text_bbox($item->{text}, 'callout')
+ or return;
+ $item->{cbox} = \@cbox;
+ $item->{cangle} = ($item->{begin} + $item->{end}) / 2;
+ my $dist = cos($item->{cangle}) * ($guessradius+
$callout_outside);
- my $co_size = $callout_leadlen + $callout_gap + $item->{cbox}[2];
- if ($dist < 0) {
- $dist -= $co_size - $guessradius;
- $dist < $ebox[0] and $ebox[0] = $dist;
- }
- else {
- $dist += $co_size - $guessradius;
- $dist > $ebox[2] and $ebox[2] = $dist;
- }
+ my $co_size = $callout_leadlen + $callout_gap + $item->{cbox}[2];
+ if ($dist < 0) {
+ $dist -= $co_size - $guessradius;
+ $dist < $ebox[0] and $ebox[0] = $dist;
+ }
+ else {
+ $dist += $co_size - $guessradius;
+ $dist > $ebox[2] and $ebox[2] = $dist;
+ }
}
}
push(@info, $item);
my $px = int($cx + $radius * cos($item->{begin}));
my $py = int($cy + $radius * sin($item->{begin}));
$item->{begin} < $item->{end}
- or next;
+ or next;
$img->line(x1=>$cx, y1=>$cy, x2=>$px, y2=>$py, color=>$outcolor);
for (my $i = $item->{begin}; $i < $item->{end}; $i += PI/180) {
- my $stroke_end = $i + PI/180;
- $stroke_end = $item->{end} if $stroke_end > $item->{end};
- my $nx = int($cx + $radius * cos($stroke_end));
- my $ny = int($cy + $radius * sin($stroke_end));
- $img->line(x1=>$px, y1=>$py, x2=>$nx, y2=>$ny, color=>$outcolor,
- antialias=>1);
- ($px, $py) = ($nx, $ny);
+ my $stroke_end = $i + PI/180;
+ $stroke_end = $item->{end} if $stroke_end > $item->{end};
+ my $nx = int($cx + $radius * cos($stroke_end));
+ my $ny = int($cy + $radius * sin($stroke_end));
+ $img->line(x1=>$px, y1=>$py, x2=>$nx, y2=>$ny, color=>$outcolor,
+ antialias=>1);
+ ($px, $py) = ($nx, $ny);
}
}
}
my $callout_inside = $radius - $self->_get_number('callout.inside');
$callout_outside += $radius;
- my %callout_text = $self->_text_style('callout')
- or return;
- my %label_text = $self->_text_style('label')
- or return;
+ my %callout_text;
+ my %label_text;
for my $label (@info) {
- if ($label->{label}) {
+ if ($label->{label} && !$label->{callout}) {
+ # at this point we know we need the label font, to calculate
+ # whether the label will fit if anything else
+ unless (%label_text) {
+ %label_text = $self->_text_style('label')
+ or return;
+ }
my @loc = $self->_fit_text($cx, $cy, 'label', $label->{text}, $radius,
$label->{begin}, $label->{end});
if (@loc) {
}
else {
$label->{callout} = 1;
- my @cbox = $self->_text_bbox($label->{text}, 'callout')
- or return;
+ my @cbox = $self->_text_bbox($label->{text}, 'callout')
+ or return;
$label->{cbox} = \@cbox;
$label->{cangle} = ($label->{begin} + $label->{end}) / 2;
}
}
if ($label->{callout}) {
+ unless (%callout_text) {
+ %callout_text = $self->_text_style('callout')
+ or return;
+ }
my $ix = floor(0.5 + $cx + $callout_inside * cos($label->{cangle}));
my $iy = floor(0.5 + $cy + $callout_inside * sin($label->{cangle}));
my $ox = floor(0.5 + $cx + $callout_outside * cos($label->{cangle}));
my $oy = floor(0.5 + $cy + $callout_outside * sin($label->{cangle}));
my $lx = ($ox < $cx) ? $ox - $callout_leadlen : $ox + $callout_leadlen;
$img->line(x1=>$ix, y1=>$iy, x2=>$ox, y2=>$oy, antialias=>1,
- color=>$self->_get_color('callout.color'));
+ color=>$self->_get_color('callout.color'));
$img->line(x1=>$ox, y1=>$oy, x2=>$lx, y2=>$oy, antialias=>1,
- color=>$self->_get_color('callout.color'));
+ color=>$self->_get_color('callout.color'));
#my $tx = $lx + $callout_gap;
my $ty = $oy + $label->{cbox}[3]/2+$label->{cbox}[1];
if ($lx < $cx) {
- $img->string(%callout_text, x=>$lx-$callout_gap-$label->{cbox}[2],
- 'y'=>$ty, text=>$label->{text});
+ $img->string(%callout_text, x=>$lx-$callout_gap-$label->{cbox}[2],
+ 'y'=>$ty, text=>$label->{text});
}
else {
- $img->string(%callout_text, x=>$lx+$callout_gap, 'y'=>$ty,
- text=>$label->{text});
+ $img->string(%callout_text, x=>$lx+$callout_gap, 'y'=>$ty,
+ text=>$label->{text});
}
}
}
my ($self, $cx, $cy, $name, $text, $radius, $begin, $end) = @_;
#print "fit: $cx, $cy '$text' $radius $begin $end\n";
- my @tbox = $self->_text_bbox($text, $name);
+ my @tbox = $self->_text_bbox($text, $name)
+ or return;
my $tcx = floor(0.5+$cx + cos(($begin+$end)/2) * $radius *3/5);
my $tcy = floor(0.5+$cy + sin(($begin+$end)/2) * $radius *3/5);
my $topy = $tcy - $tbox[3]/2;