Where to Discuss?

Local Group

Preface

Goal: Separating the formatting code and configuration. Example class using simple configuration.

After basic formatting, we can colorized the header. And also give border for both header and data.

Example Sheet

You can download the example ODS source here:

And the example XLSX result here:

This is essentially just a workbook with 40 worksheets. Unlike 42, 40 is not relly an answer of life. But it is till looks like magical number for me.


06: Step Six

From the plain, boring sheets, it’s time to add some visual appeal.

Step Overview

In this section, we’ll cover the following topics:

  • Helper: Get head range, set head rectangle, apply head border
  • Formatting Procedure: Format head border
  • Formatting Procedure: Format head colors

Macro Source

The macro source can be obtained from this repository:

In the Macro Dialog, it would look like this:

Calc Macro: Step Six: APSO Dialog

Class Diagram

You can see a highlighted private method named _format_head_colors(). This method doesn’t need to be altered whatsoever, so I set visibility as private.

You’ll also see another highlighted method: an abstract method named _format_head_borders(), with a supporting method called _apply_head_borders. The _apply_head_borders method requires two helpers: __set_head_rectangle() and __get_head_range().

Class Diagram: Formatting: Simple Configuration: Step 06

We’re almost done with the basic structure.

Import Statements

We need to import TableBorder2 from com.sun.star.

Calc Macro: Step Six: Import Statements

Services

from abc import ABC, abstractmethod

from com.sun.star.sheet import XSpreadsheetDocument
from com.sun.star.util  import XNumberFormats
from com.sun.star.table import XCellRange

And Formatting Constant

from com.sun.star.\
  awt.FontWeight import BOLD
from com.sun.star.\
  table.CellHoriJustify import LEFT, CENTER, RIGHT
from com.sun.star.\
  table import BorderLine2, BorderLineStyle, TableBorder2

Configuration Metadata

We can set the background color in the metadata dictionary.

Calc Macro: Step Six: Init Fields Metadata

  def init_field_metadata(self) -> None:
    self._fields = {
       'Year'     : { 'col': 'B', 'width': 1.5, 'bg': blueScale[3],
                      'align': 'center' },
       'Title'    : { 'col': 'C', 'width': 6,   'bg': blueScale[2] },
       'Genre'    : { 'col': 'D', 'width': 3,   'bg': blueScale[1] },
       'Plot'     : { 'col': 'E', 'width': 6,   'bg': blueScale[2] },
       'Actors'   : { 'col': 'F', 'width': 6,   'bg': blueScale[1] },
       'Director' : { 'col': 'G', 'width': 5,   'bg': blueScale[2] },

       'Rated'    : { 'col': 'I', 'width': 2,   'bg': tealScale[2],
                      'align': 'center' },
       'Runtime'  : { 'col': 'J', 'width': 2.5, 'bg': tealScale[1],
                      'align': 'center' },
       'Metascore': { 'col': 'K', 'width': 2,   'bg': tealScale[2],
                      'align': 'center' }
    }

You can see the relevance of the background color, shown in the result below:

Calc Sheet: Step Five: Head Colors

It’s a matter of taste. I like to use different color scales for each column. Let’s see another block with a different color scale.

Calc Sheet: Step Five: Head Colors

Feel free to set any color to match your preferred style. Or perhaps, to match your boss’s taste!

Basic Flow

Now we can extend the basic flow, by adding format_head_borders() and format_head_colors().

Calc Macro: Step Six: Basic Flow

  def format_one_sheet(self) -> None:
    ...

    # Apply Header Settings
    print(' * Formatting Header')
    self._add_merged_title()
    self._format_head_borders()
    self._format_head_colors()

The implementation follows below.

Formatting: Head Colors

How does the coloring work? Let’s take a look at the private _format_head_colors() method in the descendant class.

Calc Macro: Step Six: Format Head Colors

  def _format_head_colors(self) -> None:  
    for field, data in self._fields.items():
      if bg_color := data.get('bg'):
        letter = data['col']      
        row_index = 2
        col_index = self._column_letter_to_index(letter)

        cell = self._sheet.getCellByPosition(
          col_index , row_index)
        cell.CellBackColor = bg_color 

The method iterates over each metadata field. It first checks if a background color configuration exists using the walrus operator (:=). Then, it updates the color for the corresponding cell.

Abstract Method

To ensure flexibility, the format_head_borders() method needs, to be implemented in the descendant class, but it is called in the basic flow of the base class. This is why we define it as an abstract method, with protected visibility in the base class.

Calc Macro: Step Six: Abstract Methods

from abc import ABC, abstractmethod

