Chapter 29. Column Charts

All the chart examples in the next four chapters come from the same program, Chart2 Views, which loads a spreadsheet from chartsData.ods. Depending on the function, a different table in the sheet is used to create a chart from a template. The main() function of chart_2_views.py is:

# Chart2View.main() ofchart_2_views.py
def main(self) -> None:
    _ = Lo.load_office(connector=Lo.ConnectPipe(), opt=Lo.Options(verbose=True))

    try:
        doc = Calc.open_doc(fnm=self._data_fnm)
        GUI.set_visible(is_visible=True, odoc=doc)
        sheet = Calc.get_sheet(doc=doc)

        chart_doc = None
        if self._chart_kind == ChartKind.AREA:
            chart_doc = self._area_chart(doc=doc, sheet=sheet)
        elif self._chart_kind == ChartKind.BAR:
            chart_doc = self._bar_chart(doc=doc, sheet=sheet)
        elif self._chart_kind == ChartKind.BUBBLE_LABELED:
            chart_doc = self._labeled_bubble_chart(doc=doc, sheet=sheet)
        elif self._chart_kind == ChartKind.COLUMN:
            chart_doc = self._col_chart(doc=doc, sheet=sheet)
        elif self._chart_kind == ChartKind.COLUMN_LINE:
            chart_doc = self._col_line_chart(doc=doc, sheet=sheet)
        elif self._chart_kind == ChartKind.COLUMN_MULTI:
            chart_doc = self._mult_col_chart(doc=doc, sheet=sheet)
        elif self._chart_kind == ChartKind.DONUT:
            chart_doc = self._donut_chart(doc=doc, sheet=sheet)
        elif self._chart_kind == ChartKind.HAPPY_STOCK:
            chart_doc = self._happy_stock_chart(doc=doc, sheet=sheet)
        elif self._chart_kind == ChartKind.LINE:
            chart_doc = self._line_chart(doc=doc, sheet=sheet)
        elif self._chart_kind == ChartKind.LINES:
            chart_doc = self._lines_chart(doc=doc, sheet=sheet)
        elif self._chart_kind == ChartKind.NET:
            chart_doc = self._net_chart(doc=doc, sheet=sheet)
        elif self._chart_kind == ChartKind.PIE:
            chart_doc = self._pie_chart(doc=doc, sheet=sheet)
        elif self._chart_kind == ChartKind.PIE_3D:
            chart_doc = self._pie_3d_chart(doc=doc, sheet=sheet)
        elif self._chart_kind == ChartKind.SCATTER:
            chart_doc = self._scatter_chart(doc=doc, sheet=sheet)
        elif self._chart_kind == ChartKind.SCATTER_LINE_ERROR:
            chart_doc = self._scatter_line_error_chart(doc=doc, sheet=sheet)
        elif self._chart_kind == ChartKind.SCATTER_LINE_LOG:
            chart_doc = self._scatter_line_log_chart(doc=doc, sheet=sheet)
        elif self._chart_kind == ChartKind.STOCK_PRICES:
            chart_doc = self._stock_prices_chart(doc=doc, sheet=sheet)

        if chart_doc:
            Chart2.print_chart_types(chart_doc)

            template_names = Chart2.get_chart_templates(chart_doc)
            Lo.print_names(template_names, 1)

        Lo.delay(2000)
        msg_result = MsgBox.msgbox(
            "Do you wish to close document?",
            "All done",
            boxtype=MessageBoxType.QUERYBOX,
            buttons=MessageBoxButtonsEnum.BUTTONS_YES_NO,
        )
        if msg_result == MessageBoxResultsEnum.YES:
            Lo.close_doc(doc=doc, deliver_ownership=True)
            Lo.close_office()
        else:
            print("Keeping document open")
    except Exception:
        Lo.close_office()
        raise

Lets assume that self._chart_kind == ChartKind.COLUMN for now.

_col_chart() utilizes the “Sneakers Sold this Month” table in chartsData.ods (see Fig. 240) to generate the column chart in Fig. 241.

Sneakers Sold this Month Table

Fig. 240 :The “Sneakers Sold this Month” Table.

The Column Chart for previous Table

