|
@@ -29,7 +29,7 @@ $endCurrent = now()->timezone($tz);
|
|
|
$todayStart = $endCurrent->copy()->startOfDay();
|
|
$todayStart = $endCurrent->copy()->startOfDay();
|
|
|
$startCurrent = $todayStart->copy()->subDays($reportPeriodDays - 1);
|
|
$startCurrent = $todayStart->copy()->subDays($reportPeriodDays - 1);
|
|
|
$startPrev = $todayStart->copy()->subDays(2 * $reportPeriodDays - 1);
|
|
$startPrev = $todayStart->copy()->subDays(2 * $reportPeriodDays - 1);
|
|
|
-$endPrev = $todayStart->copy()->subDays($reportPeriodDays);
|
|
|
|
|
|
|
+$endPrev = $todayStart->copy()->subDays($reportPeriodDays - 1);
|
|
|
|
|
|
|
|
$db = \Illuminate\Support\Facades\DB::class;
|
|
$db = \Illuminate\Support\Facades\DB::class;
|
|
|
|
|
|
|
@@ -347,12 +347,13 @@ $buildDualChartsHtml = static function (array $curDaily, array $prevDaily): stri
|
|
|
string $legPrev
|
|
string $legPrev
|
|
|
): string {
|
|
): string {
|
|
|
$maxY = max(1, ...$cur, ...$prev);
|
|
$maxY = max(1, ...$cur, ...$prev);
|
|
|
- $W = 248;
|
|
|
|
|
- $H = 206;
|
|
|
|
|
- $padL = 38;
|
|
|
|
|
- $padR = 8;
|
|
|
|
|
- $padT = 14;
|
|
|
|
|
- $padB = 38;
|
|
|
|
|
|
|
+ $W = 296;
|
|
|
|
|
+ $H = 256;
|
|
|
|
|
+ $padL = 42;
|
|
|
|
|
+ /** 右侧、顶部留白:数值标签 text-anchor=middle 时不依赖末列特殊锚点 */
|
|
|
|
|
+ $padR = 62;
|
|
|
|
|
+ $padT = 72;
|
|
|
|
|
+ $padB = 56;
|
|
|
$gw = $W - $padL - $padR;
|
|
$gw = $W - $padL - $padR;
|
|
|
$gh = $H - $padT - $padB;
|
|
$gh = $H - $padT - $padB;
|
|
|
$n = max(1, count($labelsCur));
|
|
$n = max(1, count($labelsCur));
|
|
@@ -382,16 +383,43 @@ $buildDualChartsHtml = static function (array $curDaily, array $prevDaily): stri
|
|
|
return '<polyline fill="none" stroke="'.$stroke.'" stroke-width="'.$sw.'"'.$dashAttr.' points="'.$poly.'" />'.$circles;
|
|
return '<polyline fill="none" stroke="'.$stroke.'" stroke-width="'.$sw.'"'.$dashAttr.' points="'.$poly.'" />'.$circles;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+ /** 标签:每列只放一条「本/上」,y = 最高点y - 固定偏移(以 dominant-baseline=middle 作为中心y) */
|
|
|
|
|
+ $labelFillCur = $strokeCur === '#2563eb' ? '#1d4ed8' : '#c2410c';
|
|
|
|
|
+ $labelFillPrev = $strokePrev === '#93c5fd' ? '#475569' : '#78716c';
|
|
|
|
|
+ $valueLabels = '<g font-family="sun-exta,sans-serif">';
|
|
|
|
|
+ $labelOffset = 22; // 标签中心到点的固定上移像素
|
|
|
|
|
+ for ($vi = 0; $vi < $n; $vi++) {
|
|
|
|
|
+ $xv = $xAt($vi);
|
|
|
|
|
+ $yCv = $yAt((int) $cur[$vi]);
|
|
|
|
|
+ $yPv = $yAt((int) $prev[$vi]);
|
|
|
|
|
+ $vC = (int) $cur[$vi];
|
|
|
|
|
+ $vP = (int) $prev[$vi];
|
|
|
|
|
+ $yUpper = min($yCv, $yPv);
|
|
|
|
|
+ // 允许跑到绘图区上方的留白里;只在极端情况下防止贴到 SVG 顶边
|
|
|
|
|
+ $baseY = max(14, $yUpper - $labelOffset);
|
|
|
|
|
+ $valueLabels .= sprintf(
|
|
|
|
|
+ '<text x="%.1f" y="%.1f" text-anchor="middle" dominant-baseline="middle" font-size="9" font-family="sun-exta,sans-serif"><tspan fill="%s">%d</tspan><tspan fill="#94a3b8">/</tspan><tspan fill="%s">%d</tspan></text>',
|
|
|
|
|
+ $xv,
|
|
|
|
|
+ $baseY,
|
|
|
|
|
+ $labelFillCur,
|
|
|
|
|
+ $vC,
|
|
|
|
|
+ $labelFillPrev,
|
|
|
|
|
+ $vP
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ $valueLabels .= '</g>';
|
|
|
|
|
+
|
|
|
$xAxisY = $padT + $gh;
|
|
$xAxisY = $padT + $gh;
|
|
|
$tickTxt = '';
|
|
$tickTxt = '';
|
|
|
|
|
+ $axisRowY = $H - 48;
|
|
|
for ($i = 0; $i < $n; $i++) {
|
|
for ($i = 0; $i < $n; $i++) {
|
|
|
$x = $xAt($i);
|
|
$x = $xAt($i);
|
|
|
$lc = htmlspecialchars((string) ($labelsCur[$i] ?? ''), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
|
$lc = htmlspecialchars((string) ($labelsCur[$i] ?? ''), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
|
|
$lp = htmlspecialchars((string) ($labelsPrev[$i] ?? ''), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
|
$lp = htmlspecialchars((string) ($labelsPrev[$i] ?? ''), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
|
|
$tickTxt .= sprintf(
|
|
$tickTxt .= sprintf(
|
|
|
- '<text x="%.1f" y="%d" text-anchor="middle" font-size="7" fill="#374151" font-family="sun-exta,sans-serif">%s/%s</text>',
|
|
|
|
|
|
|
+ '<text x="%.1f" y="%d" text-anchor="middle" font-size="8" font-family="sun-exta,sans-serif"><tspan fill="#111827">%s</tspan><tspan fill="#94a3b8">/</tspan><tspan fill="#475569">%s</tspan></text>',
|
|
|
$x,
|
|
$x,
|
|
|
- $H - 9,
|
|
|
|
|
|
|
+ $axisRowY,
|
|
|
$lc,
|
|
$lc,
|
|
|
$lp
|
|
$lp
|
|
|
);
|
|
);
|
|
@@ -409,16 +437,16 @@ $buildDualChartsHtml = static function (array $curDaily, array $prevDaily): stri
|
|
|
$y
|
|
$y
|
|
|
);
|
|
);
|
|
|
$yTick .= sprintf(
|
|
$yTick .= sprintf(
|
|
|
- '<text x="%d" y="%.1f" font-size="7.5" fill="#6b7280" font-family="sun-exta,sans-serif">%d</text>',
|
|
|
|
|
|
|
+ '<text x="%d" y="%.1f" font-size="8" fill="#6b7280" font-family="sun-exta,sans-serif">%d</text>',
|
|
|
2,
|
|
2,
|
|
|
$y + 3,
|
|
$y + 3,
|
|
|
$v
|
|
$v
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- $legend = '<g font-family="sun-exta,sans-serif" font-size="8.5">';
|
|
|
|
|
- $lx = max($padL + 4, $padL + $gw - 118);
|
|
|
|
|
- $ly = $padT + 2;
|
|
|
|
|
|
|
+ $legend = '<g font-family="sun-exta,sans-serif" font-size="9">';
|
|
|
|
|
+ $lx = $padL + 4;
|
|
|
|
|
+ $ly = $H - 26;
|
|
|
$items = [
|
|
$items = [
|
|
|
[$strokeCur, $legCur, ''],
|
|
[$strokeCur, $legCur, ''],
|
|
|
[$strokePrev, $legPrev, '6,4'],
|
|
[$strokePrev, $legPrev, '6,4'],
|
|
@@ -453,6 +481,7 @@ $buildDualChartsHtml = static function (array $curDaily, array $prevDaily): stri
|
|
|
$svg .= sprintf('<line x1="%d" y1="%.1f" x2="%.1f" y2="%.1f" stroke="#9ca3af" stroke-width="1"/>', $padL, $xAxisY, $padL + $gw, $xAxisY);
|
|
$svg .= sprintf('<line x1="%d" y1="%.1f" x2="%.1f" y2="%.1f" stroke="#9ca3af" stroke-width="1"/>', $padL, $xAxisY, $padL + $gw, $xAxisY);
|
|
|
$svg .= $lineWithDots($prev, $strokePrev, '6,4', 1.4);
|
|
$svg .= $lineWithDots($prev, $strokePrev, '6,4', 1.4);
|
|
|
$svg .= $lineWithDots($cur, $strokeCur, '', 1.8);
|
|
$svg .= $lineWithDots($cur, $strokeCur, '', 1.8);
|
|
|
|
|
+ $svg .= $valueLabels;
|
|
|
$svg .= $tickTxt;
|
|
$svg .= $tickTxt;
|
|
|
$svg .= $legend;
|
|
$svg .= $legend;
|
|
|
$svg .= '</svg>';
|
|
$svg .= '</svg>';
|