234 lines
6.4 KiB
Python
Executable File
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()
|