Fig. 241 :The Column Chart for the Table in Fig. 240.

_col_chart() is:

# Chart2View._col_chart() of chart_2_views.py
def _col_chart(self, doc: XSpreadsheetDocument, sheet: XSpreadsheet) -> XChartDocument:
    # draw a column chart;
    # uses "Sneakers Sold this Month" table
    range_addr = Calc.get_address(sheet=sheet, range_name="A2:B8")
    chart_doc = Chart2.insert_chart(
        sheet=sheet,
        cells_range=range_addr,
        cell_name="C3",
        width=15,
        height=11,
        diagram_name=ChartTypes.Column.TEMPLATE_STACKED.COLUMN,
    )
    Calc.goto_cell(cell_name="A1", doc=doc)

    Chart2.set_title(chart_doc=chart_doc, title=Calc.get_string(sheet=sheet, cell_name="A1"))
    Chart2.set_x_axis_title(
        chart_doc=chart_doc, title=Calc.get_string(sheet=sheet, cell_name="A2")
    )
    Chart2.set_y_axis_title(
        chart_doc=chart_doc, title=Calc.get_string(sheet=sheet, cell_name="B2")
    )
    Chart2.rotate_y_axis_title(chart_doc=chart_doc, angle=Angle(90))
    return chart_doc

The column chart created by Chart2.insert_chart() utilizes the cell range A2:B8, which spans the two columns of the table, but not the title in cell A1. The C3 argument specifies where the top-left corner of the chart will be positioned in the sheet, and 15x11 are the dimensions of the image in millimeters.

Calc.goto_cell() causes the application window’s view of the spreadsheet to move so that cell A1 is visible, which lets the user see the sneakers table and the chart together.

If the three set methods and rotateYAxisTitle() are left out of _col_chart(), then the generated chart will have no titles as in Fig. 242.

The Column Chart for the Table in The Sneakers Sold this Month Table, with no Titles.

Fig. 242 :The Column Chart for the Table in Fig. 241, with no Titles.

29.1 Creating a Chart Title

Chart2.set_title() is passed a string which becomes the chart’s title. For example:

# part of _col_chart() in Chart2View class
Chart2.set_title(chart_doc=chart_doc, title=Calc.get_string(sheet=sheet, cell_name="A1"))

utilizes the string from cell A1 of the spreadsheet (see Fig. 240).

Setting a title requires three interfaces: XTitled, XTitle, and XFormattedString. XTitled is utilized by several chart services, as shown in Fig. 243.

Services Using the XTitled Interface

Fig. 243 :Services Using the XTitled Interface.

The XChartDocument interface is converted into XTitled by Chart2.set_title(), so an XTitle object can be assigned to the chart:

# in Chart2 class
@staticmethod
def get_title(chart_doc: XChartDocument) -> XTitle:
    try:
        xtilted = Lo.qi(XTitled, chart_doc, True)
        return xtilted.getTitleObject()
    except Exception as e:
        raise ChartError("Error getting title from chart") from e

The XTitle object is an instance of the Title service which inherits a wide assortment of properties related to the text’s paragraph, fill, and line styling, as shown in Fig. 244.

The Title Service.

Fig. 244 :The Title Service.

Text is added to the XTitle object by Chart2.create_title(), as an XFormattedString array:

# in Chart2 class
@staticmethod
def create_title(title: str) -> XTitle:
    try:
        xtitle = Lo.create_instance_mcf(XTitle, "com.sun.star.chart2.Title", raise_err=True)
        xtitle_str = Lo.create_instance_mcf(
            XFormattedString, "com.sun.star.chart2.FormattedString", raise_err=True
        )
        xtitle_str.setString(title)
        title_arr = (xtitle_str,)
        xtitle.setText(title_arr)
        return xtitle
    except Exception as e:
        raise ChartError(f'Error creating title for: "{title}"') from e

The use of an XFormattedString tuple (title_arr = (xtitle_str,)) may seem to be overkill when the title is a single string, but it also allows character properties to be associated with the string through XFormattedString2, as shown in Fig. 245.

The FormattedString Service

Fig. 245 :The FormattedString Service.

