diff --git a/.indent.pro b/.indent.pro index b91c1ba5..c1add4d0 100644 --- a/.indent.pro +++ b/.indent.pro @@ -45,6 +45,7 @@ /* libxlsxwriter typedefs. */ -T lxw_app -T lxw_autofilter +-T lxw_axis -T lxw_border -T lxw_cell -T lxw_chart diff --git a/include/xlsxwriter/chart.h b/include/xlsxwriter/chart.h index 4d4c8933..15d37c9f 100644 --- a/include/xlsxwriter/chart.h +++ b/include/xlsxwriter/chart.h @@ -17,6 +17,8 @@ STAILQ_HEAD(lxw_chart_series_list, lxw_chart_series); STAILQ_HEAD(lxw_series_data_points, lxw_series_data_point); +#define LXW_CHART_NUM_FORMAT_LEN 128 + /** Available chart types . */ enum lxw_chart_types { @@ -24,7 +26,31 @@ enum lxw_chart_types { LXW_CHART_NONE = 0, /** Bar chart. */ - LXW_CHART_BAR + LXW_CHART_BAR, + + /** Bar chart - stacked. */ + LXW_CHART_BAR_STACKED, + + /** Bar chart - percentage stacked. */ + LXW_CHART_BAR_STACKED_PERCENT, + + /** Column chart. */ + LXW_CHART_COLUMN, + + /** Column chart - stacked. */ + LXW_CHART_COLUMN_STACKED, + + /** Column chart - percentage stacked. */ + LXW_CHART_COLUMN_STACKED_PERCENT, + + LWX_CHART_END_REMOVEP_LATER +}; + +enum lxw_chart_subtypes { + + LXW_CHART_SUBTYPE_NONE = 0, + LXW_CHART_SUBTYPE_STACKED, + LXW_CHART_SUBTYPE_STACKED_PERCENT }; typedef struct lxw_series_range { @@ -56,6 +82,13 @@ typedef struct lxw_chart_series { } lxw_chart_series; +typedef struct lxw_axis { + + char num_format[LXW_CHART_NUM_FORMAT_LEN]; + char default_num_format[LXW_CHART_NUM_FORMAT_LEN]; + +} lxw_axis; + /* * Struct to represent a chart object. */ @@ -64,8 +97,12 @@ typedef struct lxw_chart { FILE *file; uint8_t type; + uint8_t subtype; uint16_t series_index; + lxw_axis x_axis; + lxw_axis y_axis; + uint32_t id; uint32_t axis_id_1; uint32_t axis_id_2; @@ -75,6 +112,13 @@ typedef struct lxw_chart { uint8_t in_use; uint8_t cat_has_num_fmt; + uint8_t has_overlap; + int series_overlap_1; + + char grouping[32]; + char cat_axis_position[2]; + char val_axis_position[2]; + struct lxw_chart_series_list *series_list; STAILQ_ENTRY (lxw_chart) list_pointers; diff --git a/src/chart.c b/src/chart.c index 7c512593..41ffc9ce 100644 --- a/src/chart.c +++ b/src/chart.c @@ -36,6 +36,18 @@ lxw_chart_new(uint8_t type) chart->type = type; + /* Set the default axis positions. */ + strcpy(chart->cat_axis_position, "b"); + strcpy(chart->val_axis_position, "l"); + + /* Set the default grouping. */ + strcpy(chart->grouping, "clustered"); + + strcpy(chart->x_axis.default_num_format, "General"); + strcpy(chart->y_axis.default_num_format, "General"); + + chart->series_overlap_1 = 100; + return chart; mem_error: @@ -187,14 +199,13 @@ _chart_write_layout(lxw_chart *self) * Write the element. */ STATIC void -_chart_write_grouping(lxw_chart *self) +_chart_write_grouping(lxw_chart *self, char *grouping) { struct xml_attribute_list attributes; struct xml_attribute *attribute; - char val[] = "clustered"; LXW_INIT_ATTRIBUTES(); - LXW_PUSH_ATTRIBUTES_STR("val", val); + LXW_PUSH_ATTRIBUTES_STR("val", grouping); lxw_xml_empty_tag(self->file, "c:grouping", &attributes); @@ -620,15 +631,14 @@ _chart_write_major_gridlines(lxw_chart *self) * Write the element. */ STATIC void -_chart_write_num_fmt(lxw_chart *self) +_chart_write_number_format(lxw_chart *self, lxw_axis *axis) { struct xml_attribute_list attributes; struct xml_attribute *attribute; - char format_code[] = "General"; char source_linked[] = "1"; LXW_INIT_ATTRIBUTES(); - LXW_PUSH_ATTRIBUTES_STR("formatCode", format_code); + LXW_PUSH_ATTRIBUTES_STR("formatCode", axis->default_num_format); LXW_PUSH_ATTRIBUTES_STR("sourceLinked", source_linked); lxw_xml_empty_tag(self->file, "c:numFmt", &attributes); @@ -773,12 +783,31 @@ _chart_write_print_settings(lxw_chart *self) lxw_xml_end_tag(self->file, "c:printSettings"); } +/* + * Write the element. + */ +STATIC void +_chart_write_overlap(lxw_chart *self, int overlap) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_INT("val", overlap); + + lxw_xml_empty_tag(self->file, "c:overlap", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + /* * Write the element. Usually the X axis. */ STATIC void _chart_write_cat_axis(lxw_chart *self) { + char *position = self->cat_axis_position; + lxw_xml_start_tag(self->file, "c:catAx", NULL); _chart_write_axis_id(self, self->axis_id_1); @@ -787,11 +816,11 @@ _chart_write_cat_axis(lxw_chart *self) _chart_write_scaling(self); /* Write the c:axPos element. */ - _chart_write_axis_pos(self, "l"); + _chart_write_axis_pos(self, position); /* Write the c:numFmt element. */ if (self->cat_has_num_fmt) - _chart_write_num_fmt(self); + _chart_write_number_format(self, &self->x_axis); /* Write the c:tickLblPos element. */ _chart_write_tick_lbl_pos(self); @@ -818,8 +847,10 @@ _chart_write_cat_axis(lxw_chart *self) * Write the element. */ STATIC void -_chart_write_val_ax(lxw_chart *self) +_chart_write_val_axis(lxw_chart *self) { + char *position = self->val_axis_position; + lxw_xml_start_tag(self->file, "c:valAx", NULL); _chart_write_axis_id(self, self->axis_id_2); @@ -828,13 +859,13 @@ _chart_write_val_ax(lxw_chart *self) _chart_write_scaling(self); /* Write the c:axPos element. */ - _chart_write_axis_pos(self, "b"); + _chart_write_axis_pos(self, position); /* Write the c:majorGridlines element. */ _chart_write_major_gridlines(self); /* Write the c:numFmt element. */ - _chart_write_num_fmt(self); + _chart_write_number_format(self, &self->y_axis); /* Write the c:tickLblPos element. */ _chart_write_tick_lbl_pos(self); @@ -859,13 +890,13 @@ _chart_write_val_ax(lxw_chart *self) * Write the element. */ STATIC void -_chart_write_bar_dir(lxw_chart *self) +_chart_write_bar_dir(lxw_chart *self, char *type) { struct xml_attribute_list attributes; struct xml_attribute *attribute; LXW_INIT_ATTRIBUTES(); - LXW_PUSH_ATTRIBUTES_STR("val", "bar"); + LXW_PUSH_ATTRIBUTES_STR("val", type); lxw_xml_empty_tag(self->file, "c:barDir", &attributes); @@ -876,23 +907,90 @@ _chart_write_bar_dir(lxw_chart *self) * Write the element. */ STATIC void -_chart_write_bar_chart(lxw_chart *self) +_chart_write_bar_chart(lxw_chart *self, uint8_t type) { lxw_chart_series *series; + if (type == LXW_CHART_BAR_STACKED) { + strcpy(self->grouping, "stacked"); + self->has_overlap = LXW_TRUE; + self->subtype = LXW_CHART_SUBTYPE_STACKED; + } + + if (type == LXW_CHART_BAR_STACKED_PERCENT) { + strcpy(self->grouping, "percentStacked"); + strcpy((&self->y_axis)->default_num_format, "0%"); + self->has_overlap = LXW_TRUE; + self->subtype = LXW_CHART_SUBTYPE_STACKED; + } + + /* Override the default axis positions for a bar chart. */ + strcpy(self->cat_axis_position, "l"); + strcpy(self->val_axis_position, "b"); + lxw_xml_start_tag(self->file, "c:barChart", NULL); /* Write the c:barDir element. */ - _chart_write_bar_dir(self); + _chart_write_bar_dir(self, "bar"); /* Write the c:grouping element. */ - _chart_write_grouping(self); + _chart_write_grouping(self, self->grouping); STAILQ_FOREACH(series, self->series_list, list_pointers) { /* Write the c:ser element. */ _chart_write_ser(self, series); } + if (self->has_overlap) { + /* Write the c:overlap element. */ + _chart_write_overlap(self, self->series_overlap_1); + } + + /* Write the c:axId elements. */ + _chart_write_axis_ids(self); + + lxw_xml_end_tag(self->file, "c:barChart"); +} + +/* + * Write the element for column charts. + */ +STATIC void +_chart_write_column_chart(lxw_chart *self, uint8_t type) +{ + lxw_chart_series *series; + + if (type == LXW_CHART_COLUMN_STACKED) { + strcpy(self->grouping, "stacked"); + self->has_overlap = LXW_TRUE; + self->subtype = LXW_CHART_SUBTYPE_STACKED; + } + + if (type == LXW_CHART_COLUMN_STACKED_PERCENT) { + strcpy(self->grouping, "percentStacked"); + strcpy((&self->y_axis)->default_num_format, "0%"); + self->has_overlap = LXW_TRUE; + self->subtype = LXW_CHART_SUBTYPE_STACKED; + } + + lxw_xml_start_tag(self->file, "c:barChart", NULL); + + /* Write the c:barDir element. */ + _chart_write_bar_dir(self, "col"); + + /* Write the c:grouping element. */ + _chart_write_grouping(self, self->grouping); + + STAILQ_FOREACH(series, self->series_list, list_pointers) { + /* Write the c:ser element. */ + _chart_write_ser(self, series); + } + + if (self->has_overlap) { + /* Write the c:overlap element. */ + _chart_write_overlap(self, self->series_overlap_1); + } + /* Write the c:axId elements. */ _chart_write_axis_ids(self); @@ -907,10 +1005,26 @@ _chart_write_bar_chart(lxw_chart *self) * Write the chart type element. */ STATIC void -_chart_write_chart_type(lxw_chart *self) +_chart_write_chart_type(lxw_chart *self, uint8_t type) { - /* Write the c:barChart element. */ - _chart_write_bar_chart(self); + switch (type) { + case LXW_CHART_BAR: + case LXW_CHART_BAR_STACKED: + case LXW_CHART_BAR_STACKED_PERCENT: + _chart_write_bar_chart(self, type); + break; + + case LXW_CHART_COLUMN: + case LXW_CHART_COLUMN_STACKED: + case LXW_CHART_COLUMN_STACKED_PERCENT: + _chart_write_column_chart(self, type); + break; + + default: + LXW_WARN_FORMAT("workbook_add_chart(): " + "unhandled chart type '%d'", type); + } + } /* @@ -925,7 +1039,7 @@ _chart_write_plot_area(lxw_chart *self) _chart_write_layout(self); /* Write the subclass chart type elements for primary and secondary axes. */ - _chart_write_chart_type(self); + _chart_write_chart_type(self, self->type); } @@ -944,7 +1058,7 @@ _chart_write_chart(lxw_chart *self) _chart_write_cat_axis(self); /* Write the c:valAx element. */ - _chart_write_val_ax(self); + _chart_write_val_axis(self); lxw_xml_end_tag(self->file, "c:plotArea"); diff --git a/test/functional/src/test_chart_bar09.c b/test/functional/src/test_chart_bar09.c new file mode 100644 index 00000000..be3b1f81 --- /dev/null +++ b/test/functional/src/test_chart_bar09.c @@ -0,0 +1,42 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2015, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = new_workbook("test_chart_bar09.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + lxw_chart *chart = workbook_add_chart(workbook, LXW_CHART_BAR_STACKED); + + /* For testing, copy the randomly generated axis ids in the target file. */ + chart->axis_id_1 = 40274560; + chart->axis_id_2 = 40295040; + + uint8_t data[5][3] = { + {1, 2, 3}, + {2, 4, 6}, + {3, 6, 9}, + {4, 8, 12}, + {5, 10, 15} + }; + + int row, col; + for (row = 0; row < 5; row++) + for (col = 0; col < 3; col++) + worksheet_write_number(worksheet, row, col, data[row][col] , NULL); + + chart_add_series(chart, NULL, "=Sheet1!$A$1:$A$5"); + chart_add_series(chart, NULL, "=Sheet1!$B$1:$B$5"); + chart_add_series(chart, NULL, "=Sheet1!$C$1:$C$5"); + + worksheet_insert_chart(worksheet, CELL("E9"), chart); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_chart_bar10.c b/test/functional/src/test_chart_bar10.c new file mode 100644 index 00000000..d1753e70 --- /dev/null +++ b/test/functional/src/test_chart_bar10.c @@ -0,0 +1,42 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2015, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = new_workbook("test_chart_bar10.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + lxw_chart *chart = workbook_add_chart(workbook, LXW_CHART_BAR_STACKED_PERCENT); + + /* For testing, copy the randomly generated axis ids in the target file. */ + chart->axis_id_1 = 40274560; + chart->axis_id_2 = 40295040; + + uint8_t data[5][3] = { + {1, 2, 3}, + {2, 4, 6}, + {3, 6, 9}, + {4, 8, 12}, + {5, 10, 15} + }; + + int row, col; + for (row = 0; row < 5; row++) + for (col = 0; col < 3; col++) + worksheet_write_number(worksheet, row, col, data[row][col] , NULL); + + chart_add_series(chart, NULL, "=Sheet1!$A$1:$A$5"); + chart_add_series(chart, NULL, "=Sheet1!$B$1:$B$5"); + chart_add_series(chart, NULL, "=Sheet1!$C$1:$C$5"); + + worksheet_insert_chart(worksheet, CELL("E9"), chart); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_chart_column01.c b/test/functional/src/test_chart_column01.c new file mode 100644 index 00000000..849ceec2 --- /dev/null +++ b/test/functional/src/test_chart_column01.c @@ -0,0 +1,42 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2015, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = new_workbook("test_chart_column01.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + lxw_chart *chart = workbook_add_chart(workbook, LXW_CHART_COLUMN); + + /* For testing, copy the randomly generated axis ids in the target file. */ + chart->axis_id_1 = 43424000; + chart->axis_id_2 = 43434368; + + uint8_t data[5][3] = { + {1, 2, 3}, + {2, 4, 6}, + {3, 6, 9}, + {4, 8, 12}, + {5, 10, 15} + }; + + int row, col; + for (row = 0; row < 5; row++) + for (col = 0; col < 3; col++) + worksheet_write_number(worksheet, row, col, data[row][col] , NULL); + + chart_add_series(chart, NULL, "=Sheet1!$A$1:$A$5"); + chart_add_series(chart, NULL, "=Sheet1!$B$1:$B$5"); + chart_add_series(chart, NULL, "=Sheet1!$C$1:$C$5"); + + worksheet_insert_chart(worksheet, CELL("E9"), chart); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_chart_column02.c b/test/functional/src/test_chart_column02.c new file mode 100644 index 00000000..e1f12e64 --- /dev/null +++ b/test/functional/src/test_chart_column02.c @@ -0,0 +1,42 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2015, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = new_workbook("test_chart_column02.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + lxw_chart *chart = workbook_add_chart(workbook, LXW_CHART_COLUMN_STACKED); + + /* For testing, copy the randomly generated axis ids in the target file. */ + chart->axis_id_1 = 49388544; + chart->axis_id_2 = 69387008; + + uint8_t data[5][3] = { + {1, 2, 3}, + {2, 4, 6}, + {3, 6, 9}, + {4, 8, 12}, + {5, 10, 15} + }; + + int row, col; + for (row = 0; row < 5; row++) + for (col = 0; col < 3; col++) + worksheet_write_number(worksheet, row, col, data[row][col] , NULL); + + chart_add_series(chart, NULL, "=Sheet1!$A$1:$A$5"); + chart_add_series(chart, NULL, "=Sheet1!$B$1:$B$5"); + chart_add_series(chart, NULL, "=Sheet1!$C$1:$C$5"); + + worksheet_insert_chart(worksheet, CELL("E9"), chart); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_chart_column03.c b/test/functional/src/test_chart_column03.c new file mode 100644 index 00000000..79678aeb --- /dev/null +++ b/test/functional/src/test_chart_column03.c @@ -0,0 +1,42 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2015, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = new_workbook("test_chart_column03.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + lxw_chart *chart = workbook_add_chart(workbook, LXW_CHART_COLUMN_STACKED_PERCENT); + + /* For testing, copy the randomly generated axis ids in the target file. */ + chart->axis_id_1 = 49388544; + chart->axis_id_2 = 69387008; + + uint8_t data[5][3] = { + {1, 2, 3}, + {2, 4, 6}, + {3, 6, 9}, + {4, 8, 12}, + {5, 10, 15} + }; + + int row, col; + for (row = 0; row < 5; row++) + for (col = 0; col < 3; col++) + worksheet_write_number(worksheet, row, col, data[row][col] , NULL); + + chart_add_series(chart, NULL, "=Sheet1!$A$1:$A$5"); + chart_add_series(chart, NULL, "=Sheet1!$B$1:$B$5"); + chart_add_series(chart, NULL, "=Sheet1!$C$1:$C$5"); + + worksheet_insert_chart(worksheet, CELL("E9"), chart); + + return workbook_close(workbook); +} diff --git a/test/functional/test_chart_bar.py b/test/functional/test_chart_bar.py index 867bd17a..fb04a27e 100644 --- a/test/functional/test_chart_bar.py +++ b/test/functional/test_chart_bar.py @@ -28,6 +28,12 @@ class TestCompareXLSXFiles(base_test_class.XLSXBaseTest): def test_chart_bar05(self): self.run_exe_test('test_chart_bar05') + def test_chart_bar09(self): + self.run_exe_test('test_chart_bar09') + + def test_chart_bar10(self): + self.run_exe_test('test_chart_bar10') + def test_chart_bar51(self): self.run_exe_test('test_chart_bar51') diff --git a/test/functional/test_chart_column.py b/test/functional/test_chart_column.py new file mode 100644 index 00000000..8dbd96c8 --- /dev/null +++ b/test/functional/test_chart_column.py @@ -0,0 +1,23 @@ +############################################################################### +# +# Tests for libxlsxwriter. +# +# Copyright 2014-2016, John McNamara, jmcnamara@cpan.org +# + +import base_test_class + +class TestCompareXLSXFiles(base_test_class.XLSXBaseTest): + """ + Test file created with libxlsxwriter against a file created by Excel. + + """ + + def test_chart_column01(self): + self.run_exe_test('test_chart_column01') + + def test_chart_column02(self): + self.run_exe_test('test_chart_column02') + + def test_chart_column03(self): + self.run_exe_test('test_chart_column03') diff --git a/test/functional/xlsx_files/chart_bar09.xlsx b/test/functional/xlsx_files/chart_bar09.xlsx new file mode 100644 index 00000000..a0ab8969 Binary files /dev/null and b/test/functional/xlsx_files/chart_bar09.xlsx differ diff --git a/test/functional/xlsx_files/chart_bar10.xlsx b/test/functional/xlsx_files/chart_bar10.xlsx new file mode 100644 index 00000000..7ff2068b Binary files /dev/null and b/test/functional/xlsx_files/chart_bar10.xlsx differ diff --git a/test/functional/xlsx_files/chart_column01.xlsx b/test/functional/xlsx_files/chart_column01.xlsx new file mode 100644 index 00000000..728ac97b Binary files /dev/null and b/test/functional/xlsx_files/chart_column01.xlsx differ diff --git a/test/functional/xlsx_files/chart_column02.xlsx b/test/functional/xlsx_files/chart_column02.xlsx new file mode 100644 index 00000000..6acd6357 Binary files /dev/null and b/test/functional/xlsx_files/chart_column02.xlsx differ diff --git a/test/functional/xlsx_files/chart_column03.xlsx b/test/functional/xlsx_files/chart_column03.xlsx new file mode 100644 index 00000000..c0381305 Binary files /dev/null and b/test/functional/xlsx_files/chart_column03.xlsx differ