From 7dbdad43a56f3530bcc4598cd45531d512009530 Mon Sep 17 00:00:00 2001 From: John McNamara Date: Fri, 28 Mar 2025 00:33:17 +0000 Subject: [PATCH] chart: add initial layout support Feature: #477 --- include/xlsxwriter/chart.h | 53 ++++++ src/chart.c | 176 +++++++++++++++++- test/functional/src/test_chart_layout01.c | 51 +++++ test/functional/src/test_chart_layout02.c | 51 +++++ test/functional/src/test_chart_layout03.c | 52 ++++++ test/functional/src/test_chart_layout04.c | 52 ++++++ test/functional/test_chart_layout.py | 27 +++ .../functional/xlsx_files/chart_layout01.xlsx | Bin 0 -> 7862 bytes .../functional/xlsx_files/chart_layout02.xlsx | Bin 0 -> 7867 bytes .../functional/xlsx_files/chart_layout03.xlsx | Bin 0 -> 7871 bytes .../functional/xlsx_files/chart_layout04.xlsx | Bin 0 -> 7905 bytes 11 files changed, 453 insertions(+), 9 deletions(-) create mode 100644 test/functional/src/test_chart_layout01.c create mode 100644 test/functional/src/test_chart_layout02.c create mode 100644 test/functional/src/test_chart_layout03.c create mode 100644 test/functional/src/test_chart_layout04.c create mode 100644 test/functional/test_chart_layout.py create mode 100644 test/functional/xlsx_files/chart_layout01.xlsx create mode 100644 test/functional/xlsx_files/chart_layout02.xlsx create mode 100644 test/functional/xlsx_files/chart_layout03.xlsx create mode 100644 test/functional/xlsx_files/chart_layout04.xlsx diff --git a/include/xlsxwriter/chart.h b/include/xlsxwriter/chart.h index 8b4d1120..d255e5bb 100644 --- a/include/xlsxwriter/chart.h +++ b/include/xlsxwriter/chart.h @@ -737,6 +737,29 @@ typedef struct lxw_chart_font { } lxw_chart_font; +/** + * @brief Struct to represent Excel chart element layout dimensions. + * + * todo: + */ +typedef struct lxw_chart_layout { + + /** TODO:*/ + double x; + + /** TODO:*/ + double y; + + /** TODO:*/ + double width; + + /** TODO:*/ + double height; + + uint8_t has_inner; + +} lxw_chart_layout; + typedef struct lxw_chart_marker { uint8_t type; @@ -751,6 +774,7 @@ typedef struct lxw_chart_legend { lxw_chart_font *font; uint8_t position; + lxw_chart_layout *layout; } lxw_chart_legend; @@ -769,6 +793,7 @@ typedef struct lxw_chart_title { lxw_series_range *range; struct lxw_series_data_point data_point; + lxw_chart_layout *layout; } lxw_chart_title; @@ -1149,8 +1174,10 @@ typedef struct lxw_chart { lxw_chart_line *chartarea_line; lxw_chart_fill *chartarea_fill; lxw_chart_pattern *chartarea_pattern; + lxw_chart_line *plotarea_line; lxw_chart_fill *plotarea_fill; + lxw_chart_layout *plotarea_layout; lxw_chart_pattern *plotarea_pattern; uint8_t has_drop_lines; @@ -3279,6 +3306,14 @@ void chart_title_set_name_font(lxw_chart *chart, lxw_chart_font *font); */ void chart_title_off(lxw_chart *chart); +/** + * @brief TODO: Add description. + * + * @param chart Pointer to a lxw_chart instance to be configured. + * @param layout A pointer to a chart #lxw_chart_layout struct. + */ +void chart_title_set_layout(lxw_chart *self, lxw_chart_layout * layout); + /** * @brief Set the position of the chart legend. * @@ -3317,6 +3352,16 @@ void chart_title_off(lxw_chart *chart); */ void chart_legend_set_position(lxw_chart *chart, uint8_t position); +/** + * @brief Set the layout of the chart legend. + * + * @param chart Pointer to a lxw_chart instance to be configured. + * @param layout A pointer to a chart #lxw_chart_layout struct. + * + * TODO: Add example and image. + */ +void chart_legend_set_layout(lxw_chart *chart, lxw_chart_layout * layout); + /** * @brief Set the font properties for a chart legend. * @@ -3484,6 +3529,14 @@ void chart_plotarea_set_fill(lxw_chart *chart, lxw_chart_fill *fill); */ void chart_plotarea_set_pattern(lxw_chart *chart, lxw_chart_pattern *pattern); +/** + * @brief Set the layout of a plotarea. TODO: + * + * @param chart + * @param layout + */ +void chart_plotarea_set_layout(lxw_chart *chart, lxw_chart_layout * layout); + /** * @brief Set the chart style type. * diff --git a/src/chart.c b/src/chart.c index 7ff6cd27..abb9a892 100644 --- a/src/chart.c +++ b/src/chart.c @@ -225,18 +225,22 @@ lxw_chart_free(lxw_chart *chart) _chart_free_font(chart->title.font); _chart_free_range(chart->title.range); free(chart->title.name); + free(chart->title.layout); /* Chart legend. */ _chart_free_font(chart->legend.font); - free(chart->delete_series); + free(chart->legend.layout); + free(chart->delete_series); free(chart->default_marker); free(chart->chartarea_line); free(chart->chartarea_fill); free(chart->chartarea_pattern); + free(chart->plotarea_line); free(chart->plotarea_fill); + free(chart->plotarea_layout); free(chart->plotarea_pattern); free(chart->drop_lines_line); @@ -449,6 +453,24 @@ _chart_convert_pattern_args(lxw_chart_pattern *user_pattern) return pattern; } +/* + * Create a copy of a user supplied layout. + */ +STATIC lxw_chart_layout * +_chart_convert_layout_args(lxw_chart_layout *user_layout) +{ + lxw_chart_layout *layout = calloc(1, sizeof(struct lxw_chart_layout)); + RETURN_ON_MEM_ERROR(layout, NULL); + + /* Copy the user supplied properties. */ + layout->x = user_layout->x; + layout->y = user_layout->y; + layout->width = user_layout->width; + layout->height = user_layout->height; + + return layout; +} + /* * Set a marker type for a series. */ @@ -645,13 +667,101 @@ _chart_write_style(lxw_chart *self) LXW_FREE_ATTRIBUTES(); } +/* + * Write the element. + */ +STATIC void +_chart_write_layout_target(lxw_chart *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("val", "inner"); + + lxw_xml_empty_tag(self->file, "c:layoutTarget", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the and element. + */ +STATIC void +_chart_write_layout_mode(lxw_chart *self, char *mode) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("val", "edge"); + + lxw_xml_empty_tag(self->file, mode, &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the layout dimension elements. + */ +STATIC void +_chart_write_layout_dimension(lxw_chart *self, char *dimension, 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, dimension, &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_chart_write_manual_layout(lxw_chart *self, lxw_chart_layout *layout) +{ + lxw_xml_start_tag(self->file, "c:manualLayout", NULL); + + /* Write the c:layoutTarget element. */ + if (layout->has_inner) + _chart_write_layout_target(self); + + /* Write the c:xMode and c:yMode elements. */ + _chart_write_layout_mode(self, "c:xMode"); + _chart_write_layout_mode(self, "c:yMode"); + + /* Write the dimension elements. */ + _chart_write_layout_dimension(self, "c:x", layout->x); + _chart_write_layout_dimension(self, "c:y", layout->y); + if (layout->width > 0.0) + _chart_write_layout_dimension(self, "c:w", layout->width); + if (layout->height > 0.0) + _chart_write_layout_dimension(self, "c:h", layout->height); + + lxw_xml_end_tag(self->file, "c:manualLayout"); +} + /* * Write the element. */ STATIC void -_chart_write_layout(lxw_chart *self) +_chart_write_layout(lxw_chart *self, lxw_chart_layout *layout) { - lxw_xml_empty_tag(self->file, "c:layout", NULL); + if (layout == NULL) { + lxw_xml_empty_tag(self->file, "c:layout", NULL); + } + else { + lxw_xml_start_tag(self->file, "c:layout", NULL); + + /* Write the c:manualLayout element. */ + _chart_write_manual_layout(self, layout); + + lxw_xml_end_tag(self->file, "c:layout"); + } } /* @@ -1538,7 +1648,7 @@ _chart_write_title_rich(lxw_chart *self, lxw_chart_title *title) title->font); /* Write the c:layout element. */ - _chart_write_layout(self); + _chart_write_layout(self, self->title.layout); lxw_xml_end_tag(self->file, "c:title"); } @@ -1555,7 +1665,7 @@ _chart_write_title_formula(lxw_chart *self, lxw_chart_title *title) _chart_write_tx_formula(self, title); /* Write the c:layout element. */ - _chart_write_layout(self); + _chart_write_layout(self, self->title.layout); /* Write the c:txPr element. */ _chart_write_tx_pr(self, title->is_horizontal, title->font); @@ -3796,7 +3906,7 @@ _chart_write_legend(lxw_chart *self) } /* Write the c:layout element. */ - _chart_write_layout(self); + _chart_write_layout(self, self->legend.layout); if (self->chart_group == LXW_CHART_PIE || self->chart_group == LXW_CHART_DOUGHNUT) { @@ -4737,7 +4847,7 @@ _chart_write_scatter_plot_area(lxw_chart *self) lxw_xml_start_tag(self->file, "c:plotArea", NULL); /* Write the c:layout element. */ - _chart_write_layout(self); + _chart_write_layout(self, self->plotarea_layout); /* Write subclass chart type elements for primary and secondary axes. */ self->write_chart_type(self); @@ -4769,7 +4879,7 @@ _chart_write_pie_plot_area(lxw_chart *self) lxw_xml_start_tag(self->file, "c:plotArea", NULL); /* Write the c:layout element. */ - _chart_write_layout(self); + _chart_write_layout(self, self->plotarea_layout); /* Write subclass chart type elements for primary and secondary axes. */ self->write_chart_type(self); @@ -4790,7 +4900,7 @@ _chart_write_plot_area(lxw_chart *self) lxw_xml_start_tag(self->file, "c:plotArea", NULL); /* Write the c:layout element. */ - _chart_write_layout(self); + _chart_write_layout(self, self->plotarea_layout); /* Write subclass chart type elements for primary and secondary axes. */ self->write_chart_type(self); @@ -6444,6 +6554,21 @@ chart_title_off(lxw_chart *self) self->title.off = LXW_TRUE; } +/* + * Set a layout for the chart title. + */ +void +chart_title_set_layout(lxw_chart *self, lxw_chart_layout *layout) +{ + if (!layout) + return; + + /* Free any previously allocated resource. */ + free(self->title.layout); + + self->title.layout = _chart_convert_layout_args(layout); +} + /* * Set the chart legend position. */ @@ -6453,6 +6578,21 @@ chart_legend_set_position(lxw_chart *self, uint8_t position) self->legend.position = position; } +/* + * Set a layout for the chart legend. + */ +void +chart_legend_set_layout(lxw_chart *self, lxw_chart_layout *layout) +{ + if (!layout) + return; + + /* Free any previously allocated resource. */ + free(self->legend.layout); + + self->legend.layout = _chart_convert_layout_args(layout); +} + /* * Set the legend font. */ @@ -6584,6 +6724,24 @@ chart_plotarea_set_pattern(lxw_chart *self, lxw_chart_pattern *pattern) self->plotarea_pattern = _chart_convert_pattern_args(pattern); } +/* + * Set a layout for the plotarea. + */ +void +chart_plotarea_set_layout(lxw_chart *self, lxw_chart_layout *layout) +{ + if (!layout) + return; + + /* Free any previously allocated resource. */ + free(self->plotarea_layout); + + self->plotarea_layout = _chart_convert_layout_args(layout); + + /* Plotarea has an additional layout field. */ + self->plotarea_layout->has_inner = LXW_TRUE; +} + /* * Turn on the chart data table. */ diff --git a/test/functional/src/test_chart_layout01.c b/test/functional/src/test_chart_layout01.c new file mode 100644 index 00000000..2d65c7ac --- /dev/null +++ b/test/functional/src/test_chart_layout01.c @@ -0,0 +1,51 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2025, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_chart_layout01.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 = 69198592; + chart->axis_id_2 = 69200128; + + 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"); + + lxw_chart_layout layout = { + .x = 0.13171062992125, + .y = 0.26436351706036, + .width = 0.73970734908136, + .height = 0.5713732137649, + }; + + chart_plotarea_set_layout(chart, &layout); + + worksheet_insert_chart(worksheet, CELL("E9"), chart); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_chart_layout02.c b/test/functional/src/test_chart_layout02.c new file mode 100644 index 00000000..d245bd41 --- /dev/null +++ b/test/functional/src/test_chart_layout02.c @@ -0,0 +1,51 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2025, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_chart_layout02.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 = 68311296; + chart->axis_id_2 = 69198208; + + 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"); + + lxw_chart_layout layout = { + .x = 0.80197353455818, + .y = 0.37442403032954, + .width = 0.12858202099737, + .height = 0.25115157480314, + }; + + chart_legend_set_layout(chart, &layout); + + worksheet_insert_chart(worksheet, CELL("E9"), chart); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_chart_layout03.c b/test/functional/src/test_chart_layout03.c new file mode 100644 index 00000000..3d2e3b96 --- /dev/null +++ b/test/functional/src/test_chart_layout03.c @@ -0,0 +1,52 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2025, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_chart_layout03.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 = 68312064; + chart->axis_id_2 = 69198592; + + 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"); + + lxw_chart_layout layout = { + .x = 0.80197353455818, + .y = 0.37442403032954, + .width = 0.12858202099737, + .height = 0.25115157480314, + }; + + chart_legend_set_layout(chart, &layout); + chart_legend_set_position(chart, LXW_CHART_LEGEND_OVERLAY_RIGHT); + + worksheet_insert_chart(worksheet, CELL("E9"), chart); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_chart_layout04.c b/test/functional/src/test_chart_layout04.c new file mode 100644 index 00000000..02ae3988 --- /dev/null +++ b/test/functional/src/test_chart_layout04.c @@ -0,0 +1,52 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2025, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_chart_layout04.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 = 68311296; + chart->axis_id_2 = 69198208; + + 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"); + + + lxw_chart_layout layout = { + .x = 0.426319335083114, + .y = 0.143518518518518, + }; + + chart_title_set_layout(chart, &layout); + chart_title_set_name(chart, "Title"); + + + worksheet_insert_chart(worksheet, CELL("E9"), chart); + + return workbook_close(workbook); +} diff --git a/test/functional/test_chart_layout.py b/test/functional/test_chart_layout.py new file mode 100644 index 00000000..e04e2b46 --- /dev/null +++ b/test/functional/test_chart_layout.py @@ -0,0 +1,27 @@ +############################################################################### +# +# Tests for libxlsxwriter. +# +# SPDX-License-Identifier: BSD-2-Clause +# Copyright 2014-2025, 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_layout01(self): + self.run_exe_test('test_chart_layout01') + + def test_chart_layout02(self): + self.run_exe_test('test_chart_layout02') + + def test_chart_layout03(self): + self.run_exe_test('test_chart_layout03') + + def test_chart_layout04(self): + self.run_exe_test('test_chart_layout04') diff --git a/test/functional/xlsx_files/chart_layout01.xlsx b/test/functional/xlsx_files/chart_layout01.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..53296f65cb9749239372c85ab0360f5430b359ae GIT binary patch literal 7862 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 literal 0 HcmV?d00001 diff --git a/test/functional/xlsx_files/chart_layout02.xlsx b/test/functional/xlsx_files/chart_layout02.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..09e7e2284b2ccecc7192b87723d0d00b8c43407d GIT binary patch literal 7867 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