Character properties allow the font and point size of the title to be changed to Arial 14pt by Chart2.set_x_title_font():

# in Chart2 class
@staticmethod
def set_x_title_font(xtitle: XTitle, font_name: str, pt_size: int) -> None:
    try:
        fo_strs = xtitle.getText()
        if fo_strs:
            Props.set_property(fo_strs[0], "CharFontName", font_name)
            Props.set_property(fo_strs[0], "CharHeight", pt_size)
    except Exception as e:
        raise ChartError("Error setting x title font") from e

The CharFontName and CharHeight properties come from the CharacterProperties class.

29.2 Creating Axis Titles

Setting the axes titles needs a reference to the XAxis interface. Incidentally, this interface name is a little misleading since X is the naming convention for interfaces, not a reference to the x-axis.

Fig. 235 shows that the XAxis interface is available via the XCoordinateSystem interface, which can be obtained by calling Chart2.get_coord_system(). XCoordinateSystem.getAxisByDimension() can then be employed to get an axis reference. This is implemented by Chart2.get_axis():

# in chart2 class
@classmethod
def get_axis(cls, chart_doc: XChartDocument, axis_val: AxisKind, idx: int) -> XAxis:
    try:
        coord_sys = cls.get_coord_system(chart_doc)
        result = coord_sys.getAxisByDimension(int(axis_val), idx)
        if result is None:
            raise UnKnownError("None Value: getAxisByDimension() returned None")
        return result
    except ChartError:
        raise
    except Exception as e:
        raise ChartError("Error getting Axis for chart") from e

See also

AxisKind

XCoordinateSystem.getAxisByDimension() takes two integer arguments: the first represents the axis (x, y, or z), while the second is a primary or secondary index (0 or 1) for the chosen axis. Chart2 includes wrapper functions for Chart2.get_axis() for the most common cases:

# in Chart2 class
@classmethod
def get_x_axis(cls, chart_doc: XChartDocument) -> XAxis:
    return cls.get_axis(chart_doc=chart_doc, axis_val=AxisKind.X, idx=0)

@classmethod
def get_y_axis(cls, chart_doc: XChartDocument) -> XAxis:
    return cls.get_axis(chart_doc=chart_doc, axis_val=AxisKind.Y, idx=0)

@classmethod
def get_x_axis2(cls, chart_doc: XChartDocument) -> XAxis:
    return cls.get_axis(chart_doc=chart_doc, axis_val=AxisKind.X, idx=1)

@classmethod
def get_y_axis2(cls, chart_doc: XChartDocument) -> XAxis:
    return cls.get_axis(chart_doc=chart_doc, axis_val=AxisKind.Y, idx=1)

Chart2.set_axis_title() calls Chart2.get_axis() to get a reference to the correct axis, and then reuses many of the methods described earlier for setting the chart title:

# in Chart2 class
@classmethod
def set_axis_title(
    cls, chart_doc: XChartDocument, title: str, axis_val: AxisKind, idx: int
) -> XTitle:
    try:
        axis = cls.get_axis(chart_doc=chart_doc, axis_val=axis_val, idx=idx)
        titled_axis = Lo.qi(XTitled, axis, True)
        xtitle = cls.create_title(title)
        titled_axis.setTitleObject(xtitle)
        fname = Info.get_font_general_name()
        cls.set_x_title_font(xtitle, fname, 12)
        return xtitle
    except ChartError:
        raise
    except Exception as e:
        raise ChartError(f'Error setting axis tile: "{title}" for chart') from e

As with Chart2.get_axis(), Chart2 includes wrapper methods for Chart2.set_axis_title() to simplify common axis cases:

# in Chart2 class
@classmethod
def set_x_axis_title(cls, chart_doc: XChartDocument, title: str) -> XTitle:
    return cls.set_axis_title(chart_doc=chart_doc, title=title, axis_val=AxisKind.X, idx=0)

@classmethod
def set_y_axis_title(cls, chart_doc: XChartDocument, title: str) -> XTitle:
    return cls.set_axis_title(chart_doc=chart_doc, title=title, axis_val=AxisKind.Y, idx=0)

