Markdown Diagram Workflows and Integration: Complete Guide for Visual Documentation and Automated Diagram Generation
Advanced Markdown diagram workflows and integration techniques enable sophisticated visual documentation that combines the simplicity of text-based markup with powerful automated diagram generation, version control compatibility, and cross-platform rendering capabilities. By mastering diagram-as-code principles, automated workflow integration, and multi-format export strategies, technical teams can create comprehensive visual documentation systems that maintain consistency, enable collaboration, and scale effectively across complex project architectures.
Why Master Diagram Workflows and Integration?
Professional diagram workflow integration provides essential benefits for modern documentation systems:
- Version Control Compatibility: Text-based diagrams integrate seamlessly with Git workflows and collaborative editing
- Automated Generation: CI/CD pipelines can automatically generate and update diagrams from code changes
- Platform Consistency: Single source diagrams render consistently across documentation platforms and export formats
- Collaborative Editing: Team members can review, modify, and improve diagrams using familiar code review processes
- Maintenance Efficiency: Automated diagram updates reduce manual maintenance overhead and improve accuracy
Foundation Diagram Technologies
Mermaid Integration Strategies
Comprehensive Mermaid diagram implementation for modern documentation workflows:
# Mermaid Diagram Integration Examples
## Basic Flowchart Syntax
```mermaid
graph TD
A[Start] --> B{Decision Point}
B -->|Yes| C[Process A]
B -->|No| D[Process B]
C --> E[End]
D --> E
classDef startEnd fill:#e1f5fe,stroke:#01579b,stroke-width:2px
classDef process fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
classDef decision fill:#fff3e0,stroke:#e65100,stroke-width:2px
class A,E startEnd
class C,D process
class B decision
```
## Sequence Diagrams for API Documentation
```mermaid
sequenceDiagram
participant Client
participant Gateway as API Gateway
participant Auth as Auth Service
participant DB as Database
Client->>Gateway: POST /api/users
Gateway->>Auth: Validate Token
Auth-->>Gateway: Token Valid
Gateway->>DB: Create User
DB-->>Gateway: User Created
Gateway-->>Client: 201 Created
Note over Client,DB: User creation flow with authentication
```
## Git Workflow Diagrams
```mermaid
gitgraph
commit id: "Initial commit"
branch develop
checkout develop
commit id: "Add feature A"
commit id: "Add feature B"
checkout main
merge develop
commit id: "Release v1.0"
branch hotfix
checkout hotfix
commit id: "Fix critical bug"
checkout main
merge hotfix
commit id: "Release v1.0.1"
```
## Architecture Diagrams
```mermaid
graph TB
subgraph "Frontend"
Web[Web App]
Mobile[Mobile App]
end
subgraph "API Layer"
Gateway[API Gateway]
Auth[Auth Service]
Users[User Service]
Orders[Order Service]
end
subgraph "Data Layer"
UserDB[(User DB)]
OrderDB[(Order DB)]
Cache[(Redis Cache)]
end
Web --> Gateway
Mobile --> Gateway
Gateway --> Auth
Gateway --> Users
Gateway --> Orders
Users --> UserDB
Orders --> OrderDB
Auth --> Cache
Users --> Cache
PlantUML Advanced Integration
Professional PlantUML workflows for complex system documentation:
# PlantUML Advanced Diagram Patterns
## Component Architecture Diagrams
```plantuml
@startuml
!define RECTANGLE class
package "Frontend Layer" {
[React App] as react
[Vue Admin] as vue
[Mobile App] as mobile
}
package "API Gateway" {
[Kong Gateway] as gateway
[Rate Limiter] as limiter
[Load Balancer] as lb
}
package "Microservices" {
[User Service] as user
[Order Service] as order
[Payment Service] as payment
[Notification Service] as notify
}
package "Data Stores" {
database "PostgreSQL" as postgres
database "MongoDB" as mongo
database "Redis" as redis
}
react --> gateway
vue --> gateway
mobile --> gateway
gateway --> limiter
limiter --> lb
lb --> user
lb --> order
lb --> payment
user --> postgres
order --> mongo
payment --> postgres
notify --> redis
note right of gateway : All API traffic flows\nthrough the gateway
note bottom of postgres : Primary relational\ndata storage
@enduml
```
## Class Diagram with Relationships
```plantuml
@startuml
class User {
-id: UUID
-email: String
-passwordHash: String
-createdAt: Date
+authenticate(password: String): Boolean
+updateProfile(data: UserData): void
+getOrders(): List<Order>
}
class Order {
-id: UUID
-userId: UUID
-status: OrderStatus
-items: List<OrderItem>
-total: BigDecimal
+addItem(item: OrderItem): void
+calculateTotal(): BigDecimal
+updateStatus(status: OrderStatus): void
}
class OrderItem {
-productId: UUID
-quantity: Integer
-unitPrice: BigDecimal
+getSubtotal(): BigDecimal
}
enum OrderStatus {
PENDING
CONFIRMED
SHIPPED
DELIVERED
CANCELLED
}
User ||--o{ Order : places
Order ||--o{ OrderItem : contains
Order ||--|| OrderStatus : has
@enduml
```
## Deployment Diagram
```plantuml
@startuml
!define AWSPUML https://raw.githubusercontent.com/awslabs/aws-icons-for-plantuml/v14.0/dist
!include AWSPUML/AWSCommon.puml
!include AWSPUML/ApplicationIntegration/APIGateway.puml
!include AWSPUML/Compute/ECS.puml
!include AWSPUML/Database/RDS.puml
!include AWSPUML/Storage/S3.puml
node "AWS Cloud" {
APIGateway(gateway, "API Gateway", "")
ECS(ecs, "ECS Cluster", "")
RDS(database, "RDS PostgreSQL", "")
S3(storage, "S3 Bucket", "")
gateway --> ecs : Routes requests
ecs --> database : Queries data
ecs --> storage : Stores files
}
actor User
User --> gateway : HTTPS requests
@enduml
```
D2 Modern Diagram Integration
Next-generation diagram syntax with D2 for modern documentation systems:
# D2 Advanced Diagram Examples
## System Architecture with D2
```d2
# Modern system architecture diagram
direction: right
users: Users {
shape: person
style.fill: "#e1f5fe"
}
frontend: Frontend {
web: Web App {
shape: rectangle
style.fill: "#f3e5f5"
}
mobile: Mobile App {
shape: rectangle
style.fill: "#f3e5f5"
}
}
api: API Layer {
gateway: API Gateway {
style.fill: "#fff3e0"
}
auth: Auth Service {
style.fill: "#fff3e0"
}
services: Microservices {
user: User Service
order: Order Service
payment: Payment Service
}
}
data: Data Layer {
postgres: PostgreSQL {
shape: cylinder
style.fill: "#e8f5e8"
}
redis: Redis Cache {
shape: cylinder
style.fill: "#e8f5e8"
}
}
# Connections
users -> frontend.web
users -> frontend.mobile
frontend.web -> api.gateway
frontend.mobile -> api.gateway
api.gateway -> api.auth
api.gateway -> api.services.user
api.gateway -> api.services.order
api.gateway -> api.services.payment
api.services.user -> data.postgres
api.services.order -> data.postgres
api.services.payment -> data.postgres
api.auth -> data.redis
```
## Data Flow Diagrams
```d2
# Data processing pipeline
direction: down
source: Data Sources {
api: REST APIs
files: CSV Files
streams: Event Streams
}
ingestion: Data Ingestion {
collector: Data Collector {
style.fill: "#e3f2fd"
}
validator: Data Validator {
style.fill: "#e3f2fd"
}
queue: Message Queue {
shape: queue
style.fill: "#fff3e0"
}
}
processing: Data Processing {
etl: ETL Pipeline {
style.fill: "#f3e5f5"
}
ml: ML Pipeline {
style.fill: "#f3e5f5"
}
}
storage: Data Storage {
warehouse: Data Warehouse {
shape: cylinder
style.fill: "#e8f5e8"
}
lake: Data Lake {
shape: cylinder
style.fill: "#e8f5e8"
}
}
# Data flow connections
source.api -> ingestion.collector: HTTP
source.files -> ingestion.collector: File upload
source.streams -> ingestion.collector: Kafka
ingestion.collector -> ingestion.validator
ingestion.validator -> ingestion.queue: Valid data
ingestion.queue -> processing.etl
ingestion.queue -> processing.ml
processing.etl -> storage.warehouse: Structured data
processing.ml -> storage.lake: Training data
```
Automated Workflow Integration
CI/CD Pipeline Diagram Generation
Implementing automated diagram generation in continuous integration workflows:
# diagram_generator.py - Automated diagram generation system
import os
import subprocess
import hashlib
import json
from typing import Dict, List, Optional, Tuple
from pathlib import Path
import logging
from dataclasses import dataclass
from enum import Enum
class DiagramFormat(Enum):
MERMAID = "mermaid"
PLANTUML = "plantuml"
D2 = "d2"
GRAPHVIZ = "dot"
@dataclass
class DiagramConfig:
"""Configuration for diagram generation"""
source_path: str
output_path: str
format: DiagramFormat
export_formats: List[str] = None # svg, png, pdf
theme: Optional[str] = None
width: Optional[int] = None
height: Optional[int] = None
def __post_init__(self):
if self.export_formats is None:
self.export_formats = ["svg", "png"]
class DiagramGenerator:
def __init__(self, config_path: str = "diagram-config.json"):
self.config_path = config_path
self.logger = logging.getLogger(__name__)
self.diagram_cache = {}
self.load_configuration()
def load_configuration(self):
"""Load diagram generation configuration"""
try:
with open(self.config_path, 'r') as f:
config_data = json.load(f)
self.base_config = {
'output_dir': config_data.get('output_dir', 'docs/diagrams'),
'cache_dir': config_data.get('cache_dir', '.diagram-cache'),
'themes': config_data.get('themes', {}),
'quality': config_data.get('quality', 'high'),
'parallel_generation': config_data.get('parallel_generation', True)
}
except FileNotFoundError:
self.logger.warning(f"Config file {self.config_path} not found, using defaults")
self.base_config = {
'output_dir': 'docs/diagrams',
'cache_dir': '.diagram-cache',
'themes': {},
'quality': 'high',
'parallel_generation': True
}
def discover_diagrams(self, source_dir: str) -> List[DiagramConfig]:
"""Discover diagram files in source directory"""
diagram_configs = []
# Define file patterns for different diagram types
patterns = {
DiagramFormat.MERMAID: ['*.mmd', '*.mermaid'],
DiagramFormat.PLANTUML: ['*.puml', '*.plantuml', '*.pu'],
DiagramFormat.D2: ['*.d2'],
DiagramFormat.GRAPHVIZ: ['*.dot', '*.gv']
}
source_path = Path(source_dir)
for diagram_format, file_patterns in patterns.items():
for pattern in file_patterns:
for file_path in source_path.rglob(pattern):
# Generate output path
relative_path = file_path.relative_to(source_path)
output_dir = Path(self.base_config['output_dir']) / relative_path.parent
config = DiagramConfig(
source_path=str(file_path),
output_path=str(output_dir / file_path.stem),
format=diagram_format,
export_formats=["svg", "png", "pdf"]
)
diagram_configs.append(config)
return diagram_configs
def calculate_diagram_hash(self, source_file: str) -> str:
"""Calculate hash of diagram source for caching"""
with open(source_file, 'rb') as f:
content = f.read()
return hashlib.md5(content).hexdigest()
def is_diagram_cached(self, config: DiagramConfig) -> bool:
"""Check if diagram output is up to date"""
cache_dir = Path(self.base_config['cache_dir'])
cache_file = cache_dir / f"{Path(config.source_path).stem}.json"
if not cache_file.exists():
return False
try:
with open(cache_file, 'r') as f:
cache_data = json.load(f)
current_hash = self.calculate_diagram_hash(config.source_path)
return cache_data.get('source_hash') == current_hash
except (json.JSONDecodeError, KeyError):
return False
def update_diagram_cache(self, config: DiagramConfig, output_files: List[str]):
"""Update cache with diagram generation information"""
cache_dir = Path(self.base_config['cache_dir'])
cache_dir.mkdir(exist_ok=True)
cache_file = cache_dir / f"{Path(config.source_path).stem}.json"
cache_data = {
'source_path': config.source_path,
'source_hash': self.calculate_diagram_hash(config.source_path),
'output_files': output_files,
'generated_at': str(subprocess.check_output(['date'], text=True).strip()),
'format': config.format.value
}
with open(cache_file, 'w') as f:
json.dump(cache_data, f, indent=2)
def generate_mermaid_diagram(self, config: DiagramConfig) -> List[str]:
"""Generate Mermaid diagram using mermaid-cli"""
output_files = []
# Ensure output directory exists
Path(config.output_path).parent.mkdir(parents=True, exist_ok=True)
for export_format in config.export_formats:
output_file = f"{config.output_path}.{export_format}"
cmd = [
'mmdc', # mermaid-cli command
'-i', config.source_path,
'-o', output_file,
'-t', config.theme or 'default'
]
if config.width:
cmd.extend(['-w', str(config.width)])
if config.height:
cmd.extend(['-H', str(config.height)])
try:
subprocess.run(cmd, check=True, capture_output=True, text=True)
output_files.append(output_file)
self.logger.info(f"Generated Mermaid diagram: {output_file}")
except subprocess.CalledProcessError as e:
self.logger.error(f"Failed to generate Mermaid diagram: {e.stderr}")
return output_files
def generate_plantuml_diagram(self, config: DiagramConfig) -> List[str]:
"""Generate PlantUML diagram"""
output_files = []
# Ensure output directory exists
Path(config.output_path).parent.mkdir(parents=True, exist_ok=True)
for export_format in config.export_formats:
if export_format == 'pdf':
format_flag = '-tpdf'
elif export_format == 'png':
format_flag = '-tpng'
else: # svg
format_flag = '-tsvg'
output_file = f"{config.output_path}.{export_format}"
cmd = [
'plantuml',
format_flag,
'-o', str(Path(config.output_path).parent),
config.source_path
]
try:
subprocess.run(cmd, check=True, capture_output=True, text=True)
output_files.append(output_file)
self.logger.info(f"Generated PlantUML diagram: {output_file}")
except subprocess.CalledProcessError as e:
self.logger.error(f"Failed to generate PlantUML diagram: {e.stderr}")
return output_files
def generate_d2_diagram(self, config: DiagramConfig) -> List[str]:
"""Generate D2 diagram"""
output_files = []
# Ensure output directory exists
Path(config.output_path).parent.mkdir(parents=True, exist_ok=True)
for export_format in config.export_formats:
output_file = f"{config.output_path}.{export_format}"
cmd = [
'd2',
config.source_path,
output_file
]
if config.theme:
cmd.extend(['--theme', config.theme])
try:
subprocess.run(cmd, check=True, capture_output=True, text=True)
output_files.append(output_file)
self.logger.info(f"Generated D2 diagram: {output_file}")
except subprocess.CalledProcessError as e:
self.logger.error(f"Failed to generate D2 diagram: {e.stderr}")
return output_files
def generate_diagram(self, config: DiagramConfig, force: bool = False) -> List[str]:
"""Generate diagram based on configuration"""
# Check cache unless force regeneration
if not force and self.is_diagram_cached(config):
self.logger.info(f"Diagram {config.source_path} is up to date, skipping")
return []
# Generate based on format
if config.format == DiagramFormat.MERMAID:
output_files = self.generate_mermaid_diagram(config)
elif config.format == DiagramFormat.PLANTUML:
output_files = self.generate_plantuml_diagram(config)
elif config.format == DiagramFormat.D2:
output_files = self.generate_d2_diagram(config)
else:
self.logger.error(f"Unsupported diagram format: {config.format}")
return []
# Update cache
if output_files:
self.update_diagram_cache(config, output_files)
return output_files
def generate_all_diagrams(self, source_dir: str, force: bool = False) -> Dict[str, List[str]]:
"""Generate all diagrams in source directory"""
diagram_configs = self.discover_diagrams(source_dir)
results = {}
self.logger.info(f"Found {len(diagram_configs)} diagrams to process")
for config in diagram_configs:
self.logger.info(f"Processing diagram: {config.source_path}")
output_files = self.generate_diagram(config, force)
results[config.source_path] = output_files
return results
def cleanup_old_diagrams(self, source_dir: str):
"""Remove generated diagrams that no longer have source files"""
output_dir = Path(self.base_config['output_dir'])
cache_dir = Path(self.base_config['cache_dir'])
if not cache_dir.exists():
return
# Get current source files
current_configs = self.discover_diagrams(source_dir)
current_sources = {config.source_path for config in current_configs}
# Check cached diagrams
for cache_file in cache_dir.glob('*.json'):
try:
with open(cache_file, 'r') as f:
cache_data = json.load(f)
source_path = cache_data.get('source_path')
# If source no longer exists, clean up outputs
if source_path and source_path not in current_sources:
output_files = cache_data.get('output_files', [])
for output_file in output_files:
output_path = Path(output_file)
if output_path.exists():
output_path.unlink()
self.logger.info(f"Removed orphaned diagram: {output_file}")
# Remove cache file
cache_file.unlink()
self.logger.info(f"Removed cache file: {cache_file}")
except (json.JSONDecodeError, KeyError) as e:
self.logger.error(f"Error reading cache file {cache_file}: {e}")
# CLI interface for CI/CD integration
def main():
import argparse
parser = argparse.ArgumentParser(description='Generate diagrams from source files')
parser.add_argument('source_dir', help='Source directory containing diagram files')
parser.add_argument('--force', '-f', action='store_true', help='Force regeneration of all diagrams')
parser.add_argument('--cleanup', '-c', action='store_true', help='Clean up orphaned diagram files')
parser.add_argument('--config', default='diagram-config.json', help='Configuration file path')
parser.add_argument('--verbose', '-v', action='store_true', help='Verbose logging')
args = parser.parse_args()
# Setup logging
logging.basicConfig(
level=logging.INFO if args.verbose else logging.WARNING,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# Generate diagrams
generator = DiagramGenerator(args.config)
if args.cleanup:
generator.cleanup_old_diagrams(args.source_dir)
results = generator.generate_all_diagrams(args.source_dir, args.force)
# Print summary
total_generated = sum(len(files) for files in results.values() if files)
print(f"Generated {total_generated} diagram files")
# Exit with error code if any diagrams failed
failed_count = sum(1 for files in results.values() if not files)
if failed_count > 0:
print(f"Warning: {failed_count} diagrams failed to generate")
exit(1)
if __name__ == "__main__":
main()
GitHub Actions Integration
Complete GitHub Actions workflow for automated diagram generation:
# .github/workflows/diagram-generation.yml
name: Generate and Update Diagrams
on:
push:
paths:
- 'docs/diagrams/**'
- 'src/**/*.puml'
- 'src/**/*.mmd'
- 'src/**/*.d2'
- '.github/workflows/diagram-generation.yml'
pull_request:
paths:
- 'docs/diagrams/**'
- 'src/**/*.puml'
- 'src/**/*.mmd'
- 'src/**/*.d2'
jobs:
generate-diagrams:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- name: Setup Node.js for Mermaid CLI
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install Mermaid CLI
run: npm install -g @mermaid-js/mermaid-cli
- name: Setup Java for PlantUML
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '11'
- name: Install PlantUML
run: |
wget https://github.com/plantuml/plantuml/releases/latest/download/plantuml.jar
sudo mv plantuml.jar /opt/plantuml.jar
echo '#!/bin/bash' | sudo tee /usr/local/bin/plantuml
echo 'java -jar /opt/plantuml.jar "$@"' | sudo tee -a /usr/local/bin/plantuml
sudo chmod +x /usr/local/bin/plantuml
- name: Install D2
run: |
curl -fsSL https://d2lang.com/install.sh | sh -s --
- name: Setup Python for diagram generator
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install Python dependencies
run: |
pip install -r requirements-diagrams.txt
- name: Generate diagrams
run: |
python scripts/diagram_generator.py docs/diagrams --verbose
- name: Check for diagram changes
id: diagram-changes
run: |
if git diff --quiet; then
echo "changes=false" >> $GITHUB_OUTPUT
else
echo "changes=true" >> $GITHUB_OUTPUT
fi
- name: Commit updated diagrams
if: steps.diagram-changes.outputs.changes == 'true' && github.event_name == 'push'
run: |
git config --local user.email "[email protected]"
git config --local user.name "GitHub Action"
git add docs/diagrams/
git commit -m "Auto-update diagrams from source changes
π€ Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <[email protected]>"
git push
- name: Upload diagram artifacts
if: steps.diagram-changes.outputs.changes == 'true'
uses: actions/upload-artifact@v4
with:
name: generated-diagrams
path: docs/diagrams/
retention-days: 30
validate-diagrams:
runs-on: ubuntu-latest
needs: generate-diagrams
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download diagram artifacts
uses: actions/download-artifact@v4
with:
name: generated-diagrams
path: docs/diagrams/
- name: Validate diagram quality
run: |
python scripts/validate_diagrams.py docs/diagrams/
- name: Check file sizes
run: |
find docs/diagrams/ -name "*.svg" -size +1M -exec echo "Warning: Large SVG file {}" \;
find docs/diagrams/ -name "*.png" -size +5M -exec echo "Warning: Large PNG file {}" \;
- name: Generate diagram index
run: |
python scripts/generate_diagram_index.py docs/diagrams/ > DIAGRAMS.md
- name: Comment on PR with diagram changes
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const path = require('path');
// Read diagram index
const diagramIndex = fs.readFileSync('DIAGRAMS.md', 'utf8');
// Create comment body
const body = `## π Diagram Changes
This PR includes diagram updates. Here's a summary of the generated diagrams:
${diagramIndex}
π€ Auto-generated by diagram workflow`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});
Version Control Integration Strategies
Git Hooks for Diagram Synchronization
Implementing Git hooks to maintain diagram consistency:
#!/bin/bash
# .git/hooks/pre-commit - Ensure diagrams are up to date before commit
set -e
echo "Checking diagram consistency..."
# Find all diagram source files that have been modified
MODIFIED_DIAGRAMS=$(git diff --cached --name-only | grep -E '\.(mmd|puml|d2)$' || true)
if [ -n "$MODIFIED_DIAGRAMS" ]; then
echo "Found modified diagram sources:"
echo "$MODIFIED_DIAGRAMS"
# Generate diagrams for modified sources
python scripts/diagram_generator.py docs/diagrams --force
# Check if any output files were generated/updated
GENERATED_FILES=$(git status --porcelain | grep -E '\.(svg|png|pdf)$' | awk '{print $2}' || true)
if [ -n "$GENERATED_FILES" ]; then
echo "Generated diagram outputs:"
echo "$GENERATED_FILES"
# Stage the generated files
echo "$GENERATED_FILES" | xargs git add
echo "β
Diagrams updated and staged for commit"
else
echo "β
No diagram updates needed"
fi
else
echo "β
No diagram sources modified"
fi
# Validate all diagrams still compile correctly
echo "Validating diagram compilation..."
python scripts/validate_diagrams.py docs/diagrams/
echo "β
Pre-commit diagram checks passed"
Branch-Specific Diagram Management
Advanced Git workflow integration for diagram management across branches:
# diagram_branch_manager.py - Manage diagrams across Git branches
import subprocess
import json
import os
from pathlib import Path
from typing import Dict, List, Set
import logging
class DiagramBranchManager:
def __init__(self, repo_path: str = "."):
self.repo_path = Path(repo_path)
self.logger = logging.getLogger(__name__)
def get_current_branch(self) -> str:
"""Get the current Git branch name"""
result = subprocess.run(
['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
cwd=self.repo_path,
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()
def get_modified_diagrams(self, base_branch: str = "main") -> Set[str]:
"""Get diagram source files modified compared to base branch"""
result = subprocess.run(
['git', 'diff', f'{base_branch}...HEAD', '--name-only'],
cwd=self.repo_path,
capture_output=True,
text=True,
check=True
)
modified_files = result.stdout.strip().split('\n')
diagram_files = set()
for file_path in modified_files:
if any(file_path.endswith(ext) for ext in ['.mmd', '.puml', '.d2', '.dot']):
diagram_files.add(file_path)
return diagram_files
def get_diagram_dependencies(self, diagram_file: str) -> List[str]:
"""Get files that depend on a specific diagram"""
dependencies = []
# Search for references to the diagram in markdown files
try:
result = subprocess.run(
['git', 'grep', '-l', Path(diagram_file).stem],
cwd=self.repo_path,
capture_output=True,
text=True
)
if result.returncode == 0:
dependencies = [f.strip() for f in result.stdout.split('\n') if f.strip()]
except subprocess.CalledProcessError:
pass # No references found
return dependencies
def create_diagram_manifest(self, branch_name: str) -> Dict:
"""Create a manifest of diagrams for the current branch"""
manifest = {
'branch': branch_name,
'timestamp': subprocess.check_output(['date', '-Iseconds'], text=True).strip(),
'diagrams': {},
'dependencies': {}
}
# Find all diagram source files
diagram_patterns = ['**/*.mmd', '**/*.puml', '**/*.d2', '**/*.dot']
for pattern in diagram_patterns:
for diagram_file in self.repo_path.glob(pattern):
rel_path = str(diagram_file.relative_to(self.repo_path))
# Calculate file hash
with open(diagram_file, 'rb') as f:
import hashlib
file_hash = hashlib.md5(f.read()).hexdigest()
manifest['diagrams'][rel_path] = {
'hash': file_hash,
'size': diagram_file.stat().st_size,
'modified': diagram_file.stat().st_mtime
}
# Get dependencies
dependencies = self.get_diagram_dependencies(rel_path)
if dependencies:
manifest['dependencies'][rel_path] = dependencies
return manifest
def compare_diagram_manifests(self, manifest1: Dict, manifest2: Dict) -> Dict:
"""Compare two diagram manifests and identify changes"""
changes = {
'added': [],
'removed': [],
'modified': [],
'unchanged': []
}
diagrams1 = set(manifest1.get('diagrams', {}).keys())
diagrams2 = set(manifest2.get('diagrams', {}).keys())
changes['added'] = list(diagrams2 - diagrams1)
changes['removed'] = list(diagrams1 - diagrams2)
# Check for modifications in common files
common_diagrams = diagrams1 & diagrams2
for diagram in common_diagrams:
hash1 = manifest1['diagrams'][diagram]['hash']
hash2 = manifest2['diagrams'][diagram]['hash']
if hash1 != hash2:
changes['modified'].append(diagram)
else:
changes['unchanged'].append(diagram)
return changes
def generate_branch_comparison_report(self, base_branch: str = "main") -> str:
"""Generate a report comparing diagrams between branches"""
current_branch = self.get_current_branch()
# Get manifests for both branches
current_manifest = self.create_diagram_manifest(current_branch)
# Switch to base branch temporarily to get its manifest
subprocess.run(['git', 'stash'], cwd=self.repo_path, capture_output=True)
subprocess.run(['git', 'checkout', base_branch], cwd=self.repo_path, check=True)
try:
base_manifest = self.create_diagram_manifest(base_branch)
finally:
subprocess.run(['git', 'checkout', current_branch], cwd=self.repo_path, check=True)
subprocess.run(['git', 'stash', 'pop'], cwd=self.repo_path, capture_output=True)
# Compare manifests
changes = self.compare_diagram_manifests(base_manifest, current_manifest)
# Generate report
report_lines = [
f"# Diagram Changes Report",
f"",
f"**Base Branch:** `{base_branch}`",
f"**Current Branch:** `{current_branch}`",
f"**Generated:** {current_manifest['timestamp']}",
f"",
]
if changes['added']:
report_lines.extend([
f"## β Added Diagrams ({len(changes['added'])})",
f""
])
for diagram in changes['added']:
report_lines.append(f"- `{diagram}`")
deps = current_manifest.get('dependencies', {}).get(diagram, [])
if deps:
report_lines.append(f" - Used in: {', '.join(f'`{d}`' for d in deps)}")
report_lines.append("")
if changes['removed']:
report_lines.extend([
f"## β Removed Diagrams ({len(changes['removed'])})",
f""
])
for diagram in changes['removed']:
report_lines.append(f"- `{diagram}`")
report_lines.append("")
if changes['modified']:
report_lines.extend([
f"## βοΈ Modified Diagrams ({len(changes['modified'])})",
f""
])
for diagram in changes['modified']:
report_lines.append(f"- `{diagram}`")
deps = current_manifest.get('dependencies', {}).get(diagram, [])
if deps:
report_lines.append(f" - Used in: {', '.join(f'`{d}`' for d in deps)}")
report_lines.append("")
if changes['unchanged']:
report_lines.extend([
f"## β
Unchanged Diagrams ({len(changes['unchanged'])})",
f"",
f"<details>",
f"<summary>Click to expand</summary>",
f""
])
for diagram in changes['unchanged']:
report_lines.append(f"- `{diagram}`")
report_lines.extend([
f"",
f"</details>",
f""
])
# Add summary statistics
total_diagrams = len(current_manifest.get('diagrams', {}))
report_lines.extend([
f"## π Summary",
f"",
f"- **Total Diagrams:** {total_diagrams}",
f"- **Added:** {len(changes['added'])}",
f"- **Modified:** {len(changes['modified'])}",
f"- **Removed:** {len(changes['removed'])}",
f"- **Unchanged:** {len(changes['unchanged'])}",
])
return '\n'.join(report_lines)
# Command line interface
def main():
import argparse
parser = argparse.ArgumentParser(description='Manage diagrams across Git branches')
parser.add_argument('action', choices=['manifest', 'compare', 'report'],
help='Action to perform')
parser.add_argument('--base-branch', default='main',
help='Base branch for comparison')
parser.add_argument('--output', '-o', help='Output file path')
args = parser.parse_args()
manager = DiagramBranchManager()
if args.action == 'manifest':
branch = manager.get_current_branch()
manifest = manager.create_diagram_manifest(branch)
output = json.dumps(manifest, indent=2)
elif args.action == 'compare':
modified = manager.get_modified_diagrams(args.base_branch)
output = '\n'.join(sorted(modified))
elif args.action == 'report':
output = manager.generate_branch_comparison_report(args.base_branch)
if args.output:
with open(args.output, 'w') as f:
f.write(output)
print(f"Output written to {args.output}")
else:
print(output)
if __name__ == "__main__":
main()
Cross-Platform Compatibility and Export
Diagram workflow integration works effectively with comprehensive content management systems. When combined with automation workflows and CI/CD pipelines, diagram generation ensures consistent visual documentation across all deployment environments while maintaining proper version control integration and collaborative editing capabilities.
For advanced documentation architectures, diagram workflows complement Progressive Web App documentation systems by providing offline-capable visual content that enhances user understanding of complex systems while maintaining performance and accessibility standards across different devices and platforms.
When building sophisticated content systems, diagram integration aligns with version control and Git workflows to ensure that visual documentation maintains consistency through collaborative editing processes, automated content validation, and systematic change tracking that preserves the integrity of complex technical documentation.
Troubleshooting Common Integration Issues
Rendering Compatibility Problems
Problem: Diagrams render differently across platforms
Solutions:
# diagram_compatibility_checker.py - Cross-platform compatibility testing
class DiagramCompatibilityChecker:
def __init__(self):
self.platforms = {
'github': {
'mermaid': True,
'plantuml': False,
'd2': False,
'max_file_size': '25MB'
},
'gitlab': {
'mermaid': True,
'plantuml': True,
'd2': False,
'max_file_size': '10MB'
},
'notion': {
'mermaid': False,
'plantuml': False,
'd2': False,
'image_formats': ['png', 'jpg', 'svg']
},
'confluence': {
'mermaid': True,
'plantuml': True,
'd2': False,
'image_formats': ['png', 'svg']
}
}
def check_platform_support(self, diagram_type: str, target_platforms: List[str]) -> Dict:
"""Check if diagram type is supported across platforms"""
results = {}
for platform in target_platforms:
platform_config = self.platforms.get(platform, {})
supported = platform_config.get(diagram_type, False)
results[platform] = {
'supported': supported,
'alternative': 'export_as_image' if not supported else 'native'
}
return results
Performance Optimization
Problem: Large diagram files slow down documentation sites
Solutions:
# diagram-optimization.yml - Optimize diagram output
name: Optimize Diagrams
on:
workflow_call:
inputs:
max_svg_size:
description: 'Maximum SVG file size in KB'
required: false
default: '500'
type: string
jobs:
optimize-diagrams:
runs-on: ubuntu-latest
steps:
- name: Install optimization tools
run: |
npm install -g svgo
sudo apt-get install optipng
- name: Optimize SVG files
run: |
find docs/diagrams -name "*.svg" -exec svgo {} \;
- name: Optimize PNG files
run: |
find docs/diagrams -name "*.png" -exec optipng -o7 {} \;
- name: Check file sizes
run: |
find docs/diagrams -name "*.svg" -size +${{ inputs.max_svg_size }}k \
-exec echo "Warning: {} exceeds size limit" \;
Conclusion
Advanced Markdown diagram workflows and integration techniques transform static documentation into dynamic, maintainable visual communication systems that scale effectively across complex development environments. By implementing automated generation pipelines, version control integration, and cross-platform compatibility strategies, teams can create comprehensive documentation systems that maintain accuracy, enable collaboration, and provide consistent user experiences across all deployment contexts.
The key to successful diagram workflow integration lies in establishing systematic generation processes, implementing proper caching and optimization strategies, and maintaining clear standards for diagram creation and maintenance. Whether youβre building API documentation, system architecture guides, or comprehensive technical specifications, these workflow techniques provide the foundation for creating sustainable, automated visual documentation systems.
Remember to test diagram generation across all target platforms, implement proper error handling and fallback strategies, and establish clear contribution guidelines that help team members effectively create and maintain diagram content. With careful attention to workflow automation and integration patterns, your visual documentation can become a powerful asset that enhances understanding while minimizing maintenance overhead and ensuring long-term sustainability.