Flexible Styling in Visio – or How I Learned to Stop Worrying and Love the Style

Started by Yacine, May 25, 2025, 06:14:43 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Yacine

I thought it's time again to drop something here to "lure you out of your caves".

Here's something I've been needing for a long time — but never got around to finishing until now.

It's about coloring. Styling. Formatting.

My juvenile over-enthusiasm and occasional arrogance made me revisit a flowsheet system I built up 20 years ago. I wanted to improve it, modernize it, make it better.

But apparently, I must have stepped on too many toes. Instead of standing ovations, I got nagging about colors and formats.

So there I was — with basically all the work done — stuck on those bloody colors. And if that weren't enough, some of my colleagues are colorblind. So flexibility became key: the system had to support switching between palettes and formats on the fly.

Here's how I did it — step by step.

---

1. Define the formats by example

I drew a bunch of lines representing all the formats I needed — hot water, product, steam, etc. In total, I defined 16 styles.

And I labeled them accordingly. Wherever possible, I used a short style code and separated it from the full name with a hyphen `-`.

You cannot view this attachment.

This allowed me to automate everything based on the prefix later.

---

2. Create the styles automatically from the examples

I wrote a macro that iterates through my selected shapes (the example lines), reads out their properties like:

- LineColor
- LinePattern
- LineWeight
- Rounding

Initialize the Visio variables:
from visiopy import vInit
vInit(g=globals())

Then collect the properties:
def collect_shape_styles(selection, cell_names):
    styles = []
    for i, shp in enumerate(list(selection)):
        style_data = {}
        # Versuche, den Namen aus dem Text des Shapes zu holen
        try:
            name = shp.Text.strip().split('-')[0]
        except:
            name = f"Shape_{i}"

        style_data["style"] = name

        for cell_name in cell_names:
            cell = shp.Cells(cell_name)
            style_data[cell_name] = {'FormulaU':cell.FormulaU, 'ResultIU': cell.ResultIU}
        styles.append(style_data)
    return styles

cell_names = ['LineColor', 'LinePattern', 'LineWeight', 'Rounding']
styles = collect_shape_styles(vWin.Selection, cell_names)
styles

