Skip to content

Commit

Permalink
Added support for clustered charts.
Browse files Browse the repository at this point in the history
  • Loading branch information
jmcnamara committed Mar 20, 2015
1 parent ac38622 commit e39b3d2
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 0 deletions.
64 changes: 64 additions & 0 deletions examples/chart_clustered.pl
@@ -0,0 +1,64 @@
#!/usr/bin/perl

#######################################################################
#
# A demo of a clustered category chart in Excel::Writer::XLSX.
#
# reverse ('(c)'), March 2015, John McNamara, jmcnamara@cpan.org
#

use strict;
use warnings;
use Excel::Writer::XLSX;

my $workbook = Excel::Writer::XLSX->new( 'chart_clustered.xlsx' );
my $worksheet = $workbook->add_worksheet();
my $bold = $workbook->add_format( bold => 1 );

# Add the worksheet data that the charts will refer to.
my $headings = [ 'Types', 'Sub Type', 'Value 1', 'Value 2', 'Value 3' ];
my $data = [
[ 'Type 1', 'Sub Type A', 5000, 8000, 6000 ],
[ '', 'Sub Type B', 2000, 3000, 4000 ],
[ '', 'Sub Type C', 250, 1000, 2000 ],
[ 'Type 2', 'Sub Type D', 6000, 6000, 6500 ],
[ '', 'Sub Type E', 500, 300, 200 ],
];

$worksheet->write( 'A1', $headings, $bold );
$worksheet->write_col( 'A2', $data );

# Create a new chart object. In this case an embedded chart.
my $chart = $workbook->add_chart( type => 'column', embedded => 1 );

# Configure the series. Note, that the categories are 2D ranges (from column A
# to column B). This creates the clusters. The series are shown as formula
# strings for clarity but you can also use the array syntax. See the docs.
$chart->add_series(
name => '=Sheet1!$C$1',
categories => '=Sheet1!$A$2:$B$6',
values => '=Sheet1!$C$2:$C$6',
);

$chart->add_series(
name => '=Sheet1!$D$1',
categories => '=Sheet1!$A$2:$B$6',
values => '=Sheet1!$D$2:$D$6',
);

$chart->add_series(
name => '=Sheet1!$E$1',
categories => '=Sheet1!$A$2:$B$6',
values => '=Sheet1!$E$2:$E$6',
);

# Set the Excel chart style.
$chart->set_style( 37 );

# Turn off the legend.
$chart->set_legend( position => 'none' );

# Insert the chart into the worksheet.
$worksheet->insert_chart( 'G3', $chart );

