/*****************************************************************************
 * chart - A library for creating Excel XLSX chart files.
 *
 * Used in conjunction with the libxlsxwriter library.
 *
 * Copyright 2014-2020, John McNamara, jmcnamara@cpan.org. See LICENSE.txt.
 *
 */

#include "xlsxwriter/xmlwriter.h"
#include "xlsxwriter/chart.h"
#include "xlsxwriter/utility.h"

/*
 * Forward declarations.
 */

STATIC void _chart_initialize(lxw_chart *self, uint8_t type);
STATIC void _chart_axis_set_default_num_format(lxw_chart_axis *axis,
                                               char *num_format);

/*****************************************************************************
 *
 * Private functions.
 *
 ****************************************************************************/

/*
 * Free a series range object.
 */
STATIC void
_chart_free_range(lxw_series_range *range)
{
    struct lxw_series_data_point *data_point;

    if (!range)
        return;

    if (range->data_cache) {
        while (!STAILQ_EMPTY(range->data_cache)) {
            data_point = STAILQ_FIRST(range->data_cache);
            free(data_point->string);
            STAILQ_REMOVE_HEAD(range->data_cache, list_pointers);

            free(data_point);
        }
        free(range->data_cache);
    }

    free(range->formula);
    free(range->sheetname);
    free(range);
}

STATIC void
_chart_free_points(lxw_chart_series *series)
{
    uint16_t index;

    for (index = 0; index < series->point_count; index++) {
        lxw_chart_point *point = &series->points[index];

        free(point->line);
        free(point->fill);
        free(point->pattern);
    }

    series->point_count = 0;
    free(series->points);
}

/*
 * Free a chart font object.
 */
STATIC void
_chart_free_font(lxw_chart_font *font)
{
    if (!font)
        return;

    free(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)
{
    if (!series)
        return;

    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);
        free(series->marker->pattern);
        free(series->marker);
    }

    _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);
    }

    if (series->y_error_bars) {
        free(series->y_error_bars->line);
        free(series->y_error_bars);
    }

    free(series->trendline_line);
    free(series->trendline_name);

    free(series);
}

/*
 * Initialize the data cache in a range object.
 */
STATIC lxw_error
_chart_init_data_cache(lxw_series_range *range)
{
    /* Initialize the series range data cache. */
    range->data_cache = calloc(1, sizeof(struct lxw_series_data_points));
    RETURN_ON_MEM_ERROR(range->data_cache, LXW_ERROR_MEMORY_MALLOC_FAILED);
    STAILQ_INIT(range->data_cache);

    return LXW_NO_ERROR;
}

/*
 * Free a chart object.
 */
void
lxw_chart_free(lxw_chart *chart)
{
    lxw_chart_series *series;

    if (!chart)
        return;

    /* Chart series. */
    if (chart->series_list) {
        while (!STAILQ_EMPTY(chart->series_list)) {
            series = STAILQ_FIRST(chart->series_list);
            STAILQ_REMOVE_HEAD(chart->series_list, list_pointers);

            _chart_series_free(series);
        }

        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);
    }

    /* Chart title. */
    _chart_free_font(chart->title.font);
    _chart_free_range(chart->title.range);
    free(chart->title.name);

    /* Chart legend. */
    _chart_free_font(chart->legend.font);
    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_pattern);

    free(chart->drop_lines_line);
    free(chart->high_low_lines_line);

    free(chart->up_bar_line);
    free(chart->up_bar_fill);
    free(chart->down_bar_line);
    free(chart->down_bar_fill);

    _chart_free_font(chart->table_font);

    free(chart);
}

/*
 * Create a new chart object.
 */
lxw_chart *
lxw_chart_new(uint8_t type)
{
    lxw_chart *chart = calloc(1, sizeof(lxw_chart));
    GOTO_LABEL_ON_MEM_ERROR(chart, mem_error);

    chart->series_list = calloc(1, sizeof(struct lxw_chart_series_list));
    GOTO_LABEL_ON_MEM_ERROR(chart->series_list, mem_error);
    STAILQ_INIT(chart->series_list);

    chart->x_axis = calloc(1, sizeof(struct lxw_chart_axis));
    GOTO_LABEL_ON_MEM_ERROR(chart->x_axis, mem_error);

    chart->y_axis = calloc(1, sizeof(struct lxw_chart_axis));
    GOTO_LABEL_ON_MEM_ERROR(chart->y_axis, mem_error);

    chart->title.range = calloc(1, sizeof(lxw_series_range));
    GOTO_LABEL_ON_MEM_ERROR(chart->title.range, mem_error);

    chart->x_axis->title.range = calloc(1, sizeof(lxw_series_range));
    GOTO_LABEL_ON_MEM_ERROR(chart->x_axis->title.range, mem_error);

    chart->y_axis->title.range = calloc(1, sizeof(lxw_series_range));
    GOTO_LABEL_ON_MEM_ERROR(chart->y_axis->title.range, mem_error);

    /* Initialize the ranges in the chart titles. */
    if (_chart_init_data_cache(chart->title.range) != LXW_NO_ERROR)
        goto mem_error;

    if (_chart_init_data_cache(chart->x_axis->title.range) != LXW_NO_ERROR)
        goto mem_error;

    if (_chart_init_data_cache(chart->y_axis->title.range) != LXW_NO_ERROR)
        goto mem_error;

    chart->type = type;
    chart->style_id = 2;
    chart->hole_size = 50;

    /* Set the default axis positions. */
    chart->x_axis->axis_position = LXW_CHART_AXIS_BOTTOM;
    chart->y_axis->axis_position = LXW_CHART_AXIS_LEFT;

    /* Set the default axis number formats. */
    _chart_axis_set_default_num_format(chart->x_axis, "General");
    _chart_axis_set_default_num_format(chart->y_axis, "General");

    chart->x_axis->major_gridlines.visible = LXW_FALSE;
    chart->y_axis->major_gridlines.visible = LXW_TRUE;

    chart->has_horiz_cat_axis = LXW_FALSE;
    chart->has_horiz_val_axis = LXW_TRUE;

    chart->legend.position = LXW_CHART_LEGEND_RIGHT;

    chart->gap_y1 = LXW_CHART_DEFAULT_GAP;
    chart->gap_y2 = LXW_CHART_DEFAULT_GAP;

    /* Initialize the chart specific properties. */
    _chart_initialize(chart, chart->type);

    return chart;

mem_error:
    lxw_chart_free(chart);
    return NULL;
}

/*
 * Create a copy of a user supplied font.
 */
STATIC lxw_chart_font *
_chart_convert_font_args(lxw_chart_font *user_font)
{
    lxw_chart_font *font;

    if (!user_font)
        return NULL;

    font = calloc(1, sizeof(struct lxw_chart_font));
    RETURN_ON_MEM_ERROR(font, NULL);

    /* Copy the user supplied properties. */
    font->name = lxw_strdup(user_font->name);
    font->size = user_font->size;
    font->bold = user_font->bold;
    font->italic = user_font->italic;
    font->underline = user_font->underline;
    font->rotation = user_font->rotation;
    font->color = user_font->color;
    font->pitch_family = user_font->pitch_family;
    font->charset = user_font->charset;
    font->baseline = user_font->baseline;

    /* Convert font size units. */
    if (font->size > 0.0)
        font->size = font->size * 100.0;

    /* Convert rotation into 60,000ths of a degree. */
    if (font->rotation)
        font->rotation = font->rotation * 60000;

    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);

    /* Copy the user supplied properties. */
    line->color = user_line->color;
    line->none = user_line->none;
    line->width = user_line->width;
    line->dash_type = user_line->dash_type;
    line->transparency = user_line->transparency;

    if (line->transparency > 100)
        line->transparency = 0;

    return line;
}

/*
 * Create a copy of a user supplied fill.
 */
STATIC lxw_chart_fill *
_chart_convert_fill_args(lxw_chart_fill *user_fill)
{
    lxw_chart_fill *fill;

    if (!user_fill)
        return NULL;

    fill = calloc(1, sizeof(struct lxw_chart_fill));
    RETURN_ON_MEM_ERROR(fill, NULL);

    /* Copy the user supplied properties. */
    fill->color = user_fill->color;
    fill->none = user_fill->none;
    fill->transparency = user_fill->transparency;

    if (fill->transparency > 100)
        fill->transparency = 0;

    return fill;
}

/*
 * Create a copy of a user supplied pattern.
 */
STATIC lxw_chart_pattern *
_chart_convert_pattern_args(lxw_chart_pattern *user_pattern)
{
    lxw_chart_pattern *pattern;

    if (!user_pattern)
        return NULL;

    if (!user_pattern->type) {
        LXW_WARN("chart_xxx_set_pattern: 'type' must be specified");
        return NULL;
    }

    if (!user_pattern->fg_color) {
        LXW_WARN("chart_xxx_set_pattern: 'fg_color' must be specified");
        return NULL;
    }

    pattern = calloc(1, sizeof(struct lxw_chart_pattern));
    RETURN_ON_MEM_ERROR(pattern, NULL);

    /* Copy the user supplied properties. */
    pattern->fg_color = user_pattern->fg_color;
    pattern->bg_color = user_pattern->bg_color;
    pattern->type = user_pattern->type;

    if (!pattern->bg_color) {
        /* Default background color in Excel is white, when unspecified. */
        pattern->bg_color = LXW_COLOR_WHITE;
    }

    return pattern;
}

/*
 * Set a marker type for a series.
 */
STATIC void
_chart_set_default_marker_type(lxw_chart *self, uint8_t type)
{
    if (!self->default_marker) {
        lxw_chart_marker *marker = calloc(1, sizeof(struct lxw_chart_marker));
        RETURN_VOID_ON_MEM_ERROR(marker);
        self->default_marker = marker;
    }

    self->default_marker->type = type;
}

/*
 * Set an axis number format.
 */
void
_chart_axis_set_default_num_format(lxw_chart_axis *axis, char *num_format)
{
    if (!num_format)
        return;

    /* Free any previously allocated resource. */
    free(axis->default_num_format);

    axis->default_num_format = lxw_strdup(num_format);
}

/*
 * Verify that a X/Y error bar property is support for the chart type.
 * All chart types, except Bar have Y error bars. Only Bar and Scatter
 * support X error bars.
 */
lxw_error
_chart_check_error_bars(lxw_series_error_bars *error_bars, char *property)
{
    /* Check that the error bar type has been set for all error bar
     * functions except the one that is used to set the type. */
    if (strlen(property) && !error_bars->is_set) {
        LXW_WARN_FORMAT1("chart_series_set_error_bars%s(): "
                         "error bar type must be set first using "
                         "chart_series_set_error_bars()", property);

        return LXW_ERROR_PARAMETER_VALIDATION;
    }

    if (error_bars->is_x) {
        if (error_bars->chart_group != LXW_CHART_SCATTER
            && error_bars->chart_group != LXW_CHART_BAR) {

            LXW_WARN_FORMAT1("chart_series_set_error_bars%s(): "
                             "'X error bar' properties only available for"
                             " Scatter and Bar charts in Excel", property);

            return LXW_ERROR_PARAMETER_VALIDATION;
        }
    }
    else {
        if (error_bars->chart_group == LXW_CHART_BAR) {
            LXW_WARN_FORMAT1("chart_series_set_error_bars%s(): "
                             "'Y error bar' properties not available for "
                             "Bar charts in Excel", property);

            return LXW_ERROR_PARAMETER_VALIDATION;
        }
    }

    return LXW_NO_ERROR;
}

/*
 * Add unique ids for primary or secondary axes.
 */
STATIC void
_chart_add_axis_ids(lxw_chart *self)
{
    uint32_t chart_id = 50010000 + self->id;
    uint32_t axis_count = 1;

    self->axis_id_1 = chart_id + axis_count;
    self->axis_id_2 = self->axis_id_1 + 1;
}

/*
 * Utility function to set a chart range.
 */
STATIC void
_chart_set_range(lxw_series_range *range, const char *sheetname,
                 lxw_row_t first_row, lxw_col_t first_col,
                 lxw_row_t last_row, lxw_col_t last_col)
{
    char formula[LXW_MAX_FORMULA_RANGE_LENGTH] = { 0 };

    /* Set the range properties. */
    range->sheetname = lxw_strdup(sheetname);
    range->first_row = first_row;
    range->first_col = first_col;
    range->last_row = last_row;
    range->last_col = last_col;

    /* Free any existing range. */
    free(range->formula);

    /* Convert the range properties to a formula like: Sheet1!$A$1:$A$5. */
    lxw_rowcol_to_formula_abs(formula, sheetname,
                              first_row, first_col, last_row, last_col);

    range->formula = lxw_strdup(formula);
}

/*****************************************************************************
 *
 * XML functions.
 *
 ****************************************************************************/

/*
 * Write the XML declaration.
 */
STATIC void
_chart_xml_declaration(lxw_chart *self)
{
    lxw_xml_declaration(self->file);
}

/*
 * Write the <c:protection> element.
 */
STATIC void
_chart_write_protection(lxw_chart *self)
{
    lxw_xml_empty_tag(self->file, "c:protection", NULL);
}

/*
 * Write the <c:chartSpace> element.
 */