class FormatterBase(ABC):
  ...

  @abstractmethod
  def _format_head_borders(self) -> None:  
    pass

Formatting: Head Borders

Now let’s look at the implementation in the descendant class. This is essentially another hardcoded configuration. We will later refactor this into a dictionary to eliminate the hardcoding.

Calc Macro: Step Six: Format Head Colors

  def _format_head_borders(self) -> None:  
    # Base Movie Data
    self._apply_head_border(
      'B', 'G', self.lfBlack, self.lfBlack)

    # Additional Data
    self._apply_head_border(
      'I', 'K', self.lfBlack, self.lfBlack)

As you can see, it calls apply_head_border(). In a real-world sheet, with many columns, apply_head_border() will be used extensively.

Helper: Apply Head Border

The _apply_head_border() helper is intended, for use only within the format_head_borders() method. Since the logic is common to any case, it’s best implemented in the base class. However, as it’s called in the descendant class, it has protected visibility.

Calc Macro: Step Six: Apply Head Border

Let’s examine what happens inside _apply_head_border(). It handles two types of borders:

  • Outside Border: For edges and corners
  • Inside Border: For vertical lines only

First, let’s look at the outside border.

  def _apply_head_border(self,
        letter_start: str, letter_end: str,
        outer_line: BorderLine2, vert_line: BorderLine2) -> None:

    self.__set_head_rectangle(
      letter_start, letter_end, outer_line)

    ...

Next, the inside border.

  def apply_head_border(...)
    ...

    border = TableBorder2()
    border.IsVerticalLineValid = True
    border.VerticalLine = vert_line

    cell_range = self.__get_head_range(
      letter_start, letter_end)
    cell_range.TableBorder2 = border

This requires two private methods:

  • _set_head_rectangle()
  • _get_head_range()

Supporting Helper: Head Rectangle

Border Outside: Edges and Corner

This supporting helper is meant to be used, only within the _apply_head_border() method.

Calc Macro: Step Six: Get Head Range

Its purpose is to define the cell range for the rows and columns: top, bottom (max row), left, and right, within the context of column blocks.

  def __set_head_rectangle(self,
        letter_start: str, letter_end: str,
        line_format: BorderLine2) -> None:

    # Define the cell range for rows and columns  
    # Top, Bottom (max row), Left, Right
    a_t = 2
    a_b = 2
    a_l = self._column_letter_to_index(letter_start)
    a_r = self._column_letter_to_index(letter_end)

    self._format_cell_rectangle(a_t, a_b, a_l, a_r, line_format)

This method uses a hardcoded row number of 2.

Supporting Helper: Head Range

This supporting helper is intended to be used, only within the _apply_head_border() method.

Calc Macro: Step Six: Set Head Rectangle

Its sole responsibility is to retrieve the cell range.

  def __get_head_range(self,
        letter_start: str, letter_end: str) -> XCellRange:

    # Define the cell range for rows and columns
    head_row = 2
    col_start = self._column_letter_to_index(letter_start)
    col_end   = self._column_letter_to_index(letter_end)

    return self._sheet.getCellRangeByPosition(
      col_start, head_row, col_end, head_row)

This method defines the cell range for the outer border and vertical lines. It uses a hardcoded row number of 2.

Sheet Result

The output result is as shown below:

Calc Sheet: Step Six: Output

Console Logging.

For this step, the result displayed in the console will look like this:

>>> tabular_single_movies()
 * Rearranging Columns
 * Formatting Columns
 * Formatting Header

These logs give a real-time update, on which steps are being executed during the formatting process.


07: Step Seven

After enhancing the visual appeal of the header, we can apply similar formatting to the data section. Much like the header, the data formatting follows similar methods.

Step Overview

In this section, we will cover the following topics:

  • Helper: Get data range, set data rectangle, apply data border
  • Formatting Procedure: Format data border

Macro Source

The macro source code for this step can be found in this repository:

Class Diagram

In the class diagram, you will notice the highlighted abstract method named _format_data_borders(), which has protected visibility.

The structure for the data border is quite similar to the header border, with a supporting method called _apply_data_borders. This method requires two helper functions: __set_data_rectangle() and __get_data_range().

Class Diagram: Formatting: Simple Configuration: Step 07

Basic Flow

Now, we extend the basic flow with _format_data_borders().

Calc Macro: Step Seven: Basic Flow

  def format_one_sheet(self) -> None:
    ...

    # Apply Header Settings
    print(' * Formatting Header')
    self._add_merged_title()
    self._format_head_borders()
    self._format_head_colors()

    # Apply borders to the specified range
    print(' * Formatting Border')
    self._format_data_borders()

Abstract Method

