File manager - Edit - /var/www/html/portalHomolog/app/Services/Reports/SvgChartBuilder.php
Back
<?php namespace App\Services\Reports; class SvgChartBuilder { private const PALETTE = ['#4F46E5', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#06B6D4']; // DomPDF ignora <svg> inline mas renderiza SVG embutido em <img> base64. private function embed(string $svg): string { return '<img src="data:image/svg+xml;base64,' . base64_encode($svg) . '" style="display:block;"/>'; } // ── Vertical bar chart ────────────────────────────────────────────────── // $data = [label => value] public function barChart(array $data, int $w = 400, int $h = 100, array $colors = []): string { if (empty($data)) return ''; $colors = $colors ?: self::PALETTE; $values = array_values($data); $labels = array_keys($data); $n = count($values); $maxVal = max(max($values), 1); $padTop = 14; $padBot = 16; $chartH = $h - $padTop - $padBot; $barW = (int) floor($w / $n); $gap = max(2, (int) ($barW * 0.18)); $bw = $barW - $gap; $baseY = $padTop + $chartH; $svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' . $w . '" height="' . $h . '">'; $svg .= '<line x1="0" y1="' . $baseY . '" x2="' . $w . '" y2="' . $baseY . '" stroke="#d1d5db" stroke-width="0.5"/>'; foreach ($values as $i => $val) { $barH = $val > 0 ? max(2, (int) round($val / $maxVal * $chartH)) : 0; $x = $i * $barW + (int) floor($gap / 2); $y = $padTop + $chartH - $barH; $color = $colors[$i % count($colors)]; if ($barH > 0) { $svg .= '<rect x="' . $x . '" y="' . $y . '" width="' . $bw . '" height="' . $barH . '" fill="' . $color . '" rx="1"/>'; } if ($val > 0) { $lx = $x + (int) floor($bw / 2); $svg .= '<text x="' . $lx . '" y="' . ($y - 2) . '" font-size="8" fill="#374151" text-anchor="middle">' . $val . '</text>'; } $abbr = mb_strtoupper(mb_substr((string) $labels[$i], 0, 3)); $svg .= '<text x="' . ($x + (int) floor($bw / 2)) . '" y="' . ($baseY + 10) . '" font-size="8" fill="#6b7280" text-anchor="middle">' . $abbr . '</text>'; } $svg .= '</svg>'; return $this->embed($svg); } // ── Line chart ─────────────────────────────────────────────────────────── // $values = plain array of numbers public function lineChart(array $values, int $w = 300, int $h = 70, string $color = '#4F46E5', array $labels = []): string { if (count($values) < 2) return ''; $n = count($values); $maxVal = max(max($values), 1); $minVal = 0; $range = max($maxVal - $minVal, 1); $padL = 4; $padR = 4; $padTop = 8; $padBot = 8; $chartW = $w - $padL - $padR; $chartH = $h - $padTop - $padBot; $pts = []; foreach ($values as $i => $v) { $px = $padL + (int) round($i / ($n - 1) * $chartW); $py = $padTop + $chartH - (int) round(($v - $minVal) / $range * $chartH); $pts[] = "$px,$py"; } $lastX = $padL + $chartW; $baseY = $padTop + $chartH; $areaPts = implode(' ', $pts) . " $lastX,$baseY $padL,$baseY"; $svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' . $w . '" height="' . $h . '">'; $svg .= '<polygon points="' . $areaPts . '" fill="' . $color . '" fill-opacity="0.1"/>'; $svg .= '<polyline points="' . implode(' ', $pts) . '" fill="none" stroke="' . $color . '" stroke-width="1.4"/>'; foreach ($values as $i => $v) { [$px, $py] = explode(',', $pts[$i]); $svg .= '<circle cx="' . $px . '" cy="' . $py . '" r="2" fill="' . $color . '"/>'; } $svg .= '</svg>'; return $this->embed($svg); } // ── Donut chart ────────────────────────────────────────────────────────── // $data = [label => value] // Math: total angle = 2π. Cada fatia = value/total × 2π radianos. // Arco externo (R) sentido horário (sweep=1), arco interno (r) anti-horário (sweep=0). // large-arc-flag = 1 quando fatia > π (180°). // Início em -π/2 para alinhar ao meio-dia (12h). public function donutChart(array $data, int $size = 110, array $colors = []): string { $data = array_filter($data, fn($v) => $v > 0); if (empty($data)) return ''; $colors = $colors ?: self::PALETTE; $total = array_sum($data); if ($total <= 0) return ''; $cx = $size / 2; $cy = $size / 2; $R = $size / 2 - 2; // raio externo $r = $R * 0.55; // raio interno → anel ≈ 45% de R $svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' . $size . '" height="' . $size . '">'; $start = -M_PI / 2; // 12h no SVG $i = 0; foreach ($data as $value) { $angle = $value / $total * 2 * M_PI; $end = $start + $angle; $large = $angle > M_PI ? 1 : 0; $color = $colors[$i % count($colors)]; $x1 = round($cx + $R * cos($start), 3); $y1 = round($cy + $R * sin($start), 3); $x2 = round($cx + $R * cos($end), 3); $y2 = round($cy + $R * sin($end), 3); $x3 = round($cx + $r * cos($end), 3); $y3 = round($cy + $r * sin($end), 3); $x4 = round($cx + $r * cos($start), 3); $y4 = round($cy + $r * sin($start), 3); $d = "M $x1 $y1 A $R $R 0 $large 1 $x2 $y2 L $x3 $y3 A $r $r 0 $large 0 $x4 $y4 Z"; $svg .= '<path d="' . $d . '" fill="' . $color . '"/>'; $start = $end; $i++; } $svg .= '<text x="' . $cx . '" y="' . ($cy + 5) . '" font-size="15" font-weight="bold" fill="#111" text-anchor="middle">' . $total . '</text>'; $svg .= '</svg>'; return $this->embed($svg); } // ── Horizontal bars ────────────────────────────────────────────────────── // $data = [label => value] public function horizontalBars(array $data, int $w = 280, int $h = 110, array $colors = [], int $total = 0): string { $data = array_filter($data, fn($v) => $v > 0); if (empty($data)) return ''; $colors = $colors ?: self::PALETTE; $n = count($data); $maxVal = max(max(array_values($data)), 1); $labelW = 72; $valW = 38; $barArea = $w - $labelW - $valW; $rowH = max(18, (int) floor($h / $n)); $bh = max(12, min(16, $rowH - 5)); $svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' . $w . '" height="' . ($rowH * $n) . '">'; $i = 0; foreach ($data as $label => $value) { $bw = (int) round($value / $maxVal * $barArea); $y = $i * $rowH; $barY = $y + (int) floor(($rowH - $bh) / 2); $color = $colors[$i % count($colors)]; $lbl = mb_substr((string) $label, 0, 15); $svg .= '<text x="' . ($labelW - 3) . '" y="' . ($barY + $bh - 2) . '" font-size="9" fill="#374151" text-anchor="end">' . $lbl . '</text>'; $svg .= '<rect x="' . $labelW . '" y="' . $barY . '" width="' . $barArea . '" height="' . $bh . '" fill="#e5e7eb" rx="2"/>'; if ($bw > 0) { $svg .= '<rect x="' . $labelW . '" y="' . $barY . '" width="' . $bw . '" height="' . $bh . '" fill="' . $color . '" rx="2"/>'; } $pct = $total > 0 ? ' (' . round($value / $total * 100, 1) . '%)' : ''; $valTxt = $value . $pct; $svg .= '<text x="' . ($labelW + $bw + 3) . '" y="' . ($barY + $bh - 2) . '" font-size="9" font-weight="bold" fill="#374151">' . $valTxt . '</text>'; $i++; } $svg .= '</svg>'; return $this->embed($svg); } // ── Stacked bar chart (meses × setores) ───────────────────────────────── // $series = ['SectorA' => [1=>n, 2=>n, ..., 12=>n], ...] // $monthLabels = [1=>'Janeiro', ..., 12=>'Dezembro'] public function stackedBarChart(array $series, array $monthLabels, int $w = 800, int $h = 160, array $colors = []): string { $series = array_filter($series, fn($counts) => array_sum($counts) > 0); if (empty($series)) return ''; $colors = $colors ?: self::PALETTE; $sectorNames = array_keys($series); $nSectors = count($sectorNames); $legendCols = min($nSectors, 4); $legendRows = (int) ceil($nSectors / $legendCols); $legendH = $legendRows * 13 + 6; $padTop = 16; $padBot = 18; $padL = 6; $padR = 6; $chartH = $h - $padTop - $padBot - $legendH; $barArea = $w - $padL - $padR; $barW = (int) floor($barArea / 12); $gap = max(2, (int) ($barW * 0.18)); $bw = $barW - $gap; $baseY = $padTop + $chartH; // totals per month $monthTotals = array_fill(1, 12, 0); foreach ($series as $counts) { for ($m = 1; $m <= 12; $m++) { $monthTotals[$m] += $counts[$m] ?? 0; } } $maxVal = max(max($monthTotals), 1); $svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' . $w . '" height="' . $h . '">'; $svg .= '<line x1="' . $padL . '" y1="' . $baseY . '" x2="' . ($w - $padR) . '" y2="' . $baseY . '" stroke="#d1d5db" stroke-width="0.5"/>'; for ($m = 1; $m <= 12; $m++) { $x = $padL + ($m - 1) * $barW + (int) floor($gap / 2); $stackY = $baseY; $total = $monthTotals[$m]; foreach ($sectorNames as $si => $name) { $val = $series[$name][$m] ?? 0; if ($val <= 0) continue; $barH = max(2, (int) round($val / $maxVal * $chartH)); $stackY -= $barH; $color = $colors[$si % count($colors)]; $lxm = $x + (int) floor($bw / 2); $svg .= '<rect x="' . $x . '" y="' . $stackY . '" width="' . $bw . '" height="' . $barH . '" fill="' . $color . '"/>'; if ($barH >= 10 && $total > 0) { $pct = round($val / $total * 100); $midY = $stackY + (int) floor($barH / 2) + 2; $svg .= '<text x="' . $lxm . '" y="' . $midY . '" font-size="5" fill="#fff" text-anchor="middle" font-weight="bold">' . $pct . '%</text>'; } } if ($total > 0) { $lx = $x + (int) floor($bw / 2); $totalH = (int) round($total / $maxVal * $chartH); $svg .= '<text x="' . $lx . '" y="' . ($baseY - $totalH - 2) . '" font-size="5.5" fill="#374151" text-anchor="middle">' . $total . '</text>'; } $abbr = mb_strtoupper(mb_substr($monthLabels[$m] ?? "M$m", 0, 3)); $svg .= '<text x="' . ($x + (int) floor($bw / 2)) . '" y="' . ($baseY + 12) . '" font-size="5.5" fill="#6b7280" text-anchor="middle">' . $abbr . '</text>'; } // legend $colW = (int) floor($barArea / $legendCols); $legendY0 = $baseY + $padBot; foreach ($sectorNames as $si => $name) { $col = $si % $legendCols; $row = (int) floor($si / $legendCols); $lx = $padL + $col * $colW; $ly = $legendY0 + $row * 13 + 9; $color = $colors[$si % count($colors)]; $label = mb_substr($name, 0, 24); $svg .= '<rect x="' . $lx . '" y="' . ($ly - 7) . '" width="7" height="7" fill="' . $color . '" rx="1"/>'; $svg .= '<text x="' . ($lx + 9) . '" y="' . $ly . '" font-size="6" fill="#374151">' . $label . '</text>'; } $svg .= '</svg>'; return $this->embed($svg); } // ── Sparkline ──────────────────────────────────────────────────────────── // Linha mínima sem eixos ou rótulos public function sparkline(array $values, int $w = 80, int $h = 22, string $color = '#4F46E5'): string { if (count($values) < 2) return ''; $n = count($values); $maxVal = max(max($values), 1); $minVal = min(min($values), 0); $range = max($maxVal - $minVal, 1); $pad = 2; $chartW = $w - $pad * 2; $chartH = $h - $pad * 2; $pts = []; foreach ($values as $i => $v) { $px = $pad + (int) round($i / ($n - 1) * $chartW); $py = $pad + $chartH - (int) round(($v - $minVal) / $range * $chartH); $pts[] = "$px,$py"; } $svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' . $w . '" height="' . $h . '">'; $svg .= '<polyline points="' . implode(' ', $pts) . '" fill="none" stroke="' . $color . '" stroke-width="1.2"/>'; [$lx, $ly] = explode(',', end($pts)); $svg .= '<circle cx="' . $lx . '" cy="' . $ly . '" r="1.5" fill="' . $color . '"/>'; $svg .= '</svg>'; return $this->embed($svg); } // ── Gauge (semicírculo) ─────────────────────────────────────────────────── // Math: semicírculo de 180° (esquerda) a 0° (direita). // Ângulo total = π radianos. // Arco do foreground vai de 180° e avança value/max × π. // Ângulo final (coord. matemática padrão): θ = π − (value/max × π). // Em SVG (eixo Y invertido): x = cx + R·cos(θ), y = cy − R·sin(θ). // large-arc-flag = 1 quando value/max > 0,5. public function gauge(float $value, float $max, int $size = 100, string $color = '#10B981'): string { $value = max(0.0, min($value, $max)); $pct = $max > 0 ? $value / $max : 0; $cx = $size / 2; $cy = (int) round($size * 0.64); $R = (int) round($size * 0.42); $r = (int) round($R * 0.62); // fundo: semicírculo completo (esquerda → direita) $bgD = "M " . ($cx - $R) . " $cy A $R $R 0 0 1 " . ($cx + $R) . " $cy" . " L " . ($cx + $r) . " $cy A $r $r 0 0 0 " . ($cx - $r) . " $cy Z"; $fgD = ''; if ($pct > 0.001) { $theta = M_PI - $pct * M_PI; $fx2 = round($cx + $R * cos($theta), 3); $fy2 = round($cy - $R * sin($theta), 3); $fi2 = round($cx + $r * cos($theta), 3); $fiy2 = round($cy - $r * sin($theta), 3); $large = $pct > 0.5 ? 1 : 0; $fgD = "M " . ($cx - $R) . " $cy A $R $R 0 $large 1 $fx2 $fy2" . " L $fi2 $fiy2 A $r $r 0 $large 0 " . ($cx - $r) . " $cy Z"; } $label = round($pct * 100, 1) . '%'; $textSize = max(8, (int) ($size * 0.13)); $svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' . $size . '" height="' . $cy . '">'; $svg .= '<path d="' . $bgD . '" fill="#e5e7eb"/>'; if ($fgD) { $svg .= '<path d="' . $fgD . '" fill="' . $color . '"/>'; } $svg .= '<text x="' . $cx . '" y="' . ($cy - 3) . '" font-size="' . $textSize . '" font-weight="bold" fill="#111" text-anchor="middle">' . $label . '</text>'; $svg .= '</svg>'; return $this->embed($svg); } }
| ver. 1.4 |
Github
|
.
| PHP 8.3.28 | Generation time: 0.02 |
proxy
|
phpinfo
|
Settings