Where to Discuss?

Local Group

Preface

Goal: Further sheet wide formatting using using simple configuration.

After building the basic script, it’s time to level up. This round, we dive deeper into configuration magic, making our formatting fancier, smarter, and way easier to maintain.

No more hunting through lines of code just to tweak a column position. Instead, we’ll put the power into a clean, centralized configuration. Because, hardcoding is so last decade.

Along the way, expect abstract methods, a bit of design pattern goodness, and plenty of log messages to keep things honest.

Buckle up, it’s going to be a smooth, configurable ride.


02: Step Two

Now that the columns are set, let’s move on to the rows.

Step Overview

Here’s what topic we’ll cover:

  • Helper: Last Row
  • Helper: Prevent Re-inserting Column
  • Formatting Procedure: Insert row gap, set row height

Script Source

The source for this step is available in the repository:

Class Diagram

What’s new this time?

We’re introducing _reset_pos_rows(), including both its abstract definition and implementation.

In the diagram, this abstract method is marked with a # symbol.

Class Diagram: Formatting: OpenPyXL: Configuration: Step 02

As always, I’ve highlighted the new additions, whether constants, properties, or methods, so you can easily track the updates.

Abstract Method

This step only adds one new abstract method: _reset_pos_rows().

openpyxl: Step Two: Abstract Method

class FormatterBase(ABC):
    @abstractmethod
    def _merge_metadatas(self) -> None:
        pass

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

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

With this in place, we can smoothly integrate row formatting into the existing flow.

Initialization

New player in town: _max_row

There’s a slight change in initialization process, with additional _max_row variable. This keeps track of the last populated row. A handy little counter to avoid overshooting into Excel’s abyss.

openpyxl: Step Two: Initialization

    def __init__(self, workbook):
        self.__workbook = workbook
        self._sheet = None
        self._gaps = []
        self._max_row = 0

        self._merge_metadatas()

We just need to know where it end. Without _max_row, we would be blindly inserting rows, without knowing where the data actually ends. This way, our row gaps stay neatly aligned, with the real content. No phantom rows in sight.

Basic Flow

Formatter Base Class

This approach mirrors the previous column arrangement process, with a twist. Now, we’re adding a new hook for handling row gaps.

Instead of just rearranging columns, Now we can inject gaps between rows and apply precise formatting to them.

openpyxl: Step Two: Format One Sheet

    def __format_one_sheet(self) -> None:
        self._max_row = self._sheet.max_row

        if not self.__is_first_column_empty():
            print(" * Rearranging Columns")
            self._reset_pos_columns()
            print(" * Setting Rows Height")
            self._reset_pos_rows()
            self._max_row += 1

        # Call the hook method (default does nothing)
        self._format_one_sheet_post()

        print(" * Finished\n")

The whole point of this section is to give us, a fine-tuned control over both rows and columns. With this enhanced flow, we maintain a clear structure, and the new hook method allows future extensions without breaking a sweat.

Helper: Prevent Column Rearrangement

Once is enough—don’t mess with the columns again.

The goal of this helper function is simple: ensure that column and row gap insertion only happens once. Any more, and we risk messing up our beautiful, orderly spreadsheet.

Well, we can run the formatting script, as many times as we like without worrying about mangling the layout. Colors? Fonts? Borders? Go nuts. But gaps? They stay put.

To check if gaps are already in place, we use the __is_first_column_empty() function. It peeks at the first few rows (we check up to row 10), to see if the first column has already been touched.

If the column is already filled with an empty cell, it means gaps are already there, no need to insert them again.

openpyxl: Step Two: Is First Column Empty

    def __is_first_column_empty(self) -> bool:
        max_sampling_row = 10

        for row_index in range(1, max_sampling_row+1):
            cell = self._sheet.cell(row_index, 1)
            # indicates an empty cell
            if cell.value != None: return False
        return True

This function ensures that the script doesn’t inadvertently re-insert gaps, preserving the order of the columns and rows after the initial setup.

Without this check, every script execution would keep adding more and more gaps, turning our sleek Excel sheet into a chaotic maze. This function prevents that madness, ensuring stability and consistency in our output.

Helper: Maximum Rows

Finding the end of the world rows

