[Rt-commit] rt branch, 4.4/stacked-bar-chart, created. rt-4.4.2-67-gcb1b18e1e
? sunnavy
sunnavy at bestpractical.com
Thu Jan 25 12:38:29 EST 2018
The branch, 4.4/stacked-bar-chart has been created
at cb1b18e1e3aa905bad93bbe8259e4e81111b54b3 (commit)
- Log -----------------------------------------------------------------
commit 9f110562664b05512312b2b12967b339546d5d9a
Author: sunnavy <sunnavy at bestpractical.com>
Date: Thu Jan 25 22:19:21 2018 +0800
basic stacked bar chart support
for now, only count chart function is supported.
diff --git a/share/html/Search/Chart b/share/html/Search/Chart
index f51898b0f..34034ecb2 100644
--- a/share/html/Search/Chart
+++ b/share/html/Search/Chart
@@ -49,6 +49,7 @@
$Cache => undef
$Query => "id > 0"
@GroupBy => ()
+$StackedGroupBy => undef
$ChartStyle => 'bar+table+sql'
@ChartFunction => 'COUNT'
$Width => undef
@@ -171,6 +172,47 @@ my $chart = $chart_class->new( $Width => $Height );
my %chart_options;
if ($chart_class eq "GD::Graph::bars") {
+
+ # $ChartStyle could be pie even if $chart_class is ::bars
+ if ( $StackedGroupBy && $ChartStyle =~ /\bbar\b/ ) {
+ if ( scalar @data > 2 ) {
+ RT->Logger->warning( "Invalid stack option: it can't apply to multiple data rows" );
+ }
+ else {
+
+ my $labels = $data[0];
+
+ # find the stacked group index
+ require List::MoreUtils;
+ my $stacked_index = List::MoreUtils::first_index { $_ eq $StackedGroupBy } @GroupBy;
+ if ( $stacked_index >= 0 ) {
+ $chart_options{cumulate} = 1;
+ my @new_labels;
+ my %rows;
+ my $i = 0;
+
+ for my $label ( @$labels ) {
+ my @new_label = @$label;
+ splice @new_label, $stacked_index, 1; # remove the stacked group
+ my $key = join ';;;', @new_label;
+ push @new_labels, \@new_label unless $rows{$key};
+ push @{$rows{$key}}, $data[1][$i] . ' ' . $label->[$stacked_index];
+ $i++;
+ }
+
+ @data = \@new_labels;
+
+ my $ea = List::MoreUtils::each_arrayref( map { $rows{join ';;;', @$_} } @new_labels );
+ while ( my ( @list ) = $ea->() ) {
+ push @data, [ map { $_ || '' } @list ];
+ }
+ }
+ else {
+ RT->Logger->warning("Invalid StackedGroupBy: $StackedGroupBy");
+ }
+ }
+ }
+
my $count = @{ $data[0] };
$chart_options{'bar_spacing'} =
$count > 30 ? 1
@@ -341,7 +383,14 @@ if ($chart_class eq "GD::Graph::bars") {
}
# try to fit in values above bars
- {
+ if ( $chart_options{cumulate} ) {
+ $chart_options{'show_values'} = 1;
+ $chart_options{'values_vertical'} = 0;
+ $chart_options{'values_space'} = -25;
+ $chart_options{'values_font'} = [ $font, 12 ];
+ $chart_options{'hide_overlapping_values'} = 1;
+ }
+ else {
# 0.8 is guess, labels for ticks on Y axis can be wider
# 1.5 for paddings around bars that GD::Graph adds
my $x_space_for_label = $Width*0.8/($count*(@data - 1)+1.5);
@@ -388,15 +437,16 @@ if ($chart_class eq "GD::Graph::bars") {
);
$chart_options{'show_values'} = 1;
$chart_options{'hide_overlapping_values'} = 1;
+
if ( $found_solution ) {
$chart_options{'values_font'} = [ $font, $found_solution ],
- $chart_options{'values_space'} = 2;
- $chart_options{'values_vertical'} =
+ $chart_options{'values_space'} ||= 2;
+ $chart_options{'values_vertical'} //=
$can{'horizontal, one line'} ? 0 : 1;
} else {
$chart_options{'values_font'} = [ $font, 9 ],
- $chart_options{'values_space'} = 1;
- $chart_options{'values_vertical'} = 1;
+ $chart_options{'values_space'} ||= 1;
+ $chart_options{'values_vertical'} //= 1;
}
}
diff --git a/share/html/Search/Chart.html b/share/html/Search/Chart.html
index 1a6768a51..bc00da6c3 100644
--- a/share/html/Search/Chart.html
+++ b/share/html/Search/Chart.html
@@ -57,7 +57,7 @@ $m->callback( ARGSRef => \%ARGS, CallbackName => 'Initial' );
my $title = loc( "Grouped search results");
-my @search_fields = qw(Query GroupBy ChartStyle ChartFunction Width Height);
+my @search_fields = qw(Query GroupBy StackedGroupBy ChartStyle ChartFunction Width Height);
my $saved_search = $m->comp( '/Widgets/SavedSearch:new',
SearchType => 'Chart',
SearchFields => [@search_fields],
@@ -197,6 +197,13 @@ $m->callback( ARGSRef => \%ARGS, QueryArgsRef => \%query );
<script type="text/javascript">
var updateChartStyle = function() {
var val = jQuery(".chart-picture [name=ChartType]").val();
+ if ( val == 'bar' ) {
+ jQuery("span.stacked-group").removeClass('hidden');
+ }
+ else {
+ jQuery("span.stacked-group").addClass('hidden');
+ }
+
if ( val != 'table' && jQuery(".chart-picture [name=ChartStyleIncludeTable]").is(':checked') ) {
val += '+table';
}
@@ -215,6 +222,35 @@ jQuery(".chart-picture [name=ChartType]").change(function(){
jQuery(".chart-picture [name=ChartStyleIncludeTable]").change( updateChartStyle );
jQuery(".chart-picture [name=ChartStyleIncludeSQL]").change( updateChartStyle );
+
+jQuery("input.stacked-group-checkbox").change( function() {
+ if ( jQuery(this).is(':checked') ) {
+ jQuery("input.stacked-group-checkbox").not(this).prop('checked', false);
+ }
+});
+
+jQuery("select[name=GroupBy]").change( function() {
+ jQuery(this).closest('fieldset').find('input.stacked-group-checkbox').val(jQuery(this).val());
+});
+
+jQuery("select[name=ChartFunction]:first").change( function() {
+ if ( jQuery(this).val() && jQuery(this).val().match(/count/i) ) {
+ jQuery("input.stacked-group-checkbox").prop('disabled', false);
+ }
+ else {
+ jQuery("input.stacked-group-checkbox").prop('disabled', true).prop('checked', false);
+ }
+});
+
+jQuery("select[name=ChartFunction]:not(:first)").change( function() {
+ if ( jQuery(this).val() ) {
+ jQuery("input.stacked-group-checkbox").prop('disabled', true).prop('checked', false);
+ }
+ else {
+ jQuery("select[name=ChartFunction]:first").change();
+ }
+});
+
</script>
<& /Elements/Submit, Label => loc('Update Chart'), Name => 'Update' &>
diff --git a/share/html/Search/Elements/SelectGroupBy b/share/html/Search/Elements/SelectGroupBy
index f9dd9aa51..3ffd02e2f 100644
--- a/share/html/Search/Elements/SelectGroupBy
+++ b/share/html/Search/Elements/SelectGroupBy
@@ -74,6 +74,11 @@ while ( my ($label, $value) = splice @options, 0, 2 ) {
</optgroup>
% }
</select>
+
+<span class="stacked-group">
+ <% loc('Stacked?') %> <input name="Stacked<% $Name %>" type="checkbox" class="stacked-group-checkbox" value="<% $Default %>" <% $DECODED_ARGS->{"Stacked$Name"} && $DECODED_ARGS->{"Stacked$Name"} eq ($Default||'') ? 'checked="checked"' : '' |n %>/>
+</span>
+
<%init>
use RT::Report::Tickets;
my $report = RT::Report::Tickets->new( $session{'CurrentUser'} );
commit 598dc7e06bff3de48caa0dbe0059204d9f1c3453
Author: sunnavy <sunnavy at bestpractical.com>
Date: Fri Jan 26 01:16:39 2018 +0800
increase chart width/height for better stacked bars rendering
diff --git a/share/html/Search/Chart b/share/html/Search/Chart
index 34034ecb2..11652a980 100644
--- a/share/html/Search/Chart
+++ b/share/html/Search/Chart
@@ -172,6 +172,15 @@ my $chart = $chart_class->new( $Width => $Height );
my %chart_options;
if ($chart_class eq "GD::Graph::bars") {
+ my $text_size = sub {
+ my ($size, $text) = (@_);
+ my $font_handle = GD::Text::Align->new(
+ $chart->get('graph'), valign => 'top', 'halign' => 'center',
+ );
+ $font_handle->set_font($font, $size);
+ $font_handle->set_text($text);
+ return $font_handle;
+ };
# $ChartStyle could be pie even if $chart_class is ::bars
if ( $StackedGroupBy && $ChartStyle =~ /\bbar\b/ ) {
@@ -200,6 +209,32 @@ if ($chart_class eq "GD::Graph::bars") {
$i++;
}
+ # increase $Width and $Height if necessary
+ require List::Util;
+ my ( $max_sum, $min_value, $max_width );
+ for my $vertical_values ( map { $rows{join ';;;', @$_} } @new_labels ) {
+ my $sum = List::Util::sum( map { defined && /^(\d+)/ ? $1 : () } @$vertical_values );
+ my $min_v = List::Util::min( map { defined && /^(\d+)/ ? $1 : () } @$vertical_values );
+ my $max_w = List::Util::max( map { defined ? $text_size->(12, $_)->get('width') : () } @$vertical_values );
+ $max_sum = $sum if !$max_sum || $max_sum < $sum;
+ $min_value = $min_v if !$min_value || ( $min_v > 0 && $min_value > $min_v );
+ $max_width = $max_w if !$max_width || $max_width < $max_w;
+ }
+
+ $chart_options{y_max_value} = int( $max_sum * 1.1 );
+ $chart_options{y_max_value} += 5 - $chart_options{y_max_value} % 5;
+ if ( $min_value ) {
+ my $pixels = $min_value * $Height / $chart_options{y_max_value};
+ if ( $pixels < 30 ) {
+ $Height = int( $Height * 30 / $pixels );
+ }
+ $Height = 200 if $Height < 200;
+ }
+
+ my $value_width = ( $max_width + 25 ) * scalar @new_labels;
+ $Width = $value_width * 2 if $Width < $value_width * 2;
+ $Width = 200 if $Width < 200;
+
@data = \@new_labels;
my $ea = List::MoreUtils::each_arrayref( map { $rows{join ';;;', @$_} } @new_labels );
@@ -248,15 +283,6 @@ if ($chart_class eq "GD::Graph::bars") {
$chart_options{'y_label_skip'} = 2;
$chart_options{'y_tick_number'} = 10;
}
- my $text_size = sub {
- my ($size, $text) = (@_);
- my $font_handle = GD::Text::Align->new(
- $chart->get('graph'), valign => 'top', 'halign' => 'center',
- );
- $font_handle->set_font($font, $size);
- $font_handle->set_text($text);
- return $font_handle;
- };
my $fitter = sub {
my %args = @_;
@@ -459,7 +485,7 @@ if ($chart_class eq "GD::Graph::bars") {
# use a top margin enough to display values over the top line if needed
t_margin => 18,
# the following line to make sure there's enough space for values to show
- y_max_value => $max_value,
+ ( $chart_options{y_max_value} || 0 ) < $max_value ? ( y_max_value => $max_value ) : (),
y_min_value => $min_value,
# if there're too many bars or at least one key is too long, use vertical
bargroup_spacing => $chart_options{'bar_spacing'}*5,
commit cb1b18e1e3aa905bad93bbe8259e4e81111b54b3
Author: sunnavy <sunnavy at bestpractical.com>
Date: Fri Jan 26 01:26:40 2018 +0800
silence "isn't numeric" warnings in GD::Graph for stacked bar charts
we want to show extra info such as status or queue names in bar values,
but values are supposed to be numeric. though strings like "2 open"
could be automatically interpreted to numbers, "isn't numeric" warnings
will be emitted too. it's ok to silence them since it's expected.
diff --git a/share/html/Search/Chart b/share/html/Search/Chart
index 11652a980..89f1d733e 100644
--- a/share/html/Search/Chart
+++ b/share/html/Search/Chart
@@ -533,6 +533,23 @@ $chart->{dclrs} = [ RT->Config->Get("ChartColors") ];
my $color_hex = $self->{dclrs}[ $_[0] % @{ $self->{dclrs} } - 1 ];
return map { hex } ( $color_hex =~ /(..)(..)(..)/ );
};
+
+ if ( $chart_options{cumulate} ) {
+ my @warning_subs = (
+ 'GD::Graph::Data::cumulate',
+ 'GD::Graph::Data::get_y_cumulative',
+ 'GD::Graph::axestype::val_to_pixel',
+ 'GD::Graph::axestype::_correct_y_min_max',
+ );
+ no strict 'refs';
+ for my $warning_sub ( @warning_subs ) {
+ my $orig = \&$warning_sub;
+ *$warning_sub = sub {
+ local $SIG{__WARN__} = sub { warn $_[0] unless $_[0] =~ /isn't numeric/ };
+ $orig->( @_ );
+ };
+ }
+ }
}
if (my $plot = eval { $chart->plot( \@data ) }) {
-----------------------------------------------------------------------
More information about the rt-commit
mailing list