@classmethod
def set_x_axis2_title(cls, chart_doc: XChartDocument, title: str) -> XTitle:
    return cls.set_axis_title(chart_doc=chart_doc, title=title, axis_val=AxisKind.X, idx=1)

@classmethod
def set_y_axis2_title(cls, chart_doc: XChartDocument, title: str) -> XTitle:
    return cls.set_axis_title(chart_doc=chart_doc, title=title, axis_val=AxisKind.Y, idx=1)

29.3 Rotating Axis Titles

The default orientation for titles is horizontal, which is fine for the chart and x-axis titles, but can cause the y-axis title to occupy too much horizontal space. The solution is to call Chart2.rotate_y_axis_title() with an angle (usually 90 degrees) to turn the text counter-clockwise so it’s vertically orientated (see Fig. 241).

The implementation accesses the XTitle interface for the axis title, and then modifies its TextRotation property from the Title service (see Fig. 244).

# in Chart2 class
@classmethod
def rotate_y_axis_title(cls, chart_doc: XChartDocument, angle: Angle) -> None:
    cls.rotate_axis_title(chart_doc=chart_doc, axis_val=AxisKind.Y, idx=0, angle=angle)

@classmethod
def rotate_axis_title(
    cls, chart_doc: XChartDocument, axis_val: AxisKind, idx: int, angle: Angle
) -> None:
    try:
        xtitle = cls.get_axis_title(chart_doc=chart_doc, axis_val=axis_val, idx=idx)
        Props.set(xtitle, TextRotation=angle.Value)
    except ChartError:
        raise
    except Exception as e:
        raise ChartError("Error while trying to rotate axis title") from e

@classmethod
def get_axis_title(cls, chart_doc: XChartDocument, axis_val: AxisKind, idx: int) -> XTitle:
    try:
        axis = cls.get_axis(chart_doc=chart_doc, axis_val=axis_val, idx=idx)
        titled_axis = Lo.qi(XTitled, axis, True)
        result = titled_axis.getTitleObject()
        if result is None:
            raise UnKnownError("None Value: getTitleObject() return a value of None")
        return result
    except ChartError:
        raise
    except Exception as e:
        raise ChartError("Error getting axis title") from e

29.4 What Chart Templates are Available?

_col_chart() in chart_2_views.py returns its XChartDocument reference. This isn’t necessary for rendering the chart, but allows the reference to be passed to Chart2.get_chart_templates():

# in main() of chart_2_views.py
# ...
chart_doc = self._col_chart(doc=doc, sheet=sheet)
# ...
template_names = Chart2.get_chart_templates(chart_doc)
Lo.print_names(template_names, 1)

The only way to list the chart templates supported by the chart2 module (i.e. those shown in Table 8) is by querying an existing chart document. That’s the purpose of Chart2.get_chart_templates():

# in Chart2 class
@staticmethod
def get_chart_templates(chart_doc: XChartDocument) -> List[str]:
    try:
        ct_man = chart_doc.getChartTypeManager()
        return Info.get_available_services(ct_man)
    except Exception as e:
        raise ChartError("Error getting chart templates") from e

Normally XChartTypeManager is used to create a template instance, but Info.get_available_services() accesses its XMultiServiceFactory.getAvailableServiceNames() method to list the names of all its supported services, which are templates:

# in Info class
@staticmethod
def get_available_services(obj: object) -> List[str]:
    services: List[str] = []
    try:
        sf = Lo.qi(XMultiServiceFactory, obj, True)
        service_names = sf.getAvailableServiceNames()
        services.extend(service_names)
        services.sort()
    except Exception as e:
        Lo.print(e)
        raise Exception() from e
    return services

The output lists has 64 names, same as Table 8, starting and ending like so:

com.sun.star.chart2.template.Area
com.sun.star.chart2.template.Bar
com.sun.star.chart2.template.Bubble
com.sun.star.chart2.template.Column
:
com.sun.star.chart2.template.ThreeDLineDeep
com.sun.star.chart2.template.ThreeDPie
com.sun.star.chart2.template.ThreeDPieAllExploded
com.sun.star.chart2.template.ThreeDScatter

29.5 Multiple Columns

The _mult_col_chart() method in chart_2_views.py uses a table containing three columns of data (see Fig. 246) to generate two column graphs in the same chart, as in Fig. 247.

