Skip to content
This repository has been archived by the owner on Dec 22, 2023. It is now read-only.

Feature/divvy #14

Merged
merged 4 commits into from Jan 23, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
75 changes: 72 additions & 3 deletions lib/Chart/Clicker/Axis.pm
@@ -1,13 +1,18 @@
package Chart::Clicker::Axis; package Chart::Clicker::Axis;
use Moose; use Moose;
use Moose::Util;


extends 'Chart::Clicker::Container'; extends 'Chart::Clicker::Container';
with 'Chart::Clicker::Positioned'; with 'Chart::Clicker::Positioned';


# ABSTRACT: An X or Y Axis # ABSTRACT: An X or Y Axis


use Class::Load;

use Chart::Clicker::Data::Range; use Chart::Clicker::Data::Range;


use English qw(-no_match_vars);

use Graphics::Color::RGB; use Graphics::Color::RGB;


use Graphics::Primitive::Font; use Graphics::Primitive::Font;
Expand Down Expand Up @@ -49,10 +54,33 @@ The angle (in radians) to rotate the tick's labels.
=cut =cut


has 'tick_label_angle' => ( has 'tick_label_angle' => (
is => 'rw', is => 'rw',
isa => 'Num' isa => 'Num'
); );


=attr tick_division_type

Selects the algorithm for dividing the graph axis into labelled ticks.

The currently included algorithms are:
L<Chart::Clicker::Data::DivisionType::Exact/Exact>,
L<Chart::Clicker::Data::DivisionType::RoundedLinear/RoundedLinear>.

You may write your own by providing a Moose Role which includes Role
L<Chart::Clicker::Data::DivisionType> and prefixing the module name
with + when setting tick_division_type.

Chart::Clicker::Axis->new(tick_division_type => '+MyApp::TickDivision');

This value should only be set once per axis.

=cut

has 'tick_division_type' => ( is => 'rw', isa => 'Str', default => 'Exact' );

# The above tick division type is loaded on the first call to divvy()
has '_tick_division_type_loaded' => ( is => 'ro', isa => 'Bool', lazy_build => 1 );

=attr baseline =attr baseline


The 'baseline' value of this axis. This is used by some renderers to change The 'baseline' value of this axis. This is used by some renderers to change
Expand Down Expand Up @@ -349,6 +377,32 @@ sub BUILD {
$self->padding(3); $self->padding(3);
} }


sub _build__tick_division_type_loaded {
my $self = shift;

# User modules are prefixed with a +.
my $divisionTypeModule;
my $extensionOf = 'Chart::Clicker::Axis::DivisionType';
if ( $self->tick_division_type =~ m/^\+(.*)$/xmisg ) {
$divisionTypeModule = $1;
}
else {
$divisionTypeModule = sprintf( '%s::%s', $extensionOf, $self->tick_division_type );
}

# Try to load the DivisionType module. An error is thrown when the class is
# not available or cannot be loaded
Class::Load::load_class($divisionTypeModule);

# Apply the newly loaded role to this class.
Moose::Util::apply_all_roles( $self => $divisionTypeModule );
if ( not $self->does($extensionOf) ) {
die("Module $divisionTypeModule does not extend $extensionOf");
}

return 1;
}

override('prepare', sub { override('prepare', sub {
my ($self, $driver) = @_; my ($self, $driver) = @_;


Expand Down Expand Up @@ -389,7 +443,7 @@ override('prepare', sub {
} }


if($self->show_ticks && !scalar(@{ $self->tick_values })) { if($self->show_ticks && !scalar(@{ $self->tick_values })) {
$self->tick_values($self->range->divvy($self->ticks + 1)); $self->tick_values($self->divvy);
} }


# Return now without setting a min height or width and allow # Return now without setting a min height or width and allow
Expand Down Expand Up @@ -707,8 +761,23 @@ sub format_value {
} }
} }


=method divvy

Retrieves the divisions or ticks for the axis.

=cut

sub divvy {
my $self = shift;

# Loads the divvy module once and only once
# which implements _real_divvy()
$self->_tick_division_type_loaded;
return $self->_real_divvy();
}

__PACKAGE__->meta->make_immutable; __PACKAGE__->meta->make_immutable;


no Moose; no Moose;


1; 1;
28 changes: 28 additions & 0 deletions lib/Chart/Clicker/Axis/DivisionType.pm
@@ -0,0 +1,28 @@
package Chart::Clicker::Axis::DivisionType;
use Moose::Role;
requires qw{range ticks};

no Moose;

1;
__END__

=head1 NAME

Chart::Clicker::Axis::DivisionType - Division style for ticks

=head1 DESCRIPTION


=head1 AUTHOR

Rod Taylor <chartclicker@rbt.ca>

=head1 SEE ALSO

perl(1)

=head1 LICENSE

You can redistribute and/or modify this code under the same terms as Perl
itself.
67 changes: 67 additions & 0 deletions lib/Chart/Clicker/Axis/DivisionType/Exact.pm
@@ -0,0 +1,67 @@
package Chart::Clicker::Axis::DivisionType::Exact;

use Moose::Role;
with qw{Chart::Clicker::Axis::DivisionType};

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

return $self->range->span / ( $self->ticks - 1 );
}

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

