diff --git a/.indent.pro b/.indent.pro index fd6e35f4..45396d03 100644 --- a/.indent.pro +++ b/.indent.pro @@ -53,6 +53,7 @@ -T lxw_chart_font -T lxw_chart_legend -T lxw_chart_legend_position +-T lxw_chart_line -T lxw_chart_series -T lxw_chart_title -T lxw_chart_type diff --git a/include/xlsxwriter/chart.h b/include/xlsxwriter/chart.h index 3d343595..1e3637ad 100644 --- a/include/xlsxwriter/chart.h +++ b/include/xlsxwriter/chart.h @@ -225,6 +225,16 @@ typedef struct lxw_series_data_point { } lxw_series_data_point; +typedef struct lxw_chart_line { + + lxw_color_t color; + uint8_t none; + uint8_t dash_type; + uint8_t has_color; + float width; + +} lxw_chart_line; + typedef struct lxw_chart_fill { lxw_color_t color; @@ -305,6 +315,7 @@ typedef struct lxw_chart_series { lxw_series_range *categories; lxw_series_range *values; lxw_chart_title title; + lxw_chart_line *line; STAILQ_ENTRY (lxw_chart_series) list_pointers; @@ -605,6 +616,8 @@ void chart_series_set_name_range(lxw_chart_series *series, const char *sheetname, lxw_row_t row, lxw_col_t col); +void chart_series_set_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 e177f072..89c21adc 100644 --- a/src/chart.c +++ b/src/chart.c @@ -56,6 +56,7 @@ _chart_series_free(lxw_chart_series *series) return; free(series->title.name); + free(series->line); _chart_free_range(series->categories); _chart_free_range(series->values); @@ -242,6 +243,28 @@ _chart_convert_font_args(lxw_chart_font *user_font) return font; } +/* + * Create a copy of a user supplied line. + */ +STATIC lxw_chart_line * +_chart_convert_line_args(lxw_chart_line *user_line) +{ + lxw_chart_line *line; + + if (!user_line) + return NULL; + + line = calloc(1, sizeof(struct lxw_chart_line)); + RETURN_ON_MEM_ERROR(line, NULL); + + memcpy(line, user_line, sizeof(lxw_chart_line)); + + if (line->color) + line->has_color = LXW_TRUE; + + return line; +} + /* * Add unique ids for primary or secondary axes. */ @@ -519,15 +542,14 @@ _chart_write_a_srgb_clr(lxw_chart *self, lxw_color_t color, * Write the element. */ STATIC void -_chart_write_a_solid_fill(lxw_chart *self, lxw_chart_fill *fill) +_chart_write_a_solid_fill(lxw_chart *self, lxw_color_t color, + uint8_t transparency) { lxw_xml_start_tag(self->file, "a:solidFill", NULL); - if (fill->has_color) { - /* Write the a:srgbClr element. */ - _chart_write_a_srgb_clr(self, fill->color, fill->transparency); - } + /* Write the a:srgbClr element. */ + _chart_write_a_srgb_clr(self, color, transparency); lxw_xml_end_tag(self->file, "a:solidFill"); } @@ -600,11 +622,7 @@ _chart_write_a_def_rpr(lxw_chart *self, lxw_chart_font *font) lxw_xml_start_tag(self->file, "a:defRPr", &attributes); if (has_color) { - lxw_chart_fill fill = { 0, 0, 0 }; - - fill.color = font->color; - fill.has_color = LXW_TRUE; - _chart_write_a_solid_fill(self, &fill); + _chart_write_a_solid_fill(self, font->color, LXW_FALSE); } if (has_latin) { @@ -676,11 +694,7 @@ _chart_write_a_r_pr(lxw_chart *self, lxw_chart_font *font) lxw_xml_start_tag(self->file, "a:rPr", &attributes); if (has_color) { - lxw_chart_fill fill = { 0, 0, 0 }; - - fill.color = font->color; - fill.has_color = LXW_TRUE; - _chart_write_a_solid_fill(self, &fill); + _chart_write_a_solid_fill(self, font->color, LXW_FALSE); } if (has_latin) { @@ -1330,19 +1344,35 @@ _chart_write_a_no_fill(lxw_chart *self) * Write the element. */ STATIC void -_chart_write_a_ln(lxw_chart *self) +_chart_write_a_ln(lxw_chart *self, lxw_chart_line *line) { struct xml_attribute_list attributes; struct xml_attribute *attribute; - char w[] = "28575"; + float width_flt = line->width; + uint32_t width_int; LXW_INIT_ATTRIBUTES(); - LXW_PUSH_ATTRIBUTES_STR("w", w); + + /* Round width to nearest 0.25, like Excel. */ + width_flt = (float) (uint32_t) ((line->width + 0.125) * 4.0) / 4.0; + + /* Convert to internal units. */ + width_int = (uint32_t) (0.5 + (12700.0 * width_flt)); + + if (width_int) + LXW_PUSH_ATTRIBUTES_INT("w", width_int); lxw_xml_start_tag(self->file, "a:ln", &attributes); - /* Write the a:noFill element. */ - _chart_write_a_no_fill(self); + /* Write the line fill. */ + if (line->none) { + /* Write the a:noFill element. */ + _chart_write_a_no_fill(self); + } + else if (line->has_color) { + /* Write the a:solidFill element. */ + _chart_write_a_solid_fill(self, line->color, LXW_FALSE); + } lxw_xml_end_tag(self->file, "a:ln"); @@ -1353,12 +1383,16 @@ _chart_write_a_ln(lxw_chart *self) * Write the element. */ STATIC void -_chart_write_sp_pr(lxw_chart *self) +_chart_write_sp_pr(lxw_chart *self, lxw_chart_series *series) { + + if (!series->line) + return; + lxw_xml_start_tag(self->file, "c:spPr", NULL); /* Write the a:ln element. */ - _chart_write_a_ln(self); + _chart_write_a_ln(self, series->line); lxw_xml_end_tag(self->file, "c:spPr"); } @@ -1627,6 +1661,9 @@ _chart_write_ser(lxw_chart *self, lxw_chart_series *series) /* Write the series name. */ _chart_write_series_name(self, series); + /* Write the c:spPr element. */ + _chart_write_sp_pr(self, series); + /* Write the c:marker element. */ _chart_write_marker(self); @@ -1656,10 +1693,11 @@ _chart_write_xval_ser(lxw_chart *self, lxw_chart_series *series) /* Write the c:order element. */ _chart_write_order(self, index); - if (self->type == LXW_CHART_SCATTER) { - /* Write the c:spPr element. */ - _chart_write_sp_pr(self); - } + /* Write the series name. */ + _chart_write_series_name(self, series); + + /* Write the c:spPr element. */ + _chart_write_sp_pr(self, series); if (self->type == LXW_CHART_SCATTER_STRAIGHT || self->type == LXW_CHART_SCATTER_SMOOTH) { @@ -2501,6 +2539,17 @@ _chart_write_scatter_chart(lxw_chart *self) _chart_write_scatter_style(self); STAILQ_FOREACH(series, self->series_list, list_pointers) { + + /* Add default scatter chart formatting to the series data unless + * it has already been specified by the user.*/ + if (self->type == LXW_CHART_SCATTER) { + if (!series->line) { + lxw_chart_line line = + { 0x000000, LXW_TRUE, 0x0, LXW_FALSE, 2.25 }; + series->line = _chart_convert_line_args(&line); + } + } + /* Write the c:ser element. */ _chart_write_xval_ser(self, series); } @@ -3043,6 +3092,18 @@ chart_series_set_values(lxw_chart_series *series, const char *sheetname, first_row, first_col, last_row, last_col); } +/* + * Set a line type for a series. + */ +void +chart_series_set_line(lxw_chart_series *series, lxw_chart_line *line) +{ + if (!line) + return; + + series->line = _chart_convert_line_args(line); +} + /* * Set an axis caption. */ diff --git a/test/functional/src/test_chart_format01.c b/test/functional/src/test_chart_format01.c new file mode 100644 index 00000000..35bb6272 --- /dev/null +++ b/test/functional/src/test_chart_format01.c @@ -0,0 +1,48 @@ +/***************************************************************************** + * 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_format01.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 = 46335872; + chart->axis_id_2 = 46365696; + + 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, + "=Sheet1!$A$1:$A$5", + "=Sheet1!$B$1:$B$5" + ); + + chart_add_series(chart, + "=Sheet1!$A$1:$A$5", + "=Sheet1!$C$1:$C$5" + ); + + worksheet_insert_chart(worksheet, CELL("E9"), chart); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_chart_format02.c b/test/functional/src/test_chart_format02.c new file mode 100644 index 00000000..eb671ee4 --- /dev/null +++ b/test/functional/src/test_chart_format02.c @@ -0,0 +1,54 @@ +/***************************************************************************** + * 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_format02.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 = 46335872; + chart->axis_id_2 = 46365696; + + 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" + ); + + /* 'line': { 'color': "red" }, */ + lxw_chart_line line = {.color = LXW_COLOR_RED}; + + chart_series_set_line(series1, &line); + + + worksheet_insert_chart(worksheet, CELL("E9"), chart); + + return workbook_close(workbook); +} diff --git a/test/functional/test_chart_format.py b/test/functional/test_chart_format.py new file mode 100644 index 00000000..fe4632b7 --- /dev/null +++ b/test/functional/test_chart_format.py @@ -0,0 +1,20 @@ +############################################################################### +# +# 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_format01(self): + self.run_exe_test('test_chart_format01') + + def test_chart_format02(self): + self.run_exe_test('test_chart_format02') diff --git a/test/functional/xlsx_files/chart_format01.xlsx b/test/functional/xlsx_files/chart_format01.xlsx new file mode 100644 index 00000000..53d3c110 Binary files /dev/null and b/test/functional/xlsx_files/chart_format01.xlsx differ diff --git a/test/functional/xlsx_files/chart_format02.xlsx b/test/functional/xlsx_files/chart_format02.xlsx new file mode 100644 index 00000000..6431ea3d Binary files /dev/null and b/test/functional/xlsx_files/chart_format02.xlsx differ