fix(douyu): aggregate audience trend by minute
This commit is contained in:
@@ -209,28 +209,56 @@ def _render_audience_trend_chart(audience_trend: Dict[str, Any]) -> str:
|
||||
plot_width = width - padding_left - padding_right
|
||||
plot_height = height - padding_top - padding_bottom
|
||||
|
||||
vip_values = [int(item.get("vip_count", 0) or 0) for item in points]
|
||||
diamond_values = [int(item.get("diamond_count", 0) or 0) for item in points]
|
||||
labels = [str(item.get("timestamp") or "")[-8:-3] for item in points]
|
||||
summary = audience_trend.get("summary", {}) or {}
|
||||
|
||||
def _compress_chart_points(raw_points: List[Dict[str, Any]], max_points: int = 36) -> List[Dict[str, Any]]:
|
||||
if len(raw_points) <= max_points:
|
||||
return list(raw_points)
|
||||
bucket_size = max(len(raw_points) / max_points, 1)
|
||||
compressed: List[Dict[str, Any]] = []
|
||||
for idx in range(max_points):
|
||||
start = int(round(idx * bucket_size))
|
||||
end = int(round((idx + 1) * bucket_size))
|
||||
bucket = raw_points[start:end] or raw_points[start:start + 1]
|
||||
if not bucket:
|
||||
continue
|
||||
compressed.append(dict(bucket[-1]))
|
||||
first = raw_points[0]
|
||||
last = raw_points[-1]
|
||||
if compressed:
|
||||
compressed[0] = dict(first)
|
||||
compressed[-1] = dict(last)
|
||||
return compressed
|
||||
|
||||
chart_points = _compress_chart_points(points, max_points=36)
|
||||
vip_values = [int(item.get("vip_count", 0) or 0) for item in chart_points]
|
||||
diamond_values = [int(item.get("diamond_count", 0) or 0) for item in chart_points]
|
||||
labels = [str(item.get("timestamp") or "")[-8:-3] for item in chart_points]
|
||||
|
||||
vip_min = min(vip_values)
|
||||
vip_max = max(vip_values)
|
||||
diamond_min = min(diamond_values)
|
||||
diamond_max = max(diamond_values)
|
||||
vip_span = max(vip_max - vip_min, 1)
|
||||
diamond_span = max(diamond_max - diamond_min, 1)
|
||||
step_x = plot_width / max(len(points) - 1, 1)
|
||||
vip_padding = max(int((vip_max - vip_min) * 0.12), 60)
|
||||
diamond_padding = 1 if diamond_max - diamond_min <= 2 else max(int((diamond_max - diamond_min) * 0.2), 1)
|
||||
vip_display_min = max(vip_min - vip_padding, 0)
|
||||
vip_display_max = vip_max + vip_padding
|
||||
diamond_display_min = max(diamond_min - diamond_padding, 0)
|
||||
diamond_display_max = diamond_max + diamond_padding
|
||||
vip_span = max(vip_display_max - vip_display_min, 1)
|
||||
diamond_span = max(diamond_display_max - diamond_display_min, 1)
|
||||
step_x = plot_width / max(len(chart_points) - 1, 1)
|
||||
bar_width = max(min(step_x * 0.58, 18), 6)
|
||||
|
||||
def x_at(index: int) -> float:
|
||||
return padding_left + step_x * index
|
||||
|
||||
def y_vip(value: int) -> float:
|
||||
ratio = (value - vip_min) / vip_span if vip_span else 0
|
||||
ratio = (value - vip_display_min) / vip_span if vip_span else 0
|
||||
return padding_top + plot_height - ratio * plot_height
|
||||
|
||||
def y_diamond(value: int) -> float:
|
||||
ratio = (value - diamond_min) / diamond_span if diamond_span else 0
|
||||
ratio = (value - diamond_display_min) / diamond_span if diamond_span else 0
|
||||
return padding_top + plot_height - ratio * plot_height
|
||||
|
||||
grid_lines = []
|
||||
@@ -239,8 +267,8 @@ def _render_audience_trend_chart(audience_trend: Dict[str, Any]) -> str:
|
||||
for idx in range(5):
|
||||
ratio = idx / 4
|
||||
y = padding_top + plot_height - ratio * plot_height
|
||||
vip_tick = round(vip_min + vip_span * ratio)
|
||||
diamond_tick = round(diamond_min + diamond_span * ratio)
|
||||
vip_tick = round(vip_display_min + vip_span * ratio)
|
||||
diamond_tick = round(diamond_display_min + diamond_span * ratio)
|
||||
grid_lines.append(
|
||||
f'<line x1="{padding_left}" y1="{y:.1f}" x2="{width - padding_right}" y2="{y:.1f}" class="chart-grid" />'
|
||||
)
|
||||
@@ -256,14 +284,14 @@ def _render_audience_trend_chart(audience_trend: Dict[str, Any]) -> str:
|
||||
dot_points = []
|
||||
label_marks = []
|
||||
annotations = []
|
||||
if len(points) <= 12:
|
||||
label_indexes = list(range(len(points)))
|
||||
if len(chart_points) <= 12:
|
||||
label_indexes = list(range(len(chart_points)))
|
||||
else:
|
||||
label_indexes = sorted(set([0, len(points) - 1] + [int(round((len(points) - 1) * i / 6)) for i in range(1, 6)]))
|
||||
label_indexes = sorted(set([0, len(chart_points) - 1] + [int(round((len(chart_points) - 1) * i / 6)) for i in range(1, 6)]))
|
||||
vip_peak_index = vip_values.index(vip_max)
|
||||
vip_valley_index = vip_values.index(vip_min)
|
||||
diamond_latest_index = len(points) - 1
|
||||
for idx, item in enumerate(points):
|
||||
diamond_latest_index = len(chart_points) - 1
|
||||
for idx, item in enumerate(chart_points):
|
||||
x = x_at(idx)
|
||||
vip_y = y_vip(int(item.get("vip_count", 0) or 0))
|
||||
diamond_y = y_diamond(int(item.get("diamond_count", 0) or 0))
|
||||
@@ -300,9 +328,14 @@ def _render_audience_trend_chart(audience_trend: Dict[str, Any]) -> str:
|
||||
)
|
||||
|
||||
polyline = f'<polyline points="{" ".join(line_points)}" class="diamond-line" />'
|
||||
summary = audience_trend.get("summary", {}) or {}
|
||||
vip_latest = int(summary.get("vip_latest", vip_values[-1]) or 0)
|
||||
diamond_latest = int(summary.get("diamond_latest", diamond_values[-1]) or 0)
|
||||
session_start = str(summary.get("session_start") or "")
|
||||
first_point_time = str(summary.get("first_point_time") or "")
|
||||
leading_gap_minutes = int(summary.get("leading_gap_minutes", 0) or 0)
|
||||
note_text = ""
|
||||
if session_start and first_point_time and leading_gap_minutes >= 20:
|
||||
note_text = f"采样起点 {first_point_time[-8:-3]},早于该时段的人数数据未记录"
|
||||
|
||||
return f"""
|
||||
<div class="chart-wrap">
|
||||
@@ -314,6 +347,7 @@ def _render_audience_trend_chart(audience_trend: Dict[str, Any]) -> str:
|
||||
<span class="legend-meta">最新:贵宾 {_escape(vip_latest)} / 钻粉 {_escape(diamond_latest)}</span>
|
||||
</div>
|
||||
</div>
|
||||
{'<div class="chart-note">' + _escape(note_text) + '</div>' if note_text else ''}
|
||||
<svg viewBox="0 0 {width} {height}" class="audience-chart" role="img" aria-label="贵宾与钻粉趋势图">
|
||||
<defs>
|
||||
<linearGradient id="vipBarGradient" x1="0" y1="0" x2="0" y2="1">
|
||||
@@ -744,6 +778,12 @@ def render_daily_report_html(
|
||||
gap: 12px;
|
||||
margin-bottom: 10px;
|
||||
}}
|
||||
.chart-note {{
|
||||
margin: -2px 0 8px;
|
||||
color: #7b8798;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}}
|
||||
.chart-title {{
|
||||
font-size: 17px;
|
||||
font-weight: 800;
|
||||
|
||||
Reference in New Issue
Block a user