Libchart 是純 PHP 圖表庫,無需 GD 扩展(內部使用 GD),支持柱狀、折線、餅圖、橫向柱狀等類型。通过 Composer 安裝:
composer require libchart/libchart
| 圖表類 | 數據集類 | 適用場景 |
|---|---|---|
VerticalBarChart | XYDataSet | 垂直柱狀圖(單系列) |
HorizontalBarChart | XYDataSet | 水平柱狀圖 |
LineChart | XYSeriesDataSet | 折線圖(支持多系列) |
PieChart | XYDataSet | 餅圖(標籤含數值) |
<?php
require_once 'vendor/autoload.php';
use Libchart\Chart\VerticalBarChart;
use Libchart\Chart\HorizontalBarChart;
use Libchart\Chart\LineChart;
use Libchart\Chart\PieChart;
use Libchart\DataSet\XYDataSet;
use Libchart\DataSet\XYSeriesDataSet;
use Libchart\Point;
use Libchart\Plot;
class ChartRenderer {
private $chart;
private int $width;
private int $height;
public function __construct(int $type = 1, int $width = 800, int $height = 400) {
$this->width = $width;
$this->height = $height;
$this->chart = match ($type) {
1 => new VerticalBarChart($width, $height),
2 => new LineChart($width, $height),
3 => new PieChart($width, $height),
default => new HorizontalBarChart($width, $height),
};
}
/** 繪製單系列圖表(柱狀/餅圖/橫向柱狀) */
public function renderSingleSeries(string $title, array $data): void {
$dataSet = new XYDataSet();
foreach ($data as $label => $value) {
// 餅圖標籤顯示「標籤(值)」
$pointLabel = ($this->chart instanceof PieChart) ? "$label ($value)" : $label;
$dataSet->addPoint(new Point($pointLabel, $value));
}
$this->chart->setTitle($this->fixEncoding($title));
$this->chart->setDataSet($dataSet);
$this->output();
}
/** 繪製多系列折線圖 */
public function renderMultiSeries(string $title, array $seriesData): void {
if (!$this->chart instanceof LineChart) {
throw new LogicException('Multi-series only for LineChart');
}
$dataSet = new XYSeriesDataSet();
foreach ($seriesData as $seriesName => $points) {
$xyData = new XYDataSet();
foreach ($points as $x => $y) {
$xyData->addPoint(new Point($x, $y));
}
$dataSet->addSerie($this->fixEncoding($seriesName), $xyData);
}
$this->chart->setTitle($this->fixEncoding($title));
$this->chart->setDataSet($dataSet);
$this->output();
}
/** 解決中文亂碼核心:輸出前統一轉 HTML 實體 */
private function fixEncoding(string $text): string {
return mb_convert_encoding($text, 'HTML-ENTITIES', 'UTF-8');
}
private function output(): void {
header('Content-Type: image/png');
$this->chart->render();
}
}
// ========== 使用示例 ==========
// 1. 單系列柱狀圖
$renderer = new ChartRenderer(1, 800, 400);
$renderer->renderSingleSeries('月度銷售額', [
'1月' => 120, '2月' => 150, '3月' => 180,
'4月' => 210, '5月' => 240, '6月' => 270,
]);
// 2. 多系列折線圖
$renderer = new ChartRenderer(2, 800, 400);
$renderer->renderMultiSeries('產品線對比', [
'產品A' => ['Q1'=>100, 'Q2'=>120, 'Q3'=>140, 'Q4'=>160],
'產品B' => ['Q1'=>80, 'Q2'=>90, 'Q3'=>110, 'Q4'=>130],
]);
// 3. 餅圖
$renderer = new ChartRenderer(3, 600, 400);
$renderer->renderSingleSeries('市場佔有率', [
'華東' => 35, '華南' => 25, '華北' => 20, '其他' => 20,
]);
?>
所有傳入標題、標籤、圖例的字符串,統一用 mb_convert_encoding($str, 'HTML-ENTITIES', 'UTF-8') 處理。
文件:vendor/libchart/libchart/classes/view/chart/Chart.php
<?php
// 原代碼
public function setTitle($title) {
$this->plot->setTitle($title);
}
// 修改為
public function setTitle($title) {
$title = mb_convert_encoding($title, 'HTML-ENTITIES', 'UTF-8');
$this->plot->setTitle($title);
}
?>
Libchart 默認字體 DejaVuSansCondensed.ttf 不含中文字形。必須替換為中文字體(如微軟雅黑、思源黑體、Noto Sans SC)。
<?php
// 文件:vendor/libchart/libchart/classes/view/Text.php
// 第 47-48 行左右
// 原代碼
$this->fontCondensed = dirname(__FILE__) . '/../fonts/DejaVuSansCondensed.ttf';
$this->fontCondensedBold = dirname(__FILE__) . '/../fonts/DejaVuSansCondensed-Bold.ttf';
// 修改為(將字體文件放入 fonts/ 目錄)
$this->fontCondensed = dirname(__FILE__) . '/../fonts/NotoSansSC-Regular.otf';
$this->fontCondensedBold = dirname(__FILE__) . '/../fonts/NotoSansSC-Bold.otf';
?>
獲取字體:從 Google Fonts 下載 Noto Sans SC,或使用系統字體 C:/Windows/Fonts/msyh.ttc(Windows)//System/Library/Fonts/PingFang.ttc(macOS)。
<?php
// render() 參數傳入路徑即保存文件
$chart->render(__DIR__ . '/storage/charts/sales_' . date('Ym') . '.png');
// 然後在頁面中引用
echo '<img src="/storage/charts/sales_' . date('Ym') . '.png" alt="銷售圖表">';
?>
| 現象 | 原因 | 解決 |
|---|---|---|
| 圖片空白/錯誤 | GD 未安裝或版本過低 | php -m | grep gd,需 GD 2.0.1+ |
| 中文顯示方框/亂碼 | 字體無中文字形 | 必須完成步驟 3 替換字體 |
| 標題截斷 | 標題過長超出畫布 | 縮短標題或增大 width |
| 餅圖標籤重疊 | 數據項過多 | 合併小項為「其他」或用圖例 |
| 坐標軸數字科學計數法 | 大數值自動格式化 | 預處理數值除以 10000 加「萬」單位 |
若項目需求複雜(動態交互、導出 SVG/PDF、實時更新),建議考慮:
Libchart 適合「服務端生成靜態圖片、無交互、部署簡單」的輕量級場景。掌握編碼轉換與字體替換兩大核心,即可穩定產出專業中文圖表。