codeigniter需配合前端图表库实现数据可视化,核心是安全高效地将数据库数据转为标准json格式;应避免n+1查询、确保数字类型正确、设置content-type并用exit终止响应。

CodeIgniter 本身不内置图表功能,数据可视化必须靠前端库(如 Chart.js、ECharts、Google Charts)配合后端数据准备来实现。关键不在“用什么画图”,而在“怎么把数据库数据高效、安全、结构化地喂给图表”。
Chart.js 需要的 JSON 格式长什么样
几乎所有前端图表库都要求 labels 和 data 两个平行数组,例如:
{
"labels": ["O+", "A-", "B+", "AB+"],
"data": [42, 18, 35, 7]
}
这不是随便拼出来的字符串——它必须是合法 JSON,且字段名、嵌套层级、数据类型(尤其是数字不能是字符串)都要严格匹配前端 JS 的预期。常见错误包括:
-
json_encode()前没清理数据,导致空值或非法字符混入 - 把
count字段当字符串传(比如"42"),Chart.js 会静默忽略该数据点 - 在控制器里用
echo json_encode(...)直接输出,却没设Content-Type: application/json,Ajax 请求可能解析失败
避免 N+1 查询:用 GROUP BY 一次拿全血型统计
查“每种血型有多少可用血包”,别写循环查 N 次。低效代码典型特征是:foreach 里套 $this->db->query()。正确做法是把聚合逻辑交给 MySQL:
$this->db->select('COUNT(PacketID) AS count, BloodType')
->from('packets')
->where('isAvailable', 1)
->group_by('BloodType');
这样一条查询就返回全部分组结果。再用 result_array() 转成 PHP 数组,遍历构造 labels 和 data 即可。注意两点:
-
BloodType字段若含空格或特殊字符(如"O +"),前端渲染时可能出错,建议在 SQL 中用TRIM()或在 PHP 中trim() -
COUNT()返回整数,但 CodeIgniter 有时会转成字符串,强制类型转换更稳妥:(int)$row['count']
Ajax 加载图表数据时 controller 的响应要点
如果图表用 Ajax 异步加载(推荐),Controller 方法不该加载视图,而应专注输出 JSON:
- 开头加
header('Content-Type: application/json'); - 不要
echo其他任何内容(包括空格、BOM、调试var_dump) - 用
exit或die终止后续执行,防止意外输出污染 JSON - 示例结构:
public function get_blood_chart_data()
{
header('Content-Type: application/json');
$data = $this->db->select('COUNT(PacketID) AS count, BloodType')
->from('packets')
->where('isAvailable', 1)
->group_by('BloodType')
->get()
->result_array();
$chart = [
'labels' => array_column($data, 'BloodType'),
'data' => array_map(fn($r) => (int)$r['count'], $data)
];
echo json_encode($chart);
exit;
}
Google Charts 的 script 加载时机容易被忽略
Google Charts 必须等 google.charts.load 完成后再调用 drawChart,否则报 google is not defined 或 drawChart is not a function。常见陷阱:
- 把
<script src="https://www.gstatic.com/charts/loader.js"></script>放在底部,但google.charts.load写在了前面——加载未完成就执行 - 多个图表共用一个
drawChart函数名,后定义的会覆盖前一个 - 没用
google.charts.setOnLoadCallback(drawChart),而是直接调用drawChart()
最稳的方式:所有 Google Charts 相关 JS 代码放在一个 <script></script> 块内,并确保它在 loader.js 之后、且用 setOnLoadCallback 包裹。