STATIC void
_chart_write_chart_space(lxw_chart *self)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;
    char xmlns_c[] = LXW_SCHEMA_DRAWING "/chart";
    char xmlns_a[] = LXW_SCHEMA_DRAWING "/main";
    char xmlns_r[] = LXW_SCHEMA_OFFICEDOC "/relationships";

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_STR("xmlns:c", xmlns_c);
    LXW_PUSH_ATTRIBUTES_STR("xmlns:a", xmlns_a);
    LXW_PUSH_ATTRIBUTES_STR("xmlns:r", xmlns_r);

    lxw_xml_start_tag(self->file, "c:chartSpace", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:lang> element.
 */
STATIC void
_chart_write_lang(lxw_chart *self)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_STR("val", "en-US");

    lxw_xml_empty_tag(self->file, "c:lang", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:style> element.
 */
STATIC void
_chart_write_style(lxw_chart *self)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    /* Don"t write an element for the default style, 2. */
    if (self->style_id == 2)
        return;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_INT("val", self->style_id);

    lxw_xml_empty_tag(self->file, "c:style", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:layout> element.
 */
STATIC void
_chart_write_layout(lxw_chart *self)
{
    lxw_xml_empty_tag(self->file, "c:layout", NULL);
}

/*
 * Write the <c:grouping> element.
 */
STATIC void
_chart_write_grouping(lxw_chart *self, uint8_t grouping)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();

    if (grouping == LXW_GROUPING_STANDARD)
        LXW_PUSH_ATTRIBUTES_STR("val", "standard");
    else if (grouping == LXW_GROUPING_PERCENTSTACKED)
        LXW_PUSH_ATTRIBUTES_STR("val", "percentStacked");
    else if (grouping == LXW_GROUPING_STACKED)
        LXW_PUSH_ATTRIBUTES_STR("val", "stacked");
    else
        LXW_PUSH_ATTRIBUTES_STR("val", "clustered");

    lxw_xml_empty_tag(self->file, "c:grouping", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:radarStyle> element.
 */
STATIC void
_chart_write_radar_style(lxw_chart *self)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();

    if (self->type == LXW_CHART_RADAR_FILLED)
        LXW_PUSH_ATTRIBUTES_STR("val", "filled");
    else
        LXW_PUSH_ATTRIBUTES_STR("val", "marker");

    lxw_xml_empty_tag(self->file, "c:radarStyle", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:varyColors> element.
 */
STATIC void
_chart_write_vary_colors(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:varyColors", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:firstSliceAng> element.
 */
STATIC void
_chart_write_first_slice_ang(lxw_chart *self)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_INT("val", self->rotation);

    lxw_xml_empty_tag(self->file, "c:firstSliceAng", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:holeSize> element.
 */
STATIC void
_chart_write_hole_size(lxw_chart *self)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_INT("val", self->hole_size);

    lxw_xml_empty_tag(self->file, "c:holeSize", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <a:alpha> element.
 */
STATIC void
_chart_write_a_alpha(lxw_chart *self, uint8_t transparency)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;
    uint32_t val;

    LXW_INIT_ATTRIBUTES();

    val = (100 - transparency) * 1000;

    LXW_PUSH_ATTRIBUTES_INT("val", val);

    lxw_xml_empty_tag(self->file, "a:alpha", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <a:srgbClr> element.
 */
STATIC void
_chart_write_a_srgb_clr(lxw_chart *self, lxw_color_t color,
                        uint8_t transparency)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;
    char rgb_str[LXW_ATTR_32];

    LXW_INIT_ATTRIBUTES();

    lxw_snprintf(rgb_str, LXW_ATTR_32, "%06X", color & LXW_COLOR_MASK);
    LXW_PUSH_ATTRIBUTES_STR("val", rgb_str);

    if (transparency) {
        lxw_xml_start_tag(self->file, "a:srgbClr", &attributes);

        /* Write the a:alpha element. */
        _chart_write_a_alpha(self, transparency);

        lxw_xml_end_tag(self->file, "a:srgbClr");
    }
    else {
        lxw_xml_empty_tag(self->file, "a:srgbClr", &attributes);
    }

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <a:solidFill> element.
 */
STATIC void
_chart_write_a_solid_fill(lxw_chart *self, lxw_color_t color,
                          uint8_t transparency)
{

    lxw_xml_start_tag(self->file, "a:solidFill", NULL);

    /* Write the a:srgbClr element. */
    _chart_write_a_srgb_clr(self, color, transparency);

    lxw_xml_end_tag(self->file, "a:solidFill");
}

/*
 * Write the <a:t> element.
 */
STATIC void
_chart_write_a_t(lxw_chart *self, char *name)
{
    lxw_xml_data_element(self->file, "a:t", name, NULL);
}

/*
 * Write the <a:endParaRPr> element.
 */
STATIC void
_chart_write_a_end_para_rpr(lxw_chart *self)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_STR("lang", "en-US");

    lxw_xml_empty_tag(self->file, "a:endParaRPr", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <a:defRPr> element.
 */
STATIC void
_chart_write_a_def_rpr(lxw_chart *self, lxw_chart_font *font)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;
    uint8_t has_color = LXW_FALSE;
    uint8_t has_latin = LXW_FALSE;
    uint8_t use_font_default = LXW_FALSE;

    LXW_INIT_ATTRIBUTES();

    if (font) {
        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)
            LXW_PUSH_ATTRIBUTES_DBL("sz", font->size);

        if (use_font_default || font->bold)
            LXW_PUSH_ATTRIBUTES_INT("b", font->bold & 0x1);

        if (use_font_default || font->italic)
            LXW_PUSH_ATTRIBUTES_INT("i", font->italic & 0x1);

        if (font->underline)
            LXW_PUSH_ATTRIBUTES_STR("u", "sng");

        if (font->baseline != -1)
            LXW_PUSH_ATTRIBUTES_INT("baseline", font->baseline);
    }

    /* There are sub-elements if the font name or color have changed. */
    if (has_latin || has_color) {

        lxw_xml_start_tag(self->file, "a:defRPr", &attributes);

        if (has_color) {
            _chart_write_a_solid_fill(self, font->color, LXW_FALSE);
        }

        if (has_latin) {
            /* Free and reuse the attribute list for the latin attributes. */
            LXW_FREE_ATTRIBUTES();

            if (font->name)
                LXW_PUSH_ATTRIBUTES_STR("typeface", font->name);

            if (font->pitch_family)
                LXW_PUSH_ATTRIBUTES_INT("pitchFamily", font->pitch_family);

            if (font->pitch_family || font->charset)
                LXW_PUSH_ATTRIBUTES_INT("charset", font->charset);

            /* Write the <a:latin> element. */
            lxw_xml_empty_tag(self->file, "a:latin", &attributes);
        }

        lxw_xml_end_tag(self->file, "a:defRPr");
    }
    else {
        lxw_xml_empty_tag(self->file, "a:defRPr", &attributes);
    }

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <a:rPr> element.
 */
STATIC void
_chart_write_a_r_pr(lxw_chart *self, lxw_chart_font *font)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;
    uint8_t has_color = LXW_FALSE;
    uint8_t has_latin = LXW_FALSE;
    uint8_t use_font_default = LXW_FALSE;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_STR("lang", "en-US");

    if (font) {
        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)
            LXW_PUSH_ATTRIBUTES_DBL("sz", font->size);

        if (use_font_default || font->bold)
            LXW_PUSH_ATTRIBUTES_INT("b", font->bold & 0x1);

        if (use_font_default || font->italic)
            LXW_PUSH_ATTRIBUTES_INT("i", font->italic & 0x1);

        if (font->underline)
            LXW_PUSH_ATTRIBUTES_STR("u", "sng");

        if (font->baseline != -1)
            LXW_PUSH_ATTRIBUTES_INT("baseline", font->baseline);
    }

    /* There are sub-elements if the font name or color have changed. */
    if (has_latin || has_color) {

        lxw_xml_start_tag(self->file, "a:rPr", &attributes);

        if (has_color) {
            _chart_write_a_solid_fill(self, font->color, LXW_FALSE);
        }

        if (has_latin) {
            /* Free and reuse the attribute list for the latin attributes. */
            LXW_FREE_ATTRIBUTES();

            if (font->name)
                LXW_PUSH_ATTRIBUTES_STR("typeface", font->name);

            if (font->pitch_family)
                LXW_PUSH_ATTRIBUTES_INT("pitchFamily", font->pitch_family);

            if (font->pitch_family || font->charset)
                LXW_PUSH_ATTRIBUTES_INT("charset", font->charset);

            /* Write the <a:latin> element. */
            lxw_xml_empty_tag(self->file, "a:latin", &attributes);
        }

        lxw_xml_end_tag(self->file, "a:rPr");
    }
    else {
        lxw_xml_empty_tag(self->file, "a:rPr", &attributes);
    }

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <a:r> element.
 */
STATIC void
_chart_write_a_r(lxw_chart *self, char *name, lxw_chart_font *font)
{
    lxw_xml_start_tag(self->file, "a:r", NULL);

    /* Write the a:rPr element. */
    _chart_write_a_r_pr(self, font);

    /* Write the a:t element. */
    _chart_write_a_t(self, name);

    lxw_xml_end_tag(self->file, "a:r");
}

/*
 * Write the <a:pPr> element.
 */
STATIC void
_chart_write_a_p_pr_formula(lxw_chart *self, lxw_chart_font *font)
{
    lxw_xml_start_tag(self->file, "a:pPr", NULL);

    /* Write the a:defRPr element. */
    _chart_write_a_def_rpr(self, font);

    lxw_xml_end_tag(self->file, "a:pPr");
}

/*
 * Write the <a:pPr> element for pie chart legends.
 */
STATIC void
_chart_write_a_p_pr_pie(lxw_chart *self, lxw_chart_font *font)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_STR("rtl", "0");

    lxw_xml_start_tag(self->file, "a:pPr", &attributes);

    /* Write the a:defRPr element. */
    _chart_write_a_def_rpr(self, font);

    lxw_xml_end_tag(self->file, "a:pPr");

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <a:pPr> element.
 */
STATIC void
_chart_write_a_p_pr_rich(lxw_chart *self, lxw_chart_font *font)
{
    lxw_xml_start_tag(self->file, "a:pPr", NULL);

    /* Write the a:defRPr element. */
    _chart_write_a_def_rpr(self, font);

    lxw_xml_end_tag(self->file, "a:pPr");
}

/*
 * Write the <a:p> element.
 */
STATIC void
_chart_write_a_p_formula(lxw_chart *self, lxw_chart_font *font)
{
    lxw_xml_start_tag(self->file, "a:p", NULL);

    /* Write the a:pPr element. */
    _chart_write_a_p_pr_formula(self, font);

    /* Write the a:endParaRPr element. */
    _chart_write_a_end_para_rpr(self);

    lxw_xml_end_tag(self->file, "a:p");
}

/*
 * Write the <a:p> element for pie chart legends.
 */
STATIC void
_chart_write_a_p_pie(lxw_chart *self, lxw_chart_font *font)
{
    lxw_xml_start_tag(self->file, "a:p", NULL);

    /* Write the a:pPr element. */
    _chart_write_a_p_pr_pie(self, font);

    /* Write the a:endParaRPr element. */
    _chart_write_a_end_para_rpr(self);

    lxw_xml_end_tag(self->file, "a:p");
}

/*
 * Write the <a:p> element.
 */
STATIC void
_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. */
    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");
}

/*
 * Write the <a:lstStyle> element.
 */
STATIC void
_chart_write_a_lst_style(lxw_chart *self)
{
    lxw_xml_empty_tag(self->file, "a:lstStyle", NULL);
}

/*
 * Write the <a:bodyPr> element.
 */
STATIC void
_chart_write_a_body_pr(lxw_chart *self, int32_t rotation,
                       uint8_t is_horizontal)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();

    if (rotation == 0 && is_horizontal)
        rotation = -5400000;

    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 {
            LXW_PUSH_ATTRIBUTES_INT("rot", rotation);
            LXW_PUSH_ATTRIBUTES_STR("vert", "horz");
        }
    }

    lxw_xml_empty_tag(self->file, "a:bodyPr", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:ptCount> element.
 */
STATIC void
_chart_write_pt_count(lxw_chart *self, uint16_t num_data_points)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_INT("val", num_data_points);

    lxw_xml_empty_tag(self->file, "c:ptCount", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:v> element.
 */
STATIC void
_chart_write_v_num(lxw_chart *self, double number)
{
    char data[LXW_ATTR_32];

    lxw_sprintf_dbl(data, number);

    lxw_xml_data_element(self->file, "c:v", data, NULL);
}

/*
 * Write the <c:v> element.
 */
STATIC void
_chart_write_v_str(lxw_chart *self, char *str)
{
    lxw_xml_data_element(self->file, "c:v", str, NULL);
}

/*
 * Write the <c:f> element.
 */
STATIC void
_chart_write_f(lxw_chart *self, char *formula)
{
    lxw_xml_data_element(self->file, "c:f", formula, NULL);
}

/*
 * Write the <c:pt> element.
 */
STATIC void
_chart_write_pt(lxw_chart *self, uint16_t index,
                lxw_series_data_point *data_point)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    /* Ignore chart points that have no data. */
    if (data_point->no_data)
        return;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_INT("idx", index);

    lxw_xml_start_tag(self->file, "c:pt", &attributes);

    if (data_point->is_string && data_point->string)
        _chart_write_v_str(self, data_point->string);
    else
        _chart_write_v_num(self, data_point->number);

    lxw_xml_end_tag(self->file, "c:pt");

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:pt> element.
 */
STATIC void
_chart_write_num_pt(lxw_chart *self, uint16_t index,
                    lxw_series_data_point *data_point)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    /* Ignore chart points that have no data. */
    if (data_point->no_data)
        return;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_INT("idx", index);

    lxw_xml_start_tag(self->file, "c:pt", &attributes);

    _chart_write_v_num(self, data_point->number);

    lxw_xml_end_tag(self->file, "c:pt");

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:formatCode> element.
 */
STATIC void
_chart_write_format_code(lxw_chart *self)
{
    lxw_xml_data_element(self->file, "c:formatCode", "General", NULL);
}

/*
 * Write the <c:numCache> element.
 */
STATIC void
_chart_write_num_cache(lxw_chart *self, lxw_series_range *range)
{
    lxw_series_data_point *data_point;
    uint16_t index = 0;

    lxw_xml_start_tag(self->file, "c:numCache", NULL);

    /* Write the c:formatCode element. */
    _chart_write_format_code(self);

    /* Write the c:ptCount element. */
    _chart_write_pt_count(self, range->num_data_points);

    STAILQ_FOREACH(data_point, range->data_cache, list_pointers) {
        /* Write the c:pt element. */
        _chart_write_num_pt(self, index, data_point);
        index++;
    }

    lxw_xml_end_tag(self->file, "c:numCache");
}

/*
 * Write the <c:strCache> element.
 */
STATIC void
_chart_write_str_cache(lxw_chart *self, lxw_series_range *range)
{
    lxw_series_data_point *data_point;
    uint16_t index = 0;

    lxw_xml_start_tag(self->file, "c:strCache", NULL);

    /* Write the c:ptCount element. */
    _chart_write_pt_count(self, range->num_data_points);

    STAILQ_FOREACH(data_point, range->data_cache, list_pointers) {
        /* Write the c:pt element. */
        _chart_write_pt(self, index, data_point);
        index++;
    }

    lxw_xml_end_tag(self->file, "c:strCache");
}

/*
 * Write the <c:numRef> element.
 */
STATIC void
_chart_write_num_ref(lxw_chart *self, lxw_series_range *range)
{
    lxw_xml_start_tag(self->file, "c:numRef", NULL);

    /* Write the c:f element. */
    _chart_write_f(self, range->formula);

    if (!STAILQ_EMPTY(range->data_cache)) {
        /* Write the c:numCache element. */
        _chart_write_num_cache(self, range);
    }

    lxw_xml_end_tag(self->file, "c:numRef");
}

/*
 * Write the <c:strRef> element.
 */
STATIC void
_chart_write_str_ref(lxw_chart *self, lxw_series_range *range)
{
    lxw_xml_start_tag(self->file, "c:strRef", NULL);

    /* Write the c:f element. */
    _chart_write_f(self, range->formula);

    if (!STAILQ_EMPTY(range->data_cache)) {
        /* Write the c:strCache element. */
        _chart_write_str_cache(self, range);
    }

    lxw_xml_end_tag(self->file, "c:strRef");
}

/*
 * Write the cached data elements.
 */
STATIC void
_chart_write_data_cache(lxw_chart *self, lxw_series_range *range,
                        uint8_t has_string_cache)
{
    if (has_string_cache) {
        /* Write the c:strRef element. */
        _chart_write_str_ref(self, range);
    }
    else {
        /* Write the c:numRef element. */
        _chart_write_num_ref(self, range);
    }
}

/*
 * Write the <c:tx> element with a simple value such as for series names.
 */
STATIC void
_chart_write_tx_value(lxw_chart *self, char *name)
{
    lxw_xml_start_tag(self->file, "c:tx", NULL);

    /* Write the c:v element. */
    _chart_write_v_str(self, name);

    lxw_xml_end_tag(self->file, "c:tx");
}

/*
 * Write the <c:tx> element with a simple value such as for series names.
 */
STATIC void
_chart_write_tx_formula(lxw_chart *self, lxw_chart_title *title)
{
    lxw_xml_start_tag(self->file, "c:tx", NULL);

    _chart_write_str_ref(self, title->range);

    lxw_xml_end_tag(self->file, "c:tx");
}

/*
 * Write the <c:txPr> element.
 */
STATIC void
_chart_write_tx_pr(lxw_chart *self, uint8_t is_horizontal,
                   lxw_chart_font *font)
{
    int32_t rotation = 0;

    if (font)
        rotation = font->rotation;

    lxw_xml_start_tag(self->file, "c:txPr", NULL);

    /* Write the a:bodyPr element. */
    _chart_write_a_body_pr(self, rotation, is_horizontal);

    /* Write the a:lstStyle element. */
    _chart_write_a_lst_style(self);

    /* Write the a:p element. */
    _chart_write_a_p_formula(self, font);

    lxw_xml_end_tag(self->file, "c:txPr");
}

/*
 * Write the <c:txPr> element for pie chart legends.
 */
STATIC void
_chart_write_tx_pr_pie(lxw_chart *self, uint8_t is_horizontal,
                       lxw_chart_font *font)
{
    int32_t rotation = 0;

    if (font)
        rotation = font->rotation;

    lxw_xml_start_tag(self->file, "c:txPr", NULL);

    /* Write the a:bodyPr element. */
    _chart_write_a_body_pr(self, rotation, is_horizontal);

    /* Write the a:lstStyle element. */
    _chart_write_a_lst_style(self);

    /* Write the a:p element. */
    _chart_write_a_p_pie(self, font);

    lxw_xml_end_tag(self->file, "c:txPr");
}

/*
 * Write the <c:txPr> element.
 */
STATIC void
_chart_write_axis_font(lxw_chart *self, lxw_chart_font *font)
{
    if (!font)
        return;

    lxw_xml_start_tag(self->file, "c:txPr", NULL);

    /* Write the a:bodyPr element. */
    _chart_write_a_body_pr(self, font->rotation, LXW_FALSE);

    /* Write the a:lstStyle element. */
    _chart_write_a_lst_style(self);

    lxw_xml_start_tag(self->file, "a:p", NULL);

    /* Write the a:pPr element. */
    _chart_write_a_p_pr_rich(self, font);

    /* Write the a:endParaRPr element. */
    _chart_write_a_end_para_rpr(self);

    lxw_xml_end_tag(self->file, "a:p");
    lxw_xml_end_tag(self->file, "c:txPr");
}

/*
 * Write the <c:rich> element.
 */
STATIC void
_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;

    lxw_xml_start_tag(self->file, "c:rich", NULL);

    /* Write the a:bodyPr element. */
    _chart_write_a_body_pr(self, rotation, is_horizontal);

    /* Write the a:lstStyle element. */
    _chart_write_a_lst_style(self);

    /* Write the a:p element. */
    _chart_write_a_p_rich(self, name, font, ignore_rich_pr);

    lxw_xml_end_tag(self->file, "c:rich");
}

/*
 * Write the <c:tx> element.
 */
STATIC void
_chart_write_tx_rich(lxw_chart *self, char *name, uint8_t is_horizontal,
                     lxw_chart_font *font)
{

    lxw_xml_start_tag(self->file, "c:tx", NULL);

    /* Write the c:rich element. */
    _chart_write_rich(self, name, font, is_horizontal, LXW_FALSE);

    lxw_xml_end_tag(self->file, "c:tx");
}

/*
 * Write the <c:title> element for rich strings.
 */
STATIC void
_chart_write_title_rich(lxw_chart *self, lxw_chart_title *title)
{
    lxw_xml_start_tag(self->file, "c:title", NULL);

    /* Write the c:tx element. */
    _chart_write_tx_rich(self, title->name, title->is_horizontal,
                         title->font);

    /* Write the c:layout element. */
    _chart_write_layout(self);

    lxw_xml_end_tag(self->file, "c:title");
}

/*
 * Write the <c:title> element for a formula style title
 */
STATIC void
_chart_write_title_formula(lxw_chart *self, lxw_chart_title *title)
{
    lxw_xml_start_tag(self->file, "c:title", NULL);

    /* Write the c:tx element. */
    _chart_write_tx_formula(self, title);

    /* Write the c:layout element. */
    _chart_write_layout(self);

    /* Write the c:txPr element. */
    _chart_write_tx_pr(self, title->is_horizontal, title->font);

    lxw_xml_end_tag(self->file, "c:title");
}

/*
 * Write the <c:delete> element.
 */
STATIC void
_chart_write_delete(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:delete", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:autoTitleDeleted> element.
 */
STATIC void
_chart_write_auto_title_deleted(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:autoTitleDeleted", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:idx> element.
 */
STATIC void
_chart_write_idx(lxw_chart *self, uint16_t index)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_INT("val", index);

    lxw_xml_empty_tag(self->file, "c:idx", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <a:prstDash> element.
 */
STATIC void
_chart_write_a_prst_dash(lxw_chart *self, uint8_t dash_type)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();

    if (dash_type == LXW_CHART_LINE_DASH_ROUND_DOT)
        LXW_PUSH_ATTRIBUTES_STR("val", "sysDot");
    else if (dash_type == LXW_CHART_LINE_DASH_SQUARE_DOT)
        LXW_PUSH_ATTRIBUTES_STR("val", "sysDash");
    else if (dash_type == LXW_CHART_LINE_DASH_DASH_DOT)
        LXW_PUSH_ATTRIBUTES_STR("val", "dashDot");
    else if (dash_type == LXW_CHART_LINE_DASH_LONG_DASH)
        LXW_PUSH_ATTRIBUTES_STR("val", "lgDash");
    else if (dash_type == LXW_CHART_LINE_DASH_LONG_DASH_DOT)
        LXW_PUSH_ATTRIBUTES_STR("val", "lgDashDot");
    else if (dash_type == LXW_CHART_LINE_DASH_LONG_DASH_DOT_DOT)
        LXW_PUSH_ATTRIBUTES_STR("val", "lgDashDotDot");
    else if (dash_type == LXW_CHART_LINE_DASH_DOT)
        LXW_PUSH_ATTRIBUTES_STR("val", "dot");
    else if (dash_type == LXW_CHART_LINE_DASH_SYSTEM_DASH_DOT)
        LXW_PUSH_ATTRIBUTES_STR("val", "sysDashDot");
    else if (dash_type == LXW_CHART_LINE_DASH_SYSTEM_DASH_DOT_DOT)
        LXW_PUSH_ATTRIBUTES_STR("val", "sysDashDotDot");
    else
        LXW_PUSH_ATTRIBUTES_STR("val", "dash");

    lxw_xml_empty_tag(self->file, "a:prstDash", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <a:noFill> element.
 */
STATIC void
_chart_write_a_no_fill(lxw_chart *self)
{
    lxw_xml_empty_tag(self->file, "a:noFill", NULL);
}

/*
 * Write the <a:ln> element.
 */
STATIC void
_chart_write_a_ln(lxw_chart *self, lxw_chart_line *line)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;
    float width_flt;
    uint32_t width_int;

    LXW_INIT_ATTRIBUTES();

    /* Round width to nearest 0.25, like Excel. */
    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)
        LXW_PUSH_ATTRIBUTES_INT("w", width_int);

    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);
    }
    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");

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <a:fgClr> element.
 */
STATIC void
_chart_write_a_fg_clr(lxw_chart *self, lxw_color_t color)
{
    lxw_xml_start_tag(self->file, "a:fgClr", NULL);

    _chart_write_a_srgb_clr(self, color, LXW_FALSE);

    lxw_xml_end_tag(self->file, "a:fgClr");
}

/*
 * Write the <a:bgClr> element.
 */
STATIC void
_chart_write_a_bg_clr(lxw_chart *self, lxw_color_t color)
{
    lxw_xml_start_tag(self->file, "a:bgClr", NULL);

    _chart_write_a_srgb_clr(self, color, LXW_FALSE);

    lxw_xml_end_tag(self->file, "a:bgClr");
}

/*
 * Write the <a:pattFill> element.
 */
STATIC void
_chart_write_a_patt_fill(lxw_chart *self, lxw_chart_pattern *pattern)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();

    if (pattern->type == LXW_CHART_PATTERN_NONE)
        LXW_PUSH_ATTRIBUTES_STR("prst", "none");
    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_5)
        LXW_PUSH_ATTRIBUTES_STR("prst", "pct5");
    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_10)
        LXW_PUSH_ATTRIBUTES_STR("prst", "pct10");
    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_20)
        LXW_PUSH_ATTRIBUTES_STR("prst", "pct20");
    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_25)
        LXW_PUSH_ATTRIBUTES_STR("prst", "pct25");
    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_30)
        LXW_PUSH_ATTRIBUTES_STR("prst", "pct30");
    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_40)
        LXW_PUSH_ATTRIBUTES_STR("prst", "pct40");
    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_50)
        LXW_PUSH_ATTRIBUTES_STR("prst", "pct50");
    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_60)
        LXW_PUSH_ATTRIBUTES_STR("prst", "pct60");
    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_70)
        LXW_PUSH_ATTRIBUTES_STR("prst", "pct70");
    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_75)
        LXW_PUSH_ATTRIBUTES_STR("prst", "pct75");
    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_80)
        LXW_PUSH_ATTRIBUTES_STR("prst", "pct80");
    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_90)
        LXW_PUSH_ATTRIBUTES_STR("prst", "pct90");
    else if (pattern->type == LXW_CHART_PATTERN_LIGHT_DOWNWARD_DIAGONAL)
        LXW_PUSH_ATTRIBUTES_STR("prst", "ltDnDiag");
    else if (pattern->type == LXW_CHART_PATTERN_LIGHT_UPWARD_DIAGONAL)
        LXW_PUSH_ATTRIBUTES_STR("prst", "ltUpDiag");
    else if (pattern->type == LXW_CHART_PATTERN_DARK_DOWNWARD_DIAGONAL)
        LXW_PUSH_ATTRIBUTES_STR("prst", "dkDnDiag");
    else if (pattern->type == LXW_CHART_PATTERN_DARK_UPWARD_DIAGONAL)
        LXW_PUSH_ATTRIBUTES_STR("prst", "dkUpDiag");
    else if (pattern->type == LXW_CHART_PATTERN_WIDE_DOWNWARD_DIAGONAL)
        LXW_PUSH_ATTRIBUTES_STR("prst", "wdDnDiag");
    else if (pattern->type == LXW_CHART_PATTERN_WIDE_UPWARD_DIAGONAL)
        LXW_PUSH_ATTRIBUTES_STR("prst", "wdUpDiag");
    else if (pattern->type == LXW_CHART_PATTERN_LIGHT_VERTICAL)
        LXW_PUSH_ATTRIBUTES_STR("prst", "ltVert");
    else if (pattern->type == LXW_CHART_PATTERN_LIGHT_HORIZONTAL)
        LXW_PUSH_ATTRIBUTES_STR("prst", "ltHorz");
    else if (pattern->type == LXW_CHART_PATTERN_NARROW_VERTICAL)
        LXW_PUSH_ATTRIBUTES_STR("prst", "narVert");
    else if (pattern->type == LXW_CHART_PATTERN_NARROW_HORIZONTAL)
        LXW_PUSH_ATTRIBUTES_STR("prst", "narHorz");
    else if (pattern->type == LXW_CHART_PATTERN_DARK_VERTICAL)
        LXW_PUSH_ATTRIBUTES_STR("prst", "dkVert");
    else if (pattern->type == LXW_CHART_PATTERN_DARK_HORIZONTAL)
        LXW_PUSH_ATTRIBUTES_STR("prst", "dkHorz");
    else if (pattern->type == LXW_CHART_PATTERN_DASHED_DOWNWARD_DIAGONAL)
        LXW_PUSH_ATTRIBUTES_STR("prst", "dashDnDiag");
    else if (pattern->type == LXW_CHART_PATTERN_DASHED_UPWARD_DIAGONAL)
        LXW_PUSH_ATTRIBUTES_STR("prst", "dashUpDiag");
    else if (pattern->type == LXW_CHART_PATTERN_DASHED_HORIZONTAL)
        LXW_PUSH_ATTRIBUTES_STR("prst", "dashHorz");
    else if (pattern->type == LXW_CHART_PATTERN_DASHED_VERTICAL)
        LXW_PUSH_ATTRIBUTES_STR("prst", "dashVert");
    else if (pattern->type == LXW_CHART_PATTERN_SMALL_CONFETTI)
        LXW_PUSH_ATTRIBUTES_STR("prst", "smConfetti");
    else if (pattern->type == LXW_CHART_PATTERN_LARGE_CONFETTI)
        LXW_PUSH_ATTRIBUTES_STR("prst", "lgConfetti");
    else if (pattern->type == LXW_CHART_PATTERN_ZIGZAG)
        LXW_PUSH_ATTRIBUTES_STR("prst", "zigZag");
    else if (pattern->type == LXW_CHART_PATTERN_WAVE)
        LXW_PUSH_ATTRIBUTES_STR("prst", "wave");
    else if (pattern->type == LXW_CHART_PATTERN_DIAGONAL_BRICK)
        LXW_PUSH_ATTRIBUTES_STR("prst", "diagBrick");
    else if (pattern->type == LXW_CHART_PATTERN_HORIZONTAL_BRICK)
        LXW_PUSH_ATTRIBUTES_STR("prst", "horzBrick");
    else if (pattern->type == LXW_CHART_PATTERN_WEAVE)
        LXW_PUSH_ATTRIBUTES_STR("prst", "weave");
    else if (pattern->type == LXW_CHART_PATTERN_PLAID)
        LXW_PUSH_ATTRIBUTES_STR("prst", "plaid");
    else if (pattern->type == LXW_CHART_PATTERN_DIVOT)
        LXW_PUSH_ATTRIBUTES_STR("prst", "divot");
    else if (pattern->type == LXW_CHART_PATTERN_DOTTED_GRID)
        LXW_PUSH_ATTRIBUTES_STR("prst", "dotGrid");
    else if (pattern->type == LXW_CHART_PATTERN_DOTTED_DIAMOND)
        LXW_PUSH_ATTRIBUTES_STR("prst", "dotDmnd");
    else if (pattern->type == LXW_CHART_PATTERN_SHINGLE)
        LXW_PUSH_ATTRIBUTES_STR("prst", "shingle");
    else if (pattern->type == LXW_CHART_PATTERN_TRELLIS)
        LXW_PUSH_ATTRIBUTES_STR("prst", "trellis");
    else if (pattern->type == LXW_CHART_PATTERN_SPHERE)
        LXW_PUSH_ATTRIBUTES_STR("prst", "sphere");
    else if (pattern->type == LXW_CHART_PATTERN_SMALL_GRID)
        LXW_PUSH_ATTRIBUTES_STR("prst", "smGrid");
    else if (pattern->type == LXW_CHART_PATTERN_LARGE_GRID)
        LXW_PUSH_ATTRIBUTES_STR("prst", "lgGrid");
    else if (pattern->type == LXW_CHART_PATTERN_SMALL_CHECK)
        LXW_PUSH_ATTRIBUTES_STR("prst", "smCheck");
    else if (pattern->type == LXW_CHART_PATTERN_LARGE_CHECK)
        LXW_PUSH_ATTRIBUTES_STR("prst", "lgCheck");
    else if (pattern->type == LXW_CHART_PATTERN_OUTLINED_DIAMOND)
        LXW_PUSH_ATTRIBUTES_STR("prst", "openDmnd");
    else if (pattern->type == LXW_CHART_PATTERN_SOLID_DIAMOND)
        LXW_PUSH_ATTRIBUTES_STR("prst", "solidDmnd");
    else
        LXW_PUSH_ATTRIBUTES_STR("prst", "percent_50");

    lxw_xml_start_tag(self->file, "a:pattFill", &attributes);

    if (pattern->fg_color)
        _chart_write_a_fg_clr(self, pattern->fg_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();
}

