shop-old/modules/import/chart_service.py
2026-04-20 01:03:43 +02:00

234 lines
6.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Chart Generation Service with Plotly
Generates modern, interactive charts from statistical data
"""
import json
import sys
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import plotly.colors as pc
def generate_chart(data, chart_config):
"""
Generate Plotly chart from statistical data
Args:
data: List of lists - first row is headers, rest is data
chart_config: Dict with chart configuration (title, type, etc.)
Returns:
HTML string with embedded Plotly chart
"""
if not data or len(data) < 2:
return "<div>Keine Daten verfügbar</div>"
headers = data[0]
rows = data[1:]
# Extract x-axis labels (first column)
x_labels = [str(row[0]) for row in rows]
# Prepare traces for each data series
fig = go.Figure()
# Color palette - modern and professional
colors = pc.qualitative.Plotly
# Create a trace for each column (except first which is x-axis)
for i in range(1, len(headers)):
y_values = [row[i] if i < len(row) else 0 for row in rows]
fig.add_trace(go.Bar(
name=headers[i],
x=x_labels,
y=y_values,
marker_color=colors[i % len(colors)],
hovertemplate='<b>%{x}</b><br>%{fullData.name}: %{y}<extra></extra>'
))
# Update layout with modern styling
fig.update_layout(
title={
'text': chart_config.get('title', 'Statistik'),
'x': 0.5,
'xanchor': 'center',
'font': {'size': 20, 'color': '#333'}
},
barmode='group',
plot_bgcolor='rgba(240, 240, 240, 0.5)',
paper_bgcolor='white',
font={'family': 'Arial, sans-serif', 'size': 12, 'color': '#333'},
hovermode='x unified',
legend={
'orientation': 'h',
'yanchor': 'bottom',
'y': -0.2,
'xanchor': 'center',
'x': 0.5,
'bgcolor': 'rgba(255, 255, 255, 0.8)',
'bordercolor': '#ccc',
'borderwidth': 1
},
xaxis={
'title': headers[0] if headers else '',
'showgrid': False,
'showline': True,
'linecolor': '#ccc'
},
yaxis={
'title': 'Anzahl',
'showgrid': True,
'gridcolor': 'rgba(200, 200, 200, 0.3)',
'showline': True,
'linecolor': '#ccc'
},
margin={'l': 60, 'r': 40, 't': 80, 'b': 100},
height=500
)
# Generate HTML with Plotly CDN
html = fig.to_html(
include_plotlyjs='cdn',
div_id='plotly-chart',
config={
'displayModeBar': True,
'displaylogo': False,
'modeBarButtonsToRemove': ['pan2d', 'lasso2d', 'select2d'],
'responsive': True
}
)
return html
def generate_line_chart(data, chart_config):
"""
Generate Plotly line chart for time series data
"""
if not data or len(data) < 2:
return "<div>Keine Daten verfügbar</div>"
headers = data[0]
rows = data[1:]
x_labels = [str(row[0]) for row in rows]
fig = go.Figure()
colors = pc.qualitative.Plotly
# Create line traces
for i in range(1, len(headers)):
y_values = [row[i] if i < len(row) else 0 for row in rows]
fig.add_trace(go.Scatter(
name=headers[i],
x=x_labels,
y=y_values,
mode='lines+markers',
line={'color': colors[i % len(colors)], 'width': 3},
marker={'size': 8},
hovertemplate='<b>%{x}</b><br>%{fullData.name}: %{y}<extra></extra>'
))
# Update layout - NO fixed y-axis minimum for line charts
fig.update_layout(
title={
'text': chart_config.get('title', 'Statistik Verlauf'),
'x': 0.5,
'xanchor': 'center',
'font': {'size': 20, 'color': '#333'}
},
plot_bgcolor='rgba(240, 240, 240, 0.5)',
paper_bgcolor='white',
font={'family': 'Arial, sans-serif', 'size': 12, 'color': '#333'},
hovermode='x unified',
legend={
'orientation': 'h',
'yanchor': 'bottom',
'y': -0.2,
'xanchor': 'center',
'x': 0.5,
'bgcolor': 'rgba(255, 255, 255, 0.8)',
'bordercolor': '#ccc',
'borderwidth': 1
},
xaxis={
'title': headers[0] if headers else '',
'showgrid': False,
'showline': True,
'linecolor': '#ccc'
},
yaxis={
'title': 'Anzahl',
'showgrid': True,
'gridcolor': 'rgba(200, 200, 200, 0.3)',
'showline': True,
'linecolor': '#ccc',
'rangemode': 'normal', # Auto-scale, don't force to 0
'autorange': True
},
margin={'l': 60, 'r': 40, 't': 80, 'b': 100},
height=500
)
html = fig.to_html(
include_plotlyjs='cdn',
div_id='plotly-line-chart',
config={
'displayModeBar': True,
'displaylogo': False,
'modeBarButtonsToRemove': ['pan2d', 'lasso2d', 'select2d'],
'responsive': True
}
)
return html
def main():
"""
Main entry point - expects JSON input via stdin
Expected format:
{
"data": [[headers], [row1], [row2], ...],
"config": {
"title": "Chart Title",
"type": "bar" or "line",
"period": "year", "month", or "day"
}
}
"""
try:
# Read JSON from stdin
input_data = json.load(sys.stdin)
data = input_data.get('data', [])
config = input_data.get('config', {})
# Generate chart based on type
chart_type = config.get('type', 'bar')
if chart_type == 'line':
html = generate_line_chart(data, config)
else:
html = generate_chart(data, config)
# Output HTML
print(html)
except Exception as e:
error_html = f"""
<div style="padding: 20px; background: #fee; border: 1px solid #fcc; border-radius: 4px;">
<h3 style="color: #c33; margin: 0 0 10px 0;">Chart-Generierung fehlgeschlagen</h3>
<p style="margin: 0; color: #666;">Fehler: {str(e)}</p>
</div>
"""
print(error_html)
sys.exit(1)
if __name__ == '__main__':
main()