package Hypatia::Chart::Clicker::Bubble; { $Hypatia::Chart::Clicker::Bubble::VERSION = '0.025'; } use Moose; use MooseX::Aliases; use Hypatia::Columns; use Moose::Util::TypeConstraints; use Scalar::Util qw(blessed); use Chart::Clicker; use Chart::Clicker::Data::DataSet; use Chart::Clicker::Data::Series::Size; use Chart::Clicker::Renderer::Bubble; use namespace::autoclean; extends 'Hypatia::Chart::Clicker'; #ABSTRACT: Line Charts with Hypatia and Chart::Clicker subtype 'HypatiaBubbleColumns' => as class_type("Hypatia::Columns"); coerce "HypatiaBubbleColumns",from "HashRef", via {Hypatia::Columns->new({columns=>$_,column_types=>[qw(x y size)]})}; has '+cols'=>(isa=>'HypatiaBubbleColumns'); sub chart { my $self=shift; my $data_arg=shift; my $data; if(defined $data_arg and $self->_validate_input_data($data_arg)) { $data=$data_arg; } else { $data=$self->_get_data(qw(x y size)); } my $cc=Chart::Clicker->new; my $dataset=$self->_build_data_set($data); unless(blessed($dataset) eq "Chart::Clicker::Data::DataSet") { confess "Returned value from the _build_data_set method is not a Chart::Clicker::Data::DataSet"; } $cc->add_to_datasets($dataset); my $dc=$cc->get_context("default"); $dc->renderer(Chart::Clicker::Renderer::Bubble->new); $cc = $self->options->apply_to($cc); return $cc; } alias graph=>'chart'; sub _build_data_set { my $self=shift; my $data=shift; my ($x,$y,$size)=($self->columns->{x},$self->columns->{y},$self->columns->{size}); my $series_array_ref=[]; foreach($x,$y,$size) { $_=[$_] unless ref($_); } my $x_cols; #Either n copies of $x->[0] (if there's only one $x column) or just $x (if there's more than one $x column) if(scalar(@$x)==1) { foreach(@$y) { push @$x_cols,$x->[0]; } } else { $x_cols=$x; } my @names=@$y; if($self->has_data_series_names) { foreach my $i(0..(scalar(@{$self->data_series_names})-1)) { if (defined($self->data_series_names->[$i]) and $i<@$y) { $names[$i]=$self->data_series_names->[$i]; } } } foreach(0..(scalar(@$y)-1)) { my @x_data; my @y_data; my @size_data; if($self->has_input_data) { my @raw_x_data=@{$data->{$x_cols->[$_]}}; my @raw_y_data=@{$data->{$y->[$_]}}; my @raw_size_data=@{$data->{$size->[$_]}}; @x_data=sort{$a<=>$b}@raw_x_data; #sorting y by the values of x: @y_data=@raw_y_data[sort{$raw_x_data[$a]<=>$raw_x_data[$b]}0..$#raw_x_data]; #ditto for size @size_data=@raw_size_data[sort{$raw_x_data[$a]<=>$raw_x_data[$b]}0..$#raw_x_data]; } else { @x_data=@{$data->{$x_cols->[$_]}}; @y_data=@{$data->{$y->[$_]}}; @size_data=@{$data->{$size->[$_]}}; } push @$series_array_ref,Chart::Clicker::Data::Series::Size->new( keys=>\@x_data, values=>\@y_data, sizes=>\@size_data, name=>$names[$_] ); } return Chart::Clicker::Data::DataSet->new(series=>$series_array_ref); } override '_guess_columns' =>sub { my $self=shift; my @columns=@{$self->_setup_guess_columns}; my $col_types={}; if(@columns < 3) { confess "One or two columns are insufficient to form a bubble chart"; } elsif(@columns == 3) { $col_types->{x} = $columns[0]; $col_types->{y} = $columns[1]; $col_types->{size} = $columns[2]; } elsif(scalar(@columns) % 3 == 0) { while(@columns) { push @{$col_types->{x}},shift @columns; push @{$col_types->{y}},shift @columns; push @{$col_types->{size}},shift @columns; } } elsif(scalar(@columns) % 2) { $col_types->{x}=shift @columns; while(@columns) { push @{$col_types->{y}}, shift @columns; push @{$col_types->{size}}, shift @columns; } } else { confess "Unable to guess which columns correspond to which type. Please use the 'columns' attribute"; } $self->cols(Hypatia::Columns->new({columns=>$col_types,column_types=>[qw(x y size)]})); }; override '_validate_input_data',sub { my $self=shift; my $data=shift; return undef unless defined $data; my $first=1; my $num_rows; my @column_list; foreach my $type(keys %{$self->columns}) { my $col=$self->columns->{$type}; if(ref $col eq ref []) { foreach my $c(@$col) { push @column_list,$c unless grep{$c eq $_}@column_list; } } else { push @column_list,$col unless grep{$col eq $_}@column_list; } } foreach my $col(@column_list) { unless(grep{$_ eq $col}(keys %$data)) { warn "WARNING: Column \"$col\" not found as a key in the input_data attribute\n"; return undef; } my @column=@{$data->{$col}}; unless(@column == grep{defined $_}@column) { warn "WARNING: Undefined values found in the input_data for column $col"; return undef; } if($first) { $num_rows=scalar(@column); $first=0; } else { unless(@{$data->{$col}} == $num_rows) { warn "WARNING: Mismatch for number of elements in input_data values"; return undef; } } } return 1; }; __PACKAGE__->meta->make_immutable; 1; __END__ =pod =head1 NAME Hypatia::Chart::Clicker::Bubble - Line Charts with Hypatia and Chart::Clicker =head1 VERSION version 0.025 =head1 SYNOPSIS This module extends L. The C method (also known as the C method) returns the C object built from the relevant data and options provided. =head1 ATTRIBUTES =head2 columns The required column types are C, C, and C. Each of the values for this attribute may be either a string (indicating one column) or an array reference of strings (indicating several columns). If C and C are array references, then they must be the same size. If C is an array reference, then it also must be the same size as C and C (and in this case, each C column will serve as x-axis values corresponding to the C and C columns). Otherwise, if C is a string, then the single C column will serve as a common set of x-values for all C and C values. Of course, since C represents size values for the given data set(s), please make sure that the data stored in any C columns contains nonnegative values. If this column isn't provided, then Hypatia will do its best job to guess which column names of your data correspond to which types, as follows: =over 4 =item 1. If there are three columns, then they'll be assigned to C, C, and C (respectively). =item 2. Otherwise, if the number of columns is a multiple of 3, then the corresponding types will be x, y, size, x, y, size,..., x, y, size (ie each consecutive triple will be assigned to C, C, and C, respectively). =item 3. If the number of columns is odd, larger than 3, but not divisible by 3, then the first column will be assigned to type C, and the remaining columns will be paired off as types: y, size, y, size,..., y, size =item 4. If none of the above are the case, then an error is thrown. =back =head1 METHODS =head2 chart([$data]), a.k.a graph([$data]) This method returns the relevant L object. If neither the C nor the C attributes have been set, then you can input your data as an argument here. =head1 AUTHOR Jack Maney =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Jack Maney. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut