dir = $dir ?: (rtrim(WRITEPATH, '/\\') . '/cache/abhi_mis_charts'); if (!is_dir($this->dir)) { @mkdir($this->dir, 0775, true); } } /** * @param array $vm * @return array chart key => absolute PNG path */ public function buildAll(array $vm): array { $out = []; $status = $vm['claimStatus'] ?? []; $out['donut'] = $this->pie( 'donut', array_map(static fn ($r) => (string) ($r['status'] ?? ''), $status), array_map(static fn ($r) => (float) ($r['reported'] ?? 0), $status), ['#2E86DE', '#1B3F8C', '#E8862C', '#6A3D9A'] ); $pre = $vm['preAuthTAT'] ?? ['within' => 100, 'above' => 0]; $out['preAuth'] = $this->pie( 'preauth', ['Within 2 Hours %', 'Above 2 Hours %'], [(float) ($pre['within'] ?? 0), (float) ($pre['above'] ?? 0)], ['#2E86DE', '#1B3F8C'] ); $bars = $vm['premiumBars'] ?? [0, 0, 0]; $out['premiumBars'] = $this->vBars( 'premium_bars', ['Premium', 'Earn Premium', 'Claim Incurred'], [(float) ($bars[0] ?? 0), (float) ($bars[1] ?? 0), (float) ($bars[2] ?? 0)], ['#2E86DE', '#1B3F8C', '#E8862C'] ); $gender = $vm['gender'] ?? []; $gLabels = []; $gCounts = []; $gAmts = []; $gColors = []; foreach (['Male' => '#6A3D9A', 'Female' => '#1A3399'] as $label => $color) { $row = null; foreach ($gender as $g) { if (($g['label'] ?? '') === $label) { $row = $g; break; } } $gLabels[] = $label; $gCounts[] = (float) ($row['count'] ?? 0); $gAmts[] = (float) ($row['amount'] ?? 0); $gColors[] = $color; } $out['genderCount'] = $this->hBars('gender_count', $gLabels, $gCounts, $gColors, false); $out['genderAmt'] = $this->hBars('gender_amt', $gLabels, $gAmts, $gColors, true); $pc = $vm['paidCharts'] ?? []; $cbt = $pc['countByType'] ?? []; $out['paidCount'] = $this->vBars( 'paid_count', ['Cashless', 'Reimbursement'], [(float) ($pc['paidCount'][0] ?? 0), (float) ($pc['paidCount'][1] ?? 0)], ['#2E86DE', '#1B3F8C'] ); $out['paidAmtAcs'] = $this->groupedVBars( 'paid_amt_acs', ['Cashless', 'Reimbursement'], [ ['label' => 'Paid Amt', 'values' => [(float) ($pc['paidAmt'][0] ?? 0), (float) ($pc['paidAmt'][1] ?? 0)], 'color' => '#2E86DE'], ['label' => 'ACS', 'values' => [(float) ($pc['acs'][0] ?? 0), (float) ($pc['acs'][1] ?? 0)], 'color' => '#E8862C'], ] ); $out['countStatus'] = $this->groupedVBars( 'count_status', ['Settled', 'Outstanding', 'Rejected'], [ [ 'label' => 'Cashless', 'values' => [ (float) ($cbt['Settled']['Cashless'] ?? 0), (float) ($cbt['Outstanding']['Cashless'] ?? 0), (float) ($cbt['Rejected']['Cashless'] ?? 0), ], 'color' => '#2E86DE', ], [ 'label' => 'Reimbursement', 'values' => [ (float) ($cbt['Settled']['Reimbursement'] ?? 0), (float) ($cbt['Outstanding']['Reimbursement'] ?? 0), (float) ($cbt['Rejected']['Reimbursement'] ?? 0), ], 'color' => '#1B3F8C', ], ] ); $out['amtStatus'] = $this->vBars( 'amt_status', ['Settled', 'Outstanding', 'Rejected'], [ (float) ($pc['amtByStatus'][0] ?? 0), (float) ($pc['amtByStatus'][1] ?? 0), (float) ($pc['amtByStatus'][2] ?? 0), ], ['#2E86DE', '#E8862C', '#6A3D9A'] ); $months = $vm['months'] ?? []; $out['monthCount'] = $this->groupedVBars( 'month_count', $months, [ ['label' => 'Paid Count', 'values' => array_map('floatval', $vm['paidCountByMonth'] ?? []), 'color' => '#2E86DE'], ['label' => 'Outstanding Count', 'values' => array_map('floatval', $vm['outCountByMonth'] ?? []), 'color' => '#1B3F8C'], ], 720, 280 ); $out['monthAmt'] = $this->groupedVBars( 'month_amt', $months, [ ['label' => 'Paid Amount', 'values' => array_map('floatval', $vm['paidAmtByMonth'] ?? []), 'color' => '#2E86DE'], ['label' => 'Outstanding Amount', 'values' => array_map('floatval', $vm['outAmtByMonth'] ?? []), 'color' => '#1B3F8C'], ], 720, 280 ); return array_filter($out); } /** * @param list $labels * @param list $values * @param list $colors */ public function pie(string $key, array $labels, array $values, array $colors, int $w = 420, int $h = 260): string { $img = imagecreatetruecolor($w, $h); $white = imagecolorallocate($img, 255, 255, 255); imagefilledrectangle($img, 0, 0, $w, $h, $white); $total = array_sum($values); $cx = (int) ($w * 0.32); $cy = (int) ($h / 2); $rx = 95; $ry = 70; $start = 0.0; foreach ($values as $i => $val) { $slice = $total > 0 ? ($val / $total) * 360.0 : 0.0; if ($slice <= 0) { continue; } $rgb = $this->hex($colors[$i % count($colors)]); $col = imagecolorallocate($img, $rgb[0], $rgb[1], $rgb[2]); $this->filledArc($img, $cx, $cy, $rx * 2, $ry * 2, (int) $start, (int) ($start + $slice), $col); $start += $slice; } // hole for donut $hole = imagecolorallocate($img, 255, 255, 255); imagefilledellipse($img, $cx, $cy, (int) ($rx * 0.9), (int) ($ry * 0.9), $hole); $black = imagecolorallocate($img, 40, 40, 40); $lx = (int) ($w * 0.58); $ly = 40; foreach ($labels as $i => $label) { $rgb = $this->hex($colors[$i % count($colors)]); $col = imagecolorallocate($img, $rgb[0], $rgb[1], $rgb[2]); imagefilledrectangle($img, $lx, $ly, $lx + 12, $ly + 12, $col); $pct = $total > 0 ? round(($values[$i] / $total) * 100, 1) : 0; imagestring($img, 3, $lx + 18, $ly - 1, $this->fit($label . ' ' . $pct . '%', 28), $black); $ly += 22; } return $this->save($key, $img); } /** * @param list $labels * @param list $values * @param list $colors */ public function hBars(string $key, array $labels, array $values, array $colors, bool $indianFmt, int $w = 480, int $h = 200): string { $img = imagecreatetruecolor($w, $h); $white = imagecolorallocate($img, 255, 255, 255); $black = imagecolorallocate($img, 40, 40, 40); imagefilledrectangle($img, 0, 0, $w, $h, $white); $max = max(1.0, ...array_map('floatval', $values ?: [1])); $left = 70; $barH = 36; $gap = 28; $y = 40; $maxW = $w - $left - 30; foreach ($labels as $i => $label) { $val = (float) ($values[$i] ?? 0); $bw = (int) (($val / $max) * $maxW); $rgb = $this->hex($colors[$i % count($colors)]); $col = imagecolorallocate($img, $rgb[0], $rgb[1], $rgb[2]); imagestring($img, 3, 8, $y + 10, $this->fit($label, 10), $black); imagefilledrectangle($img, $left, $y, $left + max(2, $bw), $y + $barH, $col); $txt = $indianFmt ? $this->fmtIn($val) : (string) (int) $val; $tx = $left + (int) ($bw / 2) - (int) (strlen($txt) * 3); $tx = max($left + 4, min($tx, $w - 80)); $box = imagecolorallocate($img, 255, 255, 255); imagefilledrectangle($img, $tx - 2, $y + 10, $tx + strlen($txt) * 7 + 2, $y + 26, $box); imagestring($img, 3, $tx, $y + 12, $txt, $black); $y += $barH + $gap; } return $this->save($key, $img); } /** * @param list $labels * @param list $values * @param list $colors */ public function vBars(string $key, array $labels, array $values, array $colors, int $w = 480, int $h = 240): string { $img = imagecreatetruecolor($w, $h); $white = imagecolorallocate($img, 255, 255, 255); $black = imagecolorallocate($img, 40, 40, 40); $grid = imagecolorallocate($img, 230, 230, 230); imagefilledrectangle($img, 0, 0, $w, $h, $white); $max = max(1.0, ...array_map('floatval', $values ?: [1])); $top = 20; $bottom = $h - 40; $left = 40; $right = $w - 20; $plotH = $bottom - $top; $n = max(1, count($labels)); $slot = ($right - $left) / $n; $barW = max(12, (int) ($slot * 0.45)); for ($g = 0; $g <= 4; $g++) { $gy = (int) ($bottom - ($plotH * $g / 4)); imageline($img, $left, $gy, $right, $gy, $grid); } foreach ($labels as $i => $label) { $val = (float) ($values[$i] ?? 0); $bh = (int) (($val / $max) * $plotH); $x = (int) ($left + $slot * $i + ($slot - $barW) / 2); $y = $bottom - $bh; $rgb = $this->hex($colors[$i % count($colors)]); $col = imagecolorallocate($img, $rgb[0], $rgb[1], $rgb[2]); imagefilledrectangle($img, $x, $y, $x + $barW, $bottom, $col); $txt = $this->fmtShort($val); imagestring($img, 2, $x, max(4, $y - 14), $txt, $black); imagestring($img, 2, $x - 4, $bottom + 8, $this->fit($label, 12), $black); } return $this->save($key, $img); } /** * @param list $labels * @param list,color:string}> $series */ public function groupedVBars(string $key, array $labels, array $series, int $w = 520, int $h = 240): string { $img = imagecreatetruecolor($w, $h); $white = imagecolorallocate($img, 255, 255, 255); $black = imagecolorallocate($img, 40, 40, 40); $grid = imagecolorallocate($img, 230, 230, 230); imagefilledrectangle($img, 0, 0, $w, $h, $white); $all = []; foreach ($series as $s) { foreach ($s['values'] as $v) { $all[] = (float) $v; } } $max = max(1.0, ...($all ?: [1.0])); $top = 28; $bottom = $h - 50; $left = 30; $right = $w - 20; $plotH = $bottom - $top; $n = max(1, count($labels)); $slot = ($right - $left) / $n; $seriesN = max(1, count($series)); $barW = max(8, (int) (($slot * 0.7) / $seriesN)); for ($g = 0; $g <= 4; $g++) { $gy = (int) ($bottom - ($plotH * $g / 4)); imageline($img, $left, $gy, $right, $gy, $grid); } foreach ($labels as $i => $label) { foreach ($series as $si => $s) { $val = (float) ($s['values'][$i] ?? 0); $bh = (int) (($val / $max) * $plotH); $x = (int) ($left + $slot * $i + ($slot - $barW * $seriesN) / 2 + $si * $barW); $y = $bottom - $bh; $rgb = $this->hex($s['color']); $col = imagecolorallocate($img, $rgb[0], $rgb[1], $rgb[2]); imagefilledrectangle($img, $x, $y, $x + $barW - 2, $bottom, $col); } imagestring($img, 2, (int) ($left + $slot * $i + 4), $bottom + 6, $this->fit((string) $label, 10), $black); } $lx = $left; foreach ($series as $s) { $rgb = $this->hex($s['color']); $col = imagecolorallocate($img, $rgb[0], $rgb[1], $rgb[2]); imagefilledrectangle($img, $lx, 8, $lx + 10, 18, $col); imagestring($img, 2, $lx + 14, 7, $this->fit($s['label'], 18), $black); $lx += 120; } return $this->save($key, $img); } /** @return array{0:int,1:int,2:int} */ private function hex(string $hex): array { $hex = ltrim($hex, '#'); if (strlen($hex) === 3) { $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; } if (strlen($hex) < 6) { return [46, 134, 222]; } return [ hexdec(substr($hex, 0, 2)), hexdec(substr($hex, 2, 2)), hexdec(substr($hex, 4, 2)), ]; } private function filledArc($img, int $cx, int $cy, int $w, int $h, int $s, int $e, $col): void { if ($e <= $s) { return; } imagefilledarc($img, $cx, $cy, $w, $h, $s, $e, $col, IMG_ARC_PIE); } private function fit(string $s, int $max): string { $s = preg_replace('/[^\x20-\x7E]/', '', $s) ?? $s; return strlen($s) > $max ? substr($s, 0, $max - 1) . '.' : $s; } private function fmtIn(float $n): string { return number_format($n, 0, '.', ','); } private function fmtShort(float $n): string { if (abs($n) >= 100000) { return number_format($n / 100000, 1) . 'L'; } if (abs($n) >= 1000) { return number_format($n / 1000, 1) . 'k'; } return (string) (int) $n; } /** @param resource|\GdImage $img */ private function save(string $key, $img): string { $path = $this->dir . DIRECTORY_SEPARATOR . $key . '_' . uniqid('', true) . '.png'; imagepng($img, $path); imagedestroy($img); return $path; } }