Browse Source

fix(report): fix dot chart avg line and mPDF SVG rendering

- Use float-based yAtF() for avg line so it renders as exact
  horizontal line instead of integer-rounded position
- Fix SVG for mPDF: use fixed width/height instead of percentage,
  remove style attribute, remove opacity/transform/g attributes
  that mPDF does not support
- Remove Chinese text from SVG (use English labels) since mPDF
  may not render Chinese inside SVG correctly

Co-authored-by: Cursor <cursoragent@cursor.com>
yemeishu 4 ngày trước cách đây
mục cha
commit
e291c5be56
1 tập tin đã thay đổi với 30 bổ sung30 xóa
  1. 30 30
      scripts/report_teacher_weekly_stats.php

+ 30 - 30
scripts/report_teacher_weekly_stats.php

@@ -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);
     $maxY = max(1, ...$values);
-    $W = min(1200, max(500, $totalDays * 8));
-    $H = 320;
+    $W = 800;
+    $H = 360;
     $padL = 48;
     $padL = 48;
-    $padR = 24;
-    $padT = 42;
-    $padB = 72;
+    $padR = 50;
+    $padT = 32;
+    $padB = 68;
     $gw = $W - $padL - $padR;
     $gw = $W - $padL - $padR;
     $gh = $H - $padT - $padB;
     $gh = $H - $padT - $padB;
     $n = max(1, count($labels));
     $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 {
     $xAt = static function (int $i) use ($padL, $gw, $n): float {
         return $padL + ($n <= 1 ? $gw / 2 : $gw * $i / ($n - 1));
         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;
         return $padT + $gh - ($v / $maxY) * $gh;
     };
     };
+    $yAt = static function (int $v) use ($yAtF): float {
+        return $yAtF((float) $v);
+    };
 
 
     $svg = sprintf(
     $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);
     $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
             $padL, $y, $padL + $gw, $y
         );
         );
         $svg .= sprintf(
         $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
             4, $y + 3, $v
         );
         );
     }
     }
 
 
-    // X axis line
+    // X axis
     $xAxisY = $padT + $gh;
     $xAxisY = $padT + $gh;
     $svg .= sprintf(
     $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
         $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(
     $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
         $padL, $avgY, $padL + $gw, $avgY
     );
     );
     $svg .= sprintf(
     $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
         $padL + $gw + 2, $avgY + 3, $avg
     );
     );
 
 
     // Dots + polyline
     // Dots + polyline
     $pts = [];
     $pts = [];
     $dots = '';
     $dots = '';
-    $xLabelStep = max(1, (int) ceil($n / 16));
+    $xLabelStep = max(1, (int) ceil($n / 12));
     $xLabels = '';
     $xLabels = '';
     for ($i = 0; $i < $n; $i++) {
     for ($i = 0; $i < $n; $i++) {
         $x = $xAt($i);
         $x = $xAt($i);
@@ -602,36 +605,33 @@ $buildDotChartHtml = static function (array $labels, array $values, int $totalDa
         $color = $values[$i] >= $avg ? '#2563eb' : '#93c5fd';
         $color = $values[$i] >= $avg ? '#2563eb' : '#93c5fd';
         $radius = $values[$i] >= $avg ? 3.5 : 2.8;
         $radius = $values[$i] >= $avg ? 3.5 : 2.8;
         $dots .= sprintf(
         $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
             $x, $y, $radius, $color
         );
         );
 
 
         if ($i % $xLabelStep === 0 || $i === $n - 1) {
         if ($i % $xLabelStep === 0 || $i === $n - 1) {
             $xLabels .= sprintf(
             $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')
                 htmlspecialchars($labels[$i], ENT_QUOTES, 'UTF-8')
             );
             );
         }
         }
     }
     }
 
 
-    // Polyline connecting dots
     $svg .= sprintf(
     $svg .= sprintf(
-        '<polyline fill="none" stroke="#93c5fd" stroke-width="1" points="%s" />',
+        '<polyline fill="none" stroke="#93c5fd" stroke-width="1" points="%s"/>',
         implode(' ', $pts)
         implode(' ', $pts)
     );
     );
     $svg .= $dots;
     $svg .= $dots;
     $svg .= $xLabels;
     $svg .= $xLabels;
 
 
     // Legend
     // 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>';
     $svg .= '</svg>';
     return $svg;
     return $svg;