/*
 * Write the <c:spPr> element.
 */
STATIC void
_chart_write_sp_pr(lxw_chart *self, lxw_chart_line *line,
                   lxw_chart_fill *fill, lxw_chart_pattern *pattern)
{
    if (!line && !fill && !pattern)
        return;

    lxw_xml_start_tag(self->file, "c:spPr", NULL);

    /* Write the series fill. Note: a pattern fill overrides a solid fill. */
    if (fill && !pattern) {
        if (fill->none) {
            /* Write the a:noFill element. */
            _chart_write_a_no_fill(self);
        }
        else {
            /* Write the a:solidFill element. */
            _chart_write_a_solid_fill(self, fill->color, fill->transparency);
        }
    }

    if (pattern) {
        /* Write the a:pattFill element. */
        _chart_write_a_patt_fill(self, pattern);
    }

    if (line) {
        /* Write the a:ln element. */
        _chart_write_a_ln(self, line);
    }

    lxw_xml_end_tag(self->file, "c:spPr");
}

/*
 * Write the <c:order> element.
 */
STATIC void
_chart_write_order(lxw_chart *self, uint16_t index)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_INT("val", index);

    lxw_xml_empty_tag(self->file, "c:order", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:axId> element.
 */
STATIC void
_chart_write_axis_id(lxw_chart *self, uint32_t axis_id)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_INT("val", axis_id);

    lxw_xml_empty_tag(self->file, "c:axId", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:axId> element.
 */
STATIC void
_chart_write_axis_ids(lxw_chart *self)
{
    if (!self->axis_id_1)
        _chart_add_axis_ids(self);

    _chart_write_axis_id(self, self->axis_id_1);
    _chart_write_axis_id(self, self->axis_id_2);
}

/*
 * Write the series name.
 */
STATIC void
_chart_write_series_name(lxw_chart *self, lxw_chart_series *series)
{
    if (series->title.name) {
        /* Write the c:tx element. */
        _chart_write_tx_value(self, series->title.name);
    }
    else if (series->title.range->formula) {
        /* Write the c:tx element. */
        _chart_write_tx_formula(self, &series->title);

    }
}

/*
 * Write the <c:majorTickMark> element.
 */
STATIC void
_chart_write_major_tick_mark(lxw_chart *self, lxw_chart_axis *axis)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    if (!axis->major_tick_mark)
        return;

    LXW_INIT_ATTRIBUTES();

    if (axis->major_tick_mark == LXW_CHART_AXIS_TICK_MARK_NONE)
        LXW_PUSH_ATTRIBUTES_STR("val", "none");
    else if (axis->major_tick_mark == LXW_CHART_AXIS_TICK_MARK_INSIDE)
        LXW_PUSH_ATTRIBUTES_STR("val", "in");
    else if (axis->major_tick_mark == LXW_CHART_AXIS_TICK_MARK_CROSSING)
        LXW_PUSH_ATTRIBUTES_STR("val", "cross");
    else
        LXW_PUSH_ATTRIBUTES_STR("val", "out");

    lxw_xml_empty_tag(self->file, "c:majorTickMark", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:minorTickMark> element.
 */
STATIC void
_chart_write_minor_tick_mark(lxw_chart *self, lxw_chart_axis *axis)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    if (!axis->minor_tick_mark)
        return;

    LXW_INIT_ATTRIBUTES();

    if (axis->minor_tick_mark == LXW_CHART_AXIS_TICK_MARK_NONE)
        LXW_PUSH_ATTRIBUTES_STR("val", "none");
    else if (axis->minor_tick_mark == LXW_CHART_AXIS_TICK_MARK_INSIDE)
        LXW_PUSH_ATTRIBUTES_STR("val", "in");
    else if (axis->minor_tick_mark == LXW_CHART_AXIS_TICK_MARK_CROSSING)
        LXW_PUSH_ATTRIBUTES_STR("val", "cross");
    else
        LXW_PUSH_ATTRIBUTES_STR("val", "out");

    lxw_xml_empty_tag(self->file, "c:minorTickMark", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:symbol> element.
 */
STATIC void
_chart_write_symbol(lxw_chart *self, uint8_t type)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();

    if (type == LXW_CHART_MARKER_SQUARE)
        LXW_PUSH_ATTRIBUTES_STR("val", "square");
    else if (type == LXW_CHART_MARKER_DIAMOND)
        LXW_PUSH_ATTRIBUTES_STR("val", "diamond");
    else if (type == LXW_CHART_MARKER_TRIANGLE)
        LXW_PUSH_ATTRIBUTES_STR("val", "triangle");
    else if (type == LXW_CHART_MARKER_X)
        LXW_PUSH_ATTRIBUTES_STR("val", "x");
    else if (type == LXW_CHART_MARKER_STAR)
        LXW_PUSH_ATTRIBUTES_STR("val", "star");
    else if (type == LXW_CHART_MARKER_SHORT_DASH)
        LXW_PUSH_ATTRIBUTES_STR("val", "short_dash");
    else if (type == LXW_CHART_MARKER_LONG_DASH)
        LXW_PUSH_ATTRIBUTES_STR("val", "long_dash");
    else if (type == LXW_CHART_MARKER_CIRCLE)
        LXW_PUSH_ATTRIBUTES_STR("val", "circle");
    else if (type == LXW_CHART_MARKER_PLUS)
        LXW_PUSH_ATTRIBUTES_STR("val", "plus");
    else
        LXW_PUSH_ATTRIBUTES_STR("val", "none");

    lxw_xml_empty_tag(self->file, "c:symbol", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:dPt> element.
 */
STATIC void
_chart_write_d_pt(lxw_chart *self, lxw_chart_point *point, uint16_t index)
{
    lxw_xml_start_tag(self->file, "c:dPt", NULL);

    /* Write the c:idx element. */
    _chart_write_idx(self, index);

    /* Scatter/Line charts have an additional marker for the point. */
    if (self->chart_group == LXW_CHART_SCATTER
        || self->chart_group == LXW_CHART_LINE)
        lxw_xml_start_tag(self->file, "c:marker", NULL);

    /* Write the c:spPr element. */
    _chart_write_sp_pr(self, point->line, point->fill, point->pattern);

    if (self->chart_group == LXW_CHART_SCATTER
        || self->chart_group == LXW_CHART_LINE)
        lxw_xml_end_tag(self->file, "c:marker");

    lxw_xml_end_tag(self->file, "c:dPt");
}

/*
 * Write the <c:dPt> element.
 */
STATIC void
_chart_write_points(lxw_chart *self, lxw_chart_series *series)
{
    uint16_t index;

    for (index = 0; index < series->point_count; index++) {
        lxw_chart_point *point = &series->points[index];

        /* Ignore empty points. */
        if (!point->line && !point->fill && !point->pattern)
            continue;

        /* Write the c:dPt element. */
        _chart_write_d_pt(self, &series->points[index], index);
    }
}

/*
 * Write the <c:invertIfNegative> element.
 */
STATIC void
_chart_write_invert_if_negative(lxw_chart *self, lxw_chart_series *series)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    if (!series->invert_if_negative)
        return;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_STR("val", "1");

    lxw_xml_empty_tag(self->file, "c:invertIfNegative", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:showVal> element.
 */
STATIC void
_chart_write_show_val(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:showVal", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:showCatName> element.
 */
STATIC void
_chart_write_show_cat_name(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:showCatName", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:showSerName> element.
 */
STATIC void
_chart_write_show_ser_name(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:showSerName", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:showLeaderLines> element.
 */
STATIC void
_chart_write_show_leader_lines(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:showLeaderLines", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:dLblPos> element.
 */
STATIC void
_chart_write_d_lbl_pos(lxw_chart *self, uint8_t position)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();

    if (position == LXW_CHART_LABEL_POSITION_RIGHT)
        LXW_PUSH_ATTRIBUTES_STR("val", "r");
    else if (position == LXW_CHART_LABEL_POSITION_LEFT)
        LXW_PUSH_ATTRIBUTES_STR("val", "l");
    else if (position == LXW_CHART_LABEL_POSITION_ABOVE)
        LXW_PUSH_ATTRIBUTES_STR("val", "t");
    else if (position == LXW_CHART_LABEL_POSITION_BELOW)
        LXW_PUSH_ATTRIBUTES_STR("val", "b");
    else if (position == LXW_CHART_LABEL_POSITION_INSIDE_BASE)
        LXW_PUSH_ATTRIBUTES_STR("val", "inBase");
    else if (position == LXW_CHART_LABEL_POSITION_INSIDE_END)
        LXW_PUSH_ATTRIBUTES_STR("val", "inEnd");
    else if (position == LXW_CHART_LABEL_POSITION_OUTSIDE_END)
        LXW_PUSH_ATTRIBUTES_STR("val", "outEnd");
    else if (position == LXW_CHART_LABEL_POSITION_BEST_FIT)
        LXW_PUSH_ATTRIBUTES_STR("val", "bestFit");
    else
        LXW_PUSH_ATTRIBUTES_STR("val", "ctr");

    lxw_xml_empty_tag(self->file, "c:dLblPos", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:separator> element.
 */
STATIC void
_chart_write_separator(lxw_chart *self, uint8_t separator)
{
    if (separator == LXW_CHART_LABEL_SEPARATOR_SEMICOLON)
        lxw_xml_data_element(self->file, "c:separator", "; ", NULL);
    else if (separator == LXW_CHART_LABEL_SEPARATOR_PERIOD)
        lxw_xml_data_element(self->file, "c:separator", ". ", NULL);
    else if (separator == LXW_CHART_LABEL_SEPARATOR_NEWLINE)
        lxw_xml_data_element(self->file, "c:separator", "\n", NULL);
    else if (separator == LXW_CHART_LABEL_SEPARATOR_SPACE)
        lxw_xml_data_element(self->file, "c:separator", " ", NULL);
    else
        lxw_xml_data_element(self->file, "c:separator", ", ", NULL);
}

/*
 * Write the <c:showLegendKey> element.
 */
STATIC void
_chart_write_show_legend_key(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:showLegendKey", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:showPercent> element.
 */
STATIC void
_chart_write_show_percent(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:showPercent", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:numFmt> element.
 */
STATIC void
_chart_write_label_num_fmt(lxw_chart *self, char *format)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_STR("formatCode", format);
    LXW_PUSH_ATTRIBUTES_STR("sourceLinked", "0");

    lxw_xml_empty_tag(self->file, "c:numFmt", &attributes);

    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)
        _chart_write_d_lbl_pos(self, series->label_position);

    /* Write the c:showLegendKey element. */
    if (series->show_labels_legend)
        _chart_write_show_legend_key(self);

    /* 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:showPercent element. */
    if (series->show_labels_percent)
        _chart_write_show_percent(self);

    /* Write the c:separator element. */
    if (series->label_separator)
        _chart_write_separator(self, series->label_separator);

    /* Write the c:showLeaderLines element. */
    if (series->show_labels_leader)
        _chart_write_show_leader_lines(self);

    lxw_xml_end_tag(self->file, "c:dLbls");
}

/*
 * Write the <c:intercept> element.
 */
STATIC void
_chart_write_intercept(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:intercept", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:dispRSqr> element.
 */
STATIC void
_chart_write_disp_rsqr(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:dispRSqr", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:trendlineLbl> element.
 */
STATIC void
_chart_write_trendline_lbl(lxw_chart *self)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    lxw_xml_start_tag(self->file, "c:trendlineLbl", NULL);

    lxw_xml_empty_tag(self->file, "c:layout", NULL);

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_STR("formatCode", "General");
    LXW_PUSH_ATTRIBUTES_INT("sourceLinked", 0);

    lxw_xml_empty_tag(self->file, "c:numFmt", &attributes);

    lxw_xml_end_tag(self->file, "c:trendlineLbl");

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:dispEq> element.
 */
STATIC void
_chart_write_disp_eq(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:dispEq", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:period> element.
 */
STATIC void
_chart_write_period(lxw_chart *self, uint8_t value)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_INT("val", value);

    lxw_xml_empty_tag(self->file, "c:period", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:forward> element.
 */
STATIC void
_chart_write_forward(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:forward", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:backward> element.
 */
STATIC void
_chart_write_backward(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:backward", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:name> element.
 */
STATIC void
_chart_write_name(lxw_chart *self, char *name)
{
    lxw_xml_data_element(self->file, "c:name", name, NULL);
}

/*
 * Write the <c:trendlineType> element.
 */
STATIC void
_chart_write_trendline_type(lxw_chart *self, uint8_t type)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();

    if (type == LXW_CHART_TRENDLINE_TYPE_LOG)
        LXW_PUSH_ATTRIBUTES_STR("val", "log");
    else if (type == LXW_CHART_TRENDLINE_TYPE_POLY)
        LXW_PUSH_ATTRIBUTES_STR("val", "poly");
    else if (type == LXW_CHART_TRENDLINE_TYPE_POWER)
        LXW_PUSH_ATTRIBUTES_STR("val", "power");
    else if (type == LXW_CHART_TRENDLINE_TYPE_EXP)
        LXW_PUSH_ATTRIBUTES_STR("val", "exp");
    else if (type == LXW_CHART_TRENDLINE_TYPE_AVERAGE)
        LXW_PUSH_ATTRIBUTES_STR("val", "movingAvg");
    else
        LXW_PUSH_ATTRIBUTES_STR("val", "linear");

    lxw_xml_empty_tag(self->file, "c:trendlineType", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:trendline> element.
 */
STATIC void
_chart_write_trendline(lxw_chart *self, lxw_chart_series *series)
{
    if (!series->has_trendline)
        return;

    lxw_xml_start_tag(self->file, "c:trendline", NULL);

    /* Write the c:name element. */
    if (series->trendline_name)
        _chart_write_name(self, series->trendline_name);

    /* Write the c:spPr element. */
    _chart_write_sp_pr(self, series->trendline_line, NULL, NULL);

    /* Write the c:trendlineType element. */
    _chart_write_trendline_type(self, series->trendline_type);

    /* Write the c:order element. */
    if (series->trendline_type == LXW_CHART_TRENDLINE_TYPE_POLY
        && series->trendline_value >= 2) {

        _chart_write_order(self, series->trendline_value);
    }

    /* Write the c:period element. */
    if (series->trendline_type == LXW_CHART_TRENDLINE_TYPE_AVERAGE
        && series->trendline_value >= 2) {

        _chart_write_period(self, series->trendline_value);
    }

    if (series->has_trendline_forecast) {
        /* Write the c:forward element. */
        _chart_write_forward(self, series->trendline_forward);

        /* Write the c:backward element. */
        _chart_write_backward(self, series->trendline_backward);
    }

    /* Write the c:intercept element. */
    if (series->has_trendline_intercept)
        _chart_write_intercept(self, series->trendline_intercept);

    /* Write the c:dispRSqr element. */
    if (series->has_trendline_r_squared)
        _chart_write_disp_rsqr(self);

    if (series->has_trendline_equation) {
        /* Write the c:dispEq element. */
        _chart_write_disp_eq(self);

        /* Write the c:trendlineLbl element. */
        _chart_write_trendline_lbl(self);

    }

    lxw_xml_end_tag(self->file, "c:trendline");
}

/*
 * Write the <c:val> 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 <c:noEndCap> 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 <c:errValType> 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 <c:errBarType> 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 <c:errDir> element.
 */
STATIC void
_chart_write_err_dir(lxw_chart *self, uint8_t is_x)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();

    if (is_x)
        LXW_PUSH_ATTRIBUTES_STR("val", "x");
    else
        LXW_PUSH_ATTRIBUTES_STR("val", "y");

    lxw_xml_empty_tag(self->file, "c:errDir", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:errBars> element.
 */
STATIC void
_chart_write_err_bars(lxw_chart *self, lxw_series_error_bars *error_bars)
{
    if (!error_bars->is_set)
        return;

    lxw_xml_start_tag(self->file, "c:errBars", NULL);

    /* Write the c:errDir element, except for Column/Bar charts. */
    if (error_bars->chart_group != LXW_CHART_BAR
        && error_bars->chart_group != LXW_CHART_COLUMN) {

        _chart_write_err_dir(self, error_bars->is_x);
    }

    /* Write the c:errBarType element. */
    _chart_write_err_bar_type(self, error_bars->direction);

    /* Write the c:errValType element. */
    _chart_write_err_val_type(self, error_bars->type);

    /* Write the c:noEndCap element. */
    if (error_bars->endcap == LXW_CHART_ERROR_BAR_NO_CAP)
        _chart_write_no_end_cap(self);

    /* Write the c:val element. */
    if (error_bars->has_value)
        _chart_write_error_val(self, error_bars->value);

    /* Write the c:spPr element. */
    _chart_write_sp_pr(self, error_bars->line, NULL, NULL);

    lxw_xml_end_tag(self->file, "c:errBars");
}

/*
 * Write the <c:errBars> element.
 */
STATIC void
_chart_write_error_bars(lxw_chart *self, lxw_chart_series *series)
{
    _chart_write_err_bars(self, series->x_error_bars);
    _chart_write_err_bars(self, series->y_error_bars);
}

/*
 * Write the <c:size> element.
 */
STATIC void
_chart_write_marker_size(lxw_chart *self, uint8_t size)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_INT("val", size);

    lxw_xml_empty_tag(self->file, "c:size", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:marker> element.
 */
STATIC void
_chart_write_marker(lxw_chart *self, lxw_chart_marker *marker)
{
    /* If there isn't a user defined marker use the default, if this chart
     * type one. The default usually turns the marker off. */
    if (!marker)
        marker = self->default_marker;

    if (!marker)
        return;

    if (marker->type == LXW_CHART_MARKER_AUTOMATIC)
        return;

    lxw_xml_start_tag(self->file, "c:marker", NULL);

    /* Write the c:symbol element. */
    _chart_write_symbol(self, marker->type);

    /* Write the c:size element. */
    if (marker->size)
        _chart_write_marker_size(self, marker->size);

    /* Write the c:spPr element. */
    _chart_write_sp_pr(self, marker->line, marker->fill, marker->pattern);

    lxw_xml_end_tag(self->file, "c:marker");
}

/*
 * Write the <c:marker> element.
 */
STATIC void
_chart_write_marker_value(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:marker", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:smooth> element.
 */
STATIC void
_chart_write_smooth(lxw_chart *self, uint8_t smooth)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    if (!smooth)
        return;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_STR("val", "1");

    lxw_xml_empty_tag(self->file, "c:smooth", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:scatterStyle> element.
 */
STATIC void
_chart_write_scatter_style(lxw_chart *self)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();

    if (self->type == LXW_CHART_SCATTER_SMOOTH
        || self->type == LXW_CHART_SCATTER_SMOOTH_WITH_MARKERS)
        LXW_PUSH_ATTRIBUTES_STR("val", "smoothMarker");
    else
        LXW_PUSH_ATTRIBUTES_STR("val", "lineMarker");

    lxw_xml_empty_tag(self->file, "c:scatterStyle", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:cat> element.
 */
STATIC void
_chart_write_cat(lxw_chart *self, lxw_chart_series *series)
{
    uint8_t has_string_cache = series->categories->has_string_cache;

    /* Ignore <c:cat> elements for charts without category values. */
    if (!series->categories->formula)
        return;

    self->cat_has_num_fmt = !has_string_cache;

    lxw_xml_start_tag(self->file, "c:cat", NULL);

    /* Write the c:numRef element. */
    _chart_write_data_cache(self, series->categories, has_string_cache);

    lxw_xml_end_tag(self->file, "c:cat");
}

/*
 * Write the <c:xVal> element.
 */
STATIC void
_chart_write_x_val(lxw_chart *self, lxw_chart_series *series)
{
    uint8_t has_string_cache = series->categories->has_string_cache;

    lxw_xml_start_tag(self->file, "c:xVal", NULL);

    /* Write the data cache elements. */
    _chart_write_data_cache(self, series->categories, has_string_cache);

    lxw_xml_end_tag(self->file, "c:xVal");
}

/*
 * Write the <c:val> element.
 */
STATIC void
_chart_write_val(lxw_chart *self, lxw_chart_series *series)
{
    lxw_xml_start_tag(self->file, "c:val", NULL);

    /* Write the data cache elements. The string_cache is set to false since
     * this should always be a number series. */
    _chart_write_data_cache(self, series->values, LXW_FALSE);

    lxw_xml_end_tag(self->file, "c:val");
}

/*
 * Write the <c:yVal> element.
 */
STATIC void
_chart_write_y_val(lxw_chart *self, lxw_chart_series *series)
{
    lxw_xml_start_tag(self->file, "c:yVal", NULL);

    /* Write the data cache elements. The string_cache is set to false since
     * this should always be a number series. */
    _chart_write_data_cache(self, series->values, LXW_FALSE);

    lxw_xml_end_tag(self->file, "c:yVal");
}

/*
 * Write the <c:ser> element.
 */
STATIC void
_chart_write_ser(lxw_chart *self, lxw_chart_series *series)
{
    uint16_t index = self->series_index++;

    lxw_xml_start_tag(self->file, "c:ser", NULL);

    /* Write the c:idx element. */
    _chart_write_idx(self, index);

    /* Write the c:order element. */
    _chart_write_order(self, index);

    /* Write the series name. */
    _chart_write_series_name(self, series);

    /* Write the c:spPr element. */
    _chart_write_sp_pr(self, series->line, series->fill, series->pattern);

    /* Write the c:marker element. */
    _chart_write_marker(self, series->marker);

    /* Write the c:invertIfNegative element. */
    _chart_write_invert_if_negative(self, series);

    /* Write the char points. */
    _chart_write_points(self, series);

    /* Write the c:dLbls element. */
    _chart_write_d_lbls(self, series);

    /* Write the c:trendline element. */
    _chart_write_trendline(self, series);

    /* Write the c:errBars element. */
    _chart_write_error_bars(self, series);

    /* Write the c:cat element. */
    _chart_write_cat(self, series);

    /* Write the c:val element. */
    _chart_write_val(self, series);

    /* Write the c:smooth element. */
    if (self->chart_group == LXW_CHART_SCATTER
        || self->chart_group == LXW_CHART_LINE)
        _chart_write_smooth(self, series->smooth);

    lxw_xml_end_tag(self->file, "c:ser");
}

/*
 * Write the <c:ser> element but with c:xVal/c:yVal instead of c:cat/c:val
 * elements.
 */
STATIC void
_chart_write_xval_ser(lxw_chart *self, lxw_chart_series *series)
{
    uint16_t index = self->series_index++;

    lxw_xml_start_tag(self->file, "c:ser", NULL);

    /* Write the c:idx element. */
    _chart_write_idx(self, index);

    /* Write the c:order element. */
    _chart_write_order(self, index);

    /* Write the series name. */
    _chart_write_series_name(self, series);

    /* Write the c:spPr element. */
    _chart_write_sp_pr(self, series->line, series->fill, series->pattern);

    /* Write the c:marker element. */
    _chart_write_marker(self, series->marker);

    /* Write the char points. */
    _chart_write_points(self, series);

    /* Write the c:dLbls element. */
    _chart_write_d_lbls(self, series);

    /* Write the c:trendline element. */
    _chart_write_trendline(self, series);

    /* Write the c:errBars element. */
    _chart_write_error_bars(self, series);

    /* Write the c:xVal element. */
    _chart_write_x_val(self, series);

    /* Write the yVal element. */
    _chart_write_y_val(self, series);

    /* Write the c:smooth element. */
    _chart_write_smooth(self, series->smooth);

    lxw_xml_end_tag(self->file, "c:ser");
}

/*
 * Write the <c:orientation> element.
 */
STATIC void
_chart_write_orientation(lxw_chart *self, uint8_t reverse)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();
    if (reverse)
        LXW_PUSH_ATTRIBUTES_STR("val", "maxMin");
    else
        LXW_PUSH_ATTRIBUTES_STR("val", "minMax");

    lxw_xml_empty_tag(self->file, "c:orientation", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:max> element.
 */
STATIC void
_chart_write_max(lxw_chart *self, double max)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;
    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_DBL("val", max);

    lxw_xml_empty_tag(self->file, "c:max", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:min> element.
 */
STATIC void
_chart_write_min(lxw_chart *self, double min)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;
    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_DBL("val", min);

    lxw_xml_empty_tag(self->file, "c:min", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:logBase> element.
 */
STATIC void
_chart_write_log_base(lxw_chart *self, uint16_t log_base)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    if (!log_base)
        return;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_INT("val", log_base);

    lxw_xml_empty_tag(self->file, "c:logBase", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:scaling> element.
 */
STATIC void
_chart_write_scaling(lxw_chart *self, uint8_t reverse,
                     uint8_t has_min, double min,
                     uint8_t has_max, double max, uint16_t log_base)
{
    lxw_xml_start_tag(self->file, "c:scaling", NULL);

    /* Write the c:logBase element. */
    _chart_write_log_base(self, log_base);

    /* Write the c:orientation element. */
    _chart_write_orientation(self, reverse);

    if (has_max) {
        /* Write the c:max element. */
        _chart_write_max(self, max);
    }

    if (has_min) {
        /* Write the c:min element. */
        _chart_write_min(self, min);
    }

    lxw_xml_end_tag(self->file, "c:scaling");
}

/*
 * Write the <c:axPos> element.
 */
STATIC void
_chart_write_axis_pos(lxw_chart *self, uint8_t position, uint8_t reverse)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();

    /* Reverse the axis direction if required. */
    position ^= reverse;

    if (position == LXW_CHART_AXIS_RIGHT)
        LXW_PUSH_ATTRIBUTES_STR("val", "r");
    else if (position == LXW_CHART_AXIS_LEFT)
        LXW_PUSH_ATTRIBUTES_STR("val", "l");
    else if (position == LXW_CHART_AXIS_TOP)
        LXW_PUSH_ATTRIBUTES_STR("val", "t");
    else if (position == LXW_CHART_AXIS_BOTTOM)
        LXW_PUSH_ATTRIBUTES_STR("val", "b");

    lxw_xml_empty_tag(self->file, "c:axPos", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:tickLblPos> element.
 */
STATIC void
_chart_write_tick_label_pos(lxw_chart *self, lxw_chart_axis *axis)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();

    if (axis->label_position == LXW_CHART_AXIS_LABEL_POSITION_HIGH)
        LXW_PUSH_ATTRIBUTES_STR("val", "high");
    else if (axis->label_position == LXW_CHART_AXIS_LABEL_POSITION_LOW)
        LXW_PUSH_ATTRIBUTES_STR("val", "low");
    else if (axis->label_position == LXW_CHART_AXIS_LABEL_POSITION_NONE)
        LXW_PUSH_ATTRIBUTES_STR("val", "none");
    else
        LXW_PUSH_ATTRIBUTES_STR("val", "nextTo");

    lxw_xml_empty_tag(self->file, "c:tickLblPos", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:crossAx> element.
 */
STATIC void
_chart_write_cross_axis(lxw_chart *self, uint32_t axis_id)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_INT("val", axis_id);

    lxw_xml_empty_tag(self->file, "c:crossAx", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:crosses> element.
 */
STATIC void
_chart_write_crosses(lxw_chart *self, lxw_chart_axis *axis)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();

    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);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:crossesAt> element.
 */
STATIC void
_chart_write_crosses_at(lxw_chart *self, lxw_chart_axis *axis)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();

    LXW_PUSH_ATTRIBUTES_DBL("val", axis->crossing);

    lxw_xml_empty_tag(self->file, "c:crossesAt", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:auto> element.
 */
STATIC void
_chart_write_auto(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:auto", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:lblAlgn> element.
 */
STATIC void
_chart_write_label_align(lxw_chart *self, lxw_chart_axis *axis)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();

    if (axis->label_align == LXW_CHART_AXIS_LABEL_ALIGN_LEFT)
        LXW_PUSH_ATTRIBUTES_STR("val", "l");
    else if (axis->label_align == LXW_CHART_AXIS_LABEL_ALIGN_RIGHT)
        LXW_PUSH_ATTRIBUTES_STR("val", "r");
    else
        LXW_PUSH_ATTRIBUTES_STR("val", "ctr");

    lxw_xml_empty_tag(self->file, "c:lblAlgn", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:tickLblSkip> element.
 */
STATIC void
_chart_write_tick_label_skip(lxw_chart *self, lxw_chart_axis *axis)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    if (!axis->interval_unit)
        return;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_INT("val", axis->interval_unit);

    lxw_xml_empty_tag(self->file, "c:tickLblSkip", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:tickMarkSkip> element.
 */
STATIC void
_chart_write_tick_mark_skip(lxw_chart *self, lxw_chart_axis *axis)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    if (!axis->interval_tick)
        return;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_INT("val", axis->interval_tick);

    lxw_xml_empty_tag(self->file, "c:tickMarkSkip", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:majorUnit> element.
 */
STATIC void
_chart_write_major_unit(lxw_chart *self, lxw_chart_axis *axis)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    if (!axis->has_major_unit)
        return;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_DBL("val", axis->major_unit);

    lxw_xml_empty_tag(self->file, "c:majorUnit", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:minorUnit> element.
 */
STATIC void
_chart_write_minor_unit(lxw_chart *self, lxw_chart_axis *axis)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    if (!axis->has_minor_unit)
        return;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_DBL("val", axis->minor_unit);

    lxw_xml_empty_tag(self->file, "c:minorUnit", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:dispUnits> element.
 */
STATIC void
_chart_write_disp_units(lxw_chart *self, lxw_chart_axis *axis)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    if (!axis->display_units)
        return;

    LXW_INIT_ATTRIBUTES();

    lxw_xml_start_tag(self->file, "c:dispUnits", NULL);

    if (axis->display_units == LXW_CHART_AXIS_UNITS_HUNDREDS)
        LXW_PUSH_ATTRIBUTES_STR("val", "hundreds");
    else if (axis->display_units == LXW_CHART_AXIS_UNITS_THOUSANDS)
        LXW_PUSH_ATTRIBUTES_STR("val", "thousands");
    else if (axis->display_units == LXW_CHART_AXIS_UNITS_TEN_THOUSANDS)
        LXW_PUSH_ATTRIBUTES_STR("val", "tenThousands");
    else if (axis->display_units == LXW_CHART_AXIS_UNITS_HUNDRED_THOUSANDS)
        LXW_PUSH_ATTRIBUTES_STR("val", "hundredThousands");
    else if (axis->display_units == LXW_CHART_AXIS_UNITS_MILLIONS)
        LXW_PUSH_ATTRIBUTES_STR("val", "millions");
    else if (axis->display_units == LXW_CHART_AXIS_UNITS_TEN_MILLIONS)
        LXW_PUSH_ATTRIBUTES_STR("val", "tenMillions");
    else if (axis->display_units == LXW_CHART_AXIS_UNITS_HUNDRED_MILLIONS)
        LXW_PUSH_ATTRIBUTES_STR("val", "hundredMillions");
    else if (axis->display_units == LXW_CHART_AXIS_UNITS_BILLIONS)
        LXW_PUSH_ATTRIBUTES_STR("val", "billions");
    else if (axis->display_units == LXW_CHART_AXIS_UNITS_TRILLIONS)
        LXW_PUSH_ATTRIBUTES_STR("val", "trillions");
    else
        LXW_PUSH_ATTRIBUTES_STR("val", "hundreds");

    lxw_xml_empty_tag(self->file, "c:builtInUnit", &attributes);

    if (axis->display_units_visible) {
        lxw_xml_start_tag(self->file, "c:dispUnitsLbl", NULL);
        lxw_xml_empty_tag(self->file, "c:layout", NULL);
        lxw_xml_end_tag(self->file, "c:dispUnitsLbl");
    }

    lxw_xml_end_tag(self->file, "c:dispUnits");

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:lblOffset> element.
 */
STATIC void
_chart_write_label_offset(lxw_chart *self)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_STR("val", "100");

    lxw_xml_empty_tag(self->file, "c:lblOffset", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:majorGridlines> element.
 */
STATIC void
_chart_write_major_gridlines(lxw_chart *self, lxw_chart_axis *axis)
{
    if (!axis->major_gridlines.visible)
        return;

    if (axis->major_gridlines.line) {
        lxw_xml_start_tag(self->file, "c:majorGridlines", NULL);

        /* Write the c:spPr element for the axis line. */
        _chart_write_sp_pr(self, axis->major_gridlines.line, NULL, NULL);

        lxw_xml_end_tag(self->file, "c:majorGridlines");
    }
    else {
        lxw_xml_empty_tag(self->file, "c:majorGridlines", NULL);
    }
}

/*
 * Write the <c:minorGridlines> element.
 */
STATIC void
_chart_write_minor_gridlines(lxw_chart *self, lxw_chart_axis *axis)
{
    if (!axis->minor_gridlines.visible)
        return;

    if (axis->minor_gridlines.line) {
        lxw_xml_start_tag(self->file, "c:minorGridlines", NULL);

        /* Write the c:spPr element for the axis line. */
        _chart_write_sp_pr(self, axis->minor_gridlines.line, NULL, NULL);

        lxw_xml_end_tag(self->file, "c:minorGridlines");

    }
    else {
        lxw_xml_empty_tag(self->file, "c:minorGridlines", NULL);
    }
}

/*
 * Write the <c:numberFormat> element. Note: It is assumed that if a user
 * defined number format is supplied (i.e., non-default) then the sourceLinked
 * attribute is 0. The user can override this if required.
 */
STATIC void
_chart_write_number_format(lxw_chart *self, lxw_chart_axis *axis)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;
    char *num_format;
    uint8_t source_linked = 1;

    /* Set the number format to the axis default if not set. */
    if (axis->num_format)
        num_format = axis->num_format;
    else
        num_format = axis->default_num_format;

    /* Check if a user defined number format has been set. */
    if (strcmp(num_format, axis->default_num_format))
        source_linked = 0;

    /* Allow override of sourceLinked. */
    if (axis->source_linked)
        source_linked = 1;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_STR("formatCode", num_format);
    LXW_PUSH_ATTRIBUTES_INT("sourceLinked", source_linked);

    lxw_xml_empty_tag(self->file, "c:numFmt", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:numFmt> element. Special case handler for category axes which
 * don't always have a number format.
 */
STATIC void
_chart_write_cat_number_format(lxw_chart *self, lxw_chart_axis *axis)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;
    char *num_format;
    uint8_t source_linked = 1;
    uint8_t default_format = LXW_TRUE;

    /* Set the number format to the axis default if not set. */
    if (axis->num_format)
        num_format = axis->num_format;
    else
        num_format = axis->default_num_format;

    /* Check if a user defined number format has been set. */
    if (strcmp(num_format, axis->default_num_format)) {
        source_linked = 0;
        default_format = LXW_FALSE;
    }

    /* Allow override of sourceLinked. */
    if (axis->source_linked)
        source_linked = 1;

    /* Skip if cat doesn't have a num format (unless it is non-default). */
    if (!self->cat_has_num_fmt && default_format)
        return;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_STR("formatCode", num_format);
    LXW_PUSH_ATTRIBUTES_INT("sourceLinked", source_linked);

    lxw_xml_empty_tag(self->file, "c:numFmt", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:crossBetween> element.
 */
STATIC void
_chart_write_cross_between(lxw_chart *self, uint8_t position)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    if (!position)
        position = self->default_cross_between;

    LXW_INIT_ATTRIBUTES();

    if (position == LXW_CHART_AXIS_POSITION_ON_TICK)
        LXW_PUSH_ATTRIBUTES_STR("val", "midCat");
    else
        LXW_PUSH_ATTRIBUTES_STR("val", "between");

    lxw_xml_empty_tag(self->file, "c:crossBetween", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:overlay> 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 <c:legendPos> element.
 */
STATIC void
_chart_write_legend_pos(lxw_chart *self, char *position)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();

    LXW_PUSH_ATTRIBUTES_STR("val", position);

    lxw_xml_empty_tag(self->file, "c:legendPos", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:legendEntry> element.
 */
STATIC void
_chart_write_legend_entry(lxw_chart *self, uint16_t index)
{
    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:dst_label element. */
    _chart_write_delete(self);

    lxw_xml_end_tag(self->file, "c:legendEntry");
}

/*
 * Write the <c:legend> element.
 */
STATIC void
_chart_write_legend(lxw_chart *self)
{
    uint8_t has_overlay = LXW_FALSE;
    uint16_t index;

    if (self->legend.position == LXW_CHART_LEGEND_NONE)
        return;

    lxw_xml_start_tag(self->file, "c:legend", NULL);

    /* Write the c:legendPos element. */
    switch (self->legend.position) {
        case LXW_CHART_LEGEND_LEFT:
            _chart_write_legend_pos(self, "l");
            break;
        case LXW_CHART_LEGEND_TOP:
            _chart_write_legend_pos(self, "t");
            break;
        case LXW_CHART_LEGEND_BOTTOM:
            _chart_write_legend_pos(self, "b");
            break;
        case LXW_CHART_LEGEND_TOP_RIGHT:
            _chart_write_legend_pos(self, "tr");
            break;
        case LXW_CHART_LEGEND_OVERLAY_RIGHT:
            _chart_write_legend_pos(self, "r");
            has_overlay = LXW_TRUE;
            break;
        case LXW_CHART_LEGEND_OVERLAY_LEFT:
            _chart_write_legend_pos(self, "l");
            has_overlay = LXW_TRUE;
            break;
        case LXW_CHART_LEGEND_OVERLAY_TOP_RIGHT:
            _chart_write_legend_pos(self, "tr");
            has_overlay = LXW_TRUE;
            break;
        default:
            _chart_write_legend_pos(self, "r");
    }

    /* Remove series labels from the legend. */
    for (index = 0; index < self->delete_series_count; index++) {
        /* Write the c:legendEntry element. */
        _chart_write_legend_entry(self, index);
    }

    /* Write the c:layout element. */
    _chart_write_layout(self);

    if (self->chart_group == LXW_CHART_PIE
        || self->chart_group == LXW_CHART_DOUGHNUT) {
        /* Write the c:overlay element. */
        if (has_overlay)
            _chart_write_overlay(self);

        /* Write the c:txPr element for Pie/Doughnut charts. */
        _chart_write_tx_pr_pie(self, LXW_FALSE, self->legend.font);
    }
    else {
        /* Write the c:txPr element for all other charts. */
        if (self->legend.font)
            _chart_write_tx_pr(self, LXW_FALSE, self->legend.font);

        /* Write the c:overlay element. */
        if (has_overlay)
            _chart_write_overlay(self);
    }

    lxw_xml_end_tag(self->file, "c:legend");
}

/*
 * Write the <c:plotVisOnly> element.
 */
STATIC void
_chart_write_plot_vis_only(lxw_chart *self)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    if (self->show_hidden_data)
        return;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_STR("val", "1");

    lxw_xml_empty_tag(self->file, "c:plotVisOnly", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:headerFooter> element.
 */
STATIC void
_chart_write_header_footer(lxw_chart *self)
{
    lxw_xml_empty_tag(self->file, "c:headerFooter", NULL);
}

/*
 * Write the <c:pageMargins> element.
 */
STATIC void
_chart_write_page_margins(lxw_chart *self)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();

    LXW_PUSH_ATTRIBUTES_STR("b", "0.75");
    LXW_PUSH_ATTRIBUTES_STR("l", "0.7");
    LXW_PUSH_ATTRIBUTES_STR("r", "0.7");
    LXW_PUSH_ATTRIBUTES_STR("t", "0.75");
    LXW_PUSH_ATTRIBUTES_STR("header", "0.3");
    LXW_PUSH_ATTRIBUTES_STR("footer", "0.3");

    lxw_xml_empty_tag(self->file, "c:pageMargins", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:pageSetup> element.
 */
STATIC void
_chart_write_page_setup(lxw_chart *self)
{
    lxw_xml_empty_tag(self->file, "c:pageSetup", NULL);
}

/*
 * Write the <c:printSettings> element.
 */
STATIC void
_chart_write_print_settings(lxw_chart *self)
{
    lxw_xml_start_tag(self->file, "c:printSettings", NULL);

    /* Write the c:headerFooter element. */
    _chart_write_header_footer(self);

    /* Write the c:pageMargins element. */
    _chart_write_page_margins(self);

    /* Write the c:pageSetup element. */
    _chart_write_page_setup(self);

    lxw_xml_end_tag(self->file, "c:printSettings");
}

/*
 * Write the <c:overlap> element.
 */
STATIC void
_chart_write_overlap(lxw_chart *self, int8_t overlap)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    if (!overlap)
        return;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_INT("val", overlap);

    lxw_xml_empty_tag(self->file, "c:overlap", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:gapWidth> element.
 */
STATIC void
_chart_write_gap_width(lxw_chart *self, uint16_t gap)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    if (gap == LXW_CHART_DEFAULT_GAP)
        return;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_INT("val", gap);

    lxw_xml_empty_tag(self->file, "c:gapWidth", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:dispBlanksAs> element.
 */
STATIC void
_chart_write_disp_blanks_as(lxw_chart *self)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    if (self->show_blanks_as != LXW_CHART_BLANKS_AS_ZERO
        && self->show_blanks_as != LXW_CHART_BLANKS_AS_CONNECTED)
        return;

    LXW_INIT_ATTRIBUTES();

    if (self->show_blanks_as == LXW_CHART_BLANKS_AS_ZERO)
        LXW_PUSH_ATTRIBUTES_STR("val", "zero");
    else
        LXW_PUSH_ATTRIBUTES_STR("val", "span");

    lxw_xml_empty_tag(self->file, "c:dispBlanksAs", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:showHorzBorder> element.
 */
STATIC void
_chart_write_show_horz_border(lxw_chart *self, uint8_t value)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    if (!value)
        return;

    LXW_INIT_ATTRIBUTES();

    LXW_PUSH_ATTRIBUTES_STR("val", "1");

    lxw_xml_empty_tag(self->file, "c:showHorzBorder", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:showVertBorder> element.
 */
STATIC void
_chart_write_show_vert_border(lxw_chart *self, uint8_t value)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    if (!value)
        return;

    LXW_INIT_ATTRIBUTES();

    LXW_PUSH_ATTRIBUTES_STR("val", "1");

    lxw_xml_empty_tag(self->file, "c:showVertBorder", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:showOutline> element.
 */
STATIC void
_chart_write_show_outline(lxw_chart *self, uint8_t value)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    if (!value)
        return;

    LXW_INIT_ATTRIBUTES();

    LXW_PUSH_ATTRIBUTES_STR("val", "1");

    lxw_xml_empty_tag(self->file, "c:showOutline", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:showKeys> element.
 */
STATIC void
_chart_write_show_keys(lxw_chart *self, uint8_t value)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    if (!value)
        return;

    LXW_INIT_ATTRIBUTES();

    LXW_PUSH_ATTRIBUTES_STR("val", "1");

    lxw_xml_empty_tag(self->file, "c:showKeys", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write the <c:dTable> element.
 */
STATIC void
_chart_write_d_table(lxw_chart *self)
{
    if (!self->has_table)
        return;

    lxw_xml_start_tag(self->file, "c:dTable", NULL);

    /* Write the c:showHorzBorder element. */
    _chart_write_show_horz_border(self, self->has_table_horizontal);

    /* Write the c:showVertBorder element. */
    _chart_write_show_vert_border(self, self->has_table_vertical);

    /* Write the c:showOutline element. */
    _chart_write_show_outline(self, self->has_table_outline);

    /* Write the c:showKeys element. */
    _chart_write_show_keys(self, self->has_table_legend_keys);

    /* Write the c:txPr element. */
    if (self->table_font)
        _chart_write_tx_pr(self, LXW_FALSE, self->table_font);

    lxw_xml_end_tag(self->file, "c:dTable");
}

/*
 * Write the <c:upBars> element.
 */
STATIC void
_chart_write_up_bars(lxw_chart *self, lxw_chart_line *line,
                     lxw_chart_fill *fill)
{
    if (line || fill) {
        lxw_xml_start_tag(self->file, "c:upBars", NULL);

        /* Write the c:spPr element. */
        _chart_write_sp_pr(self, line, fill, NULL);

        lxw_xml_end_tag(self->file, "c:upBars");
    }
    else {
        lxw_xml_empty_tag(self->file, "c:upBars", NULL);
    }
}

/*
 * Write the <c:downBars> element.
 */
STATIC void
_chart_write_down_bars(lxw_chart *self, lxw_chart_line *line,
                       lxw_chart_fill *fill)
{
    if (line || fill) {
        lxw_xml_start_tag(self->file, "c:downBars", NULL);

        /* Write the c:spPr element. */
        _chart_write_sp_pr(self, line, fill, NULL);

        lxw_xml_end_tag(self->file, "c:downBars");
    }
    else {
        lxw_xml_empty_tag(self->file, "c:downBars", NULL);
    }
}

/*
 * Write the <c:upDownBars> element.
 */
STATIC void
_chart_write_up_down_bars(lxw_chart *self)
{
    if (!self->has_up_down_bars)
        return;

    lxw_xml_start_tag(self->file, "c:upDownBars", NULL);

    /* Write the c:gapWidth element. */
    _chart_write_gap_width(self, 150);

    /* Write the c:upBars element. */
    _chart_write_up_bars(self, self->up_bar_line, self->up_bar_fill);

    /* Write the c:downBars element. */
    _chart_write_down_bars(self, self->down_bar_line, self->down_bar_fill);

    lxw_xml_end_tag(self->file, "c:upDownBars");
}

/*
 * Write the <c:dropLines> element.
 */
STATIC void
_chart_write_drop_lines(lxw_chart *self)
{
    if (!self->has_drop_lines)
        return;

    if (self->drop_lines_line) {
        lxw_xml_start_tag(self->file, "c:dropLines", NULL);

        _chart_write_sp_pr(self, self->drop_lines_line, NULL, NULL);

        lxw_xml_end_tag(self->file, "c:dropLines");
    }
    else {
        lxw_xml_empty_tag(self->file, "c:dropLines", NULL);
    }
}

/*
 * Write the <c:hiLowLines> element.
 */
STATIC void
_chart_write_hi_low_lines(lxw_chart *self)
{
    if (!self->has_high_low_lines)
        return;

    if (self->high_low_lines_line) {
        lxw_xml_start_tag(self->file, "c:hiLowLines", NULL);

        _chart_write_sp_pr(self, self->high_low_lines_line, NULL, NULL);

        lxw_xml_end_tag(self->file, "c:hiLowLines");
    }
    else {
        lxw_xml_empty_tag(self->file, "c:hiLowLines", NULL);
    }
}

/*
 * Write the <c:title> element.
 */
STATIC void
_chart_write_title(lxw_chart *self, lxw_chart_title *title)
{
    if (title->name) {
        /* Write the c:title element. */
        _chart_write_title_rich(self, title);
    }
    else if (title->range->formula) {
        /* Write the c:title element. */
        _chart_write_title_formula(self, title);
    }
}

/*
 * Write the <c:title> element.
 */
STATIC void
_chart_write_chart_title(lxw_chart *self)
{
    if (self->title.off) {
        /* Write the c:autoTitleDeleted element. */
        _chart_write_auto_title_deleted(self);
    }
    else {
        /* Write the c:title element. */
        _chart_write_title(self, &self->title);
    }
}

/*
 * Write the <c:catAx> element. Usually the X axis.
 */
STATIC void
_chart_write_cat_axis(lxw_chart *self)
{
    lxw_xml_start_tag(self->file, "c:catAx", NULL);

    _chart_write_axis_id(self, self->axis_id_1);

    /* Write the c:scaling element. Note we can't set max, min, or log base
     * for a Category axis in Excel.*/
    _chart_write_scaling(self,
                         self->x_axis->reverse,
                         LXW_FALSE, 0.0, LXW_FALSE, 0.0, 0);

    /* Write the c:delete element to hide axis. */
    if (self->x_axis->hidden)
        _chart_write_delete(self);

    /* Write the c:axPos element. */
    _chart_write_axis_pos(self, self->x_axis->axis_position,
                          self->y_axis->reverse);

    /* Write the c:majorGridlines element. */
    _chart_write_major_gridlines(self, self->x_axis);

    /* Write the c:minorGridlines element. */
    _chart_write_minor_gridlines(self, self->x_axis);

    /* Write the axis title elements. */
    self->x_axis->title.is_horizontal = self->has_horiz_cat_axis;
    _chart_write_title(self, &self->x_axis->title);

    /* Write the c:numFmt element. */
    _chart_write_cat_number_format(self, self->x_axis);

    /* Write the c:majorTickMark element. */
    _chart_write_major_tick_mark(self, self->x_axis);

    /* Write the c:minorTickMark element. */
    _chart_write_minor_tick_mark(self, self->x_axis);

    /* Write the c:tickLblPos element. */
    _chart_write_tick_label_pos(self, self->x_axis);

    /* Write the c:spPr element for the axis line. */
    _chart_write_sp_pr(self, self->x_axis->line, self->x_axis->fill,
                       self->x_axis->pattern);

    /* Write the axis font elements. */
    _chart_write_axis_font(self, self->x_axis->num_font);

    /* 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)
        _chart_write_crosses(self, self->y_axis);
    else
        _chart_write_crosses_at(self, self->y_axis);

    /* Write the c:auto element. */
    _chart_write_auto(self);

    /* Write the c:lblAlgn element. */
    _chart_write_label_align(self, self->x_axis);

    /* Write the c:lblOffset element. */
    _chart_write_label_offset(self);

    /* Write the c:tickLblSkip element. */
    _chart_write_tick_label_skip(self, self->x_axis);

    /* Write the c:tickMarkSkip element. */
    _chart_write_tick_mark_skip(self, self->x_axis);

    lxw_xml_end_tag(self->file, "c:catAx");
}

/*
 * Write the <c:valAx> element.
 */
STATIC void
_chart_write_val_axis(lxw_chart *self)
{
    lxw_xml_start_tag(self->file, "c:valAx", NULL);

    _chart_write_axis_id(self, self->axis_id_2);

    /* Write the c:scaling element. */
    _chart_write_scaling(self,
                         self->y_axis->reverse,
                         self->y_axis->has_min, self->y_axis->min,
                         self->y_axis->has_max, self->y_axis->max,
                         self->y_axis->log_base);

    /* Write the c:delete element to hide axis. */
    if (self->y_axis->hidden)
        _chart_write_delete(self);

    /* Write the c:axPos element. */
    _chart_write_axis_pos(self, self->y_axis->axis_position,
                          self->x_axis->reverse);

    /* Write the c:majorGridlines element. */
    _chart_write_major_gridlines(self, self->y_axis);

    /* Write the c:minorGridlines element. */
    _chart_write_minor_gridlines(self, self->y_axis);

    /* Write the axis title elements. */
    self->y_axis->title.is_horizontal = self->has_horiz_val_axis;
    _chart_write_title(self, &self->y_axis->title);

    /* Write the c:numFmt element. */
    _chart_write_number_format(self, self->y_axis);

    /* Write the c:majorTickMark element. */
    _chart_write_major_tick_mark(self, self->y_axis);

    /* Write the c:minorTickMark element. */
    _chart_write_minor_tick_mark(self, self->y_axis);

    /* Write the c:tickLblPos element. */
    _chart_write_tick_label_pos(self, self->y_axis);

    /* Write the c:spPr element for the axis line. */
    _chart_write_sp_pr(self, self->y_axis->line, self->y_axis->fill,
                       self->y_axis->pattern);

    /* Write the axis font elements. */
    _chart_write_axis_font(self, self->y_axis->num_font);

    /* 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)
        _chart_write_crosses(self, self->x_axis);
    else
        _chart_write_crosses_at(self, self->x_axis);

    /* Write the c:crossBetween element. */
    _chart_write_cross_between(self, self->x_axis->position_axis);

    /* Write the c:majorUnit element. */
    _chart_write_major_unit(self, self->y_axis);

    /* Write the c:minorUnit element. */
    _chart_write_minor_unit(self, self->y_axis);

    /* Write the c:dispUnits element. */
    _chart_write_disp_units(self, self->y_axis);

    lxw_xml_end_tag(self->file, "c:valAx");
}

/*
 * Write the <c:valAx> element. This is for the second valAx in scatter plots.
 */
STATIC void
_chart_write_cat_val_axis(lxw_chart *self)
{
    lxw_xml_start_tag(self->file, "c:valAx", NULL);

    _chart_write_axis_id(self, self->axis_id_1);

    /* Write the c:scaling element. */
    _chart_write_scaling(self,
                         self->x_axis->reverse,
                         self->x_axis->has_min, self->x_axis->min,
                         self->x_axis->has_max, self->x_axis->max,
                         self->x_axis->log_base);

    /* Write the c:delete element to hide axis. */
    if (self->x_axis->hidden)
        _chart_write_delete(self);

    /* Write the c:axPos element. */
    _chart_write_axis_pos(self, self->x_axis->axis_position,
                          self->y_axis->reverse);

    /* Write the c:majorGridlines element. */
    _chart_write_major_gridlines(self, self->x_axis);

    /* Write the c:minorGridlines element. */
    _chart_write_minor_gridlines(self, self->x_axis);

    /* Write the axis title elements. */
    self->x_axis->title.is_horizontal = self->has_horiz_val_axis;
    _chart_write_title(self, &self->x_axis->title);

    /* Write the c:numFmt element. */
    _chart_write_number_format(self, self->x_axis);

    /* Write the c:majorTickMark element. */
    _chart_write_major_tick_mark(self, self->x_axis);

    /* Write the c:minorTickMark element. */
    _chart_write_minor_tick_mark(self, self->x_axis);

    /* Write the c:tickLblPos element. */
    _chart_write_tick_label_pos(self, self->x_axis);

    /* Write the c:spPr element for the axis line. */
    _chart_write_sp_pr(self, self->x_axis->line, self->x_axis->fill,
                       self->x_axis->pattern);

    /* Write the axis font elements. */
    _chart_write_axis_font(self, self->x_axis->num_font);

    /* 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)
        _chart_write_crosses(self, self->y_axis);
    else
        _chart_write_crosses_at(self, self->y_axis);

    /* Write the c:crossBetween element. */
    _chart_write_cross_between(self, self->y_axis->position_axis);

    /* Write the c:majorUnit element. */
    _chart_write_major_unit(self, self->x_axis);

    /* Write the c:minorUnit element. */
    _chart_write_minor_unit(self, self->x_axis);

    /* Write the c:dispUnits element. */
    _chart_write_disp_units(self, self->x_axis);

    lxw_xml_end_tag(self->file, "c:valAx");
}

/*
 * Write the <c:barDir> element.
 */
STATIC void
_chart_write_bar_dir(lxw_chart *self, char *type)
{
    struct xml_attribute_list attributes;
    struct xml_attribute *attribute;

    LXW_INIT_ATTRIBUTES();
    LXW_PUSH_ATTRIBUTES_STR("val", type);

    lxw_xml_empty_tag(self->file, "c:barDir", &attributes);

    LXW_FREE_ATTRIBUTES();
}

/*
 * Write a area chart.
 */
STATIC void
_chart_write_area_chart(lxw_chart *self)
{
    lxw_chart_series *series;

    lxw_xml_start_tag(self->file, "c:areaChart", NULL);

    /* Write the c:grouping element. */
    _chart_write_grouping(self, self->grouping);

    STAILQ_FOREACH(series, self->series_list, list_pointers) {
        /* Write the c:ser element. */
        _chart_write_ser(self, series);
    }

    /* Write the c:dropLines element. */
    _chart_write_drop_lines(self);

    /* Write the c:axId elements. */
    _chart_write_axis_ids(self);

    lxw_xml_end_tag(self->file, "c:areaChart");
}

/*
 * Write a bar chart.
 */
STATIC void
_chart_write_bar_chart(lxw_chart *self)
{
    lxw_chart_series *series;

    lxw_xml_start_tag(self->file, "c:barChart", NULL);

    /* Write the c:barDir element. */
    _chart_write_bar_dir(self, "bar");

    /* Write the c:grouping element. */
    _chart_write_grouping(self, self->grouping);

    STAILQ_FOREACH(series, self->series_list, list_pointers) {
        /* Write the c:ser element. */
        _chart_write_ser(self, series);
    }

    /* Write the c:gapWidth element. */
    _chart_write_gap_width(self, self->gap_y1);

    /* Write the c:overlap element. */
    _chart_write_overlap(self, self->overlap_y1);

    /* Write the c:axId elements. */
    _chart_write_axis_ids(self);

    lxw_xml_end_tag(self->file, "c:barChart");
}

/*
 * Write a column chart.
 */
STATIC void
_chart_write_column_chart(lxw_chart *self)
{
    lxw_chart_series *series;

    lxw_xml_start_tag(self->file, "c:barChart", NULL);

    /* Write the c:barDir element. */
    _chart_write_bar_dir(self, "col");

    /* Write the c:grouping element. */
    _chart_write_grouping(self, self->grouping);

    STAILQ_FOREACH(series, self->series_list, list_pointers) {
        /* Write the c:ser element. */
        _chart_write_ser(self, series);
    }

    /* Write the c:gapWidth element. */
    _chart_write_gap_width(self, self->gap_y1);

    /* Write the c:overlap element. */
    _chart_write_overlap(self, self->overlap_y1);

    /* Write the c:axId elements. */
    _chart_write_axis_ids(self);

    lxw_xml_end_tag(self->file, "c:barChart");
}

/*
 * Write a doughnut chart.
 */
STATIC void
_chart_write_doughnut_chart(lxw_chart *self)
{
    lxw_chart_series *series;

    lxw_xml_start_tag(self->file, "c:doughnutChart", NULL);

    /* Write the c:varyColors element. */
    _chart_write_vary_colors(self);

    STAILQ_FOREACH(series, self->series_list, list_pointers) {
        /* Write the c:ser element. */
        _chart_write_ser(self, series);
    }

    /* Write the c:firstSliceAng element. */
    _chart_write_first_slice_ang(self);

    /* Write the c:holeSize element. */
    _chart_write_hole_size(self);

    lxw_xml_end_tag(self->file, "c:doughnutChart");
}

/*
 * Write a line chart.
 */
STATIC void
_chart_write_line_chart(lxw_chart *self)
{
    lxw_chart_series *series;

    lxw_xml_start_tag(self->file, "c:lineChart", NULL);

    /* Write the c:grouping element. */
    _chart_write_grouping(self, self->grouping);

    STAILQ_FOREACH(series, self->series_list, list_pointers) {
        /* Write the c:ser element. */
        _chart_write_ser(self, series);
    }

    /* Write the c:dropLines element. */
    _chart_write_drop_lines(self);

    /* Write the c:hiLowLines element. */
    _chart_write_hi_low_lines(self);

    /* Write the c:upDownBars element. */
    _chart_write_up_down_bars(self);

    /* Write the c:marker element. */
    _chart_write_marker_value(self);

    /* Write the c:axId elements. */
    _chart_write_axis_ids(self);

    lxw_xml_end_tag(self->file, "c:lineChart");
}

/*
 * Write a pie chart.
 */
STATIC void
_chart_write_pie_chart(lxw_chart *self)
{
    lxw_chart_series *series;

    lxw_xml_start_tag(self->file, "c:pieChart", NULL);

    /* Write the c:varyColors element. */
    _chart_write_vary_colors(self);

    STAILQ_FOREACH(series, self->series_list, list_pointers) {
        /* Write the c:ser element. */
        _chart_write_ser(self, series);
    }

    /* Write the c:firstSliceAng element. */
    _chart_write_first_slice_ang(self);

    lxw_xml_end_tag(self->file, "c:pieChart");
}

/*
 * Write a scatter chart.
 */
STATIC void
_chart_write_scatter_chart(lxw_chart *self)
{
    lxw_chart_series *series;

    lxw_xml_start_tag(self->file, "c:scatterChart", NULL);

    /* Write the c:scatterStyle element. */
    _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,
                    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);
    }

    /* Write the c:axId elements. */
    _chart_write_axis_ids(self);

    lxw_xml_end_tag(self->file, "c:scatterChart");
}

/*
 * Write a radar chart.
 */
STATIC void
_chart_write_radar_chart(lxw_chart *self)
{
    lxw_chart_series *series;

    lxw_xml_start_tag(self->file, "c:radarChart", NULL);

    /* Write the c:radarStyle element. */
    _chart_write_radar_style(self);

    STAILQ_FOREACH(series, self->series_list, list_pointers) {
        /* Write the c:ser element. */
        _chart_write_ser(self, series);
    }

    /* Write the c:axId elements. */
    _chart_write_axis_ids(self);

    lxw_xml_end_tag(self->file, "c:radarChart");
}

/*
 * Reverse the opposite axis position if crossing position is "max".
 */
STATIC void
_chart_adjust_max_crossing(lxw_chart *self)
{
    if (self->x_axis->crossing_max)
        self->y_axis->axis_position ^= 1;

    if (self->y_axis->crossing_max)
        self->x_axis->axis_position ^= 1;
}

/*
 * Write the <c:plotArea> element.
 */
STATIC void
_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);

    /* Write subclass chart type elements for primary and secondary axes. */
    self->write_chart_type(self);

    /* Reverse the opposite axis position if crossing position is "max". */
    _chart_adjust_max_crossing(self);

    /* Write the c:catAx element. */
    _chart_write_cat_val_axis(self);

    self->has_horiz_val_axis = LXW_TRUE;

    /* Write the c:valAx element. */
    _chart_write_val_axis(self);

    /* Write the c:spPr element for the plotarea formatting. */
    _chart_write_sp_pr(self, self->plotarea_line, self->plotarea_fill,
                       self->plotarea_pattern);

    lxw_xml_end_tag(self->file, "c:plotArea");
}

/*
 * Write the <c:plotArea> element. Special handling for pie/doughnut.
 */
STATIC void
_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);

    /* Write subclass chart type elements for primary and secondary axes. */
    self->write_chart_type(self);

    /* Write the c:spPr element for the plotarea formatting. */
    _chart_write_sp_pr(self, self->plotarea_line, self->plotarea_fill,
                       self->plotarea_pattern);

    lxw_xml_end_tag(self->file, "c:plotArea");
}

/*
 * Write the <c:plotArea> element.
 */
STATIC void
_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);

    /* Write subclass chart type elements for primary and secondary axes. */
    self->write_chart_type(self);

    /* Reverse the opposite axis position if crossing position is "max". */
    _chart_adjust_max_crossing(self);

    /* Write the c:catAx element. */
    _chart_write_cat_axis(self);

    /* Write the c:valAx element. */
    _chart_write_val_axis(self);

    /* Write the c:dTable element. */
    _chart_write_d_table(self);

    /* Write the c:spPr element for the plotarea formatting. */
    _chart_write_sp_pr(self, self->plotarea_line, self->plotarea_fill,
                       self->plotarea_pattern);

    lxw_xml_end_tag(self->file, "c:plotArea");
}

/*
 * Write the <c:chart> element.
 */
STATIC void
_chart_write_chart(lxw_chart *self)
{
    lxw_xml_start_tag(self->file, "c:chart", NULL);

    /* Write the c:title element. */
    _chart_write_chart_title(self);

    /* Write the c:plotArea element. */
    self->write_plot_area(self);

    /* Write the c:legend element. */
    _chart_write_legend(self);

    /* Write the c:plotVisOnly element. */
    _chart_write_plot_vis_only(self);

    /* Write the c:dispBlanksAs element. */
    _chart_write_disp_blanks_as(self);

    lxw_xml_end_tag(self->file, "c:chart");
}

/*
 * Initialize a area chart.
 */
STATIC void
_chart_initialize_area_chart(lxw_chart *self, uint8_t type)
{
    self->chart_group = LXW_CHART_AREA;
    self->grouping = LXW_GROUPING_STANDARD;
    self->default_cross_between = LXW_CHART_AXIS_POSITION_ON_TICK;
    self->x_axis->is_category = LXW_TRUE;
    self->default_label_position = LXW_CHART_LABEL_POSITION_CENTER;

    if (type == LXW_CHART_AREA_STACKED) {
        self->grouping = LXW_GROUPING_STACKED;
        self->subtype = LXW_CHART_SUBTYPE_STACKED;
    }

    if (type == LXW_CHART_AREA_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_area_chart;
    self->write_plot_area = _chart_write_plot_area;
}

/*
 * Swap/reverse the bar chart axes prior to writing. It is the only chart
 * with the category axis in the vertical direction.
 */
STATIC void
_chart_swap_bar_axes(lxw_chart *self)
{
    lxw_chart_axis *tmp = self->x_axis;
    self->x_axis = self->y_axis;
    self->y_axis = tmp;
}

/*
 * Initialize a bar chart.
 */
STATIC void
_chart_initialize_bar_chart(lxw_chart *self, uint8_t type)
{
    /* Note: Bar chart category/value axis are reversed in comparison to
     *       other charts. Some of the defaults reflect this. */
    self->chart_group = LXW_CHART_BAR;
    self->x_axis->major_gridlines.visible = LXW_TRUE;
    self->y_axis->major_gridlines.visible = LXW_FALSE;
    self->y_axis->is_category = LXW_TRUE;
    self->x_axis->is_value = LXW_TRUE;
    self->has_horiz_cat_axis = LXW_TRUE;
    self->has_horiz_val_axis = LXW_FALSE;
    self->default_label_position = LXW_CHART_LABEL_POSITION_OUTSIDE_END;

    if (type == LXW_CHART_BAR_STACKED) {
        self->grouping = LXW_GROUPING_STACKED;
        self->has_overlap = LXW_TRUE;
        self->subtype = LXW_CHART_SUBTYPE_STACKED;
        self->overlap_y1 = 100;

    }

    if (type == LXW_CHART_BAR_STACKED_PERCENT) {
        self->grouping = LXW_GROUPING_PERCENTSTACKED;
        _chart_axis_set_default_num_format(self->x_axis, "0%");
        self->has_overlap = LXW_TRUE;
        self->subtype = LXW_CHART_SUBTYPE_STACKED;
        self->overlap_y1 = 100;
    }

    /* Initialize the function pointers for this chart type. */
    self->write_chart_type = _chart_write_bar_chart;
    self->write_plot_area = _chart_write_plot_area;
}

/*
 * Initialize a column chart.
 */
STATIC void
_chart_initialize_column_chart(lxw_chart *self, uint8_t type)
{
    self->chart_group = LXW_CHART_COLUMN;
    self->has_horiz_val_axis = LXW_FALSE;
    self->x_axis->is_category = LXW_TRUE;
    self->y_axis->is_value = LXW_TRUE;
    self->default_label_position = LXW_CHART_LABEL_POSITION_OUTSIDE_END;

    if (type == LXW_CHART_COLUMN_STACKED) {
        self->grouping = LXW_GROUPING_STACKED;
        self->has_overlap = LXW_TRUE;
        self->subtype = LXW_CHART_SUBTYPE_STACKED;
        self->overlap_y1 = 100;
    }

    if (type == LXW_CHART_COLUMN_STACKED_PERCENT) {
        self->grouping = LXW_GROUPING_PERCENTSTACKED;
        _chart_axis_set_default_num_format(self->y_axis, "0%");
        self->has_overlap = LXW_TRUE;
        self->subtype = LXW_CHART_SUBTYPE_STACKED;
        self->overlap_y1 = 100;
    }

    /* Initialize the function pointers for this chart type. */
    self->write_chart_type = _chart_write_column_chart;
    self->write_plot_area = _chart_write_plot_area;
}

/*
 * Initialize a doughnut chart.
 */
STATIC void
_chart_initialize_doughnut_chart(lxw_chart *self)
{
    /* Initialize the function pointers for this chart type. */
    self->chart_group = LXW_CHART_DOUGHNUT;
    self->write_chart_type = _chart_write_doughnut_chart;
    self->write_plot_area = _chart_write_pie_plot_area;
    self->default_label_position = LXW_CHART_LABEL_POSITION_BEST_FIT;
}

/*
 * Initialize a line chart.
 */
STATIC void
_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;
}

/*
 * Initialize a pie chart.
 */
STATIC void
_chart_initialize_pie_chart(lxw_chart *self)
{
    /* Initialize the function pointers for this chart type. */
    self->chart_group = LXW_CHART_PIE;
    self->write_chart_type = _chart_write_pie_chart;
    self->write_plot_area = _chart_write_pie_plot_area;
    self->default_label_position = LXW_CHART_LABEL_POSITION_BEST_FIT;
}

/*
 * Initialize a scatter chart.
 */
STATIC void
_chart_initialize_scatter_chart(lxw_chart *self)
{
    self->chart_group = LXW_CHART_SCATTER;
    self->has_horiz_val_axis = LXW_FALSE;
    self->default_cross_between = LXW_CHART_AXIS_POSITION_ON_TICK;
    self->x_axis->is_value = LXW_TRUE;
    self->y_axis->is_value = LXW_TRUE;
    self->default_label_position = LXW_CHART_LABEL_POSITION_RIGHT;

    if (self->type == LXW_CHART_SCATTER_STRAIGHT
        || self->type == LXW_CHART_SCATTER_SMOOTH) {

        _chart_set_default_marker_type(self, LXW_CHART_MARKER_NONE);
    }

    /* Initialize the function pointers for this chart type. */
    self->write_chart_type = _chart_write_scatter_chart;
    self->write_plot_area = _chart_write_scatter_plot_area;
}

/*
 * Initialize a radar chart.
 */
STATIC void
_chart_initialize_radar_chart(lxw_chart *self, uint8_t type)
{
    if (type == LXW_CHART_RADAR)
        _chart_set_default_marker_type(self, LXW_CHART_MARKER_NONE);

    self->chart_group = LXW_CHART_RADAR;
    self->x_axis->major_gridlines.visible = LXW_TRUE;
    self->x_axis->is_category = LXW_TRUE;
    self->y_axis->is_value = LXW_TRUE;
    self->y_axis->major_tick_mark = LXW_CHART_AXIS_TICK_MARK_CROSSING;
    self->default_label_position = LXW_CHART_LABEL_POSITION_CENTER;

    /* Initialize the function pointers for this chart type. */
    self->write_chart_type = _chart_write_radar_chart;
    self->write_plot_area = _chart_write_plot_area;
}

/*
 * Initialize the chart specific properties.
 */
STATIC void
_chart_initialize(lxw_chart *self, uint8_t type)
{
    switch (type) {

        case LXW_CHART_AREA:
        case LXW_CHART_AREA_STACKED:
        case LXW_CHART_AREA_STACKED_PERCENT:
            _chart_initialize_area_chart(self, type);
            break;

        case LXW_CHART_BAR:
        case LXW_CHART_BAR_STACKED:
        case LXW_CHART_BAR_STACKED_PERCENT:
            _chart_initialize_bar_chart(self, type);
            break;

        case LXW_CHART_COLUMN:
        case LXW_CHART_COLUMN_STACKED:
        case LXW_CHART_COLUMN_STACKED_PERCENT:
            _chart_initialize_column_chart(self, type);
            break;

        case LXW_CHART_DOUGHNUT:
            _chart_initialize_doughnut_chart(self);
            break;

        case LXW_CHART_LINE:
        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;

        case LXW_CHART_SCATTER:
        case LXW_CHART_SCATTER_STRAIGHT:
        case LXW_CHART_SCATTER_STRAIGHT_WITH_MARKERS:
        case LXW_CHART_SCATTER_SMOOTH:
        case LXW_CHART_SCATTER_SMOOTH_WITH_MARKERS:
            _chart_initialize_scatter_chart(self);
            break;

        case LXW_CHART_RADAR:
        case LXW_CHART_RADAR_WITH_MARKERS:
        case LXW_CHART_RADAR_FILLED:
            _chart_initialize_radar_chart(self, type);
            break;

        default:
            LXW_WARN_FORMAT1("workbook_add_chart(): "
                             "unhandled chart type '%d'", type);
    }
}

/*
 * Assemble and write the XML file.
 */
void
lxw_chart_assemble_xml_file(lxw_chart *self)
{
    /* Reverse the X and Y axes for Bar charts. */
    if (self->type == LXW_CHART_BAR || self->type == LXW_CHART_BAR_STACKED
        || self->type == LXW_CHART_BAR_STACKED_PERCENT)
        _chart_swap_bar_axes(self);

    /* Write the XML declaration. */
    _chart_xml_declaration(self);

    /* Write the c:chartSpace element. */
    _chart_write_chart_space(self);

    /* Write the c:lang element. */
    _chart_write_lang(self);

    /* Write the c:style element. */
    _chart_write_style(self);

    /* Write the c:protection element. */
    if (self->is_protected)
        _chart_write_protection(self);

    /* Write the c:chart element. */
    _chart_write_chart(self);

    /* Write the c:spPr element for the chartarea formatting. */
    _chart_write_sp_pr(self, self->chartarea_line, self->chartarea_fill,
                       self->chartarea_pattern);

    /* Write the c:printSettings element. */
    if (!self->is_chartsheet)
        _chart_write_print_settings(self);

    lxw_xml_end_tag(self->file, "c:chartSpace");
}

/*****************************************************************************
 *
 * Public functions.
 *
 ****************************************************************************/

/*
 * Add data to a data cache in a range object, for testing only.
 */
lxw_error
lxw_chart_add_data_cache(lxw_series_range *range, uint8_t *data,
                         uint16_t rows, uint8_t cols, uint8_t col)
{
    struct lxw_series_data_point *data_point;
    uint16_t i;

    range->ignore_cache = LXW_TRUE;
    range->num_data_points = rows;

    /* Initialize the series range data cache. */
    for (i = 0; i < rows; i++) {
        data_point = calloc(1, sizeof(struct lxw_series_data_point));
        RETURN_ON_MEM_ERROR(data_point, LXW_ERROR_MEMORY_MALLOC_FAILED);
        STAILQ_INSERT_TAIL(range->data_cache, data_point, list_pointers);
        data_point->number = data[i * cols + col];
    }

    return LXW_NO_ERROR;
}

/*
 * 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;

    /* Scatter charts require categories and values. */
    if (self->chart_group == LXW_CHART_SCATTER && values && !categories) {
        LXW_WARN("chart_add_series(): scatter charts must have "
                 "'categories' and 'values'");

        return NULL;
    }

    /* Create a new object to hold the series. */
    series = calloc(1, sizeof(lxw_chart_series));
    GOTO_LABEL_ON_MEM_ERROR(series, mem_error);

    series->categories = calloc(1, sizeof(lxw_series_range));
    GOTO_LABEL_ON_MEM_ERROR(series->categories, mem_error);

    series->values = calloc(1, sizeof(lxw_series_range));
    GOTO_LABEL_ON_MEM_ERROR(series->values, mem_error);

    series->title.range = calloc(1, sizeof(lxw_series_range));
    GOTO_LABEL_ON_MEM_ERROR(series->title.range, mem_error);

    series->x_error_bars = calloc(1, sizeof(lxw_series_error_bars));
    GOTO_LABEL_ON_MEM_ERROR(series->x_error_bars, mem_error);

    series->y_error_bars = calloc(1, sizeof(lxw_series_error_bars));
    GOTO_LABEL_ON_MEM_ERROR(series->y_error_bars, mem_error);

    if (categories) {
        if (categories[0] == '=')
            series->categories->formula = lxw_strdup(categories + 1);
        else
            series->categories->formula = lxw_strdup(categories);
    }

    if (values) {
        if (values[0] == '=')
            series->values->formula = lxw_strdup(values + 1);
        else
            series->values->formula = lxw_strdup(values);
    }

    if (_chart_init_data_cache(series->categories) != LXW_NO_ERROR)
        goto mem_error;

    if (_chart_init_data_cache(series->values) != LXW_NO_ERROR)
        goto mem_error;

    if (_chart_init_data_cache(series->title.range) != LXW_NO_ERROR)
        goto mem_error;

    if (self->type == LXW_CHART_SCATTER_SMOOTH)
        series->smooth = LXW_TRUE;

    if (self->type == LXW_CHART_SCATTER_SMOOTH_WITH_MARKERS)
        series->smooth = LXW_TRUE;

    series->y_error_bars->chart_group = self->chart_group;
    series->x_error_bars->chart_group = self->chart_group;
    series->x_error_bars->is_x = LXW_TRUE;

    series->default_label_position = self->default_label_position;

    STAILQ_INSERT_TAIL(self->series_list, series, list_pointers);

    return series;

mem_error:
    _chart_series_free(series);
    return NULL;
}

/*
 * Set on of the 48 built-in Excel chart styles.
 */
void
chart_set_style(lxw_chart *self, uint8_t style_id)
{
    /* The default style is 2. The range is 1 - 48 */
    if (style_id < 1 || style_id > 48)
        style_id = 2;

    self->style_id = style_id;
}

/*
 * Set a user defined name for a series.
 */
void
chart_series_set_name(lxw_chart_series *series, const char *name)
{
    if (!name)
        return;

    if (name[0] == '=')
        series->title.range->formula = lxw_strdup(name + 1);
    else
        series->title.name = lxw_strdup(name);
}

/*
 * Set an axis caption, with a range instead or a formula..
 */
void
chart_series_set_name_range(lxw_chart_series *series, const char *sheetname,
                            lxw_row_t row, lxw_col_t col)
{
    if (!sheetname) {
        LXW_WARN("chart_series_set_name_range(): "
                 "sheetname must be specified");
        return;
    }

    /* Start and end row, col are the same for single cell range. */
    _chart_set_range(series->title.range, sheetname, row, col, row, col);
}

/*
 * Set the categories range for a series.
 */
void
chart_series_set_categories(lxw_chart_series *series, const char *sheetname,
                            lxw_row_t first_row, lxw_col_t first_col,
                            lxw_row_t last_row, lxw_col_t last_col)
{
    if (!sheetname) {
        LXW_WARN("chart_series_set_categories(): "
                 "sheetname must be specified");
        return;
    }

    _chart_set_range(series->categories, sheetname,
                     first_row, first_col, last_row, last_col);
}

/*
 * Set the values range for a series.
 */
void
chart_series_set_values(lxw_chart_series *series, const char *sheetname,
                        lxw_row_t first_row, lxw_col_t first_col,
                        lxw_row_t last_row, lxw_col_t last_col)
{
    if (!sheetname) {
        LXW_WARN("chart_series_set_values(): sheetname must be specified");
        return;
    }

    _chart_set_range(series->values, 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;

    /* Free any previously allocated resource. */
    free(series->line);

    series->line = _chart_convert_line_args(line);
}

/*
 * Set a fill type for a series.
 */
void
chart_series_set_fill(lxw_chart_series *series, lxw_chart_fill *fill)
{
    if (!fill)
        return;

    /* Free any previously allocated resource. */
    free(series->fill);

    series->fill = _chart_convert_fill_args(fill);
}

/*
 * Invert the colors of a fill for a series.
 */
void
chart_series_set_invert_if_negative(lxw_chart_series *series)
{
    series->invert_if_negative = LXW_TRUE;
}

/*
 * Set a pattern type for a series.
 */
void
chart_series_set_pattern(lxw_chart_series *series, lxw_chart_pattern *pattern)
{
    if (!pattern)
        return;

    /* Free any previously allocated resource. */
    free(series->pattern);

    series->pattern = _chart_convert_pattern_args(pattern);
}

/*
 * Set a marker type for a series.
 */
void
chart_series_set_marker_type(lxw_chart_series *series, uint8_t type)
{
    if (!series->marker) {
        lxw_chart_marker *marker = calloc(1, sizeof(struct lxw_chart_marker));
        RETURN_VOID_ON_MEM_ERROR(marker);
        series->marker = marker;
    }

    series->marker->type = type;
}

/*
 * Set a marker size for a series.
 */
void
chart_series_set_marker_size(lxw_chart_series *series, uint8_t size)
{
    if (!series->marker) {
        lxw_chart_marker *marker = calloc(1, sizeof(struct lxw_chart_marker));
        RETURN_VOID_ON_MEM_ERROR(marker);
        series->marker = marker;
    }

    series->marker->size = size;
}

/*
 * Set a line type for a series marker.
 */
void
chart_series_set_marker_line(lxw_chart_series *series, lxw_chart_line *line)
{
    if (!line)
        return;

    if (!series->marker) {
        lxw_chart_marker *marker = calloc(1, sizeof(struct lxw_chart_marker));
        RETURN_VOID_ON_MEM_ERROR(marker);
        series->marker = marker;
    }

    /* Free any previously allocated resource. */
    free(series->marker->line);

    series->marker->line = _chart_convert_line_args(line);
}

/*
 * Set a fill type for a series marker.
 */
void
chart_series_set_marker_fill(lxw_chart_series *series, lxw_chart_fill *fill)
{
    if (!fill)
        return;

    if (!series->marker) {
        lxw_chart_marker *marker = calloc(1, sizeof(struct lxw_chart_marker));
        RETURN_VOID_ON_MEM_ERROR(marker);
        series->marker = marker;
    }

    /* Free any previously allocated resource. */
    free(series->marker->fill);

    series->marker->fill = _chart_convert_fill_args(fill);
}

/*
 * Set a pattern type for a series.
 */
void
chart_series_set_marker_pattern(lxw_chart_series *series,
                                lxw_chart_pattern *pattern)
{
    if (!pattern)
        return;

    if (!series->marker) {
        lxw_chart_marker *marker = calloc(1, sizeof(struct lxw_chart_marker));
        RETURN_VOID_ON_MEM_ERROR(marker);
        series->marker = marker;
    }

    /* Free any previously allocated resource. */
    free(series->marker->pattern);

    series->marker->pattern = _chart_convert_pattern_args(pattern);
}

/*
 * Store the points for a chart.
 */
lxw_error
chart_series_set_points(lxw_chart_series *series, lxw_chart_point *points[])
{
    uint16_t i = 0;
    uint16_t point_count = 0;

    if (points == NULL)
        return LXW_ERROR_NULL_PARAMETER_IGNORED;

    while (points[point_count])
        point_count++;

    if (point_count == 0)
        return LXW_ERROR_NULL_PARAMETER_IGNORED;

    /* Free any existing resource. */
    _chart_free_points(series);

    series->points = calloc(point_count, sizeof(lxw_chart_point));
    RETURN_ON_MEM_ERROR(series->points, LXW_ERROR_MEMORY_MALLOC_FAILED);

    for (i = 0; i < point_count; i++) {
        lxw_chart_point *src_point = points[i];
        lxw_chart_point *dst_point = &series->points[i];

        dst_point->line = _chart_convert_line_args(src_point->line);
        dst_point->fill = _chart_convert_fill_args(src_point->fill);
        dst_point->pattern = _chart_convert_pattern_args(src_point->pattern);
    }

    series->point_count = point_count;

    return LXW_NO_ERROR;
}

/*
 * Set the smooth property for a line or scatter series.
 */
void
chart_series_set_smooth(lxw_chart_series *series, uint8_t smooth)
{
    series->smooth = smooth;
}

/*
 * Turn on default data labels for a series.
 */
void
chart_series_set_labels(lxw_chart_series *series)
{
    series->has_labels = LXW_TRUE;
    series->show_labels_value = LXW_TRUE;
}

/*
 * Set the data labels options for a series.
 */
void
chart_series_set_labels_options(lxw_chart_series *series, uint8_t show_name,
                                uint8_t show_category, uint8_t show_value)
{
    series->has_labels = LXW_TRUE;
    series->show_labels_name = show_name;
    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];
        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)
{
    series->has_labels = LXW_TRUE;
    series->label_separator = separator;
}

/*
 * Set the data labels position for a series.
 */
void
chart_series_set_labels_position(lxw_chart_series *series, uint8_t position)
{
    series->has_labels = LXW_TRUE;
    series->show_labels_value = LXW_TRUE;

    if (position != series->default_label_position)
        series->label_position = position;
}

/*
 * Set the data labels position for a series.
 */
void
chart_series_set_labels_leader_line(lxw_chart_series *series)
{
    series->has_labels = LXW_TRUE;
    series->show_labels_leader = LXW_TRUE;
}

/*
 * Turn on the data labels legend for a series.
 */
void
chart_series_set_labels_legend(lxw_chart_series *series)
{
    series->has_labels = LXW_TRUE;
    series->show_labels_legend = LXW_TRUE;
}

/*
 * Turn on the data labels percentage for a series.
 */
void
chart_series_set_labels_percentage(lxw_chart_series *series)
{
    series->has_labels = LXW_TRUE;
    series->show_labels_percent = LXW_TRUE;
}

/*
 * Set an data labels number format.
 */
void
chart_series_set_labels_num_format(lxw_chart_series *series,
                                   const char *num_format)
{
    if (!num_format)
        return;

    /* Free any previously allocated resource. */
    free(series->label_num_format);

    series->label_num_format = lxw_strdup(num_format);
}

/*
 * Set an data labels font.
 */
void
chart_series_set_labels_font(lxw_chart_series *series, lxw_chart_font *font)
{
    if (!font)
        return;

    /* Free any previously allocated resource. */
    _chart_free_font(series->label_font);

    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)
{
    if (type == LXW_CHART_TRENDLINE_TYPE_POLY
        || type == LXW_CHART_TRENDLINE_TYPE_AVERAGE) {

        if (value < 2) {
            LXW_WARN("chart_series_set_trendline(): order/period value must "
                     "be >= 2 for Polynomial and Moving Average types");
            return;
        }

        series->trendline_value_type = type;
    }

    series->has_trendline = LXW_TRUE;
    series->trendline_type = type;
    series->trendline_value = value;
}

/*
 * Set the trendline forecast for a chart series.
 */
void
chart_series_set_trendline_forecast(lxw_chart_series *series, double forward,
                                    double backward)
{
    if (!series->has_trendline) {
        LXW_WARN("chart_series_set_trendline_forecast(): trendline type "
                 "must be set first using chart_series_set_trendline()");
        return;
    }

    if (series->trendline_type == LXW_CHART_TRENDLINE_TYPE_AVERAGE) {
        LXW_WARN("chart_series_set_trendline(): forecast isn't available "
                 "in Excel for a Moving Average trendline");
        return;
    }

    series->has_trendline_forecast = LXW_TRUE;
    series->trendline_forward = forward;
    series->trendline_backward = backward;
}

/*
 * Display the equation for a series trendline.
 */
void
chart_series_set_trendline_equation(lxw_chart_series *series)
{
    if (!series->has_trendline) {
        LXW_WARN("chart_series_set_trendline_equation(): trendline type "
                 "must be set first using chart_series_set_trendline()");
        return;
    }

    if (series->trendline_type == LXW_CHART_TRENDLINE_TYPE_AVERAGE) {
        LXW_WARN("chart_series_set_trendline_equation(): equation isn't "
                 "available in Excel for a Moving Average trendline");
        return;
    }

    series->has_trendline_equation = LXW_TRUE;
}

/*
 * Display the R squared value for a series trendline.
 */
void
chart_series_set_trendline_r_squared(lxw_chart_series *series)
{
    if (!series->has_trendline) {
        LXW_WARN("chart_series_set_trendline_r_squared(): trendline type "
                 "must be set first using chart_series_set_trendline()");
        return;
    }

    if (series->trendline_type == LXW_CHART_TRENDLINE_TYPE_AVERAGE) {
        LXW_WARN("chart_series_set_trendline_r_squared(): R squared isn't "
                 "available in Excel for a Moving Average trendline");
        return;
    }

    series->has_trendline_r_squared = LXW_TRUE;
}

/*
 * Set the trendline intercept for a chart series.
 */
void
chart_series_set_trendline_intercept(lxw_chart_series *series,
                                     double intercept)
{
    if (!series->has_trendline) {
        LXW_WARN("chart_series_set_trendline_intercept(): trendline type "
                 "must be set first using chart_series_set_trendline()");
        return;
    }

    if (series->trendline_type != LXW_CHART_TRENDLINE_TYPE_EXP
        && series->trendline_type != LXW_CHART_TRENDLINE_TYPE_LINEAR
        && series->trendline_type != LXW_CHART_TRENDLINE_TYPE_POLY) {

        LXW_WARN("chart_series_set_trendline_intercept(): intercept is only "
                 "available in Excel for Exponential, Linear and Polynomial "
                 "trendline types");
        return;
    }

    series->has_trendline_intercept = LXW_TRUE;
    series->trendline_intercept = intercept;
}

/*
 * Set a line type for a series trendline.
 */
void
chart_series_set_trendline_name(lxw_chart_series *series, const char *name)
{
    if (!name)
        return;

    /* Free any previously allocated resource. */
    free(series->trendline_name);

    series->trendline_name = lxw_strdup(name);
}

/*
 * Set a line type for a series trendline.
 */
void
chart_series_set_trendline_line(lxw_chart_series *series,
                                lxw_chart_line *line)
{
    if (!line)
        return;

    /* Free any previously allocated resource. */
    free(series->trendline_line);

    series->trendline_line = _chart_convert_line_args(line);
}

/*
 * Set the X or Y error bars from a chart series.
 */
lxw_series_error_bars *
chart_series_get_error_bars(lxw_chart_series *series,
                            lxw_chart_error_bar_axis axis_type)
{
    if (!series)
        return NULL;

    if (axis_type == LXW_CHART_ERROR_BAR_AXIS_X)
        return series->x_error_bars;
    else if (axis_type == LXW_CHART_ERROR_BAR_AXIS_Y)
        return series->y_error_bars;
    else
        return NULL;
}

/*
 * Set the error bars and type for a chart series.
 */
void
chart_series_set_error_bars(lxw_series_error_bars *error_bars,
                            uint8_t type, double value)
{
    if (_chart_check_error_bars(error_bars, ""))
        return;

    error_bars->type = type;
    error_bars->value = value;
    error_bars->has_value = LXW_TRUE;
    error_bars->is_set = LXW_TRUE;

    if (type == LXW_CHART_ERROR_BAR_TYPE_STD_ERROR)
        error_bars->has_value = LXW_FALSE;
}

/*
 * Set the error bars direction for a chart series.
 */
void
chart_series_set_error_bars_direction(lxw_series_error_bars *error_bars,
                                      uint8_t direction)
{
    if (_chart_check_error_bars(error_bars, "_direction"))
        return;

    error_bars->direction = direction;
}

/*
 * Set the error bars end cap type for a chart series.
 */
void
chart_series_set_error_bars_endcap(lxw_series_error_bars *error_bars,
                                   uint8_t endcap)
{
    if (_chart_check_error_bars(error_bars, "_endcap"))
        return;

    error_bars->endcap = endcap;
}

/*
 * Set a line type for a series error bars.
 */
void
chart_series_set_error_bars_line(lxw_series_error_bars *error_bars,
                                 lxw_chart_line *line)
{
    if (_chart_check_error_bars(error_bars, "_line"))
        return;

    if (!line)
        return;

    /* Free any previously allocated resource. */
    free(error_bars->line);

    error_bars->line = _chart_convert_line_args(line);
}

/*
 * Get an axis pointer from a chart.
 */
lxw_chart_axis *
chart_axis_get(lxw_chart *self, lxw_chart_axis_type axis_type)
{
    if (!self)
        return NULL;

    if (axis_type == LXW_CHART_AXIS_TYPE_X)
        return self->x_axis;
    else if (axis_type == LXW_CHART_AXIS_TYPE_Y)
        return self->y_axis;
    else
        return NULL;
}

/*
 * Set an axis caption.
 */
void
chart_axis_set_name(lxw_chart_axis *axis, const char *name)
{
    if (!name)
        return;

    if (name[0] == '=')
        axis->title.range->formula = lxw_strdup(name + 1);
    else
        axis->title.name = lxw_strdup(name);
}

/*
 * Set an axis caption, with a range instead or a formula.
 */
void
chart_axis_set_name_range(lxw_chart_axis *axis, const char *sheetname,
                          lxw_row_t row, lxw_col_t col)
{
    if (!sheetname) {
        LXW_WARN("chart_axis_set_name_range(): sheetname must be specified");
        return;
    }

    /* Start and end row, col are the same for single cell range. */
    _chart_set_range(axis->title.range, sheetname, row, col, row, col);
}

/*
 * Set an axis title/name font.
 */
void
chart_axis_set_name_font(lxw_chart_axis *axis, lxw_chart_font *font)
{
    if (!font)
        return;

    /* Free any previously allocated resource. */
    _chart_free_font(axis->title.font);

    axis->title.font = _chart_convert_font_args(font);
}

/*
 * Set an axis number font.
 */
void
chart_axis_set_num_font(lxw_chart_axis *axis, lxw_chart_font *font)
{
    if (!font)
        return;

    /* Free any previously allocated resource. */
    _chart_free_font(axis->num_font);

    axis->num_font = _chart_convert_font_args(font);
}

/*
 * Set an axis number format.
 */
void
chart_axis_set_num_format(lxw_chart_axis *axis, const char *num_format)
{
    if (!num_format)
        return;

    /* Free any previously allocated resource. */
    free(axis->num_format);

    axis->num_format = lxw_strdup(num_format);
}

/*
 * Set a line type for an axis.
 */
void
chart_axis_set_line(lxw_chart_axis *axis, lxw_chart_line *line)
{
    if (!line)
        return;

    /* Free any previously allocated resource. */
    free(axis->line);

    axis->line = _chart_convert_line_args(line);
}

/*
 * Set a fill type for an axis.
 */
void
chart_axis_set_fill(lxw_chart_axis *axis, lxw_chart_fill *fill)
{
    if (!fill)
        return;

    /* Free any previously allocated resource. */
    free(axis->fill);

    axis->fill = _chart_convert_fill_args(fill);
}

/*
 * Set a pattern type for an axis.
 */
void
chart_axis_set_pattern(lxw_chart_axis *axis, lxw_chart_pattern *pattern)
{
    if (!pattern)
        return;

    /* Free any previously allocated resource. */
    free(axis->pattern);

    axis->pattern = _chart_convert_pattern_args(pattern);
}

/*
 * Reverse the direction of an axis.
 */
void
chart_axis_set_reverse(lxw_chart_axis *axis)
{
    axis->reverse = LXW_TRUE;
}

/*
 * Set the axis crossing position.
 */
void
chart_axis_set_crossing(lxw_chart_axis *axis, double value)
{
    axis->has_crossing = LXW_TRUE;
    axis->crossing = value;
}

/*
 * Set the axis crossing position as the max possible value.
 */
void
chart_axis_set_crossing_max(lxw_chart_axis *axis)
{
    axis->has_crossing = LXW_TRUE;
    axis->crossing_max = LXW_TRUE;
}

/*
 * Turn off/hide the axis.
 */
void
chart_axis_off(lxw_chart_axis *axis)
{
    axis->hidden = LXW_TRUE;
}

/*
 * Set the category axis position.
 */
void
chart_axis_set_position(lxw_chart_axis *axis, uint8_t position)
{
    LXW_WARN_CAT_AND_DATE_AXIS_ONLY("chart_axis_set_position");

    axis->position_axis = position;
}

/*
 * Set the axis label position.
 */
void
chart_axis_set_label_position(lxw_chart_axis *axis, uint8_t position)
{
    axis->label_position = position;
}

/*
 * Set the minimum value for an axis.
 */
void
chart_axis_set_min(lxw_chart_axis *axis, double min)
{
    LXW_WARN_VALUE_AND_DATE_AXIS_ONLY("chart_axis_set_min");

    axis->min = min;
    axis->has_min = LXW_TRUE;
}

/*
 * Set the maximum value for an axis.
 */
void
chart_axis_set_max(lxw_chart_axis *axis, double max)
{
    LXW_WARN_VALUE_AND_DATE_AXIS_ONLY("chart_axis_set_max");

    axis->max = max;
    axis->has_max = LXW_TRUE;
}

/*
 * Set the log base for an axis.
 */
void
chart_axis_set_log_base(lxw_chart_axis *axis, uint16_t log_base)
{
    LXW_WARN_VALUE_AXIS_ONLY("chart_axis_set_log_base");

    /* Excel log range is 2-1000. */
    if (log_base >= 2 && log_base <= 1000)
        axis->log_base = log_base;
}

/*
 * Set the major mark for an axis.
 */
void
chart_axis_set_major_tick_mark(lxw_chart_axis *axis, uint8_t type)
{
    axis->major_tick_mark = type;
}

/*
 * Set the minor mark for an axis.
 */
void
chart_axis_set_minor_tick_mark(lxw_chart_axis *axis, uint8_t type)
{
    axis->minor_tick_mark = type;
}

/*
 * Set interval unit for a category axis.
 */
void
chart_axis_set_interval_unit(lxw_chart_axis *axis, uint16_t unit)
{
    LXW_WARN_CAT_AND_DATE_AXIS_ONLY("chart_axis_set_major_unit");

    axis->interval_unit = unit;
}

/*
 * Set tick interval for a category axis.
 */
void
chart_axis_set_interval_tick(lxw_chart_axis *axis, uint16_t unit)
{
    LXW_WARN_CAT_AND_DATE_AXIS_ONLY("chart_axis_set_major_tick");

    axis->interval_tick = unit;
}

/*
 * Set major unit for a value axis.
 */
void
chart_axis_set_major_unit(lxw_chart_axis *axis, double unit)
{
    LXW_WARN_VALUE_AND_DATE_AXIS_ONLY("chart_axis_set_major_unit");

    axis->has_major_unit = LXW_TRUE;
    axis->major_unit = unit;
}

/*
 * Set minor unit for a value axis.
 */
void
chart_axis_set_minor_unit(lxw_chart_axis *axis, double unit)
{
    LXW_WARN_VALUE_AND_DATE_AXIS_ONLY("chart_axis_set_minor_unit");

    axis->has_minor_unit = LXW_TRUE;
    axis->minor_unit = unit;
}

/*
 * Set the display units for a value axis.
 */
void
chart_axis_set_display_units(lxw_chart_axis *axis, uint8_t units)
{
    LXW_WARN_VALUE_AXIS_ONLY("chart_axis_set_display_units");

    axis->display_units = units;
    axis->display_units_visible = LXW_TRUE;
}

/*
 * Turn on/off the display units for a value axis.
 */
void
chart_axis_set_display_units_visible(lxw_chart_axis *axis, uint8_t visible)
{
    LXW_WARN_VALUE_AXIS_ONLY("chart_axis_set_display_units");

    axis->display_units_visible = visible;
}

/*
 * Set the axis major gridlines on/off.
 */
void
chart_axis_major_gridlines_set_visible(lxw_chart_axis *axis, uint8_t visible)
{
    axis->major_gridlines.visible = visible;
}

/*
 * Set a line type for the major gridlines.
 */
void
chart_axis_major_gridlines_set_line(lxw_chart_axis *axis,
                                    lxw_chart_line *line)
{
    if (!line)
        return;

    /* Free any previously allocated resource. */
    free(axis->major_gridlines.line);

    axis->major_gridlines.line = _chart_convert_line_args(line);

    /* If the gridline has a format it should also be visible. */
    if (axis->major_gridlines.line)
        axis->major_gridlines.visible = LXW_TRUE;
}

/*
 * Set the axis minor gridlines on/off.
 */
void
chart_axis_minor_gridlines_set_visible(lxw_chart_axis *axis, uint8_t visible)
{
    axis->minor_gridlines.visible = visible;
}

/*
 * Set a line type for the minor gridlines.
 */
void
chart_axis_minor_gridlines_set_line(lxw_chart_axis *axis,
                                    lxw_chart_line *line)
{
    if (!line)
        return;

    /* Free any previously allocated resource. */
    free(axis->minor_gridlines.line);

    axis->minor_gridlines.line = _chart_convert_line_args(line);

    /* If the gridline has a format it should also be visible. */
    if (axis->minor_gridlines.line)
        axis->minor_gridlines.visible = LXW_TRUE;
}

/*
 * Set the chart axis label alignment.
 */
void
chart_axis_set_label_align(lxw_chart_axis *axis, uint8_t align)
{
    axis->label_align = align;
}

/*
 * Set the chart title.
 */
void
chart_title_set_name(lxw_chart *self, const char *name)
{
    if (!name)
        return;

    if (name[0] == '=')
        self->title.range->formula = lxw_strdup(name + 1);
    else
        self->title.name = lxw_strdup(name);
}

/*
 * Set the chart title, with a range instead or a formula.
 */
void
chart_title_set_name_range(lxw_chart *self, const char *sheetname,
                           lxw_row_t row, lxw_col_t col)
{
    if (!sheetname) {
        LXW_WARN("chart_title_set_name_range(): sheetname must be specified");
        return;
    }

    /* Start and end row, col are the same for single cell range. */
    _chart_set_range(self->title.range, sheetname, row, col, row, col);
}

/*
 * Set the chart title font.
 */
void
chart_title_set_name_font(lxw_chart *self, lxw_chart_font *font)
{
    /* Free any previously allocated resource. */
    _chart_free_font(self->title.font);

    self->title.font = _chart_convert_font_args(font);
}

/*
 * Turn off the chart title.
 */
void
chart_title_off(lxw_chart *self)
{
    self->title.off = LXW_TRUE;
}

/*
 * Set the chart legend position.
 */
void
chart_legend_set_position(lxw_chart *self, uint8_t position)
{
    self->legend.position = position;
}

/*
 * Set the legend font.
 */
void
chart_legend_set_font(lxw_chart *self, lxw_chart_font *font)
{
    /* Free any previously allocated resource. */
    _chart_free_font(self->legend.font);

    self->legend.font = _chart_convert_font_args(font);
}

/*
 * Remove one or more series from the the legend.
 */
lxw_error
chart_legend_delete_series(lxw_chart *self, int16_t delete_series[])
{
    uint16_t count = 0;

    if (delete_series == NULL)
        return LXW_ERROR_NULL_PARAMETER_IGNORED;

    while (delete_series[count] >= 0)
        count++;

    if (count == 0)
        return LXW_ERROR_NULL_PARAMETER_IGNORED;

    /* The maximum number of series in a chart is 255. */
    if (count > 255)
        count = 255;

    self->delete_series = calloc(count, sizeof(int16_t));
    RETURN_ON_MEM_ERROR(self->delete_series, LXW_ERROR_MEMORY_MALLOC_FAILED);
    memcpy(self->delete_series, delete_series, count * sizeof(int16_t));
    self->delete_series_count = count;

    return LXW_NO_ERROR;
}

/*
 * Set a line type for the chartarea.
 */
void
chart_chartarea_set_line(lxw_chart *self, lxw_chart_line *line)
{
    if (!line)
        return;

    /* Free any previously allocated resource. */
    free(self->chartarea_line);

    self->chartarea_line = _chart_convert_line_args(line);
}

/*
 * Set a fill type for the chartarea.
 */
void
chart_chartarea_set_fill(lxw_chart *self, lxw_chart_fill *fill)
{
    if (!fill)
        return;

    /* Free any previously allocated resource. */
    free(self->chartarea_fill);

    self->chartarea_fill = _chart_convert_fill_args(fill);
}

/*
 * Set a pattern type for the chartarea.
 */
void
chart_chartarea_set_pattern(lxw_chart *self, lxw_chart_pattern *pattern)
{
    if (!pattern)
        return;

    /* Free any previously allocated resource. */
    free(self->chartarea_pattern);

    self->chartarea_pattern = _chart_convert_pattern_args(pattern);
}

/*
 * Set a line type for the plotarea.
 */
void
chart_plotarea_set_line(lxw_chart *self, lxw_chart_line *line)
{
    if (!line)
        return;

    /* Free any previously allocated resource. */
    free(self->plotarea_line);

    self->plotarea_line = _chart_convert_line_args(line);
}

/*
 * Set a fill type for the plotarea.
 */
void
chart_plotarea_set_fill(lxw_chart *self, lxw_chart_fill *fill)
{
    if (!fill)
        return;

    /* Free any previously allocated resource. */
    free(self->plotarea_fill);

    self->plotarea_fill = _chart_convert_fill_args(fill);
}

/*
 * Set a pattern type for the plotarea.
 */
void
chart_plotarea_set_pattern(lxw_chart *self, lxw_chart_pattern *pattern)
{
    if (!pattern)
        return;

    /* Free any previously allocated resource. */
    free(self->plotarea_pattern);

    self->plotarea_pattern = _chart_convert_pattern_args(pattern);
}

/*
 * Turn on the chart data table.
 */
void
chart_set_table(lxw_chart *self)
{
    self->has_table = LXW_TRUE;
    self->has_table_horizontal = LXW_TRUE;
    self->has_table_vertical = LXW_TRUE;
    self->has_table_outline = LXW_TRUE;
    self->has_table_legend_keys = LXW_FALSE;
}

/*
 * Set the options for the chart data table grid.
 */
void
chart_set_table_grid(lxw_chart *self, uint8_t horizontal, uint8_t vertical,
                     uint8_t outline, uint8_t legend_keys)
{
    self->has_table = LXW_TRUE;
    self->has_table_horizontal = horizontal;
    self->has_table_vertical = vertical;
    self->has_table_outline = outline;
    self->has_table_legend_keys = legend_keys;
}

/*
 * Set the font for the chart data table grid.
 */
void
chart_set_table_font(lxw_chart *self, lxw_chart_font *font)
{
    self->has_table = LXW_TRUE;

    /* Free any previously allocated resource. */
    _chart_free_font(self->table_font);

    self->table_font = _chart_convert_font_args(font);
}

/*
 * Turn on up-down bars for the chart.
 */
void
chart_set_up_down_bars(lxw_chart *self)
{
    self->has_up_down_bars = LXW_TRUE;
}

/*
 * Turn on up-down bars for the chart, with formatting.
 */
void
chart_set_up_down_bars_format(lxw_chart *self, lxw_chart_line *up_bar_line,
                              lxw_chart_fill *up_bar_fill,
                              lxw_chart_line *down_bar_line,
                              lxw_chart_fill *down_bar_fill)
{
    self->has_up_down_bars = LXW_TRUE;

    /* Free any previously allocated resource. */
    free(self->up_bar_line);
    free(self->up_bar_fill);
    free(self->down_bar_line);
    free(self->down_bar_fill);

    self->up_bar_line = _chart_convert_line_args(up_bar_line);
    self->up_bar_fill = _chart_convert_fill_args(up_bar_fill);
    self->down_bar_line = _chart_convert_line_args(down_bar_line);
    self->down_bar_fill = _chart_convert_fill_args(down_bar_fill);
}

/*
 * Turn on drop lines for the chart.
 */
void
chart_set_drop_lines(lxw_chart *self, lxw_chart_line *line)
{
    /* Free any previously allocated resource. */
    free(self->drop_lines_line);

    self->has_drop_lines = LXW_TRUE;
    self->drop_lines_line = _chart_convert_line_args(line);
}

/*
 * Turn on high_low lines for the chart.
 */
void
chart_set_high_low_lines(lxw_chart *self, lxw_chart_line *line)
{
    /* Free any previously allocated resource. */
    free(self->high_low_lines_line);

    self->has_high_low_lines = LXW_TRUE;
    self->high_low_lines_line = _chart_convert_line_args(line);
}

/*
 * Set the Bar/Column overlap for all data series.
 */
void
chart_set_series_overlap(lxw_chart *self, int8_t overlap)
{
    if (overlap >= -100 && overlap <= 100)
        self->overlap_y1 = overlap;
    else
        LXW_WARN_FORMAT1("chart_set_series_overlap(): Chart series overlap "
                         "'%d' outside Excel range: -100 <= overlap <= 100",
                         overlap);
}

/*
 * Set the option for displaying blank data in a chart.
 */
void
chart_show_blanks_as(lxw_chart *self, uint8_t option)
{
    self->show_blanks_as = option;
}

/*
 * Display data on charts from hidden rows or columns.
 */
void
chart_show_hidden_data(lxw_chart *self)
{
    self->show_hidden_data = LXW_TRUE;
}

/*
 * Set the Bar/Column gap for all data series.
 */
void
chart_set_series_gap(lxw_chart *self, uint16_t gap)
{
    if (gap <= 500)
        self->gap_y1 = gap;
    else
        LXW_WARN_FORMAT1("chart_set_series_gap(): Chart series gap '%d' "
                         "outside Excel range: 0 <= gap <= 500", gap);
}

/*
 * Set the Pie/Doughnut chart rotation: the angle of the first slice.
 */
void
chart_set_rotation(lxw_chart *self, uint16_t rotation)
{
    if (rotation <= 360)
        self->rotation = rotation;
    else
        LXW_WARN_FORMAT1("chart_set_rotation(): Chart rotation '%d' outside "
                         "Excel range: 0 <= rotation <= 360", rotation);
}

/*
 * Set the Doughnut chart hole size.
 */
void
chart_set_hole_size(lxw_chart *self, uint8_t size)
{
    if (size >= 10 && size <= 90)
        self->hole_size = size;
    else
        LXW_WARN_FORMAT1("chart_set_hole_size(): Hole size '%d' outside "
                         "Excel range: 10 <= size <= 90", size);
}
