| from typing import List, Dict, Any, Optional |
| import json |
|
|
| class DataVisualizationEngine: |
| """Generate chart configurations from query results""" |
| |
| def create_chart(self, data: List[Dict], chart_type: str, config: Dict[str, Any]) -> Dict[str, Any]: |
| """ |
| Create chart configuration from data |
| |
| Args: |
| data: Query results |
| chart_type: bar, line, pie, area, scatter, heatmap |
| config: { |
| "x_column": "date", |
| "y_column": "revenue", |
| "group_by": "category", |
| "title": "Revenue by Date", |
| "colors": ["#FFB800", "#00FF88"] |
| } |
| """ |
| try: |
| if not data: |
| return {'ok': False, 'error': 'No data provided'} |
| |
| x_column = config.get('x_column') |
| y_column = config.get('y_column') |
| |
| if chart_type == 'pie': |
| return self._create_pie_chart(data, config) |
| elif chart_type == 'bar': |
| return self._create_bar_chart(data, x_column, y_column, config) |
| elif chart_type == 'line': |
| return self._create_line_chart(data, x_column, y_column, config) |
| elif chart_type == 'area': |
| return self._create_area_chart(data, x_column, y_column, config) |
| elif chart_type == 'scatter': |
| return self._create_scatter_chart(data, x_column, y_column, config) |
| elif chart_type == 'heatmap': |
| return self._create_heatmap(data, config) |
| else: |
| return {'ok': False, 'error': f'Unknown chart type: {chart_type}'} |
| |
| except Exception as e: |
| return {'ok': False, 'error': str(e)} |
| |
| def _create_pie_chart(self, data: List[Dict], config: Dict) -> Dict: |
| """Create pie chart configuration""" |
| label_column = config.get('label_column') or list(data[0].keys())[0] |
| value_column = config.get('value_column') or list(data[0].keys())[1] |
| |
| labels = [str(row.get(label_column, '')) for row in data] |
| values = [float(row.get(value_column, 0)) for row in data] |
| |
| return { |
| 'ok': True, |
| 'chart_type': 'pie', |
| 'data': { |
| 'labels': labels, |
| 'datasets': [{ |
| 'data': values, |
| 'backgroundColor': config.get('colors', self._get_default_colors(len(labels))) |
| }] |
| }, |
| 'options': { |
| 'title': config.get('title', 'Pie Chart'), |
| 'responsive': True |
| } |
| } |
| |
| def _create_bar_chart(self, data: List[Dict], x_column: str, y_column: str, config: Dict) -> Dict: |
| """Create bar chart configuration""" |
| labels = [str(row.get(x_column, '')) for row in data] |
| values = [float(row.get(y_column, 0)) for row in data] |
| |
| group_by = config.get('group_by') |
| |
| if group_by: |
| |
| datasets = self._create_grouped_datasets(data, x_column, y_column, group_by, config) |
| else: |
| |
| datasets = [{ |
| 'label': y_column, |
| 'data': values, |
| 'backgroundColor': config.get('colors', [self._get_default_colors(1)[0]])[0] |
| }] |
| |
| return { |
| 'ok': True, |
| 'chart_type': 'bar', |
| 'data': { |
| 'labels': labels if not group_by else list(set(labels)), |
| 'datasets': datasets |
| }, |
| 'options': { |
| 'title': config.get('title', 'Bar Chart'), |
| 'responsive': True, |
| 'scales': { |
| 'y': {'beginAtZero': True} |
| } |
| } |
| } |
| |
| def _create_line_chart(self, data: List[Dict], x_column: str, y_column: str, config: Dict) -> Dict: |
| """Create line chart configuration""" |
| labels = [str(row.get(x_column, '')) for row in data] |
| values = [float(row.get(y_column, 0)) for row in data] |
| |
| group_by = config.get('group_by') |
| |
| if group_by: |
| datasets = self._create_grouped_datasets(data, x_column, y_column, group_by, config, chart_type='line') |
| else: |
| datasets = [{ |
| 'label': y_column, |
| 'data': values, |
| 'borderColor': config.get('colors', [self._get_default_colors(1)[0]])[0], |
| 'fill': False, |
| 'tension': 0.4 |
| }] |
| |
| return { |
| 'ok': True, |
| 'chart_type': 'line', |
| 'data': { |
| 'labels': labels if not group_by else list(set(labels)), |
| 'datasets': datasets |
| }, |
| 'options': { |
| 'title': config.get('title', 'Line Chart'), |
| 'responsive': True, |
| 'scales': { |
| 'y': {'beginAtZero': True} |
| } |
| } |
| } |
| |
| def _create_area_chart(self, data: List[Dict], x_column: str, y_column: str, config: Dict) -> Dict: |
| """Create area chart configuration""" |
| chart = self._create_line_chart(data, x_column, y_column, config) |
| chart['chart_type'] = 'area' |
| |
| |
| for dataset in chart['data']['datasets']: |
| dataset['fill'] = True |
| dataset['backgroundColor'] = dataset['borderColor'] + '40' |
| |
| return chart |
| |
| def _create_scatter_chart(self, data: List[Dict], x_column: str, y_column: str, config: Dict) -> Dict: |
| """Create scatter chart configuration""" |
| points = [{'x': float(row.get(x_column, 0)), 'y': float(row.get(y_column, 0))} for row in data] |
| |
| return { |
| 'ok': True, |
| 'chart_type': 'scatter', |
| 'data': { |
| 'datasets': [{ |
| 'label': f'{y_column} vs {x_column}', |
| 'data': points, |
| 'backgroundColor': config.get('colors', [self._get_default_colors(1)[0]])[0] |
| }] |
| }, |
| 'options': { |
| 'title': config.get('title', 'Scatter Plot'), |
| 'responsive': True, |
| 'scales': { |
| 'x': {'title': {'display': True, 'text': x_column}}, |
| 'y': {'title': {'display': True, 'text': y_column}} |
| } |
| } |
| } |
| |
| def _create_heatmap(self, data: List[Dict], config: Dict) -> Dict: |
| """Create heatmap configuration""" |
| x_column = config.get('x_column') |
| y_column = config.get('y_column') |
| value_column = config.get('value_column') |
| |
| |
| x_values = sorted(list(set(str(row.get(x_column, '')) for row in data))) |
| y_values = sorted(list(set(str(row.get(y_column, '')) for row in data))) |
| |
| matrix = [] |
| for y in y_values: |
| row = [] |
| for x in x_values: |
| value = next((float(r.get(value_column, 0)) for r in data |
| if str(r.get(x_column)) == x and str(r.get(y_column)) == y), 0) |
| row.append(value) |
| matrix.append(row) |
| |
| return { |
| 'ok': True, |
| 'chart_type': 'heatmap', |
| 'data': { |
| 'x_labels': x_values, |
| 'y_labels': y_values, |
| 'matrix': matrix |
| }, |
| 'options': { |
| 'title': config.get('title', 'Heatmap'), |
| 'responsive': True |
| } |
| } |
| |
| def _create_grouped_datasets(self, data: List[Dict], x_column: str, y_column: str, |
| group_by: str, config: Dict, chart_type: str = 'bar') -> List[Dict]: |
| """Create datasets for grouped charts""" |
| groups = {} |
| for row in data: |
| group = str(row.get(group_by, 'Unknown')) |
| if group not in groups: |
| groups[group] = [] |
| groups[group].append(row) |
| |
| colors = config.get('colors', self._get_default_colors(len(groups))) |
| datasets = [] |
| |
| for i, (group_name, group_data) in enumerate(groups.items()): |
| values = [float(row.get(y_column, 0)) for row in group_data] |
| |
| dataset = { |
| 'label': group_name, |
| 'data': values |
| } |
| |
| if chart_type == 'line': |
| dataset['borderColor'] = colors[i] |
| dataset['fill'] = False |
| dataset['tension'] = 0.4 |
| else: |
| dataset['backgroundColor'] = colors[i] |
| |
| datasets.append(dataset) |
| |
| return datasets |
| |
| def _get_default_colors(self, count: int) -> List[str]: |
| """Get default color palette""" |
| base_colors = [ |
| '#FFB800', |
| '#00FF88', |
| '#FF3B5C', |
| '#0095FF', |
| '#9D4EDD', |
| '#06FFA5', |
| '#FF006E', |
| '#FFBE0B', |
| ] |
| |
| |
| return (base_colors * ((count // len(base_colors)) + 1))[:count] |
| |
| def get_chart_types(self) -> List[Dict[str, str]]: |
| """Get available chart types""" |
| return [ |
| {'value': 'bar', 'label': 'Bar Chart', 'icon': 'π'}, |
| {'value': 'line', 'label': 'Line Chart', 'icon': 'π'}, |
| {'value': 'area', 'label': 'Area Chart', 'icon': 'π'}, |
| {'value': 'pie', 'label': 'Pie Chart', 'icon': 'π₯§'}, |
| {'value': 'scatter', 'label': 'Scatter Plot', 'icon': 'β«'}, |
| {'value': 'heatmap', 'label': 'Heatmap', 'icon': 'π₯'}, |
| ] |
| |
| def analyze_data_for_chart(self, data: List[Dict]) -> Dict[str, Any]: |
| """Analyze data and suggest best chart type""" |
| if not data: |
| return {'ok': False, 'error': 'No data'} |
| |
| columns = list(data[0].keys()) |
| numeric_columns = [] |
| categorical_columns = [] |
| |
| for col in columns: |
| sample_value = data[0].get(col) |
| try: |
| float(sample_value) |
| numeric_columns.append(col) |
| except: |
| categorical_columns.append(col) |
| |
| suggestions = [] |
| |
| if len(categorical_columns) >= 1 and len(numeric_columns) >= 1: |
| suggestions.append({ |
| 'type': 'bar', |
| 'config': { |
| 'x_column': categorical_columns[0], |
| 'y_column': numeric_columns[0] |
| }, |
| 'reason': 'Good for comparing categories' |
| }) |
| |
| if len(numeric_columns) >= 2: |
| suggestions.append({ |
| 'type': 'scatter', |
| 'config': { |
| 'x_column': numeric_columns[0], |
| 'y_column': numeric_columns[1] |
| }, |
| 'reason': 'Good for correlation analysis' |
| }) |
| |
| if len(categorical_columns) >= 1 and len(numeric_columns) >= 1: |
| suggestions.append({ |
| 'type': 'pie', |
| 'config': { |
| 'label_column': categorical_columns[0], |
| 'value_column': numeric_columns[0] |
| }, |
| 'reason': 'Good for showing proportions' |
| }) |
| |
| return { |
| 'ok': True, |
| 'columns': { |
| 'numeric': numeric_columns, |
| 'categorical': categorical_columns |
| }, |
| 'suggestions': suggestions |
| } |
|
|
| data_visualization_engine = DataVisualizationEngine() |
|
|