PlanOpticon

planopticon / video_processor / utils / rendering.py
Source Blame History 137 lines
cd8f2f9… leo 1 """Mermaid rendering and chart reproduction utilities."""
cd8f2f9… leo 2
cd8f2f9… leo 3 import logging
cd8f2f9… leo 4 from pathlib import Path
829e24a… leo 5 from typing import Dict
cd8f2f9… leo 6
cd8f2f9… leo 7 logger = logging.getLogger(__name__)
cd8f2f9… leo 8
cd8f2f9… leo 9
cd8f2f9… leo 10 def render_mermaid(mermaid_code: str, output_dir: str | Path, name: str) -> Dict[str, Path]:
cd8f2f9… leo 11 """
cd8f2f9… leo 12 Render mermaid code to SVG and PNG files.
cd8f2f9… leo 13
cd8f2f9… leo 14 Writes {name}.mermaid (source), {name}.svg, and {name}.png.
cd8f2f9… leo 15 Uses mermaid-py if available, falls back gracefully.
cd8f2f9… leo 16
cd8f2f9… leo 17 Returns dict with keys: mermaid, svg, png (Paths to generated files).
cd8f2f9… leo 18 """
cd8f2f9… leo 19 output_dir = Path(output_dir)
cd8f2f9… leo 20 output_dir.mkdir(parents=True, exist_ok=True)
cd8f2f9… leo 21 result: Dict[str, Path] = {}
cd8f2f9… leo 22
cd8f2f9… leo 23 # Always write source
cd8f2f9… leo 24 mermaid_path = output_dir / f"{name}.mermaid"
cd8f2f9… leo 25 mermaid_path.write_text(mermaid_code)
cd8f2f9… leo 26 result["mermaid"] = mermaid_path
cd8f2f9… leo 27
cd8f2f9… leo 28 try:
cd8f2f9… leo 29 import mermaid as mmd
cd8f2f9… leo 30 from mermaid.graph import Graph
cd8f2f9… leo 31
cd8f2f9… leo 32 graph = Graph("diagram", mermaid_code)
cd8f2f9… leo 33 rendered = mmd.Mermaid(graph)
cd8f2f9… leo 34
cd8f2f9… leo 35 # SVG
cd8f2f9… leo 36 svg_path = output_dir / f"{name}.svg"
cd8f2f9… leo 37 svg_content = rendered.svg_response
cd8f2f9… leo 38 if svg_content:
cd8f2f9… leo 39 if isinstance(svg_content, bytes):
cd8f2f9… leo 40 svg_path.write_bytes(svg_content)
cd8f2f9… leo 41 else:
cd8f2f9… leo 42 svg_path.write_text(svg_content)
cd8f2f9… leo 43 result["svg"] = svg_path
cd8f2f9… leo 44
cd8f2f9… leo 45 # PNG
cd8f2f9… leo 46 png_path = output_dir / f"{name}.png"
cd8f2f9… leo 47 png_content = rendered.img_response
cd8f2f9… leo 48 if png_content:
cd8f2f9… leo 49 if isinstance(png_content, bytes):
cd8f2f9… leo 50 png_path.write_bytes(png_content)
cd8f2f9… leo 51 else:
829e24a… leo 52 png_path.write_bytes(
829e24a… leo 53 png_content.encode() if isinstance(png_content, str) else png_content
829e24a… leo 54 )
cd8f2f9… leo 55 result["png"] = png_path
cd8f2f9… leo 56
cd8f2f9… leo 57 except ImportError:
829e24a… leo 58 logger.warning(
829e24a… leo 59 "mermaid-py not installed, skipping SVG/PNG rendering. "
829e24a… leo 60 "Install with: pip install mermaid-py"
829e24a… leo 61 )
cd8f2f9… leo 62 except Exception as e:
cd8f2f9… leo 63 logger.warning(f"Mermaid rendering failed for '{name}': {e}")
cd8f2f9… leo 64
cd8f2f9… leo 65 return result
cd8f2f9… leo 66
cd8f2f9… leo 67
cd8f2f9… leo 68 def reproduce_chart(
cd8f2f9… leo 69 chart_data: dict,
cd8f2f9… leo 70 output_dir: str | Path,
cd8f2f9… leo 71 name: str,
cd8f2f9… leo 72 ) -> Dict[str, Path]:
cd8f2f9… leo 73 """
cd8f2f9… leo 74 Reproduce a chart from extracted data using matplotlib.
cd8f2f9… leo 75
cd8f2f9… leo 76 chart_data should contain: labels, values, chart_type (bar/line/pie/scatter).
cd8f2f9… leo 77 Returns dict with keys: svg, png (Paths to generated files).
cd8f2f9… leo 78 """
cd8f2f9… leo 79 output_dir = Path(output_dir)
cd8f2f9… leo 80 output_dir.mkdir(parents=True, exist_ok=True)
cd8f2f9… leo 81 result: Dict[str, Path] = {}
cd8f2f9… leo 82
cd8f2f9… leo 83 labels = chart_data.get("labels", [])
cd8f2f9… leo 84 values = chart_data.get("values", [])
cd8f2f9… leo 85 chart_type = chart_data.get("chart_type", "bar")
cd8f2f9… leo 86
cd8f2f9… leo 87 if not labels or not values:
cd8f2f9… leo 88 logger.warning(f"Insufficient chart data for '{name}': missing labels or values")
cd8f2f9… leo 89 return result
cd8f2f9… leo 90
cd8f2f9… leo 91 try:
cd8f2f9… leo 92 import matplotlib
cd8f2f9… leo 93
cd8f2f9… leo 94 matplotlib.use("Agg") # Non-interactive backend
cd8f2f9… leo 95 import matplotlib.pyplot as plt
cd8f2f9… leo 96
cd8f2f9… leo 97 fig, ax = plt.subplots(figsize=(10, 6))
cd8f2f9… leo 98
cd8f2f9… leo 99 if chart_type == "bar":
cd8f2f9… leo 100 ax.bar(labels, values)
cd8f2f9… leo 101 elif chart_type == "line":
cd8f2f9… leo 102 ax.plot(labels, values, marker="o")
cd8f2f9… leo 103 elif chart_type == "pie":
cd8f2f9… leo 104 ax.pie(values, labels=labels, autopct="%1.1f%%")
cd8f2f9… leo 105 elif chart_type == "scatter":
cd8f2f9… leo 106 ax.scatter(range(len(values)), values)
cd8f2f9… leo 107 if labels:
cd8f2f9… leo 108 ax.set_xticks(range(len(labels)))
cd8f2f9… leo 109 ax.set_xticklabels(labels, rotation=45, ha="right")
cd8f2f9… leo 110 else:
cd8f2f9… leo 111 ax.bar(labels, values)
cd8f2f9… leo 112
cd8f2f9… leo 113 if chart_type != "pie":
cd8f2f9… leo 114 ax.set_xlabel("")
cd8f2f9… leo 115 ax.set_ylabel("")
cd8f2f9… leo 116 plt.xticks(rotation=45, ha="right")
cd8f2f9… leo 117
cd8f2f9… leo 118 plt.tight_layout()
cd8f2f9… leo 119
cd8f2f9… leo 120 # SVG
cd8f2f9… leo 121 svg_path = output_dir / f"{name}_chart.svg"
cd8f2f9… leo 122 fig.savefig(svg_path, format="svg")
cd8f2f9… leo 123 result["svg"] = svg_path
cd8f2f9… leo 124
cd8f2f9… leo 125 # PNG
cd8f2f9… leo 126 png_path = output_dir / f"{name}_chart.png"
cd8f2f9… leo 127 fig.savefig(png_path, format="png", dpi=150)
cd8f2f9… leo 128 result["png"] = png_path
cd8f2f9… leo 129
cd8f2f9… leo 130 plt.close(fig)
cd8f2f9… leo 131
cd8f2f9… leo 132 except ImportError:
cd8f2f9… leo 133 logger.warning("matplotlib not installed, skipping chart reproduction")
cd8f2f9… leo 134 except Exception as e:
cd8f2f9… leo 135 logger.warning(f"Chart reproduction failed for '{name}': {e}")
cd8f2f9… leo 136
cd8f2f9… leo 137 return result

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button