你的报表系统需要展示销售数据,但Excel导出的图表样式老旧。你的监控系统需要实时显示流量趋势,但不知道如何生成图表。JpGraph就是你要找的答案——一个功能强大、支持中文的PHP图表生成库,让你用代码生成媲美专业报表软件的精美图表。

JpGraph 是一个老牌的PHP图表库,从PHP 5时代一直维护到现在,最新版本支持 PHP 8.5。虽然官网设计看起来有些年头了(上图),但它的功能一点不比现代方案弱——而且它是纯PHP服务端渲染,不需要JavaScript,对SEO友好,也不依赖任何外部服务。
安装
推荐方式:Composer安装(mitoteam 维护版)
composer require mitoteam/jpgraph
为什么不用 jpgraph/jpgraph?这个包已被标记为 abandoned(已废弃),在 PHP 8.4 上直接崩溃。社区维护的 mitoteam/jpgraph(当前版本 10.5.4)基于官方 4.4.3 打了大量 PHP 8.x 兼容性补丁,是实际可用的选择。
官方下载地址:https://jpgraph.net/download/ (免费版 4.4.3,支持 PHP 5/7/8)
环境要求
# JpGraph 需要 GD 扩展(带 FreeType 和 JPEG 支持)
sudo apt-get install php8.4-gd fonts-droid-fallback
# 验证
php -m | grep gd
# gd ✅
快速开始
基础配置
<?php
require_once __DIR__ . '/vendor/autoload.php';
use mitoteam\jpgraph\MtJpGraph;
// 加载所需模块(按需加载)
MtJpGraph::load(['line']);
// 中文字体配置(重要!)
// FF_CHINESE 使用 iconv 做 UTF-8 转换,兼容性最好
DEFINE('CHINESE_TTF_FONT', '/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf');
第一个折线图
<?php
require_once __DIR__ . '/vendor/autoload.php';
use mitoteam\jpgraph\MtJpGraph;
DEFINE('CHINESE_TTF_FONT', '/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf');
MtJpGraph::load(['line']);
$data = [12, 8, 15, 6, 10, 20];
$months = ['1月', '2月', '3月', '4月', '5月', '6月'];
$graph = new Graph(400, 300);
$graph->SetScale('textlin');
$graph->xaxis->SetTickLabels($months);
$graph->xaxis->SetFont(FF_CHINESE, FS_NORMAL, 9);
$graph->yaxis->SetTitle('销售额(万)', 'center');
$graph->yaxis->title->SetFont(FF_CHINESE, FS_NORMAL, 9);
$lineplot = new LinePlot($data);
$lineplot->SetColor('#FF6B6B');
$lineplot->SetWeight(3);
$graph->Add($lineplot);
$graph->title->Set('月度销售趋势');
$graph->title->SetFont(FF_CHINESE, FS_NORMAL, 14);
$graph->Stroke('sales_trend.png');

几个关键点:
-
类名没有命名空间:是
new Graph()不是new \Amenadiel\JpGraph\Graph\Graph()(网上不少教程写的是不存在的命名空间) -
模块按需加载:
MtJpGraph::load(['line'])只加载需要的部分 -
中文字体用
FF_CHINESE:不要用FF_SIMSUN,它的 GB2312 编码转换表缺大量常用汉字
核心图表类型
折线图(Line Plot)— 展示趋势变化

MtJpGraph::load(['line']);
$data1 = [12, 8, 15, 6, 10, 20];
$data2 = [5, 10, 8, 12, 15, 18];
$months = ['1月', '2月', '3月', '4月', '5月', '6月'];
$graph = new Graph(600, 400);
$graph->SetScale('textlin');
$graph->xaxis->SetTickLabels($months);
$graph->xaxis->SetFont(FF_CHINESE, FS_NORMAL, 9);
$graph->yaxis->SetTitle('销售额(万)', 'center');
$graph->yaxis->title->SetFont(FF_CHINESE, FS_NORMAL, 9);
$graph->img->SetMargin(60, 80, 40, 60); // 增加边距避免图例遮挡数据
$l1 = new LinePlot($data1);
$l1->SetColor('#FF6B6B');
$l1->SetLegend('产品A');
$l2 = new LinePlot($data2);
$l2->SetColor('#4ECDC4');
$l2->SetLegend('产品B');
$graph->Add($l1);
$graph->Add($l2);
$graph->legend->SetFont(FF_CHINESE, FS_NORMAL, 9);
$graph->legend->Pos(0.02, 0.1, 'right', 'top'); // 图例放右上角
$graph->title->Set('产品销售对比');
$graph->title->SetFont(FF_CHINESE, FS_NORMAL, 14);
$graph->Stroke('comparison.png');
适用场景:销售趋势分析、流量监控、股票走势、温度变化
柱状图(Bar Plot)— 对比数据差异