The States with the Most Colleges Table

Fig. 246 :The “States with the Most Colleges” Table.

A Multiple Column Chart Generated from the Table in previous figure

Fig. 247 :A Multiple Column Chart Generated from the Table in Fig. 246.

_mult_col_chart() is:

#
def _mult_col_chart(self, doc: XSpreadsheetDocument, sheet: XSpreadsheet) -> XChartDocument:
    range_addr = Calc.get_address(sheet=sheet, range_name="E15:G21")
    d_name = ChartTypes.Column.TEMPLATE_STACKED.COLUMN
    # d_name = ChartTypes.Column.TEMPLATE_PERCENT.COLUMN_DEEP_3D
    # d_name = ChartTypes.Column.TEMPLATE_PERCENT.COLUMN_FLAT_3D
    chart_doc = Chart2.insert_chart(
        sheet=sheet,
        cells_range=range_addr,
        cell_name="A22",
        width=20,
        height=11,
        diagram_name=d_name,
    )
    ChartTypes.Column.TEMPLATE_STACKED.COLUMN
    Calc.goto_cell(cell_name="A13", doc=doc)

    Chart2.set_title(chart_doc=chart_doc, title=Calc.get_string(sheet=sheet, cell_name="E13"))
    Chart2.set_x_axis_title(
        chart_doc=chart_doc, title=Calc.get_string(sheet=sheet, cell_name="E15")
    )
    Chart2.set_y_axis_title(
        chart_doc=chart_doc, title=Calc.get_string(sheet=sheet, cell_name="F14")
    )
    Chart2.rotate_y_axis_title(chart_doc=chart_doc, angle=Angle(90))
    Chart2.view_legend(chart_doc=chart_doc, is_visible=True)

    # for the 3D versions
    # Chart2.show_axis_label(chart_doc=chart_doc, axis_val=AxisKind.Z, idx=0, is_visible=False)
    # Chart2.set_chart_shape_3d(chart_doc=chart_doc, shape=DataPointGeometry3DEnum.CYLINDER)
    return chart_doc

The same Column chart template is used as in _col_chart(), and the additional column of data is treated as an extra column graph. The chart title and axis titles are added in the same way as before, and a legend is included by calling Chart2.view_legend():

# in Chart2 class
@staticmethod
def view_legend(chart_doc: XChartDocument, is_visible: bool) -> None:
    try:
        diagram = chart_doc.getFirstDiagram()
        legend = diagram.getLegend()
        if is_visible and legend is None:
            leg = Lo.create_instance_mcf(XLegend, "com.sun.star.chart2.Legend", raise_err=True)
            Props.set(
                leg,
                LineStyle=LineStyle.NONE,
                FillStyle=FillStyle.SOLID,
                FillTransparence=100
            )
            diagram.setLegend(leg)

        Props.set(leg, Show=is_visible)
    except Exception as e:
        raise ChartError("Error while setting legend visibility") from e

The legend is accessible via the chart Diagram service. view_legend() creates an instance, and sets a few properties to make it look nicer.

Fig. 248 shows the Legend service, which defines several properties, and inherits many others from FillProperties and LineProperties. The LineStyle, FillStyle, and FillTransparence properties utilized in view_legend() come from the inherited property classes, but Show is from the Legend service.

The Legend Service.

Fig. 248 :The Legend Service.

The XLegend interface contains no methods, and is used only to access the properties in its defining service.

29.6 3D Pizazz

You may not be a fan of 3D charts which are often harder to understand than their 2D equivalents, even if they do look more “hi-tech”. But if you really want a 3D version of a chart, it’s mostly just a matter of changing the template name in the call to Chart2.insert_chart().

If d_name were were set to enum value of ChartTypes.Column.TEMPLATE_PERCENT.COLUMN_DEEP_3D or string value of ThreeDColumnDeep or enum value of ChartTypes.Column.TEMPLATE_PERCENT.COLUMN_FLAT_3D or string value of ThreeDColumnFlat in _mult_col_chart(), then the charts in Fig. 249 appear.

Deep and Flat 3D Column Charts