Result:
[{'style': 'PR ',
  'LineColor': {'FormulaU': 'THEMEGUARD(RGB(255,0,0))', 'ResultIU': 52.0},
  'LinePattern': {'FormulaU': '1', 'ResultIU': 1.0},
  'LineWeight': {'FormulaU': '4 pt', 'ResultIU': 0.05555555555555555},
  'Rounding': {'FormulaU': '2 mm', 'ResultIU': 0.07874015748031496}},
 {'style': 'I ',
  'LineColor': {'FormulaU': 'THEMEGUARD(RGB(255,96,0))', 'ResultIU': 158.0},
  'LinePattern': {'FormulaU': '1', 'ResultIU': 1.0},
  'LineWeight': {'FormulaU': '2.5 pt', 'ResultIU': 0.034722222222222224},
  'Rounding': {'FormulaU': '0 mm', 'ResultIU': 0.0}}, ...


Then another macro creates new Visio Styles from these values.

But here's the catch!

Some of those styles had very big line weights and corner roundings. Applying them directly to small shapes like valves or pumps would have completely messed them up.

At first, I thought about formatting each shape manually — just applying the LineColor via code. 
But honestly? That's exactly what I wanted to avoid. The whole point was to apply a Style once, and then let users modify it visually — no macros required.

So I went with the better idea:

---

3. Two styles per category: 1D and 2D

For every category (`PR`, `CW`, ...), I created two styles:

Style NameApplies toWhat it contains
FS_1_PR1D Shapes (lines)Full format: color, pattern, weight, rounding
FS_2_PR2D Shapes (valves, devices)Only the color

How?

- For the 1D styles, I kept everything: color, pattern, weight, and rounding.
- For the 2D styles, I started with a generic base style (`2D_FS`) and derived specific ones from it — changing only the `LineColor`.

def create_visio_style(doc, style_name, base_on="Normal", cell_values=None):
    try:
        new_style = doc.Styles.Add(style_name, base_on, 0, 1, 0)
        if cell_values:
            for cell_name, value in cell_values.items():
                if value is not None:
                    try:
                        new_style.Cells(cell_name).FormulaU = str(value)
                    except Exception as e:
                        print(f"Fehler beim Setzen von {cell_name} für Style '{style_name}': {e}")

        print(f"✅ Style '{style_name}' wurde erfolgreich erstellt.")
    except Exception as e:
        print(f"❌ Fehler beim Erstellen des Styles '{style_name}': {e}")

line_cells_1 = ['LineColor', 'LinePattern', 'LineWeight', 'Rounding']
line_cells_2 = ['LineColor']

base_on_1 = "Normal"
base_on_2 = "2D_FS"

for style_data in styles:
    original_name = style_data["style"].strip()
    new_style_name_1 = f"FS_1_{original_name}"
    new_style_name_2 = f"FS_2_{original_name}"

    # Extrahiere Werte für 1D-Style (alle Zellen)
    cell_values_1 = {
        cell_name: style_data[cell_name]['FormulaU']
        for cell_name in line_cells_1
        if cell_name in style_data
    }

    # Extrahiere Werte für 2D-Style (nur Farbe)
    cell_values_2 = {
        'LineColor': style_data['LineColor']['FormulaU']
    }

    # Erstelle 1D-Style
    create_visio_style(
        doc=vApp.ActiveDocument,
        style_name=new_style_name_1,
        base_on=base_on_1,
        cell_values=cell_values_1
    )

    # Erstelle 2D-Style
    create_visio_style(
        doc=vApp.ActiveDocument,
        style_name=new_style_name_2,
        base_on=base_on_2,
        cell_values=cell_values_2
    )       


Paff, done!

---

4. Apply the styles dynamically

I looped through all shapes in the drawing and applied either the 1D or 2D style based on the shape type:

def assign_style(shape, style):
    for shp in shape.Shapes:
        shp.LineStyle = style
        assign_style(shp, style)

for shp in vApp.ActivePage.Shapes:
    if shp.CellExists(cell_name, False):
        kat = shp.Cells(cell_name).ResultStr('')
        if kat:
            if shp.OneD:
                try:
                    shp.LineStyle = "FS_1_" + kat
                except:
                    pass
            else:
                try:
                    assign_style(shp, "FS_2_" + kat)
                except:
                    pass
Bonus: If a shape was a group, I recursively assigned the style to its sub-shapes as well.

Before - After - comparison:

You cannot view this attachment.

---

Why this matters

Yes, I built this for flowsheets — but the technique is universal.

You can use it wherever you need consistent formatting across different kinds of shapes — network diagrams, org charts, UMLs, P&IDs... You name it.

The real power lies in this principle:

Let a few example shapes define the format. Automatically create styles. Then apply them dynamically.

This way, even complex documents stay clean, editable, and flexible.

---

Final thoughts

I hope this article was entertaining — and maybe even useful to someone out there.

Still alive. Still drawing. 
And now, styling smarter too.

Cheers, 
Yacine
Yacine

Browser ID: smf (possibly_robot)
Templates: 4: index (default), Display (default), GenericControls (default), GenericControls (default).
Sub templates: 6: init, html_above, body_above, main, body_below, html_below.
Language files: 4: index+Modifications.english (default), Post.english (default), Editor.english (default), Drafts.english (default).
Style sheets: 4: index.css, attachments.css, jquery.sceditor.css, responsive.css.
Hooks called: 203 (show)
Files included: 32 - 1207KB. (show)
Memory used: 1032KB.
Tokens: post-login.
Cache hits: 14: 0.00177s for 26,588 bytes (show)
Cache misses: 3: (show)
Queries used: 19.

[Show Queries]