diff --git a/.indent.pro b/.indent.pro index 8254648e..2cd06365 100644 --- a/.indent.pro +++ b/.indent.pro @@ -54,6 +54,9 @@ -T lxw_chart_axis_tick_mark -T lxw_chart_axis_tick_position -T lxw_chart_blank +-T lxw_chart_error_bar_cap +-T lxw_chart_error_bar_direction +-T lxw_chart_error_bar_type -T lxw_chart_fill -T lxw_chart_font -T lxw_chart_gridline @@ -108,6 +111,7 @@ -T lxw_row_t -T lxw_selection -T lxw_series_data_point +-T lxw_series_error_bar -T lxw_series_range -T lxw_sst -T lxw_styles diff --git a/include/xlsxwriter/chart.h b/include/xlsxwriter/chart.h index 5d471c2c..a4e5840e 100644 --- a/include/xlsxwriter/chart.h +++ b/include/xlsxwriter/chart.h @@ -569,28 +569,6 @@ typedef enum lxw_chart_axis_tick_mark { LXW_CHART_AXIS_TICK_MARK_CROSSING } lxw_chart_tick_mark; -/** - * @brief Define how blank values are displayed in a chart. - */ -typedef enum lxw_chart_blank { - - /** Show empty chart cells as gaps in the data. The default. */ - LXW_CHART_BLANKS_AS_GAP, - - /** Show empty chart cells as zeros. */ - LXW_CHART_BLANKS_AS_ZERO, - - /** Show empty chart cells as connected. Only for charts with lines. */ - LXW_CHART_BLANKS_AS_CONNECTED -} lxw_chart_blank; - -enum lxw_chart_position { - LXW_CHART_AXIS_RIGHT, - LXW_CHART_AXIS_LEFT, - LXW_CHART_AXIS_TOP, - LXW_CHART_AXIS_BOTTOM -}; - typedef struct lxw_series_range { char *formula; char *sheetname; @@ -776,6 +754,81 @@ typedef struct lxw_chart_point { } lxw_chart_point; +/** + * @brief Define how blank values are displayed in a chart. + */ +typedef enum lxw_chart_blank { + + /** Show empty chart cells as gaps in the data. The default. */ + LXW_CHART_BLANKS_AS_GAP, + + /** Show empty chart cells as zeros. */ + LXW_CHART_BLANKS_AS_ZERO, + + /** Show empty chart cells as connected. Only for charts with lines. */ + LXW_CHART_BLANKS_AS_CONNECTED +} lxw_chart_blank; + +enum lxw_chart_position { + LXW_CHART_AXIS_RIGHT, + LXW_CHART_AXIS_LEFT, + LXW_CHART_AXIS_TOP, + LXW_CHART_AXIS_BOTTOM +}; + +/** + * @brief Type/amount of data series error bar. + */ +typedef enum lxw_chart_error_bar_type { + /** Error bar type: Standard error. */ + LXW_CHART_ERROR_BAR_TYPE_STD_ERROR, + + /** Error bar type: Fixed value. */ + LXW_CHART_ERROR_BAR_TYPE_FIXED, + + /** Error bar type: Percentage. */ + LXW_CHART_ERROR_BAR_TYPE_PERCENTAGE, + + /** Error bar type: Standard deviation(s). */ + LXW_CHART_ERROR_BAR_TYPE_STD_DEV +} lxw_chart_error_bar_type; + +/** + * @brief Direction for a data series error bar. + */ +typedef enum lxw_chart_error_bar_direction { + + /** Error bar extends in both directions. The default. */ + LXW_CHART_ERROR_BAR_DIR_BOTH, + + /** Error bar extends in positive direction. */ + LXW_CHART_ERROR_BAR_DIR_PLUS, + + /** Error bar extends in negative direction. */ + LXW_CHART_ERROR_BAR_DIR_MINUS +} lxw_chart_error_bar_direction; + +/** + * @brief End cap styles for a data series error bar. + */ +typedef enum lxw_chart_error_bar_cap { + /** Flat end cap. The default. */ + LXW_CHART_ERROR_BAR_END_CAP, + + /** No end cap. */ + LXW_CHART_ERROR_BAR_NO_CAP +} lxw_chart_error_bar_cap; + +typedef struct lxw_series_error_bar { + uint8_t type; + uint8_t direction; + uint8_t endcap; + uint8_t has_value; + double value; + lxw_chart_line *line; + +} lxw_series_error_bar; + /** * @brief Struct to represent an Excel chart data series. * @@ -812,6 +865,11 @@ typedef struct lxw_chart_series { char *label_num_format; lxw_chart_font *label_font; + lxw_series_error_bar x_error_bar; + lxw_series_error_bar y_error_bar; + uint8_t has_x_error_bar; + uint8_t has_y_error_bar; + STAILQ_ENTRY (lxw_chart_series) list_pointers; } lxw_chart_series; @@ -1699,6 +1757,9 @@ void chart_series_set_labels_num_format(lxw_chart_series *series, void chart_series_set_labels_font(lxw_chart_series *series, lxw_chart_font *font); +void chart_series_set_y_error_bars_line(lxw_chart_series *series, + lxw_chart_line *line); + /** * @brief Set the name caption of the an axis. * diff --git a/src/chart.c b/src/chart.c index d8dcabfa..7cfd8441 100644 --- a/src/chart.c +++ b/src/chart.c @@ -110,6 +110,9 @@ _chart_series_free(lxw_chart_series *series) _chart_free_range(series->title.range); _chart_free_points(series); + free(series->x_error_bar.line); + free(series->y_error_bar.line); + free(series); } @@ -2239,6 +2242,161 @@ _chart_write_d_lbls(lxw_chart *self, lxw_chart_series *series) lxw_xml_end_tag(self->file, "c:dLbls"); } +/* + * Write the element. + */ +STATIC void +_chart_write_error_val(lxw_chart *self, double value) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_DBL("val", value); + + lxw_xml_empty_tag(self->file, "c:val", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_chart_write_no_end_cap(lxw_chart *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("val", "1"); + + lxw_xml_empty_tag(self->file, "c:noEndCap", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_chart_write_err_val_type(lxw_chart *self, uint8_t type) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + if (type == LXW_CHART_ERROR_BAR_TYPE_FIXED) + LXW_PUSH_ATTRIBUTES_STR("val", "fixedVal"); + else if (type == LXW_CHART_ERROR_BAR_TYPE_PERCENTAGE) + LXW_PUSH_ATTRIBUTES_STR("val", "percentage"); + else if (type == LXW_CHART_ERROR_BAR_TYPE_STD_DEV) + LXW_PUSH_ATTRIBUTES_STR("val", "stdDev"); + else + LXW_PUSH_ATTRIBUTES_STR("val", "stdErr"); + + lxw_xml_empty_tag(self->file, "c:errValType", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_chart_write_err_bar_type(lxw_chart *self, uint8_t direction) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + if (direction == LXW_CHART_ERROR_BAR_DIR_PLUS) + LXW_PUSH_ATTRIBUTES_STR("val", "plus"); + else if (direction == LXW_CHART_ERROR_BAR_DIR_MINUS) + LXW_PUSH_ATTRIBUTES_STR("val", "minus"); + else + LXW_PUSH_ATTRIBUTES_STR("val", "both"); + + lxw_xml_empty_tag(self->file, "c:errBarType", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_chart_write_err_dir(lxw_chart *self, uint8_t vertical) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + if (vertical) + LXW_PUSH_ATTRIBUTES_STR("val", "y"); + else + LXW_PUSH_ATTRIBUTES_STR("val", "n"); + + lxw_xml_empty_tag(self->file, "c:errDir", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_chart_write_err_bars(lxw_chart *self, uint8_t vertical, + lxw_chart_series *series) +{ + lxw_series_error_bar *error_bar; + + if (vertical) + error_bar = &series->y_error_bar; + else + error_bar = &series->x_error_bar; + + lxw_xml_start_tag(self->file, "c:errBars", NULL); + + /* Write the c:errDir element. */ + _chart_write_err_dir(self, vertical); + + /* Write the c:errBarType element. */ + _chart_write_err_bar_type(self, error_bar->direction); + + /* Write the c:errValType element. */ + _chart_write_err_val_type(self, error_bar->type); + + /* Write the c:noEndCap element. */ + if (error_bar->endcap == LXW_CHART_ERROR_BAR_NO_CAP) + _chart_write_no_end_cap(self); + + /* Write the c:val element. */ + if (error_bar->has_value) + _chart_write_error_val(self, error_bar->value); + + /* Write the c:spPr element. */ + _chart_write_sp_pr(self, error_bar->line, NULL, NULL); + + lxw_xml_end_tag(self->file, "c:errBars"); +} + +/* + * Write the element. + */ +STATIC void +_chart_write_error_bars(lxw_chart *self, lxw_chart_series *series) +{ + if (series->has_x_error_bar) + _chart_write_err_bars(self, LXW_FALSE, series); + + if (series->has_y_error_bar) + _chart_write_err_bars(self, LXW_TRUE, series); +} + /* * Write the element. */ @@ -2450,6 +2608,9 @@ _chart_write_ser(lxw_chart *self, lxw_chart_series *series) /* Write the c:dLbls element. */ _chart_write_d_lbls(self, series); + /* Write the c:errBars element. */ + _chart_write_error_bars(self, series); + /* Write the c:cat element. */ _chart_write_cat(self, series); @@ -2495,6 +2656,9 @@ _chart_write_xval_ser(lxw_chart *self, lxw_chart_series *series) /* Write the c:dLbls element. */ _chart_write_d_lbls(self, series); + /* Write the c:errBars element. */ + _chart_write_error_bars(self, series); + /* Write the c:xVal element. */ _chart_write_x_val(self, series); @@ -4947,6 +5111,22 @@ chart_series_set_labels_font(lxw_chart_series *series, lxw_chart_font *font) series->label_font = _chart_convert_font_args(font); } +/* + * Set a line type for a series marker. + */ +void +chart_series_set_y_error_bars_line(lxw_chart_series *series, + lxw_chart_line *line) +{ + if (!line) + return; + + /* Free any previously allocated resource. */ + free(series->y_error_bar.line); + + series->y_error_bar.line = _chart_convert_line_args(line); +} + /* * Set an axis caption. */ diff --git a/test/functional/src/test_chart_errorbars01.c b/test/functional/src/test_chart_errorbars01.c new file mode 100644 index 00000000..4ca5e8b1 --- /dev/null +++ b/test/functional/src/test_chart_errorbars01.c @@ -0,0 +1,51 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2017, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = new_workbook("test_chart_errorbars01.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + lxw_chart *chart = workbook_add_chart(workbook, LXW_CHART_LINE); + + /* For testing, copy the randomly generated axis ids in the target file. */ + chart->axis_id_1 = 63386752; + chart->axis_id_2 = 63388288; + + 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); + + lxw_chart_series *series1 = chart_add_series(chart, + "=Sheet1!$A$1:$A$5", + "=Sheet1!$B$1:$B$5" + ); + + chart_add_series(chart, + "=Sheet1!$A$1:$A$5", + "=Sheet1!$C$1:$C$5" + ); + + + series1->has_y_error_bar = 1; + + worksheet_insert_chart(worksheet, CELL("E9"), chart); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_chart_errorbars02.c b/test/functional/src/test_chart_errorbars02.c new file mode 100644 index 00000000..2c454e1d --- /dev/null +++ b/test/functional/src/test_chart_errorbars02.c @@ -0,0 +1,62 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2017, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = new_workbook("test_chart_errorbars02.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + lxw_chart *chart = workbook_add_chart(workbook, LXW_CHART_LINE); + + /* For testing, copy the randomly generated axis ids in the target file. */ + chart->axis_id_1 = 63385984; + chart->axis_id_2 = 63387904; + + 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); + + lxw_chart_series *series1 = chart_add_series(chart, + "=Sheet1!$A$1:$A$5", + "=Sheet1!$B$1:$B$5" + ); + + lxw_chart_series *series2 = chart_add_series(chart, + "=Sheet1!$A$1:$A$5", + "=Sheet1!$C$1:$C$5" + ); + + series1->has_y_error_bar = 1; + series1->y_error_bar.type = LXW_CHART_ERROR_BAR_TYPE_FIXED; + series1->y_error_bar.direction = LXW_CHART_ERROR_BAR_DIR_MINUS; + series1->y_error_bar.endcap = LXW_CHART_ERROR_BAR_NO_CAP; + series1->y_error_bar.has_value = 1; + series1->y_error_bar.value = 2; + + series2->has_y_error_bar = 1; + series2->y_error_bar.type = LXW_CHART_ERROR_BAR_TYPE_PERCENTAGE; + series2->y_error_bar.direction = LXW_CHART_ERROR_BAR_DIR_PLUS; + series2->y_error_bar.endcap = LXW_CHART_ERROR_BAR_END_CAP; + series2->y_error_bar.has_value = 1; + series2->y_error_bar.value = 5; + + worksheet_insert_chart(worksheet, CELL("E9"), chart); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_chart_errorbars03.c b/test/functional/src/test_chart_errorbars03.c new file mode 100644 index 00000000..ec7af065 --- /dev/null +++ b/test/functional/src/test_chart_errorbars03.c @@ -0,0 +1,56 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2017, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = new_workbook("test_chart_errorbars03.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + lxw_chart *chart = workbook_add_chart(workbook, LXW_CHART_LINE); + + /* For testing, copy the randomly generated axis ids in the target file. */ + chart->axis_id_1 = 52288896; + chart->axis_id_2 = 53605504; + + 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); + + lxw_chart_series *series1 = chart_add_series(chart, + "=Sheet1!$A$1:$A$5", + "=Sheet1!$B$1:$B$5" + ); + + chart_add_series(chart, + "=Sheet1!$A$1:$A$5", + "=Sheet1!$C$1:$C$5" + ); + + + lxw_chart_line line = {.color = LXW_COLOR_RED, + .dash_type = LXW_CHART_LINE_DASH_ROUND_DOT}; + + series1->has_y_error_bar = 1; + series1->y_error_bar.type = LXW_CHART_ERROR_BAR_TYPE_STD_ERROR; + chart_series_set_y_error_bars_line(series1, &line); + + worksheet_insert_chart(worksheet, CELL("E9"), chart); + + return workbook_close(workbook); +} diff --git a/test/functional/test_chart_errorbars.py b/test/functional/test_chart_errorbars.py new file mode 100644 index 00000000..b0a765ab --- /dev/null +++ b/test/functional/test_chart_errorbars.py @@ -0,0 +1,23 @@ +############################################################################### +# +# Tests for libxlsxwriter. +# +# Copyright 2014-2017, 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_errorbars01(self): + self.run_exe_test('test_chart_errorbars01') + + def test_chart_errorbars02(self): + self.run_exe_test('test_chart_errorbars02') + + def test_chart_errorbars03(self): + self.run_exe_test('test_chart_errorbars03') diff --git a/test/functional/xlsx_files/chart_errorbars01.xlsx b/test/functional/xlsx_files/chart_errorbars01.xlsx new file mode 100644 index 00000000..c8e607f8 Binary files /dev/null and b/test/functional/xlsx_files/chart_errorbars01.xlsx differ diff --git a/test/functional/xlsx_files/chart_errorbars02.xlsx b/test/functional/xlsx_files/chart_errorbars02.xlsx new file mode 100644 index 00000000..2c78325f Binary files /dev/null and b/test/functional/xlsx_files/chart_errorbars02.xlsx differ diff --git a/test/functional/xlsx_files/chart_errorbars03.xlsx b/test/functional/xlsx_files/chart_errorbars03.xlsx new file mode 100644 index 00000000..1b927d4f Binary files /dev/null and b/test/functional/xlsx_files/chart_errorbars03.xlsx differ