|
|
@@ -531,14 +531,14 @@ foreach ($dotValues as $di => $dv) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-$buildDotChartHtml = static function (array $labels, array $values, int $totalDays, float $avg): string {
|
|
|
+$buildDotChartHtml = static function (array $labels, array $values, int $totalDays, float $avg) use ($tz, $generatedAt): string {
|
|
|
$maxY = max(1, ...$values);
|
|
|
- $W = min(1200, max(500, $totalDays * 8));
|
|
|
- $H = 320;
|
|
|
+ $W = 800;
|
|
|
+ $H = 360;
|
|
|
$padL = 48;
|
|
|
- $padR = 24;
|
|
|
- $padT = 42;
|
|
|
- $padB = 72;
|
|
|
+ $padR = 50;
|
|
|
+ $padT = 32;
|
|
|
+ $padB = 68;
|
|
|
$gw = $W - $padL - $padR;
|
|
|
$gh = $H - $padT - $padB;
|
|
|
$n = max(1, count($labels));
|
|
|
@@ -546,13 +546,16 @@ $buildDotChartHtml = static function (array $labels, array $values, int $totalDa
|
|
|
$xAt = static function (int $i) use ($padL, $gw, $n): float {
|
|
|
return $padL + ($n <= 1 ? $gw / 2 : $gw * $i / ($n - 1));
|
|
|
};
|
|
|
- $yAt = static function (int $v) use ($padT, $gh, $maxY): float {
|
|
|
+ $yAtF = static function (float $v) use ($padT, $gh, $maxY): float {
|
|
|
return $padT + $gh - ($v / $maxY) * $gh;
|
|
|
};
|
|
|
+ $yAt = static function (int $v) use ($yAtF): float {
|
|
|
+ return $yAtF((float) $v);
|
|
|
+ };
|
|
|
|
|
|
$svg = sprintf(
|
|
|
- '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 %d %d" width="100%%" height="auto" style="max-width:100%%;">',
|
|
|
- $W, $H
|
|
|
+ '<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" viewBox="0 0 %d %d">',
|
|
|
+ $W, $H, $W, $H
|
|
|
);
|
|
|
$svg .= sprintf('<rect x="0" y="0" width="%d" height="%d" fill="#fafafa"/>', $W, $H);
|
|
|
|
|
|
@@ -566,33 +569,33 @@ $buildDotChartHtml = static function (array $labels, array $values, int $totalDa
|
|
|
$padL, $y, $padL + $gw, $y
|
|
|
);
|
|
|
$svg .= sprintf(
|
|
|
- '<text x="%d" y="%.1f" font-size="8" fill="#6b7280" font-family="sun-exta,sans-serif">%d</text>',
|
|
|
+ '<text x="%d" y="%.1f" font-size="8" fill="#6b7280">%d</text>',
|
|
|
4, $y + 3, $v
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- // X axis line
|
|
|
+ // X axis
|
|
|
$xAxisY = $padT + $gh;
|
|
|
$svg .= sprintf(
|
|
|
- '<line x1="%d" y1="%.1f" x2="%d" y1="%.1f" stroke="#9ca3af" stroke-width="1"/>',
|
|
|
+ '<line x1="%d" y1="%.1f" x2="%d" y2="%.1f" stroke="#9ca3af" stroke-width="1"/>',
|
|
|
$padL, $xAxisY, $padL + $gw, $xAxisY
|
|
|
);
|
|
|
|
|
|
- // Avg line
|
|
|
- $avgY = $yAt((int) round($avg));
|
|
|
+ // Avg line — use float for exact horizontal position
|
|
|
+ $avgY = $yAtF($avg);
|
|
|
$svg .= sprintf(
|
|
|
- '<line x1="%d" y1="%.1f" x2="%d" y1="%.1f" stroke="#f59e0b" stroke-width="1.2" stroke-dasharray="5,3"/>',
|
|
|
+ '<line x1="%d" y1="%.2f" x2="%d" y2="%.2f" stroke="#f59e0b" stroke-width="1.2" stroke-dasharray="5,3"/>',
|
|
|
$padL, $avgY, $padL + $gw, $avgY
|
|
|
);
|
|
|
$svg .= sprintf(
|
|
|
- '<text x="%d" y="%.1f" font-size="8" fill="#d97706" font-family="sun-exta,sans-serif">均值 %.0f</text>',
|
|
|
+ '<text x="%d" y="%.1f" font-size="8" fill="#d97706">avg %.0f</text>',
|
|
|
$padL + $gw + 2, $avgY + 3, $avg
|
|
|
);
|
|
|
|
|
|
// Dots + polyline
|
|
|
$pts = [];
|
|
|
$dots = '';
|
|
|
- $xLabelStep = max(1, (int) ceil($n / 16));
|
|
|
+ $xLabelStep = max(1, (int) ceil($n / 12));
|
|
|
$xLabels = '';
|
|
|
for ($i = 0; $i < $n; $i++) {
|
|
|
$x = $xAt($i);
|
|
|
@@ -602,36 +605,33 @@ $buildDotChartHtml = static function (array $labels, array $values, int $totalDa
|
|
|
$color = $values[$i] >= $avg ? '#2563eb' : '#93c5fd';
|
|
|
$radius = $values[$i] >= $avg ? 3.5 : 2.8;
|
|
|
$dots .= sprintf(
|
|
|
- '<circle cx="%.1f" cy="%.1f" r="%.1f" fill="%s" opacity="0.85"/>',
|
|
|
+ '<circle cx="%.1f" cy="%.1f" r="%.1f" fill="%s"/>',
|
|
|
$x, $y, $radius, $color
|
|
|
);
|
|
|
|
|
|
if ($i % $xLabelStep === 0 || $i === $n - 1) {
|
|
|
$xLabels .= sprintf(
|
|
|
- '<text x="%.1f" y="%d" text-anchor="middle" font-size="7.5" fill="#6b7280" font-family="sun-exta,sans-serif" transform="rotate(-45,%.1f,%d)">%s</text>',
|
|
|
- $x, $xAxisY + 14, $x, $xAxisY + 14,
|
|
|
+ '<text x="%.1f" y="%.1f" text-anchor="middle" font-size="7" fill="#6b7280">%s</text>',
|
|
|
+ $x, $xAxisY + 12,
|
|
|
htmlspecialchars($labels[$i], ENT_QUOTES, 'UTF-8')
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // Polyline connecting dots
|
|
|
$svg .= sprintf(
|
|
|
- '<polyline fill="none" stroke="#93c5fd" stroke-width="1" points="%s" />',
|
|
|
+ '<polyline fill="none" stroke="#93c5fd" stroke-width="1" points="%s"/>',
|
|
|
implode(' ', $pts)
|
|
|
);
|
|
|
$svg .= $dots;
|
|
|
$svg .= $xLabels;
|
|
|
|
|
|
// Legend
|
|
|
- $svg .= '<g font-family="sun-exta,sans-serif" font-size="9">';
|
|
|
- $svg .= sprintf('<circle cx="%d" cy="%d" r="3.5" fill="#2563eb" opacity="0.85"/>', $padL + 4, $H - 20);
|
|
|
- $svg .= sprintf('<text x="%d" y="%d" fill="#111">≥ 均值</text>', $padL + 12, $H - 16);
|
|
|
- $svg .= sprintf('<circle cx="%d" cy="%d" r="2.8" fill="#93c5fd" opacity="0.85"/>', $padL + 64, $H - 20);
|
|
|
- $svg .= sprintf('<text x="%d" y="%d" fill="#111">< 均值</text>', $padL + 72, $H - 16);
|
|
|
- $svg .= sprintf('<line x1="%d" y1="%d" x2="%d" y2="%d" stroke="#f59e0b" stroke-width="1.2" stroke-dasharray="5,3"/>', $padL + 124, $H - 20, $padL + 140, $H - 20);
|
|
|
- $svg .= sprintf('<text x="%d" y="%d" fill="#d97706">日均线</text>', $padL + 144, $H - 16);
|
|
|
- $svg .= '</g>';
|
|
|
+ $svg .= sprintf('<circle cx="%d" cy="%d" r="3.5" fill="#2563eb"/>', $padL + 4, $H - 16);
|
|
|
+ $svg .= sprintf('<text x="%d" y="%d" font-size="9" fill="#111">≥ avg</text>', $padL + 12, $H - 12);
|
|
|
+ $svg .= sprintf('<circle cx="%d" cy="%d" r="2.8" fill="#93c5fd"/>', $padL + 54, $H - 16);
|
|
|
+ $svg .= sprintf('<text x="%d" y="%d" font-size="9" fill="#111">< avg</text>', $padL + 62, $H - 12);
|
|
|
+ $svg .= sprintf('<line x1="%d" y1="%d" x2="%d" y2="%d" stroke="#f59e0b" stroke-width="1.2" stroke-dasharray="5,3"/>', $padL + 104, $H - 16, $padL + 120, $H - 16);
|
|
|
+ $svg .= sprintf('<text x="%d" y="%d" font-size="9" fill="#d97706">daily avg</text>', $padL + 124, $H - 12);
|
|
|
|
|
|
$svg .= '</svg>';
|
|
|
return $svg;
|