plugins/drawio-diagramming/skills/conditional-formatting/SKILL.md
Data-driven conditional formatting, live data binding, and dynamic diagram updates for draw.io
npx skillsauth add markus41/claude plugins/drawio-diagramming/skills/conditional-formattingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Wrap mxCell in an <object> tag to attach arbitrary key-value metadata to any diagram element:
<object label="Service A" id="2"
service-id="svc-api-gateway"
status="healthy"
latency="45ms"
uptime="99.97%"
owner="platform-team"
environment="production"
region="us-east-1">
<mxCell style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;"
vertex="1" parent="1">
<mxGeometry x="100" y="100" width="120" height="60" as="geometry"/>
</mxCell>
</object>
Key points:
label replaces value as the display textid moves from mxCell to <object>Display property values dynamically inside labels using %property% syntax. Enable with placeholders=1 in the style:
<object label="%name%<br>Status: %status%<br>Latency: %latency%"
id="3" placeholders="1"
name="API Gateway"
status="healthy"
latency="42ms">
<mxCell style="rounded=1;whiteSpace=wrap;html=1;placeholders=1;fillColor=#dae8fc;strokeColor=#6c8ebf;"
vertex="1" parent="1">
<mxGeometry x="100" y="100" width="160" height="80" as="geometry"/>
</mxCell>
</object>
Rendered label:
API Gateway
Status: healthy
Latency: 42ms
| Placeholder | Value |
|-------------|-------|
| %label% | Cell label |
| %id% | Cell ID |
| %date% | Current date |
| %time% | Current time |
| %timestamp% | ISO timestamp |
| %page% | Page number |
| %pagenumber% | Page number (alias) |
| %pagecount% | Total pages |
| %filename% | File name |
Set global variables at the <mxfile> level, accessible by all cells:
<mxfile vars='{"env":"production","version":"2.1.0","region":"us-east-1","last_updated":"2026-03-14"}'>
<diagram id="page1" name="Status Dashboard">
<mxGraphModel>
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<object label="Environment: %env%<br>Version: %version%<br>Region: %region%"
id="header" placeholders="1">
<mxCell style="text;fontSize=14;fontStyle=1;placeholders=1;" vertex="1" parent="1">
<mxGeometry x="20" y="20" width="300" height="60" as="geometry"/>
</mxCell>
</object>
</root>
</mxGraphModel>
</diagram>
</mxfile>
File-level variables are inherited by all cells that use placeholders=1 and do not have a local property with the same name.
The most common pattern: map a property value to fill/stroke colors.
| Status | Fill Color | Stroke Color | Icon |
|--------|-----------|-------------|------|
| healthy / up / ok | #D5E8D4 | #82B366 | Green |
| warning / degraded | #FFF2CC | #D6B656 | Yellow |
| critical / down / error | #F8CECC | #B85450 | Red |
| maintenance / paused | #E1D5E7 | #9673A6 | Purple |
| unknown / pending | #F5F5F5 | #666666 | Gray |
| info / active | #DAE8FC | #6C8EBF | Blue |
Map numeric values to colors using thresholds:
| Metric | Green | Yellow | Red | |--------|-------|--------|-----| | Latency | < 100ms | 100-500ms | > 500ms | | CPU | < 60% | 60-85% | > 85% | | Error Rate | < 1% | 1-5% | > 5% | | Uptime | > 99.9% | 99-99.9% | < 99% | | Disk | < 70% | 70-90% | > 90% |
Show or hide elements based on boolean properties:
| Condition | Visible Style | Hidden Style |
|-----------|--------------|-------------|
| Feature enabled | opacity=100; | opacity=20;dashed=1; |
| Service active | Normal styles | strokeColor=#CCCCCC;fillColor=#F5F5F5;fontColor=#999999; |
| Deprecated | Normal + label | dashed=1;dashPattern=8 4;fontStyle=2; (italic) |
Map state values to different visual representations:
| State | Visual | |-------|--------| | running | Green circle icon | | stopped | Red square icon | | starting | Yellow spinner icon | | terminating | Orange triangle icon |
Full-featured script that reads a data source and updates diagram XML:
#!/usr/bin/env python3
"""
update_diagram.py - Update draw.io diagram with live data.
Usage:
python update_diagram.py --input arch.drawio --output arch-live.drawio --data-url https://api.example.com/health
python update_diagram.py --input arch.drawio --output arch-live.drawio --data-file status.json
"""
import xml.etree.ElementTree as ET
import json
import argparse
import re
import sys
import urllib.request
import urllib.error
import base64
import zlib
from datetime import datetime, timezone
# Status-to-color mapping
STATUS_COLORS = {
'healthy': {'fill': '#D5E8D4', 'stroke': '#82B366'},
'up': {'fill': '#D5E8D4', 'stroke': '#82B366'},
'ok': {'fill': '#D5E8D4', 'stroke': '#82B366'},
'warning': {'fill': '#FFF2CC', 'stroke': '#D6B656'},
'degraded': {'fill': '#FFF2CC', 'stroke': '#D6B656'},
'critical': {'fill': '#F8CECC', 'stroke': '#B85450'},
'down': {'fill': '#F8CECC', 'stroke': '#B85450'},
'error': {'fill': '#F8CECC', 'stroke': '#B85450'},
'maintenance': {'fill': '#E1D5E7', 'stroke': '#9673A6'},
'unknown': {'fill': '#F5F5F5', 'stroke': '#666666'},
}
def update_style_property(style: str, key: str, value: str) -> str:
"""Update a single property in a draw.io style string."""
pattern = re.compile(rf'{re.escape(key)}=[^;]*')
if pattern.search(style):
style = pattern.sub(f'{key}={value}', style)
else:
if not style.endswith(';'):
style += ';'
style += f'{key}={value};'
return style
def get_threshold_color(value: float, thresholds: list) -> dict:
"""
Determine color based on value thresholds.
thresholds: list of (max_value, colors_dict) sorted ascending.
Example: [(100, green), (500, yellow), (float('inf'), red)]
"""
for threshold, colors in thresholds:
if value <= threshold:
return colors
return thresholds[-1][1] # fallback to last
def decompress_diagram(compressed: str) -> str:
"""Decompress a draw.io compressed diagram XML string."""
decoded = base64.b64decode(compressed)
decompressed = zlib.decompress(decoded, -15)
return urllib.parse.unquote(decompressed.decode('utf-8'))
def compress_diagram(xml_str: str) -> str:
"""Compress diagram XML back to draw.io compressed format."""
encoded = urllib.parse.quote(xml_str, safe='')
compressed = zlib.compress(encoded.encode('utf-8'), 9)
# Strip zlib header (first 2 bytes) and checksum (last 4 bytes)
raw = compressed[2:-4]
return base64.b64encode(raw).decode('utf-8')
def update_diagram(input_path: str, output_path: str, data: dict,
compress: bool = False):
"""Update diagram cells based on service data."""
tree = ET.parse(input_path)
root = tree.getroot()
# Handle both mxfile wrapper and bare mxGraphModel
if root.tag == 'mxfile':
for diagram_elem in root.findall('diagram'):
# Check if diagram content is compressed
text = diagram_elem.text
if text and text.strip():
# Compressed format
xml_str = decompress_diagram(text.strip())
model = ET.fromstring(xml_str)
process_model(model, data)
if compress:
diagram_elem.text = compress_diagram(
ET.tostring(model, encoding='unicode'))
else:
diagram_elem.text = None
diagram_elem.append(model)
else:
# Uncompressed format
model = diagram_elem.find('mxGraphModel')
if model is not None:
process_model(model, data)
elif root.tag == 'mxGraphModel':
process_model(root, data)
tree.write(output_path, encoding='UTF-8', xml_declaration=True)
print(f'Updated diagram written to {output_path}')
def process_model(model: ET.Element, data: dict):
"""Process all object elements in an mxGraphModel."""
updated = 0
root_elem = model.find('root')
if root_elem is None:
return
for obj in root_elem.iter('object'):
service_id = obj.get('service-id')
if not service_id or service_id not in data:
continue
service_data = data[service_id]
cell = obj.find('mxCell')
if cell is None:
continue
style = cell.get('style', '')
# Update status color
status = service_data.get('status', 'unknown').lower()
colors = STATUS_COLORS.get(status, STATUS_COLORS['unknown'])
style = update_style_property(style, 'fillColor', colors['fill'])
style = update_style_property(style, 'strokeColor', colors['stroke'])
# Update latency threshold coloring (for edge labels etc.)
latency = service_data.get('latency_ms')
if latency is not None:
latency_colors = get_threshold_color(latency, [
(100, {'fill': '#D5E8D4', 'stroke': '#82B366'}),
(500, {'fill': '#FFF2CC', 'stroke': '#D6B656'}),
(float('inf'), {'fill': '#F8CECC', 'stroke': '#B85450'}),
])
style = update_style_property(style, 'fillColor',
latency_colors['fill'])
style = update_style_property(style, 'strokeColor',
latency_colors['stroke'])
cell.set('style', style)
# Update object properties (for placeholder display)
for key, value in service_data.items():
obj.set(key, str(value))
updated += 1
# Update file-level timestamp
now = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
for obj in root_elem.iter('object'):
if obj.get('id') == 'header' or obj.get('type') == 'header':
obj.set('last_updated', now)
print(f'Updated {updated} service(s)')
def fetch_data(source: str) -> dict:
"""Fetch service data from URL or file."""
if source.startswith('http://') or source.startswith('https://'):
req = urllib.request.Request(source)
req.add_header('Accept', 'application/json')
try:
with urllib.request.urlopen(req, timeout=30) as resp:
return json.loads(resp.read().decode('utf-8'))
except urllib.error.URLError as e:
print(f'Error fetching data: {e}', file=sys.stderr)
sys.exit(1)
else:
with open(source) as f:
return json.load(f)
def main():
parser = argparse.ArgumentParser(
description='Update draw.io diagram with live service data')
parser.add_argument('--input', '-i', required=True,
help='Input .drawio file')
parser.add_argument('--output', '-o', required=True,
help='Output .drawio file')
parser.add_argument('--data-url',
help='URL to fetch JSON service data')
parser.add_argument('--data-file',
help='Path to JSON service data file')
parser.add_argument('--compress', action='store_true',
help='Compress output diagram XML')
args = parser.parse_args()
if not args.data_url and not args.data_file:
parser.error('Either --data-url or --data-file is required')
source = args.data_url or args.data_file
data = fetch_data(source)
# Data format: {"service-id": {"status": "healthy", "latency_ms": 45, ...}}
update_diagram(args.input, args.output, data, compress=args.compress)
if __name__ == '__main__':
main()
The script expects JSON in this format:
{
"svc-api-gateway": {
"status": "healthy",
"latency_ms": 42,
"uptime": "99.97%",
"requests_per_sec": 1250
},
"svc-auth-service": {
"status": "warning",
"latency_ms": 320,
"uptime": "99.85%",
"requests_per_sec": 450
},
"svc-database": {
"status": "critical",
"latency_ms": 1200,
"uptime": "98.50%",
"connections": 95
}
}
#!/usr/bin/env bash
# update-diagram-status.sh - Update draw.io diagram from health API
#
# Usage: ./update-diagram-status.sh <input.drawio> <output.drawio> <health-api-url>
set -euo pipefail
INPUT="${1:?Usage: $0 <input.drawio> <output.drawio> <health-api-url>}"
OUTPUT="${2:?Missing output path}"
API_URL="${3:?Missing health API URL}"
echo "Fetching service status from ${API_URL}..."
DATA=$(curl -sf --max-time 30 "${API_URL}")
if [ -z "$DATA" ]; then
echo "ERROR: Empty response from health API" >&2
exit 1
fi
# Write data to temp file
TMPDATA=$(mktemp)
echo "$DATA" > "$TMPDATA"
echo "Updating diagram..."
python3 "$(dirname "$0")/update_diagram.py" \
--input "$INPUT" \
--output "$OUTPUT" \
--data-file "$TMPDATA"
rm -f "$TMPDATA"
echo "Diagram updated: ${OUTPUT}"
# Export to SVG for web display
if command -v drawio &>/dev/null || command -v xvfb-run &>/dev/null; then
SVG_OUTPUT="${OUTPUT%.drawio}.drawio.svg"
xvfb-run -a drawio --export --format svg --embed-diagram \
--output "$SVG_OUTPUT" "$OUTPUT" 2>/dev/null || true
echo "SVG exported: ${SVG_OUTPUT}"
fi
# Update diagram every 5 minutes
*/5 * * * * /path/to/update-diagram-status.sh /path/to/arch.drawio /path/to/arch-live.drawio https://api.example.com/health >> /var/log/diagram-update.log 2>&1
name: Update Status Diagram
on:
schedule:
- cron: '*/15 * * * *' # Every 15 minutes
workflow_dispatch:
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Update diagram from API
env:
HEALTH_API_URL: ${{ secrets.HEALTH_API_URL }}
run: |
python3 scripts/update_diagram.py \
--input docs/architecture.drawio \
--output docs/architecture.drawio \
--data-url "$HEALTH_API_URL"
- name: Export to SVG
run: |
sudo apt-get install -y xvfb
wget -q https://github.com/jgraph/drawio-desktop/releases/download/v24.7.17/drawio-amd64-24.7.17.deb
sudo dpkg -i drawio-amd64-24.7.17.deb || sudo apt-get install -f -y
xvfb-run -a drawio --export --format svg --embed-diagram \
--output docs/architecture.drawio.svg docs/architecture.drawio
- name: Commit changes
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add docs/architecture.drawio docs/architecture.drawio.svg
git diff --cached --quiet || git commit -m "chore: update status diagram"
git push
Flask-based webhook handler that updates diagrams on service status changes:
#!/usr/bin/env python3
"""
webhook_handler.py - Receive status webhooks and update diagram in real-time.
Listens for POST requests with service status updates and regenerates
the diagram SVG.
Usage:
python webhook_handler.py --diagram docs/architecture.drawio --port 8080
"""
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
import subprocess
import threading
import argparse
# In-memory status store
service_status = {}
diagram_path = ''
lock = threading.Lock()
class WebhookHandler(BaseHTTPRequestHandler):
def do_POST(self):
content_length = int(self.headers.get('Content-Length', 0))
body = self.rfile.read(content_length)
try:
payload = json.loads(body)
except json.JSONDecodeError:
self.send_response(400)
self.end_headers()
self.wfile.write(b'Invalid JSON')
return
# Expected: {"service_id": "svc-api", "status": "healthy", ...}
service_id = payload.get('service_id')
if not service_id:
self.send_response(400)
self.end_headers()
self.wfile.write(b'Missing service_id')
return
with lock:
service_status[service_id] = payload
# Trigger async diagram update
threading.Thread(target=update_diagram_async).start()
self.send_response(200)
self.end_headers()
self.wfile.write(b'OK')
def log_message(self, format, *args):
print(f'[webhook] {args[0]}')
def update_diagram_async():
"""Write status to temp file and run update script."""
with lock:
data = dict(service_status)
import tempfile
with tempfile.NamedTemporaryFile(mode='w', suffix='.json',
delete=False) as f:
json.dump(data, f)
tmp_path = f.name
try:
subprocess.run([
'python3', 'update_diagram.py',
'--input', diagram_path,
'--output', diagram_path,
'--data-file', tmp_path,
], check=True, timeout=30)
print('[update] Diagram updated successfully')
except subprocess.CalledProcessError as e:
print(f'[update] Failed: {e}')
finally:
import os
os.unlink(tmp_path)
def main():
global diagram_path
parser = argparse.ArgumentParser()
parser.add_argument('--diagram', required=True)
parser.add_argument('--port', type=int, default=8080)
args = parser.parse_args()
diagram_path = args.diagram
server = HTTPServer(('0.0.0.0', args.port), WebhookHandler)
print(f'Webhook listener on port {args.port}')
server.serve_forever()
if __name__ == '__main__':
main()
Send status updates via curl:
curl -X POST http://localhost:8080/ \
-H "Content-Type: application/json" \
-d '{"service_id": "svc-api-gateway", "status": "warning", "latency_ms": 350}'
Create a grid of service status cards:
<mxGraphModel>
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<!-- Title -->
<mxCell id="title" value="Service Status Dashboard" style="text;fontSize=20;fontStyle=1;align=center;" vertex="1" parent="1">
<mxGeometry x="50" y="10" width="500" height="30" as="geometry"/>
</mxCell>
<!-- Timestamp -->
<object label="Last updated: %last_updated%" id="ts" placeholders="1" last_updated="2026-03-14T12:00:00Z">
<mxCell style="text;fontSize=10;fontColor=#999999;align=center;placeholders=1;" vertex="1" parent="1">
<mxGeometry x="50" y="40" width="500" height="20" as="geometry"/>
</mxCell>
</object>
<!-- Service Cards Row 1 -->
<object label="<b>%name%</b><br>%status%<br>%latency%" id="svc1" placeholders="1"
service-id="svc-api-gateway" name="API Gateway" status="healthy" latency="42ms">
<mxCell style="rounded=1;whiteSpace=wrap;html=1;placeholders=1;fillColor=#D5E8D4;strokeColor=#82B366;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="50" y="80" width="150" height="80" as="geometry"/>
</mxCell>
</object>
<object label="<b>%name%</b><br>%status%<br>%latency%" id="svc2" placeholders="1"
service-id="svc-auth" name="Auth Service" status="warning" latency="320ms">
<mxCell style="rounded=1;whiteSpace=wrap;html=1;placeholders=1;fillColor=#FFF2CC;strokeColor=#D6B656;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="220" y="80" width="150" height="80" as="geometry"/>
</mxCell>
</object>
<object label="<b>%name%</b><br>%status%<br>%latency%" id="svc3" placeholders="1"
service-id="svc-database" name="Database" status="critical" latency="1200ms">
<mxCell style="rounded=1;whiteSpace=wrap;html=1;placeholders=1;fillColor=#F8CECC;strokeColor=#B85450;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="390" y="80" width="150" height="80" as="geometry"/>
</mxCell>
</object>
</root>
</mxGraphModel>
Map numeric values to a color gradient for heat map effects:
def value_to_heatmap_color(value: float, min_val: float, max_val: float) -> str:
"""
Map a value to a green-yellow-red gradient.
Low values = green, mid = yellow, high = red.
"""
if max_val == min_val:
return '#D5E8D4'
ratio = (value - min_val) / (max_val - min_val)
ratio = max(0.0, min(1.0, ratio))
if ratio < 0.5:
# Green to Yellow
r = int(213 + (255 - 213) * (ratio * 2))
g = int(232 + (242 - 232) * (ratio * 2))
b = int(212 + (204 - 212) * (ratio * 2))
else:
# Yellow to Red
t = (ratio - 0.5) * 2
r = int(255 - (255 - 248) * t)
g = int(242 - (242 - 206) * t)
b = int(204 - (204 - 204) * t)
return f'#{r:02X}{g:02X}{b:02X}'
Apply to cells for CPU usage, latency, error rates, or any numeric metric to create visual heat maps across your architecture diagram.
Structure your diagram with these zones:
Standardize across all service objects:
| Property | Type | Example |
|----------|------|---------|
| service-id | string | svc-api-gateway |
| name | string | API Gateway |
| status | enum | healthy, warning, critical |
| latency | string | 42ms |
| uptime | string | 99.97% |
| version | string | 2.1.0 |
| owner | string | platform-team |
| environment | string | production |
| region | string | us-east-1 |
| last-check | string | ISO timestamp |
development
Enhanced plan-authoring skill with Pre-Writing context gathering, task metadata, non-TDD templates, Red Flags, telemetry, and an automated plan linter. Use when you have a spec or requirements for a multi-step task, before touching code.
tools
Documentation intelligence engine with graph-based API docs, algorithm library, and drift detection
tools
Ultraplan cloud planning — kick off a plan in the cloud from your terminal, review and revise in the browser, then execute remotely or send back to CLI
tools
--- name: mcp description: Configure MCP servers for Claude Code — stdio vs HTTP, authentication, Tools/Resources/Prompts distinction, channels (CI webhook, mobile relay, Discord bridge, fakechat), and cost of always-loaded tools. Use this skill whenever adding an MCP server, debugging connection issues, choosing between MCP Tools vs Prompts vs Resources, installing channel servers, or managing .mcp.json. Triggers on: "MCP server", "mcp config", "add Obsidian MCP", "install context7", "channels"