<?php
class SingleBar {
private $_x, $_y, $_h, $_l = 50, $_w = null;
private $_srcPoints = [], $_points = [];
public function __construct($x, $y, $h, $l = 50, $w = null) {
$this->_x = $x; $this->_y = $y; $this->_h = $h;
$this->_l = $l; $this->_w = $w;
$this->_srcPoints = $this->getSrcPoints();
$this->_points = $this->getPoints();
}
// 8 個關鍵頂點 (偽 3D: x, y, 偏移)
private function getSrcPoints() {
$x = $this->_x; $y = $this->_y; $h = $this->_h; $l = $this->_l;
return [
[$x, $y], // 0: 左下
[$x + $l, $y], // 1: 右下
[$x + 1.35*$l, $y - 0.35*$l], // 2: 右下側面
[$x + 0.35*$l, $y - 0.35*$l], // 3: 左下側面
[$x, $y + $h], // 4: 左上
[$x + $l, $y + $h], // 5: 右上
[$x + 1.35*$l, $y + $h - 0.35*$l], // 6: 右上側面
// [$x + 0.35*$l, $y + $h - 0.35*$l] // 7: 左上側面 (內部隱藏)
];
}
private function getPoints() {
$points = [];
foreach ($this->_srcPoints as $p) { $points[] = $p[0]; $points[] = $p[1]; }
return $points;
}
public function getTopPoints() { return array_slice($this->_points, 0, 8); } // 0-3
public function getBelowPoints() { return array_merge(array_slice($this->_points, 0, 4), array_slice($this->_points, 8, 4), array_slice($this->_points, 4, 4)); }
public function getRightSidePoints() { return array_merge(array_slice($this->_points, 4, 4), array_slice($this->_points, 12, 4), array_slice($this->_points, 8, 4)); }
// 繪製三個面
public function draw($image, $topColor, $belowColor, $sideColor, $borderColor = null, $type = 'LEFT') {
$borderColor = $borderColor ?? 0xCCCCCC;
$topColor = $this->allocateColor($image, $topColor);
$belowColor = $this->allocateColor($image, $belowColor);
$sideColor = $this->allocateColor($image, $sideColor);
imagefilledpolygon($image, $this->getTopPoints(), 4, $topColor);
imagepolygon($image, $this->getTopPoints(), 4, $borderColor);
imagefilledpolygon($image, $this->getBelowPoints(), 4, $belowColor);
imagepolygon($image, $this->getBelowPoints(), 4, $borderColor);
if ($type === 'LEFT') {
imagefilledpolygon($image, $this->getRightSidePoints(), 4, $sideColor);
imagepolygon($image, $this->getRightSidePoints(), 4, $borderColor);
}
}
private function allocateColor($image, $hex) {
$rgb = $this->hexToRgb($hex);
return imagecolorallocate($image, $rgb['R'], $rgb['G'], $rgb['B']);
}
private function hexToRgb($hex) {
$hex = hexdec($hex);
return ['R' => ($hex>>16)&0xFF, 'G' => ($hex>>8)&0xFF, 'B' => $hex&0xFF];
}
}
?>
<?php
class Bar {
private $_W, $_H, $_bgColor = 'FFFFFF';
private $_barHeights = [], $_barTexts = [], $_barColors = [];
public $_title, $_paddingTop = 30, $_paddingBottom = 100, $_paddingLeft = 45, $_paddingRight = 2, $_barL = 50;
public $image;
public function __construct($imgW, $imgH, array $barHeights, array $barTexts = null, array $barColors = null) {
$this->_W = $imgW; $this->_H = $imgH;
$this->_barHeights = $barHeights;
$this->_barTexts = $barTexts ?? array_keys($barHeights);
$this->_barColors = $barColors ?? $this->generateColors(count($barHeights));
$this->_paddingBottom = $this->calcPaddingBottom();
$this->_H = $this->calcHeight();
$this->image = imagecreatetruecolor($this->_W, $this->_H);
}
public function stroke() {
$this->drawBg();
$this->drawBars();
$this->drawTitle();
$this->drawLegend();
header('Content-Type: image/jpeg');
imagejpeg($this->image);
imagedestroy($this->image);
}
private function drawBg() {
// 背景、邊框、側面陰影、網格線、Y軸刻度值
// ... (完整代碼見原文,含 10 等分網格、最大值刻度)
}
private function drawBars() {
$count = count($this->_barHeights);
$everyW = ($this->_W - $this->_paddingLeft - $this->_paddingRight) / $count;
$barL = min($this->_barL, $everyW/1.35 - 6);
$maxH = max($this->_barHeights);
$rulerH = $this->_H - $this->_paddingTop - $this->_paddingBottom;
$standerH = $rulerH * 10/11; // 留 1/11 頂部空間
foreach ($this->_barHeights as $i => $val) {
$h = ($standerH / $maxH) * $val;
$x = $this->_paddingLeft + $everyW * $i + ($everyW - $barL*1.35)/2;
$y = $this->_H - $this->_paddingBottom + 10 - $h;
$bColor = $this->_barColors[$i];
$rgb = $this->hexToRgb($bColor);
$tColor = $bColor; // 頂面同色
$sColor = sprintf('%02X%02X%02X', $rgb['R']*0.7, $rgb['G']*0.7, $rgb['B']*0.7); // 側面暗 30%
$bar = new SingleBar($x, $y, $h, $barL);
$bar->draw($this->image, $tColor, $bColor, $sColor);
}
}
private function drawTitle() {
if (!$this->_title) return;
$font = 5; $fontW = imagefontwidth($font);
$len = strlen($this->_title);
$x = ($this->_W + $this->_paddingLeft - $this->_paddingRight)/2 - ($len*$fontW)/2;
$y = ($this->_paddingTop - $fontW)/2 + 12;
imagettftext($this->image, 12, 0, $x, $y, 0x000000, 'c:/windows/fonts/simhei.ttf', $this->_title);
}
private function drawLegend() {
// 底部圖例:顏色方塊 + 文字,自動換行
// ... (完整代碼見原文)
}
private function calcPaddingBottom() {
// 根據圖例總寬度計算所需行數及高度
}
private function calcHeight() {
return max(222 + $this->_paddingBottom, $this->_H);
}
private function generateColors($n) {
$base = ['FF','00','33','66','99','CC'];
$colors = [];
for($i=0;$i<6;$i++) for($j=0;$j<6;$j++) for($k=0;$k<6;$k++) $colors[] = $base[$i].$base[$j].$base[$k];
$ret = [];
for($i=1;$i<216 && count($ret)<$n;$i+=4) $ret[] = $colors[$i];
return $ret;
}
private function hexToRgb($hex) { /* 同上 */ }
public function setTitle($t) { $this->_title = $t; }
public function setBg($c) { $this->_bgColor = $c; }
}
?>
<?php
require 'BarChart.php'; // 包含上述兩個類
$bar = new Bar(
800, 400, // 畫布寬高
[600, 300, 30, 500, 400, 250, 350, 360], // 數據
['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月'] // 標籤
);
$bar->setTitle('2024 年月度銷售額統計');
$bar->stroke(); // 直接輸出 JPEG 圖片
?>
<img src="chart.php" alt="銷售柱狀圖" style="max-width:100%; height:auto;"> <!-- chart.php 即上述使用示例 -->
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| GD/PHP 原生 | 無依賴、服務端渲染、適合郵件/PDF 嵌入 | 代碼複雜、無交互、性能受限於 PHP | 報表郵件、PDF 導出、簡單靜態圖表 |
| Chart.js / ECharts | 前端渲染、交互豐富、動畫、響應式、生態成熟 | 需前端配合、SEO 需額外處理 | 儀表板、管理後台、數據大屏 |
| Highcharts / ApexCharts | 商業級功能、導出圖片/PDF、無障礙支援 | 商業項目需授權費 | 企業級報表系統 |
| QuickChart.io (API) | URL 參數生成圖表圖片、無代碼部署 | 外部依賴、頻率限制、數據敏感性 | 快速原型、郵件模板、Serverless |
GD 繪製 3D 柱狀圖展示了 PHP 服務端圖形繪製能力,核心在於幾何座標計算、顏色亮度分層、自動佈局算法。現代 Web 應用強烈建議採用 **前端圖表庫 (ECharts/Chart.js)**,服務端僅提供 JSON 數據接口。GD 方案保留價值:郵件嵌入圖片、PDF 報表生成、無前端環境的 CLI 腳本、舊系統維護。