diff --git a/META6.json b/META6.json
index bb2994c..0575456 100644
--- a/META6.json
+++ b/META6.json
@@ -14,6 +14,8 @@
"name": "CLI::Graphing::BarChart",
"perl": "6.d",
"provides": {
+ "CLI::Graphing::BarChart::Core": "lib/CLI/Graphing/BarChart/Core.rakumod",
+ "CLI::Graphing::BarChart::Horizontal": "lib/CLI/Graphing/BarChart/Horizontal.rakumod",
"CLI::Graphing::BarChart::Vertical": "lib/CLI/Graphing/BarChart/Vertical.rakumod"
},
"resources": [
diff --git a/horizontal-graph-tester.raku b/horizontal-graph-tester.raku
new file mode 100755
index 0000000..82972dd
--- /dev/null
+++ b/horizontal-graph-tester.raku
@@ -0,0 +1,43 @@
+#!/usr/bin/env raku
+
+use lib 'lib';
+use CLI::Graphing::BarChart::Horizontal;
+
+
+say "X and Y axis\n";
+my $x_and_y_axis_graph = CLI::Graphing::BarChart::Horizontal.new(
+ data => [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
+ graph_height => 10,
+ x_axis_labels => ,
+ y_axis_labels => <0 1 2 3 4 5 6 7 8 9 10>
+);
+
+$x_and_y_axis_graph.print();
+
+
+say "\n\nJust Y axis\n";
+my $y_axis_graph = CLI::Graphing::BarChart::Horizontal.new(
+ data => [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
+ graph_height => 10,
+ y_axis_labels => <0 1 2 3 4 5 6 7 8 9 10 11>,
+);
+$y_axis_graph.print();
+
+
+say "\n\nWide Y axis labels\n";
+$x_and_y_axis_graph = CLI::Graphing::BarChart::Horizontal.new(
+ data => [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
+ graph_height => 10,
+ x_axis_labels => ,
+ y_axis_labels => ,
+);
+$x_and_y_axis_graph.print();
+
+say "\n\nInsufficent Wide Y axis labels\n";
+$x_and_y_axis_graph = CLI::Graphing::BarChart::Horizontal.new(
+ data => [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
+ graph_height => 10,
+ x_axis_labels => ,
+ y_axis_labels => ,
+);
+$x_and_y_axis_graph.print();
diff --git a/lib/CLI/Graphing/BarChart/Core.rakumod b/lib/CLI/Graphing/BarChart/Core.rakumod
new file mode 100644
index 0000000..0cd01af
--- /dev/null
+++ b/lib/CLI/Graphing/BarChart/Core.rakumod
@@ -0,0 +1,65 @@
+class CLI::Graphing::BarChart::Core {
+ use Listicles;
+ has $.data;
+ has $.bar_length is rw = 10;
+ has $.y_axis_labels is rw = [];
+ has $.x_axis_labels is rw = [];
+ # has $.bar_drawing_character = '█';
+
+ # Implement these in subclasses
+ method generate(){...}
+
+ #| Prints the graph to standard out.
+ method print() {
+ say self.generate();
+ }
+
+ method validate-bar-length() {
+ unless $!bar_length {
+ die("You must specify a bar_length (number of lines for the core graph).");
+ }
+ }
+
+ # each row is an array of the characters that make up that row
+ method generate-core-graph(Array $data,
+ Int $bar_length,
+ Str $bar_element_character
+ ) returns Array {
+
+ my @rows = [];
+ for $data.pairs -> $pair {
+ my $num_chars = $pair.value * ($bar_length / 100);
+
+ my $bar_chars = ($bar_element_character x $num_chars);
+ my @new_row = $bar_length == 1
+ ?? $bar_chars.split('', skip-empty => True).Array
+ !! self.pad-with-x-array($bar_chars, $bar_length);
+
+ # remember, the graph is sideways, so the x axis is currently on the left
+ # if it's not there we don't do anything
+ # if it is there we prepend to the left side of the row
+ @rows.push: @new_row;
+ }
+ return @rows;
+ }
+ # pads the string you passed in with spaces up to $width
+ method pad-with-space(Str $string, Int $width, Bool $pad_right=True) returns Str {
+ return $string if $string.chars >= $width;
+
+ return sprintf('%-' ~ $width ~ 's', $string) if $pad_right;
+ return sprintf('%' ~ $width ~ 's', $string);
+ }
+ # method !pad-with-x-array(Str $string, Int $width, Str $padding_char=' ') returns Array {
+ method pad-with-x-array(Str $string,
+ Int $width,
+ Str $padding_char=' ',
+ Bool $pad_right = True) returns Array {
+ my @response = $string.split('', skip-empty => True).Array;
+ return @response if @response.elems >= $width;
+ if $pad_right {
+ return @response.append($padding_char xx $width - @response.elems);
+ }
+ return ($padding_char xx $width - @response.elems).Array.append: @response;
+ }
+
+}
diff --git a/lib/CLI/Graphing/BarChart/Horizontal.rakumod b/lib/CLI/Graphing/BarChart/Horizontal.rakumod
new file mode 100644
index 0000000..29d75bf
--- /dev/null
+++ b/lib/CLI/Graphing/BarChart/Horizontal.rakumod
@@ -0,0 +1,90 @@
+use CLI::Graphing::BarChart::Core;
+
+class CLI::Graphing::BarChart::Horizontal is CLI::Graphing::BarChart::Core {
+ use Listicles;
+ # From Core...
+ # has $.data;
+ # has $.bar_length;
+ # has $.y_axis_labels = [];
+ # has $.x_axis_labels = [];
+
+ has $.bar_drawing_character = '▄';
+
+ method generate() returns Str {
+ self.validate-or-die();
+
+
+ my $rows = self.generate-core-graph($.data, $.bar_length, $.bar_drawing_character);
+ # X axis labels are just another row
+ # Y axis labels just get prepended
+ # 0x0 is top left
+ # the last row is the x axis labels (if present)
+ #
+ # 1. find the length of the longest y_axis_labels
+ my $max_y_label = $.y_axis_labels.map({.chars}).max;
+ my $border =[' ', '│', ' '];
+ if $.y_axis_labels {
+ for (0..^$.data.elems) -> $index {
+ if $index < $.y_axis_labels.elems {
+ my $prepension = self.pad-with-x-array($.y_axis_labels[$index],
+ $max_y_label,
+ ' ',
+ False);
+ $rows[$index] = $prepension
+ .append($border.Array)
+ .append($rows[$index].Array);
+ } else {
+ # they have fewer Y axis items than data points
+ $rows[$index] = self.pad-with-x-array(" │ ",
+ $max_y_label + 3,
+ ' ',
+ False
+ ).append($rows[$index].Array);
+ }
+ }
+ if ! $.x_axis_labels.is-empty {
+ $rows.push(self.pad-with-x-array(" │ ",
+ $max_y_label + 3,
+ ' ',
+ False
+ )
+ .append($.x_axis_labels.Array))
+ }
+ } elsif ! $.x_axis_labels.is-empty {
+ $rows.push($.x_axis_labels)
+ }
+
+ self!join-rows($rows);
+ }
+ method !join-rows($rows){
+ $rows.map({.join('')}).join("\n");
+ }
+
+ method validate-or-die(){
+ self.validate-bar-length();
+ if $.x_axis_labels {
+ self.validate-x-labels($.x_axis_labels);
+ self.x_axis_labels = $.x_axis_labels.map({.Str}).Array;
+ }
+ if $.y_axis_labels {
+ self.validate-y-labels($.y_axis_labels, $.data);
+ self.y_axis_labels = $.y_axis_labels.map({.Str}).Array;
+ }
+
+ }
+ #| either works or dies
+ method validate-x-labels($x_labels) {
+ return True unless $x_labels;
+ my $all_good = $x_labels.all-are(-> $x {$x.chars <= 1 });
+ die("x labels on horizontal graphs must be 1 or zero characters long") unless $all_good;
+ if $x_labels.elems > $.bar_length {
+ die ("You can't have more x labels than characters in the bar_length");
+ }
+ }
+ method validate-y-labels($y_labels, $data){
+ return True unless $y_labels;
+ if $y_labels.elems > 0 & $y_labels.elems > $data.elems {
+ die("You can't have more y labels than data elements.")
+ }
+ }
+}
diff --git a/lib/CLI/Graphing/BarChart/Vertical.rakumod b/lib/CLI/Graphing/BarChart/Vertical.rakumod
index 5955b22..d09458a 100644
--- a/lib/CLI/Graphing/BarChart/Vertical.rakumod
+++ b/lib/CLI/Graphing/BarChart/Vertical.rakumod
@@ -1,51 +1,40 @@
-class CLI::Graphing::BarChart::Vertical {
+use CLI::Graphing::BarChart::Core;
+
+class CLI::Graphing::BarChart::Vertical is CLI::Graphing::BarChart::Core {
use Listicles;
- has $.data;
- has $.graph_height;
- has $.y_axis_labels = [];
- has $.x_axis_labels = [];
- has $.bar_column_character = '█';
+
+ # From Core...
+ # has $.data;
+ # has $.bar_length;
+ # has $.y_axis_labels = [];
+ # has $.x_axis_labels = [];
+
+ has $.bar_drawing_character = '█';
has $.x_axis_divider_character = '─';
has $.space_between_columns = True;
+ has $.graph_height is rw = 10;
- #| Prints the graph to standard out.
- method print() {
- say self.generate();
- }
#| Generates a string representation of the graph.
method generate() returns Str {
- unless $!graph_height {
- die("You must specify a graph_height (number of lines for the core graph).");
- }
- if $!x_axis_labels.elems > 0 and $!x_axis_labels.elems != $!data.elems {
- die("There must be 1 x axis label for each data element.");
- } elsif $!x_axis_labels.elems > 0 {
- $!x_axis_labels = $!x_axis_labels.map({.Str}).Array;
- }
- if $!y_axis_labels.elems > 0 and $!y_axis_labels.elems != $!graph_height {
- die("There must be 1 y axis label for each row of the graph.");
- } elsif $!y_axis_labels.elems > 0 {
- $!y_axis_labels = $!y_axis_labels.map({.Str}).Array;
- }
-
+ self.bar_length = $.graph_height;
+ self.validate-or-die();
# data is a list of numbers
# we're going to assume numbers are percentages
- # graph_height is the max height of the table
- # graph_height = 100%
- # column height is $graph_height * ($datum / 100)
+ # bar_length is the max height of the table
+ # bar_length = 100%
+ # column height is $bar_length * ($datum / 100)
#
# BUT we're actually going to bulid the table horizontally
# then rotate it left 90°
# 1st datum = first row.
#
- my $rows = self!generate-sideways-graph($!data,
- $!x_axis_labels,
- $!graph_height,
- $!bar_column_character);
+ my $rows = self.generate-core-graph($.data,
+ $.bar_length,
+ $.bar_drawing_character);
# now we've got a bunch of horizontal bars
@@ -72,12 +61,12 @@ class CLI::Graphing::BarChart::Vertical {
# reversing the labels because 0x0 on the grid is bottom left
# but we need 0 to be the top one not the bottom
- my @reversed_y_labels = $!y_axis_labels.reverse;
+ my @reversed_y_labels = $.y_axis_labels.reverse;
# Going to need these in a moment.
- my $max_x_label = $!x_axis_labels.is-empty
+ my $max_x_label = $.x_axis_labels.is-empty
?? 0
- !! $!x_axis_labels.map({.chars}).max;
+ !! $.x_axis_labels.map({.chars}).max;
my $max_y_label = @reversed_y_labels.is-empty
?? 0
!! @reversed_y_labels.map({.chars}).max;
@@ -95,15 +84,15 @@ class CLI::Graphing::BarChart::Vertical {
# This is separated out because the special handling
# required for the divider line resulted in a whole
# pile of "oh but if there's an x label" complications
- if ! $!x_axis_labels.is-empty {
- $padded_rows = self!append-x-label-rows($padded_rows, $!x_axis_labels, $max_y_label);
+ if ! $.x_axis_labels.is-empty {
+ $padded_rows = self!append-x-label-rows($padded_rows, $.x_axis_labels, $max_y_label);
}
# join the cells together into one big string
# with or without spaces between columns.
my $response = self!join-rows($padded_rows,
- $!space_between_columns,
- (! $!x_axis_labels.is-empty),
+ $.space_between_columns,
+ (! $.x_axis_labels.is-empty),
$max_y_label);
return $response;
}
@@ -112,45 +101,6 @@ class CLI::Graphing::BarChart::Vertical {
#### Private stuff you don't need to worry about
#### ... unless there's a bug ;)
- # pads the string you passed in with spaces up to $width
- method !pad-with-space(Str $string, Int $width, Bool $pad_right=True) returns Str {
- return $string if $string.chars >= $width;
-
- return sprintf('%-' ~ $width ~ 's', $string) if $pad_right;
- return sprintf('%' ~ $width ~ 's', $string);
- }
- # method !pad-with-x-array(Str $string, Int $width, Str $padding_char=' ') returns Array {
- method !pad-with-x-array(Str $string,
- Int $width,
- Str $padding_char=' ',
- Bool $pad_right = True) returns Array {
- my @response = $string.split('', skip-empty => True).Array;
- return @response if @response.elems >= $width;
- if $pad_right {
- return @response.append($padding_char xx $width - @response.elems);
- }
- return ($padding_char xx $width - @response.elems).Array.append: @response;
- }
-
- method !generate-sideways-graph(Array $data,
- Array $x_axis_labels,
- Int $graph_height,
- Str $bar_column_character) returns Array {
-
- my @rows = [];
- my @col_widths = []; # if your x axis label is > 1 char we need to compensate
- for $data.pairs -> $pair {
- my $num_chars = $graph_height * ($pair.value / 100);
-
- my @new_row = self!pad-with-x-array(($bar_column_character x $num_chars), $graph_height);
- # remember, the graph is sideways, so the x axis is currently on the left
- # if it's not there we don't do anything
- # if it is there we prepend to the left side of the row
- @rows.push: @new_row;
- }
- return @rows;
- }
-
method !append-x-label-rows(Array $rows,
Array $x_axis_labels,
Int $max_y_label) returns Array {
@@ -166,10 +116,10 @@ class CLI::Graphing::BarChart::Vertical {
my @label_row = @divider_row;
# (one column's worth of ─) repeated for num columns
@divider_row.append: (
- ($!x_axis_divider_character x $max_x_label) xx $x_axis_labels.elems
+ ($.x_axis_divider_character x $max_x_label) xx $x_axis_labels.elems
).Array;
- @label_row.append: $x_axis_labels.map({self!pad-with-space($_, $max_x_label)}).Array;
+ @label_row.append: $x_axis_labels.map({self.pad-with-space($_, $max_x_label)}).Array;
$rows.push: @divider_row;
$rows.push: @label_row;
@@ -196,7 +146,7 @@ class CLI::Graphing::BarChart::Vertical {
my @row = [];
# $row_pair: 9 => $(" ", " ", " ", " ", " ", "█")
if $has_y_labels {
- my $left_chars = self!pad-with-x-array(
+ my $left_chars = self.pad-with-x-array(
$reversed_y_labels[$row_pair.key],
$max_y_label,
' ',
@@ -206,7 +156,7 @@ class CLI::Graphing::BarChart::Vertical {
@row.push: $left_chars;
}
for $original_row.Array -> $column {
- my $padded_column = self!pad-with-space($column, $column_width);
+ my $padded_column = self.pad-with-space($column, $column_width);
@row.append: $padded_column;
}
@result.push: @row;
@@ -237,7 +187,7 @@ class CLI::Graphing::BarChart::Vertical {
}
# now deal with the last 2 rows
@response.append: $padded_rows[*-2].join(
- $has_x_axis ?? $!x_axis_divider_character !! ' '
+ $has_x_axis ?? $.x_axis_divider_character !! ' '
);
@response.append: "\n";
@response.append: $padded_rows[*-1].join(' ');
@@ -246,4 +196,28 @@ class CLI::Graphing::BarChart::Vertical {
return $padded_rows.map({.join('')}).join("\n");
}
}
+ method validate-or-die(){
+ self.validate-bar-length();
+ if $.x_axis_labels {
+ self.validate-x-labels($.x_axis_labels, $.data);
+ self.x_axis_labels = $.x_axis_labels.map({.Str}).Array;
+ # $.x_axis_labels.map({.Str}).Array;
+ }
+ if $.y_axis_labels {
+ self.validate-y-labels($.y_axis_labels);
+ self.y_axis_labels = $.y_axis_labels.map({.Str}).Array;
+ # $.y_axis_labels.map({.Str}).Array;
+ }
+ }
+ method validate-x-labels($x_labels, $data){
+ return True unless $x_labels;
+ if $x_labels.elems > 0 and $x_labels.elems != $data.elems {
+ die("There must be 1 x axis label for each data element.");
+ }
+ }
+ method validate-y-labels($y_labels){
+ if $y_labels.elems > 0 and $y_labels.elems != $.bar_length {
+ die("There must be 1 y axis label for each row of the graph.");
+ }
+ }
}
diff --git a/t/01-basic.rakutest b/t/01-vertical.rakutest
similarity index 100%
rename from t/01-basic.rakutest
rename to t/01-vertical.rakutest
diff --git a/t/02-horizontal.rakutest b/t/02-horizontal.rakutest
new file mode 100644
index 0000000..3b46d31
--- /dev/null
+++ b/t/02-horizontal.rakutest
@@ -0,0 +1,58 @@
+use Test;
+use lib 'lib';
+use CLI::Graphing::BarChart::Horizontal;
+
+my $data = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
+my $bare_graph = CLI::Graphing::BarChart::Horizontal.new(
+ data => $data,
+ bar_length => 10
+);
+
+
+my $expected_graph =
+" \n▄ \n▄▄ \n▄▄▄ \n▄▄▄▄ \n▄▄▄▄▄ \n▄▄▄▄▄▄ \n▄▄▄▄▄▄▄ \n▄▄▄▄▄▄▄▄ \n▄▄▄▄▄▄▄▄▄ \n▄▄▄▄▄▄▄▄▄▄";
+
+is $bare_graph.generate(), $expected_graph, 'bad bare graph';
+
+
+
+my $x_axis_graph = CLI::Graphing::BarChart::Horizontal.new(
+ data => $data,
+ bar_length => 10,
+ x_axis_labels => .Array
+);
+throws-like { $x_axis_graph.generate }, Exception, message => /"can't have more x labels"/;
+
+$x_axis_graph = CLI::Graphing::BarChart::Horizontal.new(
+ data => $data,
+ bar_length => 10,
+ x_axis_labels => .Array
+);
+
+$expected_graph =
+" \n▄ \n▄▄ \n▄▄▄ \n▄▄▄▄ \n▄▄▄▄▄ \n▄▄▄▄▄▄ \n▄▄▄▄▄▄▄ \n▄▄▄▄▄▄▄▄ \n▄▄▄▄▄▄▄▄▄ \n▄▄▄▄▄▄▄▄▄▄\nabcdefghij";
+
+is $x_axis_graph.generate, $expected_graph, 'bad x axis only graph';
+
+my $y_axis_graph = CLI::Graphing::BarChart::Horizontal.new(
+ data => $data,
+ bar_length => 10,
+ y_axis_labels => .Array
+);
+
+$expected_graph = "a │ \nb │ ▄ \nc │ ▄▄ \nd │ ▄▄▄ \ne │ ▄▄▄▄ \nf │ ▄▄▄▄▄ \ng │ ▄▄▄▄▄▄ \nh │ ▄▄▄▄▄▄▄ \ni │ ▄▄▄▄▄▄▄▄ \nj │ ▄▄▄▄▄▄▄▄▄ \n │ ▄▄▄▄▄▄▄▄▄▄";
+
+is $y_axis_graph.generate, $expected_graph, 'bad y axis only graph';
+
+my $x_and_y_axis_graph = CLI::Graphing::BarChart::Horizontal.new(
+ data => $data,
+ bar_length => 10,
+ x_axis_labels => .Array,
+ y_axis_labels => <0 1 2 3 4 5 6 7 8 9>.Array
+);
+
+$expected_graph ="0 │ \n1 │ ▄ \n2 │ ▄▄ \n3 │ ▄▄▄ \n4 │ ▄▄▄▄ \n5 │ ▄▄▄▄▄ \n6 │ ▄▄▄▄▄▄ \n7 │ ▄▄▄▄▄▄▄ \n8 │ ▄▄▄▄▄▄▄▄ \n9 │ ▄▄▄▄▄▄▄▄▄ \n │ ▄▄▄▄▄▄▄▄▄▄\n │ abcdefghij";
+
+is $x_and_y_axis_graph.generate, $expected_graph, 'bad x + y axis graph';
+
+done-testing;
diff --git a/tester.raku b/vertical-graph-tester.raku
similarity index 100%
rename from tester.raku
rename to vertical-graph-tester.raku