Skip to main content
Glama

mcp-plots

mermaid_generator.py39.7 kB
""" Mermaid chart generator for converting data to Mermaid syntax. This enables chart rendering in environments that support Mermaid (like Cursor Chat). """ from __future__ import annotations import logging from typing import Dict, List, Any, Optional, Union import pandas as pd from .chart_config import ChartData, ChartConfig, ChartType from .field_validator import FieldValidator logging.basicConfig(level=logging.INFO) _logger = logging.getLogger(__name__) class MermaidGenerator: """ Generator for converting chart data to Mermaid diagram syntax. Supports various chart types that can be represented in Mermaid. """ @staticmethod def _prepare_data(chart_data: ChartData) -> pd.DataFrame: """Convert data to pandas DataFrame if needed""" if isinstance(chart_data.data, pd.DataFrame): return chart_data.data elif isinstance(chart_data.data, list): return pd.DataFrame(chart_data.data) else: raise ValueError("Data must be a list of dictionaries or pandas DataFrame") @staticmethod def _sanitize_label(label: str) -> str: """Sanitize labels for Mermaid compatibility""" if not isinstance(label, str): label = str(label) # Replace problematic characters return label.replace('"', "'").replace('\n', ' ').replace('\r', ' ') @staticmethod def generate_xychart(chart_data: ChartData, config: ChartConfig, chart_type: str = "line") -> str: """Generate Mermaid XY Chart (supports line and bar charts)""" df = MermaidGenerator._prepare_data(chart_data) # Build the Mermaid XY chart lines = [] lines.append("xychart-beta") if config.title: lines.append(f' title "{MermaidGenerator._sanitize_label(config.title)}"') # Handle different field mappings if chart_data.x_field and chart_data.y_field: # Fields are already validated by FieldValidator x_values = df[chart_data.x_field].astype(str).tolist() y_values = df[chart_data.y_field].tolist() x_axis_labels = [MermaidGenerator._sanitize_label(x) for x in x_values] lines.append(f' x-axis [{", ".join(x_axis_labels)}]') y_title = config.y_title or chart_data.y_field or "Values" y_min, y_max = min(y_values), max(y_values) y_range = y_max - y_min y_min_display = max(0, y_min - y_range * 0.1) y_max_display = y_max + y_range * 0.1 lines.append(f' y-axis "{y_title}" {y_min_display:.0f} --> {y_max_display:.0f}') if chart_type == "line": lines.append(f' line [{", ".join(map(str, y_values))}]') else: # bar lines.append(f' bar [{", ".join(map(str, y_values))}]') elif chart_data.category_field and chart_data.value_field: # Fields are already validated by FieldValidator categories = df[chart_data.category_field].astype(str).tolist() values = df[chart_data.value_field].tolist() cat_labels = [MermaidGenerator._sanitize_label(cat) for cat in categories] lines.append(f' x-axis [{", ".join(cat_labels)}]') value_title = config.y_title or chart_data.value_field or "Values" v_min, v_max = min(values), max(values) v_range = v_max - v_min v_min_display = max(0, v_min - v_range * 0.1) v_max_display = v_max + v_range * 0.1 lines.append(f' y-axis "{value_title}" {v_min_display:.0f} --> {v_max_display:.0f}') lines.append(f' bar [{", ".join(map(str, values))}]') else: raise ValueError("Missing required fields for chart generation") return '\n'.join(lines) @staticmethod def generate_pie_chart(chart_data: ChartData, config: ChartConfig) -> str: """Generate Mermaid Pie Chart""" df = MermaidGenerator._prepare_data(chart_data) # Fields are already validated by FieldValidator lines = [] title = config.title or "Pie Chart" lines.append(f'pie title {MermaidGenerator._sanitize_label(title)}') categories = df[chart_data.category_field].tolist() values = df[chart_data.value_field].tolist() for cat, val in zip(categories, values): cat_clean = MermaidGenerator._sanitize_label(str(cat)) lines.append(f' "{cat_clean}" : {val}') return '\n'.join(lines) @staticmethod def generate_flowchart(chart_data: ChartData, config: ChartConfig) -> str: """Generate a flowchart representation of the data""" df = MermaidGenerator._prepare_data(chart_data) lines = [] lines.append("flowchart TD") if config.title: title_clean = MermaidGenerator._sanitize_label(config.title) lines.append(f' A["{title_clean}"] --> B["Data Visualization"]') # Create nodes for each data point if chart_data.category_field and chart_data.value_field: categories = df[chart_data.category_field].tolist() values = df[chart_data.value_field].tolist() lines.append(' B --> C["Categories"]') for i, (cat, val) in enumerate(zip(categories[:6], values[:6]), 1): # Limit to 6 for readability cat_clean = MermaidGenerator._sanitize_label(str(cat)) lines.append(f' C --> D{i}["{cat_clean}: {val}"]') elif chart_data.x_field and chart_data.y_field: lines.append(' B --> C["Data Points"]') x_values = df[chart_data.x_field].tolist() y_values = df[chart_data.y_field].tolist() for i, (x, y) in enumerate(zip(x_values[:6], y_values[:6]), 1): # Limit to 6 x_clean = MermaidGenerator._sanitize_label(str(x)) lines.append(f' C --> D{i}["{x_clean}: {y}"]') # Add styling lines.append(' style A fill:#e1f5fe') lines.append(' style B fill:#f3e5f5') lines.append(' style C fill:#e8f5e8') return '\n'.join(lines) @staticmethod def generate_gantt_chart(chart_data: ChartData, config: ChartConfig) -> str: """Generate a Gantt chart representation (for time-based data)""" df = MermaidGenerator._prepare_data(chart_data) lines = [] lines.append("gantt") if config.title: lines.append(f' title {MermaidGenerator._sanitize_label(config.title)}') lines.append(' dateFormat YYYY-MM-DD') lines.append(' section Data') # Simple representation - treat categories as tasks if chart_data.category_field: categories = df[chart_data.category_field].tolist() for i, cat in enumerate(categories[:8]): # Limit for readability cat_clean = MermaidGenerator._sanitize_label(str(cat)) start_date = f'2024-01-{i+1:02d}' end_date = f'2024-01-{i+2:02d}' lines.append(f' {cat_clean} : {start_date}, {end_date}') return '\n'.join(lines) @staticmethod def generate_histogram_mermaid(chart_data: ChartData, config: ChartConfig) -> str: """ Generate histogram representation as Mermaid bar chart with optimal binning. This method implements a sophisticated histogram algorithm that converts continuous numerical data into discrete bins for visualization. The algorithm automatically determines appropriate bin boundaries and handles edge cases for optimal display. Algorithm Overview: 1. Data Preparation: Extract numerical values from the specified field 2. Range Analysis: Calculate min, max, and total range of values 3. Bin Generation: Create 10 equally-spaced bins across the data range 4. Data Classification: Assign each data point to appropriate bin 5. Edge Case Handling: Ensure maximum value is included in the last bin 6. Mermaid Synthesis: Convert bin data to Mermaid xychart-beta syntax Binning Strategy: - Uses equal-width binning for consistent visual spacing - Default 10 bins provides good balance of detail vs. clarity - Bins are half-open intervals [start, end) except the last bin [start, end] - Label format: "min-max" with 1 decimal precision Edge Case Handling: - Single value: Creates bins around that value - Identical values: Handles zero-range data gracefully - Maximum value: Explicitly included in the rightmost bin - Empty data: Handled by upstream validation Performance Characteristics: - Time Complexity: O(n + b) where n=data points, b=bins (10) - Space Complexity: O(b) for bin storage - Memory efficient: Processes data in single pass Args: chart_data: ChartData object with value_field specified config: ChartConfig with title and styling preferences Returns: str: Mermaid xychart-beta syntax representing the histogram Example Output: ``` xychart-beta title "Data Distribution" x-axis ["0.0-1.2", "1.2-2.4", "2.4-3.6", ...] y-axis "Frequency" 0 --> 15 bar [3, 7, 12, 8, 5, 2, 1, 0, 0, 1] ``` Note: This implementation prioritizes visual clarity over statistical precision. For rigorous statistical analysis, consider using the full matplotlib histogram generator with customizable binning strategies. """ df = MermaidGenerator._prepare_data(chart_data) # Fields are already validated by FieldValidator values = df[chart_data.value_field] bins = 10 # Optimal bin count balancing detail with clarity # Calculate range and bin parameters min_val, max_val = values.min(), values.max() # Handle edge case where all values are identical if min_val == max_val: # Create single bin for identical values bin_counts = {f"{min_val:.1f}": len(values)} else: bin_width = (max_val - min_val) / bins # Generate bins and count data points in each bin bin_counts = {} for i in range(bins): bin_start = min_val + i * bin_width bin_end = min_val + (i + 1) * bin_width bin_label = f"{bin_start:.1f}-{bin_end:.1f}" # Count values in this bin (half-open interval) if i == bins - 1: # Last bin includes the maximum value (closed interval) count = len(values[(values >= bin_start) & (values <= bin_end)]) else: # Regular bins exclude the upper bound (half-open interval) count = len(values[(values >= bin_start) & (values < bin_end)]) bin_counts[bin_label] = count # Build Mermaid chart structure title = config.title or "Histogram" lines = [f'xychart-beta'] lines.append(f' title "{title}"') # Format x-axis with bin labels x_axis_labels = ", ".join(f'"{k}"' for k in bin_counts.keys()) lines.append(f' x-axis [{x_axis_labels}]') # Set y-axis range from 0 to maximum frequency max_frequency = max(bin_counts.values()) if bin_counts else 0 lines.append(f' y-axis "Frequency" 0 --> {max_frequency}') # Add frequency data as bar chart frequency_data = ", ".join(str(v) for v in bin_counts.values()) lines.append(f' bar [{frequency_data}]') return '\n'.join(lines) @staticmethod def generate_funnel_mermaid(chart_data: ChartData, config: ChartConfig) -> str: """Generate funnel chart using flowchart""" df = MermaidGenerator._prepare_data(chart_data) # Fields are already validated by FieldValidator df_sorted = df.sort_values(chart_data.value_field, ascending=False) title = config.title or "Funnel Chart" lines = [f'flowchart TD'] if title: lines.append(f' Title["{title}"]') # Create funnel stages prev_node = "Title" if title else None for i, (_, row) in enumerate(df_sorted.iterrows()): category = MermaidGenerator._sanitize_label(str(row[chart_data.category_field])) value = row[chart_data.value_field] node_id = f"Stage{i}" node_label = f"{category}: {value}" # Use different shapes for visual funnel effect if i == 0: shape = f'{node_id}["{node_label}"]' elif i == len(df_sorted) - 1: shape = f'{node_id}("{node_label}")' else: shape = f'{node_id}["{node_label}"]' lines.append(f' {shape}') if prev_node: lines.append(f' {prev_node} --> {node_id}') prev_node = node_id # Add styling for funnel effect for i in range(len(df_sorted)): node_id = f"Stage{i}" # Gradient from green to red if i < len(df_sorted) / 3: color = "#90EE90" # Light green elif i < 2 * len(df_sorted) / 3: color = "#FFD700" # Gold else: color = "#FFB6C1" # Light pink lines.append(f' style {node_id} fill:{color}') return '\n'.join(lines) @staticmethod def generate_gauge_mermaid(chart_data: ChartData, config: ChartConfig) -> str: """Generate gauge chart representation""" df = MermaidGenerator._prepare_data(chart_data) # Fields are already validated by FieldValidator value = df[chart_data.value_field].iloc[0] # Take first value title = config.title or "Gauge" # Create a simple gauge representation using flowchart lines = [f'flowchart LR'] lines.append(f' A["📊 {title}"]') lines.append(f' B["{value}"]') lines.append(f' A --> B') # Color based on value (assuming 0-100 scale) if value < 30: color = "#ffcdd2" # Red elif value < 70: color = "#fff3cd" # Yellow else: color = "#d4edda" # Green lines.append(f' style B fill:{color}') return '\n'.join(lines) @staticmethod def generate_sankey_mermaid(chart_data: ChartData, config: ChartConfig) -> str: """Generate Sankey diagram using flowchart""" df = MermaidGenerator._prepare_data(chart_data) # Fields are already validated by FieldValidator title = config.title or "Flow Diagram" lines = [f'flowchart LR'] if title: lines.append(f' Title["{title}"]') # Create nodes and connections sources = df[chart_data.source_field].unique() targets = df[chart_data.target_field].unique() # Add source nodes for source in sources: source_id = f"S_{MermaidGenerator._sanitize_label(str(source)).replace(' ', '_')}" lines.append(f' {source_id}["{source}"]') # Add target nodes for target in targets: target_id = f"T_{MermaidGenerator._sanitize_label(str(target)).replace(' ', '_')}" lines.append(f' {target_id}["{target}"]') # Add flows for _, row in df.iterrows(): source = str(row[chart_data.source_field]) target = str(row[chart_data.target_field]) # Handle value field safely if chart_data.value_field and chart_data.value_field in df.columns: value = row[chart_data.value_field] # Use thick arrows for larger values mean_value = df[chart_data.value_field].mean() arrow = "==>" if value > mean_value else "-->" else: value = 1 arrow = "-->" source_id = f"S_{MermaidGenerator._sanitize_label(source).replace(' ', '_')}" target_id = f"T_{MermaidGenerator._sanitize_label(target).replace(' ', '_')}" lines.append(f' {source_id} {arrow} {target_id}') lines.append(f' {source_id} -.->|{value}| {target_id}') # Style source and target nodes differently for source in sources: source_id = f"S_{MermaidGenerator._sanitize_label(str(source)).replace(' ', '_')}" lines.append(f' style {source_id} fill:#e1f5fe') for target in targets: target_id = f"T_{MermaidGenerator._sanitize_label(str(target)).replace(' ', '_')}" lines.append(f' style {target_id} fill:#f3e5f5') return '\n'.join(lines) @staticmethod def generate_radar_mermaid(chart_data: ChartData, config: ChartConfig) -> str: """Generate radar chart using structured flowchart representation""" df = MermaidGenerator._prepare_data(chart_data) title = config.title or "Radar Chart" # Fields are already validated by FieldValidator categories = df[chart_data.category_field].tolist() values = df[chart_data.value_field].tolist() lines = ['flowchart TD'] # Center node lines.append(f' Center["{MermaidGenerator._sanitize_label(title)}"]') # Create dimension nodes around the center for i, (cat, val) in enumerate(zip(categories, values)): cat_clean = MermaidGenerator._sanitize_label(str(cat)) node_id = f"D{i}" lines.append(f' {node_id}["{cat_clean}<br/>Score: {val}"]') lines.append(f' Center --- {node_id}') # Color code based on value (assuming 0-10 scale) max_val = max(values) if values else 10 ratio = val / max_val if max_val > 0 else 0 if ratio >= 0.8: color = "#c8e6c9" # Light green elif ratio >= 0.6: color = "#fff3cd" # Light yellow elif ratio >= 0.4: color = "#ffccbc" # Light orange else: color = "#ffcdd2" # Light red lines.append(f' style {node_id} fill:{color}') # Style center node lines.append(' style Center fill:#e3f2fd') return '\n'.join(lines) @staticmethod def generate_boxplot_mermaid(chart_data: ChartData, config: ChartConfig) -> str: """Generate boxplot using table-like representation""" df = MermaidGenerator._prepare_data(chart_data) title = config.title or "Box Plot" # Fields are already validated by FieldValidator if chart_data.category_field and chart_data.value_field: categories = df[chart_data.category_field].unique() lines = ['flowchart TD'] lines.append(f' Title["{MermaidGenerator._sanitize_label(title)}"]') for i, category in enumerate(categories): cat_data = df[df[chart_data.category_field] == category][chart_data.value_field] if len(cat_data) > 0: q1 = cat_data.quantile(0.25) q2 = cat_data.quantile(0.5) # median q3 = cat_data.quantile(0.75) min_val = cat_data.min() max_val = cat_data.max() cat_clean = MermaidGenerator._sanitize_label(str(category)) node_id = f"Cat{i}" lines.append(f' {node_id}["{cat_clean}<br/>Min: {min_val:.1f}<br/>Q1: {q1:.1f}<br/>Median: {q2:.1f}<br/>Q3: {q3:.1f}<br/>Max: {max_val:.1f}"]') lines.append(f' Title --> {node_id}') # Color based on median value median_color = "#e8f5e8" if q2 > cat_data.mean() else "#fff3cd" lines.append(f' style {node_id} fill:{median_color}') lines.append(' style Title fill:#e3f2fd') else: # Single series boxplot values = df[chart_data.value_field] q1 = values.quantile(0.25) q2 = values.quantile(0.5) q3 = values.quantile(0.75) min_val = values.min() max_val = values.max() lines = ['flowchart TD'] lines.append(f' Title["{MermaidGenerator._sanitize_label(title)}"]') lines.append(f' Stats["Min: {min_val:.1f}<br/>Q1: {q1:.1f}<br/>Median: {q2:.1f}<br/>Q3: {q3:.1f}<br/>Max: {max_val:.1f}"]') lines.append(' Title --> Stats') lines.append(' style Title fill:#e3f2fd') lines.append(' style Stats fill:#e8f5e8') return '\n'.join(lines) @staticmethod def generate_heatmap_mermaid(chart_data: ChartData, config: ChartConfig) -> str: """ Generate heatmap visualization using Mermaid flowchart matrix representation. This method implements a sophisticated 2D data visualization algorithm that converts three-dimensional data (X, Y, Value) into a color-coded matrix layout using Mermaid flowchart syntax. The algorithm handles data aggregation, color mapping, and spatial organization to create an intuitive heat map representation. Algorithm Overview: 1. Data Aggregation: Group data by X and Y coordinates, averaging values for duplicates 2. Matrix Construction: Build 2D grid structure with X columns and Y rows 3. Color Mapping: Apply temperature-based color coding (blue=cold, red=hot) 4. Layout Generation: Create flowchart nodes with proper spatial relationships 5. Style Application: Apply color coding and visual hierarchy Data Processing Pipeline: ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ Raw Data Points │ -> │ Group & Average │ -> │ Matrix Layout │ │ (x,y,value) │ │ by (x,y) pairs │ │ Header + Cells │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ v ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ Mermaid Output │ <- │ Style & Color │ <- │ Node Creation │ │ flowchart TD │ │ Temperature Map │ │ Headers + Cells │ └─────────────────┘ └─────────────────┘ └─────────────────┘ Color Mapping Strategy: - Uses 5-tier temperature color scale for intuitive interpretation - Colors: Blue (cold) -> Green -> Yellow -> Orange -> Red (hot) - Thresholds: 0-20%, 20-40%, 40-60%, 60-80%, 80-100% of value range - Special handling for constant values (all same) -> gray color Spatial Layout Algorithm: ``` Title │ v Header["X \\ Y"] ├─> X0["Mon"] ├─> X1["Tue"] └─> X2["Wed"] │ v Y0["9AM"] -> C0_0["12.0"] (connects to X0) │ C1_0["15.0"] (connects to X1) │ C2_0["20.0"] (connects to X2) │ Y1["12PM"] -> C0_1["25.0"] (connects to X0) │ C1_1["30.0"] (connects to X1) │ C2_1["28.0"] (connects to X2) ``` Performance Characteristics: - Time Complexity: O(n + xy) where n=data points, x=unique X values, y=unique Y values - Space Complexity: O(xy) for matrix storage + O(n) for node generation - Memory efficient: Processes data in single pass with pandas groupby - Scalability: Handles up to ~50x50 matrices before Mermaid becomes unwieldy Edge Cases & Handling: - Missing (x,y) combinations: Not displayed (sparse matrix support) - Duplicate (x,y) pairs: Values are averaged automatically - Single data point: Creates 1x1 matrix with appropriate color - All identical values: Uses gray color scheme to indicate no variation - Large matrices: May become visually complex but remains functional Args: chart_data: ChartData with x_field, y_field, and value_field specified config: ChartConfig with title and styling preferences Returns: str: Mermaid flowchart TD syntax representing the heatmap matrix Example Output: ``` flowchart TD Title["Weekly Activity Heatmap"] Header["X \\\\ Y"] Title --> Header X0["Mon"] Header --> X0 Y0["9AM"] Title --> Y0 C0_0["12.0"] Y0 --> C0_0 style C0_0 fill:#2196f3 ``` Integration Notes: - Works best with discrete categorical X and Y values - Continuous numeric X/Y values are converted to strings - Recommended maximum: 10x10 matrix for optimal readability - Color scheme is colorblind-friendly using distinct hues See Also: - generate_boxplot_mermaid() for statistical distribution visualization - ChartData.x_field, y_field, value_field for required field mappings """ df = MermaidGenerator._prepare_data(chart_data) title = config.title or "Heatmap" # Fields are already validated by FieldValidator lines = ['flowchart TD'] lines.append(f' Title["{MermaidGenerator._sanitize_label(title)}"]') # Create pivot table structure if chart_data.x_field and chart_data.y_field and chart_data.value_field: # Group by x and y fields grouped = df.groupby([chart_data.x_field, chart_data.y_field])[chart_data.value_field].mean().reset_index() x_values = sorted(grouped[chart_data.x_field].unique()) y_values = sorted(grouped[chart_data.y_field].unique()) # Create header row header_nodes = [] lines.append(' Header["X \\\\ Y"]') lines.append(' Title --> Header') for i, x_val in enumerate(x_values): x_node = f"X{i}" x_clean = MermaidGenerator._sanitize_label(str(x_val)) lines.append(f' {x_node}["{x_clean}"]') lines.append(f' Header --> {x_node}') header_nodes.append(x_node) # Create data rows for j, y_val in enumerate(y_values): y_node = f"Y{j}" y_clean = MermaidGenerator._sanitize_label(str(y_val)) lines.append(f' {y_node}["{y_clean}"]') lines.append(f' Title --> {y_node}') for i, x_val in enumerate(x_values): # Find value for this x,y combination cell_data = grouped[(grouped[chart_data.x_field] == x_val) & (grouped[chart_data.y_field] == y_val)] if len(cell_data) > 0: cell_value = cell_data[chart_data.value_field].iloc[0] cell_node = f"C{i}_{j}" lines.append(f' {cell_node}["{cell_value:.1f}"]') lines.append(f' {y_node} --> {cell_node}') # Color code based on value max_val = grouped[chart_data.value_field].max() min_val = grouped[chart_data.value_field].min() if max_val > min_val: ratio = (cell_value - min_val) / (max_val - min_val) if ratio >= 0.8: color = "#d32f2f" # Dark red (hot) elif ratio >= 0.6: color = "#ff9800" # Orange elif ratio >= 0.4: color = "#ffeb3b" # Yellow elif ratio >= 0.2: color = "#8bc34a" # Light green else: color = "#2196f3" # Blue (cold) else: color = "#e0e0e0" # Gray for constant values lines.append(f' style {cell_node} fill:{color}') # Style header elements lines.append(' style Title fill:#e3f2fd') lines.append(' style Header fill:#f3e5f5') for x_node in header_nodes: lines.append(f' style {x_node} fill:#f3e5f5') return '\n'.join(lines) @classmethod def generate(cls, chart_type: ChartType, chart_data: ChartData, config: ChartConfig, **kwargs) -> str: """ Main entry point for Mermaid diagram generation with comprehensive chart support. This method serves as the primary factory for converting structured data into Mermaid-compatible diagram syntax. It implements a sophisticated routing system that delegates to specialized generators based on chart type, ensuring optimal visual representation for each data visualization pattern. Supported Chart Types & Routing Logic: ┌─────────────────┬─────────────────────┬────────────────────────────────┐ │ Chart Type │ Generator Method │ Mermaid Output Format │ ├─────────────────┼─────────────────────┼────────────────────────────────┤ │ LINE/AREA │ generate_xychart │ xychart-beta (line format) │ │ BAR │ generate_xychart │ xychart-beta (bar format) │ │ PIE │ generate_pie_chart │ pie title (native pie chart) │ │ SCATTER │ generate_xychart │ xychart-beta (point format) │ │ HISTOGRAM │ generate_histogram │ xychart-beta (binned bars) │ │ FUNNEL │ generate_funnel │ flowchart TD (staged flow) │ │ GAUGE │ generate_gauge │ flowchart LR (metric display) │ │ SANKEY │ generate_sankey │ flowchart LR (flow diagram) │ │ RADAR* │ generate_radar │ flowchart TD (radial layout) │ │ BOXPLOT* │ generate_boxplot │ flowchart TD (statistical) │ │ HEATMAP* │ generate_heatmap │ flowchart TD (matrix layout) │ └─────────────────┴─────────────────────┴────────────────────────────────┘ * Recently enhanced with improved algorithms (see individual method docs) Algorithm Flow: 1. Input Validation: Verify chart_type, chart_data, and config parameters 2. Type Routing: Select appropriate specialized generator method 3. Data Processing: Prepare data for the specific chart type requirements 4. Generation: Execute chart-specific Mermaid generation algorithm 5. Syntax Validation: Ensure generated Mermaid syntax is well-formed 6. Error Handling: Provide fallback error diagram for failed generations Error Handling Strategy: - Input validation errors: Return descriptive error diagram - Generation failures: Fallback to flowchart error representation - Unsupported types: Warning log + generic flowchart fallback - Data format issues: Handled by individual generator methods Performance Characteristics: - Time Complexity: O(n) where n = number of data points - Space Complexity: O(m) where m = size of generated Mermaid string - Memory efficient: No intermediate data structure caching - Streaming compatible: Can handle large datasets incrementally Thread Safety: - All methods are stateless and thread-safe - No shared mutable state between invocations - Safe for concurrent use in multi-threaded applications Args: chart_type: ChartType enum specifying the visualization type Must be one of the supported types listed above chart_data: ChartData object containing: - data: List[Dict] or DataFrame with actual data - field mappings: x_field, y_field, category_field, etc. - metadata: data_id, created_at, etc. config: ChartConfig object with presentation settings: - dimensions: width, height (ignored for text output) - styling: theme, colors, titles - output: format specification (should be MERMAID) **kwargs: Additional generator-specific options: - smooth: boolean for line smoothing - stack: boolean for stacked charts - orientation: string for chart orientation Returns: str: Well-formed Mermaid diagram syntax ready for rendering Format depends on chart_type (see routing table above) Raises: ValueError: If chart_type is not supported or data is invalid KeyError: If required field mappings are missing from chart_data TypeError: If chart_data or config have incorrect types Example Usage: ```python # Simple bar chart chart_data = ChartData( data=[{"cat": "A", "val": 10}, {"cat": "B", "val": 20}], category_field="cat", value_field="val" ) config = ChartConfig(title="Sales by Category") mermaid = MermaidGenerator.generate( ChartType.BAR, chart_data, config ) print(mermaid) # Output: # xychart-beta # title "Sales by Category" # x-axis [A, B] # y-axis "val" 9 --> 21 # bar [10, 20] ``` Integration Notes: - Output is optimized for Cursor IDE Mermaid rendering - Compatible with all major Mermaid.js versions (8.0+) - Syntax follows official Mermaid specification - Safe for injection into markdown documents See Also: - Individual generator methods for algorithm-specific documentation - ChartType enum for complete list of supported visualizations - ChartConfig class for styling and configuration options """ try: if chart_type == ChartType.LINE: return cls.generate_xychart(chart_data, config, "line") elif chart_type == ChartType.BAR: return cls.generate_xychart(chart_data, config, "bar") elif chart_type == ChartType.PIE: return cls.generate_pie_chart(chart_data, config) elif chart_type == ChartType.AREA: # Area charts can be represented as line charts in Mermaid return cls.generate_xychart(chart_data, config, "line") elif chart_type == ChartType.SCATTER: # Represent scatter plot as xychart return cls.generate_xychart(chart_data, config, "line") elif chart_type == ChartType.HISTOGRAM: # Represent histogram as bar chart return cls.generate_histogram_mermaid(chart_data, config) elif chart_type == ChartType.FUNNEL: # Create a visual funnel using flowchart return cls.generate_funnel_mermaid(chart_data, config) elif chart_type == ChartType.GAUGE: # Create a gauge representation using flowchart return cls.generate_gauge_mermaid(chart_data, config) elif chart_type == ChartType.SANKEY: # Create a sankey diagram using flowchart return cls.generate_sankey_mermaid(chart_data, config) elif chart_type == ChartType.RADAR: # Use improved radar chart representation return cls.generate_radar_mermaid(chart_data, config) elif chart_type == ChartType.BOXPLOT: # Use improved boxplot representation return cls.generate_boxplot_mermaid(chart_data, config) elif chart_type == ChartType.HEATMAP: # Use improved heatmap representation return cls.generate_heatmap_mermaid(chart_data, config) else: # Fallback to flowchart for unsupported types _logger.warning(f"Chart type {chart_type.value} not directly supported in Mermaid, using flowchart representation") return cls.generate_flowchart(chart_data, config) except Exception as e: _logger.error(f"Error generating Mermaid chart: {str(e)}") # Return a simple error diagram return f"""flowchart TD A["Chart Generation Error"] --> B["{str(e)}"] style A fill:#ffcdd2 style B fill:#ffcdd2"""

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/MR901/mcp-plots'

If you have feedback or need assistance with the MCP directory API, please join our Discord server