We can simlpy utilize built-in method max_row from openpyxl. This method is essential when we’re working with dynamic data in openpyxl, as it ensures the script adapts to any changes in the sheet’s data range.

self._sheet.max_row

In the real world, data isn’t static (unless we’re a museum curator). This method ensures our script gracefully handles changing row counts, without breaking a sweat, or your spreadsheet.

Scaling Factor

I calculated the scaling factor through a sophisticated method, known as “trial and error”:

  • Scaling factor: 29.53

Why 29.53? Honestly, I have no idea. No clue, about this one. If you figure out the cosmic truth behind this number, do let me know. Any suggestions or better calculation, would help. I’ll owe you a coffee (or at least a witty comment).

Basic Flow: Row Position

Formatter Base Class

In this step, we first ensure the height of all rows is consistent. 0.5 cm to be precise (although the multiplier may vary depending on your locale or the whims of spreadsheet physics).

openpyxl: Step Two: Reset Pos Rows

    def _reset_pos_rows(self) -> None:
        # Approx. conversion of cm row height
        factor = 29.53
        row_height = 0.5 * factor
        wsrd = self._sheet.row_dimensions

        # Range to be processed
        # Omit header and plus one for the last
        range_rows = range(2, self._max_row + 1)

        for row_index in range_rows :
            wsrd[row_index].height = row_height

After giving your rows a height makeover, we insert two rows at the top to create a column group heading, setting it to 0.3 cm for the first row, and the row after the end.

        # Insert two rows at the top
        self._sheet.insert_rows(1, 2)

        # Adjust header row height
        row_height_div = 0.3 * factor
        wsrd[1].height = row_height_div
        wsrd[self._max_row + 3].height = row_height_div

Why not add a little flair? Because neatly spaced rows make your spreadsheet easier to read, and our future self will thank us when debugging it at 2 a.m.

Breakdown

Here’s a quick recap for those skimming while sipping coffee:

  • Row Height Adjustment: Every row (from row 2 to the last) is set to 0.5 cm. This ensures uniformity across the rows. Consistency is key, unless you want your spreadsheet to resemble a chaotic art piece.

  • Inserting Gap Rows: We insert two rows at the top (index 0) and apply a 0.3 cm gap to the first and after last rows. These gap rows are intended to visually separate the table from any other content that might be above or below it. Think of it as a cozy buffer zone for your data.

  • Maintaining Simple Formatting: This setup sticks to basic row height adjustments. The formatting uses consistent 0.5 cm and 0.3 cm values for the rows. We can tweak it further if we’re feeling fancy, but for now, this clean and consistent format does the trick.

This approach keeps our sheet well-organized, with structured gaps and uniform row heights.

Ensuring the header, and yet and empty footer, are appropriately spaced. It’s like giving our spreadsheet a proper haircut—neat, tidy, and easy on the eyes.

Sheet Result

The result? Your spreadsheet should now have well-aligned rows, complete with stylish gaps. Here’s a preview of the output:

Calc Sheet: Step Two: Output

Yes, the fonts look like they escaped from the ’90s. We will tackle that horror in step eight. Patience, my friend.

Terminal Log

To keep things transparent (and to know what’s going wrong when everything inevitably does), I use simple print statements for logging. This provides real-time feedback on the script’s execution.

For this step, here’s what you’ll see in the terminal:

openpyxl: Step Two: Terminal Log

❯ python 02-formatter-tabular.py
01-1980
 * Rearranging Columns
   - Insert Gap: A
   - Insert Gap: H
   - Insert Gap: L
 * Setting Rows Height
 * Finished

02-1981
 * Rearranging Columns
   - Insert Gap: A
   - Insert Gap: H
   - Insert Gap: L
 * Setting Rows Height
 * Finished

 ...

Logs are our best friend when debugging. They give us a play-by-play of what’s happening, so we’re not left guessing why our rows are suddenly doing the Macarena.

We’ll dive deeper into the logging magic later, but for now, we’re officially a spreadsheet sorcerer.


03: Step Three

In this step, we go beyond cell-level adjustments, and introduce settings for the entire sheet. This includes freezing columns and rows, as well as managing gridlines.

Why bother with this?

Well, while gridlines are like training wheels for data entry, helpful but clunky, they can turn a professional report into a chaotic mess. Plus, freezing panes keeps your headers in view, while you scroll through the spreadsheet abyss. This step is all about making your sheet both cleaner and easier to navigate.