my $per = $self->best_tick_size;

my @vals;
for ( 0 .. ( $self->ticks - 1 ) ) {
push( @vals, $self->range->lower + ( $_ * $per ) );
}

return \@vals;
}

no Moose;
1;
__END__

=head1 NAME

Chart::Clicker::Axis::DivisionType::Exact - Divide axis in exact increments, linear scale.

=head1 DESCRIPTION

Role describing how to divide data for Chart::Clicker::Axis.

=head1 SYNOPSIS

use Chart::Clicker::Axis;

my $axis = Chart::Clicker::Axis->new({
tick_division_type => 'Exact'
});

=head1 METHODS

=head2 best_tick_size

The tick division calculated by taking the range and dividing by the requested number of ticks.

=head2 divvy

Divides the range up into exact division for L<Chart::Clicker::Axis>.

=head1 AUTHOR

Rod Taylor <chartclicker@rbt.ca>

=head1 SEE ALSO

perl(1)

=head1 LICENSE

You can redistribute and/or modify this code under the same terms as Perl
itself.

136 changes: 136 additions & 0 deletions lib/Chart/Clicker/Axis/DivisionType/LinearExpandGraph.pm
@@ -0,0 +1,136 @@
package Chart::Clicker::Axis::DivisionType::LinearExpandGraph;

use Moose::Role;
with qw{Chart::Clicker::Axis::DivisionType};

use POSIX qw(ceil floor);

# Positive only
has 'tick_slop' => (
is => 'rw',
isa => 'Num',
default => 0.1,
documentation =>
q{Percent of a tick unit above or below graphed which we allow inserting an additional tick. Whitespace allowance above or below.}
);

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

my $n = $self->ticks;

my $min = $self->range->lower;
my $max = $self->range->upper;

my $range = _nicenum($self->range->span, 0);
my $d = _nicenum($range / ($n - 1), 1);
my $graphmin = $min;
my $graphmax = $max;

# Expand the graph as needed
$graphmin = floor($min / $d) * $d;
$self->range->min($graphmin);
$graphmax = ceil($max / $d) * $d;
$self->range->max($graphmax);

my $x = $graphmin;
my @ticks;
do {
push(@ticks, $x);
$x += .5 * $d;
} while($x < $graphmax);

return \@ticks;
}

sub _nicenum {
my ($num, $round) = @_;

my $exp = floor(_log10($num));
my $f = $num / (10 ** $exp);
my $nice;

if($round) {
if($f < 1.5) {
$nice = 1.5;
} elsif($f < 3) {
$nice = 2;
} elsif($f < 7) {
$nice = 5;
} else {
$nice = 10;
}
} else {
if($f <= 1) {
$nice = 1;
} elsif($f <= 2) {
$nice = 2;
} elsif($f <= 5) {
$nice = 5;
} else {
$nice = 10;
}
}

return $nice * (10 ** $exp);
}



sub _log10 {
my $n = shift;
return log($n) / log(10);
}

no Moose;
1;
__END__

=head1 NAME

Chart::Clicker::Axis::DivisionType::RoundedLinear - Nicely rounded segments on a linear scale.

=head1 DESCRIPTION

Role describing how to divide data for Chart::Clicker::Axis.

=head1 SYNOPSIS

use Chart::Clicker::Axis;

my $axis = Chart::Clicker::Axis->new({
tick_division_type => 'RoundedLinear'
});

=head1 ATTRIBUTES

=head2 tick_slop

This setting determines whether to add a tick outside of the data. If the tick would be
within the percentage of a ticks size specified here as a decimal (10% would be 0.1), then
the tick will be added expanding the graph.

=head1 METHODS

=head2 best_tick_size

The tick division considered best for the approximate number of ticks requested
and data within the range.

=head2 divvy

Divides the range up into nicely rounded chunks for L<Chart::Clicker::Axis>.

=head1 AUTHOR

Rod Taylor <chartclicker@rbt.ca>

=head1 SEE ALSO

perl(1)

=head1 LICENSE

You can redistribute and/or modify this code under the same terms as Perl
itself.