libxlsxwriter/src/chart.c in fast_excel-0.4.1 vs libxlsxwriter/src/chart.c in fast_excel-0.5.0

- old
+ new

@@ -1,11 +1,11 @@ /***************************************************************************** * chart - A library for creating Excel XLSX chart files. * * Used in conjunction with the libxlsxwriter library. * - * Copyright 2014-2019, John McNamara, jmcnamara@cpan.org. See LICENSE.txt. + * Copyright 2014-2022, John McNamara, jmcnamara@cpan.org. See LICENSE.txt. * */ #include "xlsxwriter/xmlwriter.h" #include "xlsxwriter/chart.h" @@ -76,14 +76,34 @@ _chart_free_font(lxw_chart_font *font) { if (!font) return; - free(font->name); + free((void *) font->name); free(font); } +STATIC void +_chart_free_data_labels(lxw_chart_series *series) +{ + uint16_t index; + + for (index = 0; index < series->data_label_count; index++) { + lxw_chart_custom_label *data_label = &series->data_labels[index]; + + free(data_label->value); + _chart_free_range(data_label->range); + _chart_free_font(data_label->font); + free(data_label->line); + free(data_label->fill); + free(data_label->pattern); + } + + series->data_label_count = 0; + free(series->data_labels); +} + /* * Free a series object. */ STATIC void _chart_series_free(lxw_chart_series *series) @@ -94,10 +114,14 @@ free(series->title.name); free(series->line); free(series->fill); free(series->pattern); free(series->label_num_format); + free(series->label_line); + free(series->label_fill); + free(series->label_pattern); + _chart_free_font(series->label_font); if (series->marker) { free(series->marker->line); free(series->marker->fill); @@ -107,10 +131,11 @@ _chart_free_range(series->categories); _chart_free_range(series->values); _chart_free_range(series->title.range); _chart_free_points(series); + _chart_free_data_labels(series); if (series->x_error_bars) { free(series->x_error_bars->line); free(series->x_error_bars); } @@ -329,15 +354,10 @@ /* Convert rotation into 60,000ths of a degree. */ if (font->rotation) font->rotation = font->rotation * 60000; - if (font->color) { - font->color = lxw_format_check_color(font->color); - font->has_color = LXW_TRUE; - } - return font; } /* * Create a copy of a user supplied line. @@ -358,15 +378,10 @@ line->none = user_line->none; line->width = user_line->width; line->dash_type = user_line->dash_type; line->transparency = user_line->transparency; - if (line->color) { - line->color = lxw_format_check_color(line->color); - line->has_color = LXW_TRUE; - } - if (line->transparency > 100) line->transparency = 0; return line; } @@ -388,15 +403,10 @@ /* Copy the user supplied properties. */ fill->color = user_fill->color; fill->none = user_fill->none; fill->transparency = user_fill->transparency; - if (fill->color) { - fill->color = lxw_format_check_color(fill->color); - fill->has_color = LXW_TRUE; - } - if (fill->transparency > 100) fill->transparency = 0; return fill; } @@ -428,21 +438,13 @@ /* Copy the user supplied properties. */ pattern->fg_color = user_pattern->fg_color; pattern->bg_color = user_pattern->bg_color; pattern->type = user_pattern->type; - pattern->fg_color = lxw_format_check_color(pattern->fg_color); - pattern->has_fg_color = LXW_TRUE; - - if (pattern->bg_color) { - pattern->bg_color = lxw_format_check_color(pattern->bg_color); - pattern->has_bg_color = LXW_TRUE; - } - else { + if (!pattern->bg_color) { /* Default background color in Excel is white, when unspecified. */ pattern->bg_color = LXW_COLOR_WHITE; - pattern->has_bg_color = LXW_TRUE; } return pattern; } @@ -462,11 +464,11 @@ } /* * Set an axis number format. */ -void +STATIC void _chart_axis_set_default_num_format(lxw_chart_axis *axis, char *num_format) { if (!num_format) return; @@ -855,11 +857,11 @@ uint8_t use_font_default = LXW_FALSE; LXW_INIT_ATTRIBUTES(); if (font) { - has_color = font->color || font->has_color; + has_color = !!font->color; has_latin = font->name || font->pitch_family || font->charset; use_font_default = !(has_color || has_latin || font->baseline == -1); /* Set the font attributes. */ if (font->size > 0.0) @@ -927,11 +929,11 @@ LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_STR("lang", "en-US"); if (font) { - has_color = font->color || font->has_color; + has_color = !!font->color; has_latin = font->name || font->pitch_family || font->charset; use_font_default = !(has_color || has_latin || font->baseline == -1); /* Set the font attributes. */ if (font->size > 0.0) @@ -1088,16 +1090,18 @@ /* * Write the <a:p> element. */ STATIC void -_chart_write_a_p_rich(lxw_chart *self, char *name, lxw_chart_font *font) +_chart_write_a_p_rich(lxw_chart *self, char *name, lxw_chart_font *font, + uint8_t ignore_rich_pr) { lxw_xml_start_tag(self->file, "a:p", NULL); /* Write the a:pPr element. */ - _chart_write_a_p_pr_rich(self, font); + if (!ignore_rich_pr) + _chart_write_a_p_pr_rich(self, font); /* Write the a:r element. */ _chart_write_a_r(self, name, font); lxw_xml_end_tag(self->file, "a:p"); @@ -1125,16 +1129,32 @@ LXW_INIT_ATTRIBUTES(); if (rotation == 0 && is_horizontal) rotation = -5400000; - if (rotation) - LXW_PUSH_ATTRIBUTES_INT("rot", rotation); + if (rotation) { + if (rotation == 16200000) { + /* 270 deg/stacked angle. */ + LXW_PUSH_ATTRIBUTES_STR("rot", "0"); + LXW_PUSH_ATTRIBUTES_STR("vert", "wordArtVert"); + } + else if (rotation == 16260000) { + /* 271 deg/East Asian vertical. */ + LXW_PUSH_ATTRIBUTES_STR("rot", "0"); + LXW_PUSH_ATTRIBUTES_STR("vert", "eaVert"); + } + else if (rotation == 21600000) { + /* 360 deg = 0 for y axis. */ + LXW_PUSH_ATTRIBUTES_STR("rot", "0"); + LXW_PUSH_ATTRIBUTES_STR("vert", "horz"); + } + else { + LXW_PUSH_ATTRIBUTES_INT("rot", rotation); + LXW_PUSH_ATTRIBUTES_STR("vert", "horz"); + } + } - if (is_horizontal) - LXW_PUSH_ATTRIBUTES_STR("vert", "horz"); - lxw_xml_empty_tag(self->file, "a:bodyPr", &attributes); LXW_FREE_ATTRIBUTES(); } @@ -1464,12 +1484,12 @@ /* * Write the <c:rich> element. */ STATIC void -_chart_write_rich(lxw_chart *self, char *name, uint8_t is_horizontal, - lxw_chart_font *font) +_chart_write_rich(lxw_chart *self, char *name, lxw_chart_font *font, + uint8_t is_horizontal, uint8_t ignore_rich_pr) { int32_t rotation = 0; if (font) rotation = font->rotation; @@ -1481,11 +1501,11 @@ /* Write the a:lstStyle element. */ _chart_write_a_lst_style(self); /* Write the a:p element. */ - _chart_write_a_p_rich(self, name, font); + _chart_write_a_p_rich(self, name, font, ignore_rich_pr); lxw_xml_end_tag(self->file, "c:rich"); } /* @@ -1497,11 +1517,11 @@ { lxw_xml_start_tag(self->file, "c:tx", NULL); /* Write the c:rich element. */ - _chart_write_rich(self, name, is_horizontal, font); + _chart_write_rich(self, name, font, is_horizontal, LXW_FALSE); lxw_xml_end_tag(self->file, "c:tx"); } /* @@ -1656,33 +1676,38 @@ width_flt = (float) (uint32_t) ((line->width + 0.125) * 4.0F) / 4.0F; /* Convert to internal units. */ width_int = (uint32_t) (0.5 + (12700.0 * width_flt)); - if (width_int) + if (line->width > 0.0) LXW_PUSH_ATTRIBUTES_INT("w", width_int); - lxw_xml_start_tag(self->file, "a:ln", &attributes); + if (line->none || line->color || line->dash_type) { + lxw_xml_start_tag(self->file, "a:ln", &attributes); - /* Write the line fill. */ - if (line->none) { - /* 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->color) { + /* Write the a:solidFill element. */ + _chart_write_a_solid_fill(self, line->color, line->transparency); + } + + /* Write the line/dash type. */ + if (line->dash_type) { + /* Write the a:prstDash element. */ + _chart_write_a_prst_dash(self, line->dash_type); + } + + lxw_xml_end_tag(self->file, "a:ln"); } - else if (line->has_color) { - /* Write the a:solidFill element. */ - _chart_write_a_solid_fill(self, line->color, line->transparency); + else { + lxw_xml_empty_tag(self->file, "a:ln", &attributes); } - /* Write the line/dash type. */ - if (line->dash_type) { - /* Write the a:prstDash element. */ - _chart_write_a_prst_dash(self, line->dash_type); - } - - lxw_xml_end_tag(self->file, "a:ln"); - LXW_FREE_ATTRIBUTES(); } /* * Write the <a:fgClr> element. @@ -1822,14 +1847,14 @@ else LXW_PUSH_ATTRIBUTES_STR("prst", "percent_50"); lxw_xml_start_tag(self->file, "a:pattFill", &attributes); - if (pattern->has_fg_color) + if (pattern->fg_color) _chart_write_a_fg_clr(self, pattern->fg_color); - if (pattern->has_bg_color) + if (pattern->bg_color) _chart_write_a_bg_clr(self, pattern->bg_color); lxw_xml_end_tag(self->file, "a:pattFill"); LXW_FREE_ATTRIBUTES(); @@ -2267,24 +2292,166 @@ LXW_FREE_ATTRIBUTES(); } /* + * Write parts of the <c:dLbl> elements where only formatting is changed. + */ +STATIC void +_chart_write_custom_label_format_only(lxw_chart *self, + lxw_chart_custom_label *data_label) +{ + if (data_label->line || data_label->fill || data_label->pattern) { + _chart_write_sp_pr(self, data_label->line, data_label->fill, + data_label->pattern); + _chart_write_tx_pr(self, LXW_FALSE, data_label->font); + } + else if (data_label->font) { + lxw_xml_empty_tag(self->file, "c:spPr", NULL); + _chart_write_tx_pr(self, LXW_FALSE, data_label->font); + } +} + +/* + * Write parts of the <c:dLbl> elements for formula custom labels. + */ +STATIC void +_chart_write_custom_label_formula(lxw_chart *self, lxw_chart_series *series, + lxw_chart_custom_label *data_label) +{ + lxw_xml_empty_tag(self->file, "c:layout", NULL); + lxw_xml_start_tag(self->file, "c:tx", NULL); + + _chart_write_str_ref(self, data_label->range); + + lxw_xml_end_tag(self->file, "c:tx"); + + _chart_write_custom_label_format_only(self, data_label); + + /* Write the c:dLblPos element. */ + if (series->label_position) + _chart_write_d_lbl_pos(self, series->label_position); + + /* Write the c:showVal element. */ + if (series->show_labels_value) + _chart_write_show_val(self); + + /* Write the c:showCatName element. */ + if (series->show_labels_category) + _chart_write_show_cat_name(self); + + /* Write the c:showSerName element. */ + if (series->show_labels_name) + _chart_write_show_ser_name(self); + +} + +/* + * Write parts of the <c:dLbl> elements for string custom labels. + */ +STATIC void +_chart_write_custom_label_str(lxw_chart *self, lxw_chart_series *series, + lxw_chart_custom_label *data_label) +{ + uint8_t ignore_rich_pr = LXW_TRUE; + + if (data_label->line || data_label->fill || data_label->pattern) + ignore_rich_pr = LXW_FALSE; + + lxw_xml_empty_tag(self->file, "c:layout", NULL); + lxw_xml_start_tag(self->file, "c:tx", NULL); + + /* Write the c:rich element. */ + _chart_write_rich(self, data_label->value, data_label->font, + LXW_FALSE, ignore_rich_pr); + + lxw_xml_end_tag(self->file, "c:tx"); + + /* Write the c:spPr element. */ + _chart_write_sp_pr(self, data_label->line, data_label->fill, + data_label->pattern); + + /* Write the c:dLblPos element. */ + if (series->label_position) + _chart_write_d_lbl_pos(self, series->label_position); + + /* Write the c:showVal element. */ + if (series->show_labels_value) + _chart_write_show_val(self); + + /* Write the c:showCatName element. */ + if (series->show_labels_category) + _chart_write_show_cat_name(self); + + /* Write the c:showSerName element. */ + if (series->show_labels_name) + _chart_write_show_ser_name(self); + +} + +/* + * Write the <c:dLbl> elements for custom labels. + */ +STATIC void +_chart_write_custom_labels(lxw_chart *self, lxw_chart_series *series) +{ + uint16_t index = 0; + + for (index = 0; index < series->data_label_count; index++) { + lxw_chart_custom_label *data_label = &series->data_labels[index]; + + if (!data_label->value && !data_label->range && !data_label->hide + && !data_label->font) { + + continue; + } + + lxw_xml_start_tag(self->file, "c:dLbl", NULL); + + /* Write the c:idx element. */ + _chart_write_idx(self, index); + + if (data_label->hide) { + /* Write the c:delete element. */ + _chart_write_delete(self); + } + else if (data_label->value) { + _chart_write_custom_label_str(self, series, data_label); + } + else if (data_label->range) { + _chart_write_custom_label_formula(self, series, data_label); + } + else if (data_label->font) { + _chart_write_custom_label_format_only(self, data_label); + } + + lxw_xml_end_tag(self->file, "c:dLbl"); + } +} + +/* * Write the <c:dLbls> element. */ STATIC void _chart_write_d_lbls(lxw_chart *self, lxw_chart_series *series) { if (!series->has_labels) return; lxw_xml_start_tag(self->file, "c:dLbls", NULL); + if (series->data_labels) + _chart_write_custom_labels(self, series); + /* Write the c:numFmt element. */ if (series->label_num_format) _chart_write_label_num_fmt(self, series->label_num_format); + /* Write the c:spPr element. */ + _chart_write_sp_pr(self, series->label_line, series->label_fill, + series->label_pattern); + if (series->label_font) _chart_write_tx_pr(self, LXW_FALSE, series->label_font); /* Write the c:dLblPos element. */ if (series->label_position) @@ -3159,11 +3326,13 @@ struct xml_attribute_list attributes; struct xml_attribute *attribute; LXW_INIT_ATTRIBUTES(); - if (axis->crossing_max) + if (axis->crossing_min) + LXW_PUSH_ATTRIBUTES_STR("val", "min"); + else if (axis->crossing_max) LXW_PUSH_ATTRIBUTES_STR("val", "max"); else LXW_PUSH_ATTRIBUTES_STR("val", "autoZero"); lxw_xml_empty_tag(self->file, "c:crosses", &attributes); @@ -3567,11 +3736,11 @@ lxw_xml_start_tag(self->file, "c:legendEntry", NULL); /* Write the c:idx element. */ _chart_write_idx(self, self->delete_series[index]); - /* Write the c:delete element. */ + /* Write the c:dst_label element. */ _chart_write_delete(self); lxw_xml_end_tag(self->file, "c:legendEntry"); } @@ -4102,11 +4271,12 @@ /* Write the c:crossAx element. */ _chart_write_cross_axis(self, self->axis_id_2); /* Write the c:crosses element. */ - if (!self->y_axis->has_crossing || self->y_axis->crossing_max) + if (!self->y_axis->has_crossing || self->y_axis->crossing_min + || self->y_axis->crossing_max) _chart_write_crosses(self, self->y_axis); else _chart_write_crosses_at(self, self->y_axis); /* Write the c:auto element. */ @@ -4183,11 +4353,12 @@ /* Write the c:crossAx element. */ _chart_write_cross_axis(self, self->axis_id_1); /* Write the c:crosses element. */ - if (!self->x_axis->has_crossing || self->x_axis->crossing_max) + if (!self->x_axis->has_crossing || self->x_axis->crossing_min + || self->x_axis->crossing_max) _chart_write_crosses(self, self->x_axis); else _chart_write_crosses_at(self, self->x_axis); /* Write the c:crossBetween element. */ @@ -4261,11 +4432,12 @@ /* Write the c:crossAx element. */ _chart_write_cross_axis(self, self->axis_id_2); /* Write the c:crosses element. */ - if (!self->y_axis->has_crossing || self->y_axis->crossing_max) + if (!self->y_axis->has_crossing || self->y_axis->crossing_min + || self->y_axis->crossing_max) _chart_write_crosses(self, self->y_axis); else _chart_write_crosses_at(self, self->y_axis); /* Write the c:crossBetween element. */ @@ -4495,22 +4667,19 @@ 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, - 2.25, - LXW_CHART_LINE_DASH_SOLID, - 0, - LXW_FALSE - }; - series->line = _chart_convert_line_args(&line); - } + if (self->type == LXW_CHART_SCATTER && !series->line) { + lxw_chart_line line = { + 0x000000, + LXW_TRUE, + 2.25, + LXW_CHART_LINE_DASH_SOLID, + 0 + }; + series->line = _chart_convert_line_args(&line); } /* Write the c:ser element. */ _chart_write_xval_ser(self, series); } @@ -4795,19 +4964,30 @@ /* * Initialize a line chart. */ STATIC void -_chart_initialize_line_chart(lxw_chart *self) +_chart_initialize_line_chart(lxw_chart *self, uint8_t type) { self->chart_group = LXW_CHART_LINE; _chart_set_default_marker_type(self, LXW_CHART_MARKER_NONE); self->grouping = LXW_GROUPING_STANDARD; self->x_axis->is_category = LXW_TRUE; self->y_axis->is_value = LXW_TRUE; self->default_label_position = LXW_CHART_LABEL_POSITION_RIGHT; + if (type == LXW_CHART_LINE_STACKED) { + self->grouping = LXW_GROUPING_STACKED; + self->subtype = LXW_CHART_SUBTYPE_STACKED; + } + + if (type == LXW_CHART_LINE_STACKED_PERCENT) { + self->grouping = LXW_GROUPING_PERCENTSTACKED; + _chart_axis_set_default_num_format(self->y_axis, "0%"); + self->subtype = LXW_CHART_SUBTYPE_STACKED; + } + /* Initialize the function pointers for this chart type. */ self->write_chart_type = _chart_write_line_chart; self->write_plot_area = _chart_write_plot_area; } @@ -4898,11 +5078,13 @@ case LXW_CHART_DOUGHNUT: _chart_initialize_doughnut_chart(self); break; case LXW_CHART_LINE: - _chart_initialize_line_chart(self); + case LXW_CHART_LINE_STACKED: + case LXW_CHART_LINE_STACKED_PERCENT: + _chart_initialize_line_chart(self, type); break; case LXW_CHART_PIE: _chart_initialize_pie_chart(self); break; @@ -4997,11 +5179,11 @@ return LXW_NO_ERROR; } /* - * Insert an image into the worksheet. + * Add a series to the chart. */ lxw_chart_series * chart_add_series(lxw_chart *self, const char *categories, const char *values) { lxw_chart_series *series; @@ -5304,11 +5486,11 @@ series->marker->pattern = _chart_convert_pattern_args(pattern); } /* - * Store the horizontal page breaks on a worksheet. + * Store the points for a chart. */ lxw_error chart_series_set_points(lxw_chart_series *series, lxw_chart_point *points[]) { uint16_t i = 0; @@ -5374,10 +5556,87 @@ series->show_labels_category = show_category; series->show_labels_value = show_value; } /* + * Store the custom data_labels for a chart. + */ +lxw_error +chart_series_set_labels_custom(lxw_chart_series *series, + lxw_chart_data_label *data_labels[]) +{ + uint16_t i = 0; + uint16_t data_label_count = 0; + + if (data_labels == NULL) + return LXW_ERROR_NULL_PARAMETER_IGNORED; + + while (data_labels[data_label_count]) + data_label_count++; + + if (data_label_count == 0) + return LXW_ERROR_NULL_PARAMETER_IGNORED; + + series->has_labels = LXW_TRUE; + + /* Set the Value label type if no other type is set. */ + if (!series->show_labels_name && !series->show_labels_category + && !series->show_labels_value) { + series->show_labels_value = LXW_TRUE; + } + + /* Free any existing resource. */ + _chart_free_data_labels(series); + + series->data_labels = calloc(data_label_count, + sizeof(lxw_chart_custom_label)); + RETURN_ON_MEM_ERROR(series->data_labels, LXW_ERROR_MEMORY_MALLOC_FAILED); + + /* Copy the user data into the array of new structs. The struct types + * are different since the internal version has more fields. */ + for (i = 0; i < data_label_count; i++) { + lxw_chart_data_label *user_label = data_labels[i]; + lxw_chart_custom_label *data_label = &series->data_labels[i]; + const char *src_value = user_label->value; + + data_label->hide = user_label->hide; + data_label->font = _chart_convert_font_args(user_label->font); + data_label->line = _chart_convert_line_args(user_label->line); + data_label->fill = _chart_convert_fill_args(user_label->fill); + data_label->pattern = + _chart_convert_pattern_args(user_label->pattern); + + if (src_value) { + if (*src_value == '=') { + /* The value is a formula. Handle like other chart ranges. */ + data_label->range = calloc(1, sizeof(lxw_series_range)); + GOTO_LABEL_ON_MEM_ERROR(data_label->range, mem_error); + + data_label->range->formula = lxw_strdup(src_value + 1); + + /* Add the formula to the data cache to allow value to be looked + * up and filled in when the file is closed. */ + if (_chart_init_data_cache(data_label->range) != LXW_NO_ERROR) + goto mem_error; + } + else { + /* The value is a simple string. */ + data_label->value = lxw_strdup(src_value); + } + } + } + + series->data_label_count = data_label_count; + + return LXW_NO_ERROR; + +mem_error: + _chart_free_data_labels(series); + return LXW_ERROR_MEMORY_MALLOC_FAILED; +} + +/* * Set the data labels separator for a series. */ void chart_series_set_labels_separator(lxw_chart_series *series, uint8_t separator) { @@ -5458,10 +5717,56 @@ series->label_font = _chart_convert_font_args(font); } /* + * Set a line type for a series data labels. + */ +void +chart_series_set_labels_line(lxw_chart_series *series, lxw_chart_line *line) +{ + if (!line) + return; + + /* Free any previously allocated resource. */ + free(series->label_line); + + series->label_line = _chart_convert_line_args(line); +} + +/* + * Set a fill type for a series data labels. + */ +void +chart_series_set_labels_fill(lxw_chart_series *series, lxw_chart_fill *fill) +{ + if (!fill) + return; + + /* Free any previously allocated resource. */ + free(series->label_fill); + + series->label_fill = _chart_convert_fill_args(fill); +} + +/* + * Set a pattern type for a series data labels. + */ +void +chart_series_set_labels_pattern(lxw_chart_series *series, + lxw_chart_pattern *pattern) +{ + if (!pattern) + return; + + /* Free any previously allocated resource. */ + free(series->label_pattern); + + series->label_pattern = _chart_convert_pattern_args(pattern); +} + +/* * Set the trendline for a chart series. */ void chart_series_set_trendline(lxw_chart_series *series, uint8_t type, uint8_t value) @@ -5845,10 +6150,20 @@ axis->has_crossing = LXW_TRUE; axis->crossing = value; } /* - * Set the axis crossing position as the max possible value. + * Set the axis crossing position as the minimum possible value. + */ +void +chart_axis_set_crossing_min(lxw_chart_axis *axis) +{ + axis->has_crossing = LXW_TRUE; + axis->crossing_min = LXW_TRUE; +} + +/* + * Set the axis crossing position as the maximum possible value. */ void chart_axis_set_crossing_max(lxw_chart_axis *axis) { axis->has_crossing = LXW_TRUE;