MtJpGraph::load(['bar']);
$data = [
[12, 8, 15, 6, 10], // Q1
[15, 10, 18, 8, 12], // Q2
];
$cats = ['华东', '华南', '华北', '西南', '西北'];
$graph = new Graph(600, 400);
$graph->SetScale('textlin');
$graph->xaxis->SetTickLabels($cats);
$graph->xaxis->SetFont(FF_CHINESE, FS_NORMAL, 9);
$graph->yaxis->SetTitle('销量(万)', 'center');
$graph->yaxis->title->SetFont(FF_CHINESE, FS_NORMAL, 9);
$barPlots = [];
foreach ($data as $values) {
$barPlots[] = new BarPlot($values);
}
$groupBar = new GroupBarPlot($barPlots);
$graph->Add($groupBar);
$barPlots[0]->SetFillColor('#FF6B6B');
$barPlots[0]->SetLegend('Q1');
$barPlots[1]->SetFillColor('#4ECDC4');
$barPlots[1]->SetLegend('Q2');
$graph->legend->SetFont(FF_CHINESE, FS_NORMAL, 9);
$graph->title->Set('各区域季度销售对比');
$graph->title->SetFont(FF_CHINESE, FS_NORMAL, 14);
$graph->Stroke('quarterly_sales.png');
适用场景:季度销售对比、部门业绩对比、产品销量排名
饼图(Pie Plot)— 展示占比分布

MtJpGraph::load(['pie', 'pie3d']); // 注意:3D饼图需要同时加载 pie 和 pie3d
$data = [30, 25, 20, 15, 10];
$labels = ['电子产品', '服装配饰', '食品饮料', '家居日用', '其他'];
$graph = new PieGraph(550, 450); // 加大画布给标签留空间
$graph->title->Set('产品线收入占比');
$graph->title->SetFont(FF_CHINESE, FS_NORMAL, 14);
$pie = new PiePlot3D($data);
$pie->SetLegends($labels); // 图例显示名称
$pie->ExplodeSlice(0, 20); // 突出第一块
$pie->SetSliceColors([ // 自定义配色
'#FF6B6B', '#4ECDC4', '#45B7D1',
'#FFA07A', '#98D8C8'
]);
$pie->value->SetFont(FF_FONT1, FS_BOLD, 10); // 数值标签是纯数字,用内置字体即可
$pie->value->SetColor('#333');
$pie->SetCenter(0.4, 0.55); // 饼图偏移,避免标签遮挡数值
$graph->legend->SetFont(FF_CHINESE, FS_NORMAL, 9);
$graph->legend->Pos(0.02, 0.1, 'right', 'top');
$graph->Add($pie);
$graph->Stroke('market_share.png');
注意两个坑:
-
3D饼图必须同时加载
pie和pie3d模块,否则报错找不到父类 -
SetCenter()调整饼图位置很重要,否则扇区标签和数值会互相遮挡
适用场景:市场份额、预算分配、用户来源分析
面积图(Area Plot)— 展示累积效果

MtJpGraph::load(['line']);
$data = [120, 80, 150, 60, 100, 200, 180, 220];
$weeks = ['W1', 'W2', 'W3', 'W4', 'W5', 'W6', 'W7', 'W8'];
$graph = new Graph(600, 400);
$graph->SetScale('textlin');
$graph->xaxis->SetTickLabels($weeks);
$graph->xaxis->SetFont(FF_FONT2, FS_NORMAL, 9); // X轴是英文标签(W1-W8),用内置字体
$graph->yaxis->SetTitle('访问量(千)', 'center');
$graph->yaxis->title->SetFont(FF_CHINESE, FS_NORMAL, 9);
// ⚠️ JpGraph 没有 AreaPlot 类!面积图用 LinePlot + Fill 实现
$area = new LinePlot($data);
$area->SetFillColor('#4ECDC4');
$area->SetFillGradient('#E8F5E9', '#4ECDC4');
$area->SetColor('#3BA99C');
$area->SetWeight(2);
$graph->Add($area);
$graph->title->Set('网站流量趋势');
$graph->title->SetFont(FF_CHINESE, FS_NORMAL, 14);
$graph->Stroke('area_chart.png');
这是一个常见的误区——很多教程写 new Plot\AreaPlot($data),但这个类根本不存在。正确做法是用 LinePlot 加上填充色和渐变。
适用场景:累计收入、网站流量、用户增长
雷达图(Radar Plot)— 多维数据对比

