diff --git a/.indent.pro b/.indent.pro index 06299a25..1b790cc6 100644 --- a/.indent.pro +++ b/.indent.pro @@ -70,6 +70,7 @@ -T lxw_chart_gridline -T lxw_chart_label_position -T lxw_chart_label_separator +-T lxw_chart_layout -T lxw_chart_legend -T lxw_chart_legend_position -T lxw_chart_line diff --git a/docs/images/chart_layout.png b/docs/images/chart_layout.png new file mode 100644 index 00000000..8c3ca85f Binary files /dev/null and b/docs/images/chart_layout.png differ diff --git a/docs/src/working_with_charts.dox b/docs/src/working_with_charts.dox index 9c619557..b41c2d67 100644 --- a/docs/src/working_with_charts.dox +++ b/docs/src/working_with_charts.dox @@ -1232,6 +1232,66 @@ The following patterns #lxw_chart_pattern_type can be applied: @note If a pattern fill is used on a chart object it overrides the solid fill properties of the object. + +@section chart_layout Chart Layout + +Excel allows the user to positions chart objects like axis labels or the legend +is two ways. The first method is via standard positions such as top, bottom, +left and right. The `libxlsxwriter` library replicates this via enums such as +#lxw_chart_axis_label_position and #lxw_chart_legend_position and the associated +functions that use them. + +The second method Excel supports is manual positioning of elements such as the +chart axis labels, the chart legend, the chart plot area and the chart title. +The `libxlsxwriter` library replicates this type of positioning via the +#lxw_chart_layout struct: + +@code + lxw_chart_layout layout = { + .x = 0.13, + .y = 0.26, + .width = 0.73, + .height = 0.57, + }; +@endcode + +The layout units used by Excel are relative units expressed as a percentage of +the chart dimensions and are double values in the range `0.0 < x <= 1.0`. Excel +calculates these dimensions as shown below: + +@image html chart_layout.png + +With reference to the above figure the layout units are calculated as follows: + +```text + x = a / W + y = b / H +``` + +These units are cumbersome and can vary depending on other elements in the chart +such as text lengths. However, these are the units that are required by Excel to +allow relative positioning. Some trial and error is generally required. + +For the chart `chart_plotarea_set_layout()` and `chart_legend_set_layout()` +functions you can also set the width and height based on the following +calculation: + +```text + width = w / W + height = h / H +``` + +For other text based objects the width and height are changed by the font +dimensions. + +The chart functions that support #lxw_chart_layout are: + +- `chart_title_set_layout()` +- `chart_legend_set_layout()` +- `chart_plotarea_set_layout()` +- `chart_axis_set_name_layout()` + + @section ww_charts_limitations Chart Limitations The following chart features aren't currently supported in libxlsxwriter but diff --git a/include/xlsxwriter/chart.h b/include/xlsxwriter/chart.h index 8b4d1120..984932b5 100644 --- a/include/xlsxwriter/chart.h +++ b/include/xlsxwriter/chart.h @@ -737,6 +737,70 @@ typedef struct lxw_chart_font { } lxw_chart_font; +/** + * @brief Struct to represent Excel chart element layout dimensions. + * + * Excel supports manual positioning of elements such as the chart axis labels, + * the chart legend, the chart plot area and the chart title. The + * `lxw_chart_layout` struct represents the layout dimension for these elements. + * + * The layout units used by Excel are relative units expressed as a percentage + * of the chart dimensions and are double values in the range `0.0 < x <= 1.0`. + * Excel calculates these dimensions as shown below: + * + * @image html chart_layout.png + * + * With reference to the above figure the layout units are calculated as + * follows: + * + * ```text + * x = a / W + * y = b / H + * ``` + * + * These units are cumbersome and can vary depending on other elements in the + * chart such as text lengths. However, these are the units that are required by + * Excel to allow relative positioning. Some trial and error is generally + * required. + * + * For the chart `chart_plotarea_set_layout()` and `chart_legend_set_layout()` + * functions you can also set the width and height based on the following + * calculation: + * + * ```text + * width = w / W + * height = h / H + * ``` + * + * For other text based objects the width and height are changed by the font + * dimensions. + * + * The chart functions that support `lxw_chart_layout` are: + * + * - `chart_title_set_layout()` + * - `chart_legend_set_layout()` + * - `chart_plotarea_set_layout()` + * - `chart_axis_set_name_layout()` + * + */ +typedef struct lxw_chart_layout { + + /** The x offset in the range `0.0 < x <= 1.0` */ + double x; + + /** The y offset in the range `0.0 < y <= 1.0` */ + double y; + + /** The width of the plotarea or legend in the range `0.0 < x <= 1.0` */ + double width; + + /** The height of the plotarea or legend in the range `0.0 < x <= 1.0` */ + double height; + + uint8_t has_inner; + +} lxw_chart_layout; + typedef struct lxw_chart_marker { uint8_t type; @@ -751,6 +815,7 @@ typedef struct lxw_chart_legend { lxw_chart_font *font; uint8_t position; + lxw_chart_layout *layout; } lxw_chart_legend; @@ -763,12 +828,14 @@ typedef struct lxw_chart_title { uint8_t off; uint8_t is_horizontal; uint8_t ignore_cache; + uint8_t has_overlay; /* We use a range to hold the title formula properties even though it * will only have 1 point in order to re-use similar functions.*/ lxw_series_range *range; struct lxw_series_data_point data_point; + lxw_chart_layout *layout; } lxw_chart_title; @@ -861,6 +928,13 @@ enum lxw_chart_position { LXW_CHART_AXIS_BOTTOM }; +enum lxw_chart_layout_type { + LXW_CHART_LAYOUT_TITLE, + LXW_CHART_LAYOUT_LEGEND, + LXW_CHART_LAYOUT_PLOTAREA, + LXW_CHART_LAYOUT_AXIS_NAME +}; + /** * @brief Type/amount of data series error bar. */ @@ -1149,8 +1223,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; @@ -2503,6 +2579,18 @@ void chart_axis_set_name(lxw_chart_axis *axis, const char *name); void chart_axis_set_name_range(lxw_chart_axis *axis, const char *sheetname, lxw_row_t row, lxw_col_t col); +/** + * @brief Set the manual position of the chart axis name. + * + * @param axis A pointer to a chart #lxw_chart_axis object. + * @param layout A pointer to a chart #lxw_chart_layout struct. + * + * This function is used to simulate setting the manual position of a chart + * axis name. See @ref chart_layout for more information. + */ +void chart_axis_set_name_layout(lxw_chart_axis *axis, + lxw_chart_layout *layout); + /** * @brief Set the font properties for a chart axis name. * @@ -3279,6 +3367,25 @@ void chart_title_set_name_font(lxw_chart *chart, lxw_chart_font *font); */ void chart_title_off(lxw_chart *chart); +/** + * @brief Set the manual position of the chart title. + * + * @param chart Pointer to a lxw_chart instance to be configured. + * @param layout A pointer to a chart #lxw_chart_layout struct. + * + * This function is used to simulate setting the manual position of the chart + * title. See @ref chart_layout for more information. + */ +void chart_title_set_layout(lxw_chart *chart, lxw_chart_layout *layout); + +/** + * @brief + * + * @param chart + * @param overlay + */ +void chart_title_set_overlay(lxw_chart *chart, uint8_t overlay); + /** * @brief Set the position of the chart legend. * @@ -3317,6 +3424,17 @@ void chart_title_off(lxw_chart *chart); */ void chart_legend_set_position(lxw_chart *chart, uint8_t position); +/** + * @brief Set the manual 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. + * + * This function is used to simulate setting the manual position of the chart + * legend. See @ref chart_layout for more information. + */ +void chart_legend_set_layout(lxw_chart *chart, lxw_chart_layout *layout); + /** * @brief Set the font properties for a chart legend. * @@ -3484,6 +3602,17 @@ 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 manual layout of the chart plotarea. + * + * @param chart Pointer to a lxw_chart instance to be configured. + * @param layout A pointer to a chart #lxw_chart_layout struct. + * + * This function is used to simulate setting the manual position of the chart + * plotarea. See @ref chart_layout for more information. + */ +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..a50ce2e7 100644 --- a/src/chart.c +++ b/src/chart.c @@ -103,6 +103,29 @@ _chart_free_data_labels(lxw_chart_series *series) free(series->data_labels); } +STATIC void +_chart_free_axis(lxw_chart_axis *axis) +{ + if (!axis) + return; + + _chart_free_font(axis->num_font); + _chart_free_font(axis->title.font); + _chart_free_range(axis->title.range); + + free(axis->fill); + free(axis->line); + free(axis->pattern); + free(axis->title.name); + free(axis->title.layout); + free(axis->num_format); + free(axis->default_num_format); + free(axis->major_gridlines.line); + free(axis->minor_gridlines.line); + + free(axis); +} + /* * Free a series object. */ @@ -189,54 +212,30 @@ lxw_chart_free(lxw_chart *chart) free(chart->series_list); } - /* X Axis. */ - if (chart->x_axis) { - _chart_free_font(chart->x_axis->title.font); - _chart_free_font(chart->x_axis->num_font); - _chart_free_range(chart->x_axis->title.range); - free(chart->x_axis->title.name); - free(chart->x_axis->line); - free(chart->x_axis->fill); - free(chart->x_axis->pattern); - free(chart->x_axis->major_gridlines.line); - free(chart->x_axis->minor_gridlines.line); - free(chart->x_axis->num_format); - free(chart->x_axis->default_num_format); - free(chart->x_axis); - } - - /* Y Axis. */ - if (chart->y_axis) { - _chart_free_font(chart->y_axis->title.font); - _chart_free_font(chart->y_axis->num_font); - _chart_free_range(chart->y_axis->title.range); - free(chart->y_axis->title.name); - free(chart->y_axis->line); - free(chart->y_axis->fill); - free(chart->y_axis->pattern); - free(chart->y_axis->major_gridlines.line); - free(chart->y_axis->minor_gridlines.line); - free(chart->y_axis->num_format); - free(chart->y_axis->default_num_format); - free(chart->y_axis); - } + /* X and Y Axis. */ + _chart_free_axis(chart->x_axis); + _chart_free_axis(chart->y_axis); /* Chart title. */ _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 +448,44 @@ _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, + enum lxw_chart_layout_type type) +{ + lxw_chart_layout *layout = calloc(1, sizeof(struct lxw_chart_layout)); + RETURN_ON_MEM_ERROR(layout, NULL); + + /* Copy the user supplied properties. */ + switch (type) { + case LXW_CHART_LAYOUT_LEGEND: + layout->x = user_layout->x; + layout->y = user_layout->y; + layout->width = user_layout->width; + layout->height = user_layout->height; + layout->has_inner = LXW_FALSE; + break; + case LXW_CHART_LAYOUT_PLOTAREA: + layout->x = user_layout->x; + layout->y = user_layout->y; + layout->width = user_layout->width; + layout->height = user_layout->height; + layout->has_inner = LXW_TRUE; + break; + default: + layout->x = user_layout->x; + layout->y = user_layout->y; + layout->width = 0.0; + layout->height = 0.0; + layout->has_inner = LXW_FALSE; + break; + } + + return layout; +} + /* * Set a marker type for a series. */ @@ -645,13 +682,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"); + } } /* @@ -1525,6 +1650,23 @@ _chart_write_tx_rich(lxw_chart *self, char *name, uint8_t is_horizontal, lxw_xml_end_tag(self->file, "c:tx"); } +/* + * Write the element. + */ +STATIC void +_chart_write_overlay(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:overlay", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + /* * Write the element for rich strings. */ @@ -1538,7 +1680,11 @@ _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, title->layout); + + /* Write the c:overlay element. */ + if (title->has_overlay) + _chart_write_overlay(self); lxw_xml_end_tag(self->file, "c:title"); } @@ -1555,7 +1701,11 @@ _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, title->layout); + + /* Write the c:overlay element. */ + if (title->has_overlay) + _chart_write_overlay(self); /* Write the c:txPr element. */ _chart_write_tx_pr(self, title->is_horizontal, title->font); @@ -3693,23 +3843,6 @@ _chart_write_cross_between(lxw_chart *self, uint8_t position) LXW_FREE_ATTRIBUTES(); } -/* - * Write the element. - */ -STATIC void -_chart_write_overlay(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:overlay", &attributes); - - LXW_FREE_ATTRIBUTES(); -} - /* * Write the element. */ @@ -3796,7 +3929,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 +4870,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 +4902,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 +4923,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); @@ -6043,6 +6176,22 @@ chart_axis_set_name_range(lxw_chart_axis *axis, const char *sheetname, _chart_set_range(axis->title.range, sheetname, row, col, row, col); } +/* + * Set a layout for the chart axis name. + */ +void +chart_axis_set_name_layout(lxw_chart_axis *axis, lxw_chart_layout *layout) +{ + if (!layout) + return; + + /* Free any previously allocated resource. */ + free(axis->title.layout); + + axis->title.layout = + _chart_convert_layout_args(layout, LXW_CHART_LAYOUT_AXIS_NAME); +} + /* * Set an axis title/name font. */ @@ -6444,6 +6593,31 @@ 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, LXW_CHART_LAYOUT_TITLE); +} + +/* + * Overlay the chart title on the chart. + */ +void +chart_title_set_overlay(lxw_chart *self, uint8_t overlay) +{ + self->title.has_overlay = overlay; +} + /* * Set the chart legend position. */ @@ -6453,6 +6627,22 @@ 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, LXW_CHART_LAYOUT_LEGEND); +} + /* * Set the legend font. */ @@ -6584,6 +6774,23 @@ 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, LXW_CHART_LAYOUT_PLOTAREA); + +} + /* * 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/src/test_chart_layout05.c b/test/functional/src/test_chart_layout05.c new file mode 100644 index 00000000..5e4ea98d --- /dev/null +++ b/test/functional/src/test_chart_layout05.c @@ -0,0 +1,68 @@ +/***************************************************************************** + * 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_layout05.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + lxw_chart *chart = workbook_add_chart(workbook, LXW_CHART_AREA); + + /* For testing, copy the randomly generated axis ids in the target file. */ + chart->axis_id_1 = 43495808; + chart->axis_id_2 = 43497728; + + uint8_t data[5][3] = { + {1, 8, 3}, + {2, 7, 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" + ); + + + + lxw_chart_layout layout_x = { + .x = 0.346203193350831, + .y = 0.850902595508894, + }; + + chart_axis_set_name(chart->x_axis, "XXX"); + chart_axis_set_name_layout(chart->x_axis, &layout_x); + + + + lxw_chart_layout layout_y = { + .x = 0.213888888888888, + .y = 0.263499198016914, + }; + + chart_axis_set_name(chart->y_axis, "YYY"); + chart_axis_set_name_layout(chart->y_axis, &layout_y); + + worksheet_insert_chart(worksheet, CELL("E9"), chart); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_chart_layout06.c b/test/functional/src/test_chart_layout06.c new file mode 100644 index 00000000..a838e091 --- /dev/null +++ b/test/functional/src/test_chart_layout06.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_layout06.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 = 43496576; + chart->axis_id_2 = 45486080; + + 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.42354155730533, + .y = 0.16203703703703, + }; + + chart_title_set_name(chart, "Title"); + chart_title_set_layout(chart, &layout); + chart_title_set_overlay(chart, LXW_TRUE); + + worksheet_insert_chart(worksheet, CELL("E9"), chart); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_chart_layout07.c b/test/functional/src/test_chart_layout07.c new file mode 100644 index 00000000..21673c4e --- /dev/null +++ b/test/functional/src/test_chart_layout07.c @@ -0,0 +1,50 @@ +/***************************************************************************** + * 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_layout07.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 = 45861120; + chart->axis_id_2 = 45867008; + + 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.359652668416448, + .y = 0.1388888888888889, + }; + + chart_title_set_name_range(chart, "Sheet1", 0, 0); + chart_title_set_layout(chart, &layout); + + worksheet_insert_chart(worksheet, CELL("E9"), chart); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_chart_layout08.c b/test/functional/src/test_chart_layout08.c new file mode 100644 index 00000000..2f73b41e --- /dev/null +++ b/test/functional/src/test_chart_layout08.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_layout08.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 = 46317568; + chart->axis_id_2 = 46319488; + + 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.359652668416448, + .y = 0.162037037037037, + }; + + chart_title_set_name_range(chart, "Sheet1", 0, 0); + chart_title_set_layout(chart, &layout); + chart_title_set_overlay(chart, LXW_TRUE); + + 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..bfbefaed --- /dev/null +++ b/test/functional/test_chart_layout.py @@ -0,0 +1,40 @@ +############################################################################### +# +# 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') + + def test_chart_layout05(self): + self.run_exe_test('test_chart_layout05') + + def test_chart_layout06(self): + self.run_exe_test('test_chart_layout06') + + + def test_chart_layout07(self): + self.run_exe_test('test_chart_layout07') + + def test_chart_layout08(self): + self.run_exe_test('test_chart_layout08') \ No newline at end of file diff --git a/test/functional/xlsx_files/chart_layout01.xlsx b/test/functional/xlsx_files/chart_layout01.xlsx new file mode 100644 index 00000000..53296f65 Binary files /dev/null and b/test/functional/xlsx_files/chart_layout01.xlsx differ diff --git a/test/functional/xlsx_files/chart_layout02.xlsx b/test/functional/xlsx_files/chart_layout02.xlsx new file mode 100644 index 00000000..09e7e228 Binary files /dev/null and b/test/functional/xlsx_files/chart_layout02.xlsx differ diff --git a/test/functional/xlsx_files/chart_layout03.xlsx b/test/functional/xlsx_files/chart_layout03.xlsx new file mode 100644 index 00000000..c468ccdb Binary files /dev/null and b/test/functional/xlsx_files/chart_layout03.xlsx differ diff --git a/test/functional/xlsx_files/chart_layout04.xlsx b/test/functional/xlsx_files/chart_layout04.xlsx new file mode 100644 index 00000000..6e3d4af9 Binary files /dev/null and b/test/functional/xlsx_files/chart_layout04.xlsx differ diff --git a/test/functional/xlsx_files/chart_layout05.xlsx b/test/functional/xlsx_files/chart_layout05.xlsx new file mode 100644 index 00000000..813b99b2 Binary files /dev/null and b/test/functional/xlsx_files/chart_layout05.xlsx differ diff --git a/test/functional/xlsx_files/chart_layout06.xlsx b/test/functional/xlsx_files/chart_layout06.xlsx new file mode 100644 index 00000000..fe334a6c Binary files /dev/null and b/test/functional/xlsx_files/chart_layout06.xlsx differ diff --git a/test/functional/xlsx_files/chart_layout07.xlsx b/test/functional/xlsx_files/chart_layout07.xlsx new file mode 100644 index 00000000..2b39e5d4 Binary files /dev/null and b/test/functional/xlsx_files/chart_layout07.xlsx differ diff --git a/test/functional/xlsx_files/chart_layout08.xlsx b/test/functional/xlsx_files/chart_layout08.xlsx new file mode 100644 index 00000000..2dd2ab84 Binary files /dev/null and b/test/functional/xlsx_files/chart_layout08.xlsx differ