Fig. 249 :Deep and Flat 3D Column Charts

deep orders the two 3D graphs along the z-axis, and labels the axis.

The x-axis labels are rotated automatically in the top-most chart of Fig. 249 because the width of the chart wasn’t sufficient to draw them horizontally, and that’s caused the graphs to be squashed into less vertical space.

_mult_col_chart() contains two commented out lines which illustrate how a 3D graph can be changed:

# in _mult_col_chart()...
# hide labels
Chart2.show_axis_label(chart_doc=chart_doc, axis_val=AxisKind.Z, idx=0, is_visible=False)
Chart2.set_chart_shape_3d(chart_doc=chart_doc, shape=DataPointGeometry3DEnum.CYLINDER)

Chart2.show_axis_label() is passed the boolean False to switch off the display of the z-axis labels. Chart2.set_chart_shape_3d() changes the shape of the columns; in this case to cylinders, as in Fig. 250.

Modified Deep 3D Column Chart

Fig. 250 :Modified Deep 3D Column Chart.

Chart2.show_axis_label() uses Chart2.get_axis() to access the XAxis interface, and then modifies its Show property:

# in Chart2 class
@classmethod
def get_axis(cls, chart_doc: XChartDocument, axis_val: AxisKind, idx: int) -> XAxis:
    try:
        coord_sys = cls.get_coord_system(chart_doc)
        result = coord_sys.getAxisByDimension(int(axis_val), idx)
        if result is None:
            raise UnKnownError("None Value: getAxisByDimension() returned None")
        return result
    except ChartError:
        raise
    except Exception as e:
        raise ChartError("Error getting Axis for chart") from e

The Axis service contains a large assortment of properties, and inherits character and line properties depicted in Fig. 251.

The Axis Service.

Fig. 251 :The Axis Service.

Chart2.set_chart_shape_3d() affects the data points (which in a 3D column chart are boxes by default). This requires access to the XDataSeries array of data points by calling Chart2.get_data_series(), and then the Geometry3D property in the DataSeries service is modified. Fig. 237 shows the service and its interfaces, and most of its properties are inherited from the DataPointProperties class, including Geometry3D. The code for Chart2.set_chart_shape_3d():

# in Chart2 class
@classmethod
def set_chart_shape_3d(cls, chart_doc: XChartDocument, shape: DataPointGeometry3DEnum) -> None:
    try:
        data_series_arr = cls.get_data_series(chart_doc=chart_doc)
        for data_series in data_series_arr:
            Props.set_property(data_series, "Geometry3D", int(shape))
    except ChartError:
        raise
    except Exception as e:
        raise ChartError("Error setting chart shape 3d") from e

29.7 The Column and Line Chart

Another way to display the multiple columns of data in the “States with the Most Colleges” table (Fig. 246) is to draw a column and line chart. The column is generated from the first data column, and the line graph uses the second column. The result is shown in Fig. 252.

A Column and Line Chart Generated from the Table in Figure 7

Fig. 252 :A Column and Line Chart Generated from the Table in Fig. 246.

_col_line_chart() in chart_2_views.py generates Fig. 252:

# Chart2View._col_line_chart() in chart_2_views.py
def _col_line_chart(self, doc: XSpreadsheetDocument, sheet: XSpreadsheet) -> XChartDocument:
    range_addr = Calc.get_address(sheet=sheet, range_name="E15:G21")
    chart_doc = Chart2.insert_chart(
        sheet=sheet,
        cells_range=range_addr,
        cell_name="B3",
        width=20,
        height=11,
        diagram_name=ChartTypes.ColumnAndLine.TEMPLATE_STACKED.COLUMN_WITH_LINE,
    )
    Calc.goto_cell(cell_name="A13", doc=doc)

    Chart2.set_title(
        chart_doc=chart_doc, title=Calc.get_string(sheet=sheet, cell_name="E13")
    )
    Chart2.set_x_axis_title(
        chart_doc=chart_doc, title=Calc.get_string(sheet=sheet, cell_name="E15")
    )
    Chart2.set_y_axis_title(
        chart_doc=chart_doc, title=Calc.get_string(sheet=sheet, cell_name="F14")
    )
    Chart2.rotate_y_axis_title(chart_doc=chart_doc, angle=Angle(90))
    Chart2.view_legend(chart_doc=chart_doc, is_visible=True)
    return chart_doc