Step Overview

This section covers the following topics:

  • Formatting Procedure: Show grid, Freeze panes

These tweaks may seem cosmetic, but they elevate the user experience, by enhancing both visual clarity and usability.

Script Source

You can find the script source in this repository:

Feel free to browse it, copy it, or marvel at its beauty.

Class Diagram

Yes! We’re adding the _set_sheetwide_view() method. This method handles sheet-level formatting, and is highlighted in the class diagram below.

Class Diagram: Formatting: OpenPyXL: Configuration: Step 03

Like a good detective novel, the diagram reveals the new twist, while keeping the familiar characters (methods) intact.

Abstract Method

Just write the abstract.

And here it is, our newest addition to the abstract method club:

openpyxl: Step Three: Abstract Method

class FormatterBase(ABC):
    @abstractmethod
    def _merge_metadatas(self) -> None:
        pass

    ...

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

This method allowing us to extend the base class without breaking a sweat. With it in place, we’re ready to take sheet-wide control like a spreadsheet overlord.

Basic Flow

Formatter Base Class

With the abstract method in place, we can summon it right in the heart of the base class. No secret handshakes required.

openpyxl: Step Three: Format One Sheet

    def __format_one_sheet(self) -> None:
        self._max_row = self._sheet.max_row

        if not self.__is_first_column_empty():
            print(" * Rearranging Columns")
            self._reset_pos_columns()
            print(" * Setting Rows Height")
            self._reset_pos_rows()
            self._max_row += 1

And now, for the grand finale, the sheet-wide formatting:

        # Apply Sheet Wide
        print(" * Formatting Sheet-Wide View")
        self._set_sheetwide_view()

        # Call the hook method (default does nothing)
        self._format_one_sheet_post()

        print(" * Finished\n")

This approach keeps the process modular, while giving you the power to customize, the sheet’s macro-level aesthetics.

Configuration: Freeze

Why stop at columns and rows when you can freeze time. Well, at least parts of the sheet? Here, we add a self._freeze variable to the child class, which the base class will obediently process.

openpyxl: Step Three: Merge Metadata

class FormatterTabularMovies(FormatterCommon):
    def _merge_metadatas(self) -> None:
        self._gaps = [0, 7, 11]
        self._freeze = "C4"

With this setup, you can configure a different freeze position, for each worksheet flavor, because variety is the spice of life.

Formatting: Sheetwide View

The implementation is stored in base class. The heavy lifting happens here, inside the _set_sheetwide_view() method. This is where we fine-tune the big picture.

openpyxl: Step Three: Sheetwide View

    # Formatting Procedure: Abstract Override
    def _set_sheetwide_view(self) -> None:
        # Disable gridlines
        self._sheet.sheet_view.showGridLines = False

        # Freeze at position C3 (Column 3, Row 3
        self._sheet.freeze_panes = self._freeze

Now we can use different placement of freeze, for each sheet formatting variant. Readability boost, clarity wins, configurable freedom.

Sheet Result

The resulting sheet will look as shown below, cleaner, sharper, and with a frozen header to keepus grounded:

Calc Sheet: Step Three: Output

Terminal Log

The terminal output is your trusty sidekick, letting you track the magic as it happens. Here’s how it looks for this step:

openpyxl: Step Three: Terminal Log

❯ python 03-formatter-tabular.py
01-1980
 * Rearranging Columns
   - Insert Gap: A
   - Insert Gap: H
   - Insert Gap: L
 * Setting Rows Height
 * Formatting Sheet-Wide View
 * Finished

 ...

This logging approach not only helps with debugging, but also gives us a satisfying play-by-play of the script’s handiwork.

And that’s a wrap for step three. our spreadsheets are now gridline-free zones, with rock-solid headers!

Onward to the next layer of formatting wizardry.


What is Next 🤔?

So, we have conquered sheet-wide formatting—frozen panes, hidden gridlines, and all. What’s next? We’re diving deeper into the world of unified configuration, and unlocking the full power of Excel formatting magic.

Buckle up, things are about to get even more interesting.

Feel free to keep reading [ Formatter - Excel Config - Three ], because the nerdy ride is just beginning!