Similar to _format_head_borders(), the _format_data_borders() method in the base class is, defined as abstract with a protected visibility specifier. This ensures flexibility and enforces implementation in the descendant class.

Calc Macro: Step Seven: Abstract Method

from abc import ABC, abstractmethod

class FormatterBase(ABC):
  ...

  @abstractmethod
  def _format_head_borders(self) -> None:  
    pass

  @abstractmethod
  def _format_data_borders(self) -> None:  
    pass

Formatting: Data Borders

The implementation in the descendant class is, basically another hardcoded configuration, similar to the header border configuration.

Calc Macro: Step Seven: Format Data Border

  def _format_data_borders(self) -> None:  
    # Base Movie Data
    self._apply_data_border(
      'B', 'C', self.lfBlack, self.lfBlack, self.lfGray)
    self._apply_data_border(
      'D', 'G', self.lfBlack, self.lfGray, self.lfGray)

    # Additional Data
    self._apply_data_border(
      'I', 'K', self.lfBlack, self.lfGray, self.lfGray)

As seen here, _apply_data_border() is used to apply borders. In a real-world scenario, where sheets have many columns, _apply_data_border() will be utilized frequently. This pattern needs to be refactored into a unified configuration later on.

Helper: Apply Data Border

For the same reason as the header counterpart, the _apply_data_border() method has a protected visibility specifier.

Calc Macro: Step Seven: Apply Data Border

Inside the _apply_data_border() method, there are two types of borders:

  • Border Outside: Edges and corners
  • Border Inside: Vertical and horizontal

First, let’s look at the outside border.

  def _apply_data_border(self,
        letter_start: str, letter_end: str,
        outer_line: BorderLine2, vert_line: BorderLine2,
        horz_line: BorderLine2) -> None:

    self.__set_data_rectangle(
      letter_start, letter_end, outer_line)

    ...

Next, the inside border, which includes both vertical and horizontal borders.

  def apply_data_border(...)
    ...

    border = TableBorder2()
    border.IsVerticalLineValid   = True
    border.IsHorizontalLineValid = True
    border.VerticalLine   = vert_line
    border.HorizontalLine = horz_line

    cell_range = self.__get_data_range(
      letter_start, letter_end)
    cell_range.TableBorder2 = border

This also requires two private methods:

  • set_data_rectangle()
  • get_data_range()

Keep scrolling, and we will take a look at these methods.

Supporting Helper: Data Rectangle

Border Outside: Edges and Corner

This supporting helper is meant to be used, only within the _apply_data_border() method.

Calc Macro: Step Seven: Set Data Rectangle

Its purpose is to define the cell range for the rows and columns, specifying the top, bottom (maximum row), left, and right boundaries, all within the context of column blocks.

  def __set_data_rectangle(self,
        letter_start: str, letter_end: str,
        line_format: BorderLine2) -> None:

    # Define the cell range for rows and columns  
    # Top, Bottom (max row), Left, Right
    a_t = 3  
    a_b = self._max_row
    a_l = self._column_letter_to_index(letter_start)
    a_r = self._column_letter_to_index(letter_end)

    self._format_cell_rectangle(a_t, a_b, a_l, a_r, line_format)

Supporting Helper: Data Range

This supporting helper is meant to be used, only within the _apply_data_border() method.

Calc Macro: Step Seven: Get Data Range

Its function is to retrieve the range for the data section.

  def __get_data_range(self,
        letter_start: str, letter_end:  str) -> XCellRange:

    # Define the cell range for rows and columns
    start_row = 3
    end_row = self._max_row
    col_start = self._column_letter_to_index(letter_start)
    col_end   = self._column_letter_to_index(letter_end)

    return self._sheet.getCellRangeByPosition(
      col_start, start_row, col_end, end_row)

The return value defines the cell range for both the outer border and vertical lines.

Sheet Result

The output result is shown below:

Calc Sheet: Step Seven: Output

With the core functionality complete, I’d like to add some additional material. You can easily insert hooks into the basic flow, which will allow modifications for special cases. For example, you could add logic to color rows based on specific values.

Console Logging.

For this step, the result can be displayed as shown below:

>>> tabular_single_movies()
 * Rearranging Columns
 * Formatting Columns
 * Formatting Header
 * Formatting Border

This way is better, than constatly watching htop, showing one CPU core hogging up to 100%.


What is Next 🤔?

We’re almost done! The next step is to explore how to extend this class structure, for instance, by adding an additional data coloring procedure. Finally, we’ll look at how to separate the macro, making it reusable across multiple projects with the same library.

I really have some fun writing the article, also preparing the material and drawing the diagram.

Consider continuing with [ Formatter - Simple Config - Four ].