MtJpGraph::load(['radar']);
$data = [90, 80, 85, 70, 95, 88];
$labels = ['技术能力', '沟通能力', '团队协作', '创新能力', '学习能力', '执行力'];
$graph = new RadarGraph(600, 600);
$graph->SetTitles($labels); // ⚠️ 标签设在 graph 上,不是 RadarPlot 上
$graph->axis->SetFont(FF_CHINESE, FS_NORMAL, 10); // 轴标签字体
$graph->title->Set('员工能力评估');
$graph->title->SetFont(FF_CHINESE, FS_NORMAL, 14);
$p1 = new RadarPlot($data); // 构造函数只接受 data,不接受 labels
$p1->SetLegend('员工A');
$p1->SetColor('#FF6B6B');
$p1->SetFillColor('#FF6B6B');
$graph->legend->SetFont(FF_CHINESE, FS_NORMAL, 10);
$graph->Add($p1);
$graph->Stroke('skill_radar.png');
雷达图有两个常见错误:
-
RadarPlot($data, $labels)— 第二个参数会被静默忽略,标签要用$graph->SetTitles($labels) - 中文轴标签乱码 — 这是 JpGraph 的已知 bug(文本管线问题),需要额外处理(见下文"中文问题详解")
适用场景:员工绩效考核、产品功能对比、多维数据分析
为什么选择 JpGraph?
| 对比项 | JpGraph | Chart.js | Google Charts | ECharts |
|---|---|---|---|---|
| 语言 | 纯PHP | JavaScript | JavaScript | JavaScript |
| 服务端渲染 | 支持 | 不支持 | 不支持 | 不支持 |
| SEO友好 | 是 | 否 | 否 | 否 |
| 外部依赖 | 无 | CDN | Google服务器 | 无 |
| 国内访问 | 正常 | 正常 | 可能较慢 | 正常 |
| 中文支持 | 需要配置 | 原生支持 | 原生支持 | 原生支持 |
| 图表类型 | 10+种 | 8种 | 多种 | 丰富 |
| 交互性 | 无 | 强 | 中 | 很强 |
一句话总结:需要服务端生成静态图片时选 JpGraph,需要前端交互时选 Chart.js/ECharts。
适用场景
适合:
- 数据报表系统(销售、财务、运营)
- PDF报表导出(发票、证书、合同)
- 邮件图表(营销邮件、周报)
- 后台管理系统(数据统计)
- 监控大屏(配合定时任务生成图片)
不适合:
- 交互式前端图表 → 用 Chart.js / ECharts
- 实时动态图表 → 用 WebSocket + ECharts
- 移动端APP内嵌图表 → 用原生图表库
中文问题详解
JpGraph 的中文支持是一个老大难问题。经过实际测试,情况如下:
字体方案对比
| 方案 | 常量 | 编码转换 | 效果 |
|---|---|---|---|
FF_SIMSUN |
30 | GB2312 表 | 大量常用汉字缺失或乱码 |
FF_CHINESE |
31 | iconv UTF-8 | 标题/图例/坐标轴正常 |
FF_CHINESE + 雷达轴 |
31 | iconv UTF-8 | 轴标签乱码(已知bug) |
推荐配置
// 字体文件:系统自带的 Droid Sans Fallback(支持中文)
DEFINE('CHINESE_TTF_FONT', '/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf');
// 所有中文文本统一使用 FF_CHINESE
$graph->title->SetFont(FF_CHINESE, FS_NORMAL, 14);
$graph->xaxis->SetFont(FF_CHINESE, FS_NORMAL, 9);
$graph->yaxis->title->SetFont(FF_CHINESE, FS_NORMAL, 9);
$graph->legend->SetFont(FF_CHINESE, FS_NORMAL, 9);
字体选择规则(重要)
FF_CHINESE 在部分渲染上下文中不稳定,会显示为方块。按内容类型选字体:
| 内容类型 | 示例 | 推荐字体 | 原因 |
|---|---|---|---|
| 中文文本 | 标题、图例、轴标题 | FF_CHINESE |
需要 iconv UTF-8 转换 |
| 纯数字/百分号 | 饼图数值标签 30%
|
FF_FONT1 |
内置字体,稳定 |
| 英文/数字混合 | X轴刻度 W1, W2
|
FF_FONT2 |
内置字体,稳定 |
| 中文坐标轴标签 | X/Y轴的中文刻度 | FF_CHINESE |
需要 TTF 渲染 |
核心原则:只有真正包含中文字符的地方才用 FF_CHINESE,纯英文/数字用内置字体更可靠。
雷达图中文的特殊处理
如果使用雷达图且轴标签出现乱码,可以通过后处理方式解决——先用英文占位生成图表,再用 GD 在精确位置覆盖中文:
// 1. 用英文占位生成
$dummy = ['A','B','C','D','E','F'];
$graph->SetTitles($dummy);
$graph->axis->SetFont(FF_FONT2, FS_NORMAL, 8); // 内置英文字体
ob_start();
$graph->Stroke();
$img_data = ob_get_clean();
// 2. 用 GD 在同一位置写中文
$im = imagecreatefromstring($img_data);
$font = '/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf';
$dark = imagecolorallocate($im, 40, 40, 40);
// 从 JpGraph 内部提取的坐标(6个轴标签位置)
$coords = [
['x'=>300, 'y'=>84], // 顶部
['x'=>113, 'y'=>192], // 左上
['x'=>113, 'y'=>408], // 左下
['x'=>300, 'y'=>516], // 底部
['x'=>487, 'y'=>408], // 右下
['x'=>487, 'y'=>192], // 右上
];
$labels = ['技术能力','沟通能力','团队协作','创新能力','学习能力','执行力'];
$white = imagecolorallocate($im, 255, 255, 255);
foreach ($coords as $i => $c) {
imagefilledrectangle($im, $c['x']-2, $c['y']-16, $c['x']+60, $c['y']+4, $white);
imagettftext($im, 11, 0, $c['x'], $c['y'], $dark, $font, $labels[$i]);
}
imagepng($im, 'radar_chinese.png');
imagedestroy($im);
这个问题的根因是 JpGraph 的文本渲染管线(
StrokeText→LanguageConv→_StrokeTTF)在处理雷达轴标签时有 bug。官方示例全部使用英文标签,所以这个问题从未被修复。mitoteam 的 fork 也继承了同样的行为。
最佳实践
性能优化:缓存生成的图表
$cacheFile = "cache/chart_{$type}_{$date}.png";
if (file_exists($cacheFile) && filemtime($cacheFile) > strtotime('-1 hour')) {
header('Content-Type: image/png');
readfile($cacheFile);
exit;
}
$graph->Stroke($cacheFile);
错误处理:优雅降级
try {
$graph->Stroke();
} catch (Exception $e) {
$errorImg = imagecreate(400, 200);
imagecolorallocate($errorImg, 255, 255, 255);
$red = imagecolorallocate($errorImg, 255, 0, 0);
imagestring($errorImg, 3, 20, 80, "图表生成失败:" . $e->getMessage(), $red);
header('Content-Type: image/png');
imagepng($errorImg);
imagedestroy($errorImg);
}
输出格式
// PNG(默认,推荐)
$graph->Stroke('chart.png');
// JPEG(适合照片类图表)
$graph->img->SetImgFormat('jpeg');
$graph->img->SetQuality(90);
// GIF(需要透明背景时)
$graph->img->SetImgFormat('gif');
总结
JpGraph 虽然是个老牌库,但在纯PHP图表生成领域仍然是最全面的选择之一。它支持 10+ 种图表类型,输出格式丰富,高度可定制。唯一需要注意的是中文配置要使用 FF_CHINESE + iconv 方案,避开有缺陷的 FF_SIMSUN/GB2312 路径。
部署清单:
composer require mitoteam/jpgraph- 确保 PHP 安装了 GD 扩展(
php-gd) - 安装中文字体包(
fonts-droid-fallback或fonts-wqy-microhei) - 配置
CHINESE_TTF_FONT指向可用的中文字体文件 - 全局统一使用
FF_CHINESE设置中文字体
本文所有代码均在 PHP 8.4 + mitoteam/jpgraph 10.5.4 环境下测试通过。
原文标题: JpGraph:用PHP生成专业级图表
原文地址: https://phpreturn.com/index/a6a1686a101f99.html
原文平台: PHP武器库
版权声明: 本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。