It’s nearly identical to _mul_col_chart() except for ChartTypes.ColumnAndLine.TEMPLATE_STACKED.COLUMN_WITH_LINE passed to Chart2.insert_chart().

A chart’s coordinate system may utilize multiple chart types. Up to now a chart template (i.e. Column) has been converted to a single chart type (i.e. ColumnChartType) by the chart API (specifically by the chart type manager), but the ColumnWithLine template is different. The manager implements that template using two chart types, ColumnChartType and LineChartType. This is reported by Chart2.insert_chart() calling Chart2.print_chart_types():

No. of chart types: 2
  com.sun.star.chart2.ColumnChartType
  com.sun.star.chart2.LineChartType

Chart2.print_chart_types() uses Chart2.get_chart_types(), which was defined earlier:

# in Chart2 class
@classmethod
def print_chart_types(cls, chart_doc: XChartDocument) -> None:
    chart_types = cls.get_chart_types(chart_doc)
    if len(chart_types) > 1:
        print(f"No. of chart types: {len(chart_types)}")
        for ct in chart_types:
            print(f"  {ct.getChartType()}")
    else:
        print(f"Chart Type: {chart_types[0].getChartType()}")
    print()

Why is this separation of a single template into two chart types important? The short answer is that it complicates the search for a chart template’s data. For example Chart2.get_chart_type() returns the first chart type in the XChartType array since most templates only use a single chart type:

# in Chart2 class
@classmethod
def get_chart_type(cls, chart_doc: XChartDocument) -> XChartType:
    try:
        chart_types = cls.get_chart_types(chart_doc)
        return chart_types[0]
    except ChartError:
        raise
    except Exception as e:
        raise ChartError("Error getting chart type") from e

This method is insufficient for examining a chart created with the ColumnWithLine template since the XChartType array holds two chart types. A programmer will have to use Chart2.find_chart_type(), which searches the array for the specified chart type:

# in Chart2 class
@classmethod
def find_chart_type(
    cls, chart_doc: XChartDocument, chart_type: ChartTypeNameBase | str
) -> XChartType:
    # Ensure chart_type is ChartTypeNameBase | str
    Info.is_type_enum_multi(
        alt_type="str", enum_type=ChartTypeNameBase, enum_val=chart_type, arg_name="chart_type"
    )
    try:
        srch_name = f"com.sun.star.chart2.{str(chart_type).lower()}"
        chart_types = cls.get_chart_types(chart_doc)
        for ct in chart_types:
            ct_name = ct.getChartType().lower()
            if ct_name == srch_name:
                return ct
    except Exception as e:
        raise ChartError(f'Error Finding chart for "{chart_type}"') from e
    raise NotFoundError(f'Chart for type "{chart_type}" was not found')

For example, the following call returns a reference to the line chart type:

line_ct = Chart2.find_chart_type(chart_doc=chart_doc, chart_type="LineChartType") # XChartType

The simple get_chart_type() is used in Chart2.get_data_series():

# in Chart2 class
@classmethod
def get_data_series(
    cls, chart_doc: XChartDocument, chart_type: ChartTypeNameBase | str = ""
) -> Tuple[XDataSeries, ...]:
    try:
        if chart_type:
            xchart_type = cls.find_chart_type(chart_doc, chart_type)
        else:
            xchart_type = cls.get_chart_type(chart_doc)
        ds_con = Lo.qi(XDataSeriesContainer, xchart_type, True)
        return ds_con.getDataSeries()
    except Exception as e:
        raise ChartError("Error getting chart data series") from e

When chart_type is omitted it means that Chart2.get_data_series() can only access the data associated with the column (the first chart type) in a ColumnWithLine chart document.

When chart_type is included it requires a chart_type argument to get the correct chart type. For example, the call:

ds = Chart2.get_data_series(chart_doc=chart_doc, chart_type=ChartTypes.Line.NAMED.LINE_CHART)
#   chart_type could also be "LineChartType"

returns the data series associated with the line chart type.