__END__
56 changes: 56 additions & 0 deletions lib/Excel/Writer/XLSX/Chart.pm
Expand Up @@ -862,6 +862,10 @@ sub _get_data_type {
return 'none' if !defined $data;
return 'none' if @$data == 0;

if (ref $data->[0] eq 'ARRAY') {
return 'multi_str'
}

# If the token isn't a number assume it is a string.
for my $token ( @$data ) {
next if !defined $token;
Expand Down Expand Up @@ -2268,6 +2272,13 @@ sub _write_cat {
# Write the c:numRef element.
$self->_write_str_ref( $formula, $data, $type );
}
elsif ( $type eq 'multi_str') {

$self->{_cat_has_num_fmt} = 0;

# Write the c:multiLvLStrRef element.
$self->_write_multi_lvl_str_ref( $formula, $data );
}
else {

$self->{_cat_has_num_fmt} = 1;
Expand All @@ -2276,6 +2287,7 @@ sub _write_cat {
$self->_write_num_ref( $formula, $data, $type );
}


$self->xml_end_tag( 'c:cat' );
}

Expand Down Expand Up @@ -2371,6 +2383,50 @@ sub _write_str_ref {
}


##############################################################################
#
# _write_multi_lvl_str_ref()
#
# Write the <c:multiLvLStrRef> element.
#
sub _write_multi_lvl_str_ref {

my $self = shift;
my $formula = shift;
my $data = shift;
my $count = @$data;

return if !$count;

$self->xml_start_tag( 'c:multiLvlStrRef' );

# Write the c:f element.
$self->_write_series_formula( $formula );

$self->xml_start_tag( 'c:multiLvlStrCache' );

# Write the c:ptCount element.
$count = @{ $data->[-1] };
$self->_write_pt_count( $count );

# Write the data arrays in reverse order.
for my $aref ( reverse @$data ) {
$self->xml_start_tag( 'c:lvl' );

for my $i ( 0 .. @$aref - 1 ) {
# Write the c:pt element.
$self->_write_pt( $i, $aref->[$i] );
}

$self->xml_end_tag( 'c:lvl' );
}

$self->xml_end_tag( 'c:multiLvlStrCache' );

$self->xml_end_tag( 'c:multiLvlStrRef' );
}


##############################################################################
#
# _write_series_formula()
Expand Down
109 changes: 109 additions & 0 deletions t/regression/chart_clustered01.t
@@ -0,0 +1,109 @@
###############################################################################
#
# Tests the output of Excel::Writer::XLSX against Excel generated files.
#
# reverse ('(c)'), October 2011, John McNamara, jmcnamara@cpan.org
#

use lib 't/lib';
use TestFunctions qw(_compare_xlsx_files _is_deep_diff);
use strict;
use warnings;

use Test::More tests => 1;

###############################################################################
#
# Tests setup.
#
my $filename = 'chart_clustered01.xlsx';
my $dir = 't/regression/';
my $got_filename = $dir . "ewx_$filename";
my $exp_filename = $dir . 'xlsx_files/' . $filename;

my $ignore_members = [];
my $ignore_elements = {

# Ignore the page margins.
'xl/charts/chart1.xml' => [ '<c:pageMargins' ],
};


###############################################################################
#
# Test the creation of a simple Excel::Writer::XLSX file.
#
use Excel::Writer::XLSX;

my $workbook = Excel::Writer::XLSX->new( $got_filename );
my $worksheet = $workbook->add_worksheet();
my $chart = $workbook->add_chart(type => 'column', embedded => 1);


# For testing, copy the randomly generated axis ids in the target xlsx file.
$chart->{_axis_ids} = [ 45886080, 45928832 ];

my $data = [
[ 'Types', 'Sub Type', 'Value 1', 'Value 2', 'Value 3' ],
[ 'Type 1', 'Sub Type A', 5000, 8000, 6000 ],
[ '', 'Sub Type B', 2000, 3000, 4000 ],
[ '', 'Sub Type C', 250, 1000, 2000 ],
[ 'Type 2', 'Sub Type D', 6000, 6000, 6500 ],
[ '', 'Sub Type E', 500, 300, 200 ],
];

my $cat_data = [
[ 'Type 1', undef, undef, 'Type 2', undef ],
[ 'Sub Type A', 'Sub Type B', 'Sub Type C', 'Sub Type D', 'Sub Type E' ]
];


$worksheet->write_col( 'A1', $data );

$chart->add_series(
name => '=Sheet1!$C$1',
categories => '=Sheet1!$A$2:$B$6',
values => '=Sheet1!$C$2:$C$6',
categories_data => $cat_data,
);

$chart->add_series(
name => '=Sheet1!$D$1',
categories => '=Sheet1!$A$2:$B$6',
values => '=Sheet1!$D$2:$D$6',
);

$chart->add_series(
name => '=Sheet1!$E$1',
categories => '=Sheet1!$A$2:$B$6',
values => '=Sheet1!$E$2:$E$6',
);

$worksheet->insert_chart( 'E9', $chart );

$workbook->close();


###############################################################################
#
# Compare the generated and existing Excel files.
#

my ( $got, $expected, $caption ) = _compare_xlsx_files(

$got_filename,
$exp_filename,
$ignore_members,
$ignore_elements,
);

_is_deep_diff( $got, $expected, $caption );


###############################################################################
#
# Cleanup.
#
unlink $got_filename;

__END__
Binary file added t/regression/xlsx_files/chart_clustered01.xlsx
Binary file not shown.

0 comments on commit e39b3d2

Please sign in to comment.