Spaces:
Sleeping
Sleeping
| import json | |
| import logging | |
| from typing import Dict, List, Optional, Any | |
| class ChartGenerator: | |
| """Generate Chart.js configurations for data visualization""" | |
| def __init__(self): | |
| self.chart_colors = [ | |
| '#007bff', '#28a745', '#ffc107', '#dc3545', '#6f42c1', | |
| '#fd7e14', '#20c997', '#6c757d', '#343a40', '#007bff' | |
| ] | |
| def create_chart_config(self, metrics: List[Dict], section_title: str) -> Optional[Dict]: | |
| """Create Chart.js configuration based on metrics data""" | |
| if not metrics: | |
| return None | |
| # Analyze metrics to determine best chart type | |
| chart_type = self._determine_chart_type(metrics) | |
| if chart_type == 'line': | |
| return self._create_line_chart(metrics, section_title) | |
| elif chart_type == 'bar': | |
| return self._create_bar_chart(metrics, section_title) | |
| elif chart_type == 'pie': | |
| return self._create_pie_chart(metrics, section_title) | |
| elif chart_type == 'doughnut': | |
| return self._create_doughnut_chart(metrics, section_title) | |
| else: | |
| return self._create_default_chart(metrics, section_title) | |
| def _determine_chart_type(self, metrics: List[Dict]) -> str: | |
| """Determine the most appropriate chart type for the data""" | |
| # Analyze metric types | |
| has_percentages = any('%' in str(metric.get('metric', '')) for metric in metrics) | |
| has_time_series = any('year' in str(metric.get('context', '')).lower() for metric in metrics) | |
| has_categories = len(metrics) <= 6 # Good for pie/doughnut charts | |
| if has_time_series and len(metrics) > 2: | |
| return 'line' | |
| elif has_percentages and has_categories: | |
| return 'doughnut' | |
| elif len(metrics) <= 5: | |
| return 'bar' | |
| else: | |
| return 'line' | |
| def _create_line_chart(self, metrics: List[Dict], title: str) -> Dict: | |
| """Create line chart configuration""" | |
| labels = [] | |
| data_points = [] | |
| for i, metric in enumerate(metrics[:10]): # Limit to 10 points | |
| labels.append(f"Point {i+1}") | |
| data_points.append(self._extract_numeric_value(metric.get('metric', '0'))) | |
| return { | |
| 'type': 'line', | |
| 'data': { | |
| 'labels': labels, | |
| 'datasets': [{ | |
| 'label': f'{title} Trend', | |
| 'data': data_points, | |
| 'borderColor': self.chart_colors[0], | |
| 'backgroundColor': self.chart_colors[0] + '20', | |
| 'tension': 0.4, | |
| 'fill': True | |
| }] | |
| }, | |
| 'options': { | |
| 'responsive': True, | |
| 'plugins': { | |
| 'title': { | |
| 'display': True, | |
| 'text': f'{title} - Data Analysis' | |
| }, | |
| 'legend': { | |
| 'position': 'top' | |
| } | |
| }, | |
| 'scales': { | |
| 'y': { | |
| 'beginAtZero': True, | |
| 'title': { | |
| 'display': True, | |
| 'text': 'Value' | |
| } | |
| } | |
| } | |
| } | |
| } | |
| def _create_bar_chart(self, metrics: List[Dict], title: str) -> Dict: | |
| """Create bar chart configuration""" | |
| labels = [] | |
| data_points = [] | |
| for metric in metrics[:8]: # Limit to 8 bars for readability | |
| context = metric.get('context', '') | |
| # Extract meaningful label from context | |
| label = self._extract_label_from_context(context) or f"Metric {len(labels)+1}" | |
| labels.append(label) | |
| data_points.append(self._extract_numeric_value(metric.get('metric', '0'))) | |
| return { | |
| 'type': 'bar', | |
| 'data': { | |
| 'labels': labels, | |
| 'datasets': [{ | |
| 'label': title, | |
| 'data': data_points, | |
| 'backgroundColor': self.chart_colors[:len(data_points)], | |
| 'borderColor': self.chart_colors[:len(data_points)], | |
| 'borderWidth': 1 | |
| }] | |
| }, | |
| 'options': { | |
| 'responsive': True, | |
| 'plugins': { | |
| 'title': { | |
| 'display': True, | |
| 'text': f'{title} - Comparative Analysis' | |
| }, | |
| 'legend': { | |
| 'display': False | |
| } | |
| }, | |
| 'scales': { | |
| 'y': { | |
| 'beginAtZero': True, | |
| 'title': { | |
| 'display': True, | |
| 'text': 'Value' | |
| } | |
| }, | |
| 'x': { | |
| 'title': { | |
| 'display': True, | |
| 'text': 'Categories' | |
| } | |
| } | |
| } | |
| } | |
| } | |
| def _create_pie_chart(self, metrics: List[Dict], title: str) -> Dict: | |
| """Create pie chart configuration""" | |
| labels = [] | |
| data_points = [] | |
| for metric in metrics[:6]: # Limit to 6 slices for readability | |
| context = metric.get('context', '') | |
| label = self._extract_label_from_context(context) or f"Category {len(labels)+1}" | |
| labels.append(label) | |
| data_points.append(self._extract_numeric_value(metric.get('metric', '0'))) | |
| return { | |
| 'type': 'pie', | |
| 'data': { | |
| 'labels': labels, | |
| 'datasets': [{ | |
| 'data': data_points, | |
| 'backgroundColor': self.chart_colors[:len(data_points)], | |
| 'borderColor': '#ffffff', | |
| 'borderWidth': 2 | |
| }] | |
| }, | |
| 'options': { | |
| 'responsive': True, | |
| 'plugins': { | |
| 'title': { | |
| 'display': True, | |
| 'text': f'{title} - Distribution Analysis' | |
| }, | |
| 'legend': { | |
| 'position': 'right' | |
| } | |
| } | |
| } | |
| } | |
| def _create_doughnut_chart(self, metrics: List[Dict], title: str) -> Dict: | |
| """Create doughnut chart configuration""" | |
| config = self._create_pie_chart(metrics, title) | |
| config['type'] = 'doughnut' | |
| config['options']['plugins']['title']['text'] = f'{title} - Key Metrics Overview' | |
| return config | |
| def _create_default_chart(self, metrics: List[Dict], title: str) -> Dict: | |
| """Create default chart when type cannot be determined""" | |
| return self._create_bar_chart(metrics, title) | |
| def _extract_numeric_value(self, metric_str: str) -> float: | |
| """Extract numeric value from metric string""" | |
| import re | |
| if not metric_str: | |
| return 0.0 | |
| # Remove common non-numeric characters | |
| cleaned = re.sub(r'[^0-9.,\-+]', '', str(metric_str)) | |
| # Handle percentages | |
| if '%' in str(metric_str): | |
| cleaned = cleaned.replace('%', '') | |
| # Handle currency | |
| if '$' in str(metric_str): | |
| cleaned = cleaned.replace('$', '') | |
| # Handle billions, millions, etc. | |
| if 'billion' in str(metric_str).lower(): | |
| try: | |
| return float(cleaned.replace(',', '')) * 1000000000 | |
| except: | |
| return 0.0 | |
| elif 'million' in str(metric_str).lower(): | |
| try: | |
| return float(cleaned.replace(',', '')) * 1000000 | |
| except: | |
| return 0.0 | |
| # Try to convert to float | |
| try: | |
| return float(cleaned.replace(',', '')) | |
| except: | |
| return 0.0 | |
| def _extract_label_from_context(self, context: str) -> Optional[str]: | |
| """Extract meaningful label from context""" | |
| if not context: | |
| return None | |
| # Simple extraction of first few words | |
| words = context.split()[:3] | |
| return ' '.join(words) if words else None | |
| def create_multi_series_chart(self, data_series: List[Dict], title: str) -> Dict: | |
| """Create chart with multiple data series""" | |
| datasets = [] | |
| labels = [] | |
| for i, series in enumerate(data_series): | |
| series_data = series.get('data', []) | |
| datasets.append({ | |
| 'label': series.get('name', f'Series {i+1}'), | |
| 'data': [self._extract_numeric_value(str(val)) for val in series_data], | |
| 'borderColor': self.chart_colors[i % len(self.chart_colors)], | |
| 'backgroundColor': self.chart_colors[i % len(self.chart_colors)] + '20', | |
| 'tension': 0.4 | |
| }) | |
| if not labels and series.get('labels'): | |
| labels = series.get('labels', []) | |
| if not labels: | |
| labels = [f"Point {i+1}" for i in range(max(len(ds['data']) for ds in datasets) if datasets else 0)] | |
| return { | |
| 'type': 'line', | |
| 'data': { | |
| 'labels': labels, | |
| 'datasets': datasets | |
| }, | |
| 'options': { | |
| 'responsive': True, | |
| 'plugins': { | |
| 'title': { | |
| 'display': True, | |
| 'text': f'{title} - Multi-Series Analysis' | |
| }, | |
| 'legend': { | |
| 'position': 'top' | |
| } | |
| }, | |
| 'scales': { | |
| 'y': { | |
| 'beginAtZero': True, | |
| 'title': { | |
| 'display': True, | |
| 'text': 'Value' | |
| } | |
| } | |
| } | |
| } | |
| } | |
| def generate_chart_html(self, chart_config: Dict, chart_id: str) -> str: | |
| """Generate HTML for embedding chart""" | |
| return f""" | |
| <div class="chart-container" style="position: relative; height: 400px; margin: 20px 0;"> | |
| <canvas id="{chart_id}"></canvas> | |
| </div> | |
| <script> | |
| const chartConfig_{chart_id} = {json.dumps(chart_config)}; | |
| const ctx_{chart_id} = document.getElementById('{chart_id}').getContext('2d'); | |
| new Chart(ctx_{chart_id}, chartConfig_{chart_id}); | |
| </script> | |
| """ | |