szymach/c-pchart
Modern Composer port of the pChart 2 charting library. Render bar, line, pie, scatter and other charts as PNG or JPEG images from PHP data, with namespaced classes and PHP 7+ support. A drop-in replacement for legacy pChart code.
Install the package and verify the one non-negotiable runtime dependency — GD with FreeType support:
composer require szymach/c-pchart
php -r "var_dump(gd_info()['FreeType Support']);"
Without FreeType, charts still render but all text (labels, legends, titles) will be missing or broken.
The library is a plain Composer package — no Laravel service provider, no config file, no facade. The mental model is:
CpChart\Data — holds series, abscissa labels, palettes, and axis metadata.CpChart\Chart\Line, Bar, Pie, Stock, Scatter, Radar, Gauge, Bubble, Surface, etc.CpChart\Cache — file-based render cache passed to the chart constructor.$chart->render($path) — writes a PNG/JPEG/GIF, or outputs directly if no path is given.A first useful route can render a line chart to storage/app/charts and stream it back:
use CpChart\Chart\Line;
use CpChart\Data;
$data = new Data();
$data->addPoints([10, 12, 9, 14], 'Revenue');
$data->addPoints(['Jan', 'Feb', 'Mar', 'Apr'], 'Labels');
$data->setAbscissa('Labels');
$data->setSerieDescription('Revenue', 'Monthly revenue');
$chart = new Line(800, 400, $data);
$chart->setFontProperties([
'FontName' => base_path('vendor/szymach/c-pchart/src/Resources/fonts/tahoma.ttf'),
'FontSize' => 10,
]);
$chart->setGraphArea(60, 30, 760, 350);
$chart->drawScale();
$chart->drawGrid();
$chart->drawLineChart();
$path = storage_path('app/charts/revenue.png');
$chart->render($path);
return response()->file($path);
Where to look first:
vendor/szymach/c-pchart/README.md — install notes and examples.vendor/szymach/c-pchart/src/Chart/ — available chart classes and their draw* methods.Render → render, SetFontProperties → setFontProperties).Separate data building from drawing. Put data assembly in one method/class and drawing in another. You can then unit-test the dataset without GD and reuse the same chart styling across report types.
Return a file, not a raw echo. The safest Laravel integration is to render to an absolute path and return response()->file() or response()->download(). If you must return bytes without touching disk:
ob_start();
$chart->render();
$png = ob_get_clean();
return response($png, 200, ['Content-Type' => 'image/png']);
Be aware some versions call header() internally when rendering to output, which can conflict with Laravel middleware and tests.
Use a dedicated renderer service. Bind a ChartRenderer class in the container with config for font path, default dimensions, palette, and output directory. This avoids scattering setFontProperties() and color arrays across controllers.
$data->setPalette('Revenue', ['R' => 41, 'G' => 128, 'B' => 185]);
$data->setPalette('Forecast', ['R' => 200, 'G' => 80, 'B' => 60, 'Alpha' => 70]);
Cache expensive renders with Laravel, not only pChart. The built-in CpChart\Cache is file-based and tied to chart settings; for application-level reuse, key a Laravel cache entry on the data hash and store the generated file path:
$key = 'chart:sales:' . md5(json_encode($rows));
$path = Cache::remember($key, now()->addMinutes(10), function () use ($rows) {
return $this->renderSalesChart($rows);
});
return response()->file($path);
Queue large or multi-chart generation. GD rendering is CPU- and memory-bound. For PDF packs, emails, or scheduled reports, generate in a job, store on S3, and serve via a signed URL. Keep the web request path limited to small, cached charts.
Standard chart workflow. For most chart types the sequence is:
$chart->setFontProperties([...]);
$chart->setGraphArea(...);
$chart->drawScale([...]); // axes
$chart->drawGrid([...]); // optional
$chart->drawLineChart(); // or drawBarChart(), drawStackedBarChart(), etc.
$chart->drawLegend(...); // optional
$chart->render($absolutePath);
Order matters: graph area and scale before the series drawing, legend after.
Pick the right class and method. Line charts have drawLineChart(), drawFilledLineChart(), drawCubicCurve(); bars have drawBarChart() and drawStackedBarChart(); pies have drawBasicPieGraph(), drawFlatPieGraph(), draw3DPieGraph(), plus drawPieLegend(). Stock, scatter, radar, gauge, bubble, and surface charts each expose their own draw* entry point — inspect the class rather than assuming a shared API.
Embedding in email/PDF. Render to a temp file, read it, and base64-encode it for inline HTML, or pass the file path to DomPDF/Snappy. Delete temp files after use.
Testing. Feature-test the HTTP response (200, Content-Type: image/png) and validate the file with getimagesize(). Unit-test the data-building step. Avoid pixel-perfect snapshot tests; GD output varies slightly across versions and platforms.
Keep chart instances disposable. A chart object accumulates draw calls and internal state. Construct a new chart per render rather than reusing one for different datasets.
GPL-3.0 is the biggest practical risk. c-pchart is GPL-3.0. If you distribute the application, GPL obligations attach to the combined work. Internal SaaS use is usually different from distribution, but get legal review before shipping it in a proprietary product. This alone often disqualifies it for commercial closed-source distribution.
Legacy API names are everywhere. Most tutorials, Stack Overflow answers, and the official pChart docs use PascalCase (Render, SetFontProperties, DrawLineChart). c-pchart uses camelCase. Translate before copying examples.
Fonts are file paths, not family names. FontName must point to a real .ttf file. Relative paths resolve from the current working directory, which is fragile under Laravel. Always use base_path()/storage_path() and verify the file exists. A missing font typically produces warnings and blank text rather than a clean exception.
render() behavior depends on the argument. render('/abs/path/chart.png') writes a file; render() outputs to the browser and may call header(). The extension controls the format (.png, .jpg, .gif); there is no SVG or modern WebP support. Relative output paths depend on CWD — always pass absolute paths.
Global constants are part of the API. Constants such as PIE_PERCENTAGE, LEGEND_NOBORDER, LEGEND_HORIZONTAL, and various LINE_*/BAR_* values are legacy global definitions loaded through Composer autoload files. They can collide in large applications, and they are easy to typo silently. Prefer class constants where the installed version exposes them; otherwise centralize the constants you use.
Data alignment is strict. Every series needs the same number of points, and the abscissa series must be added and set with setAbscissa() before drawing. Mismatched counts produce warnings, missing series, or truncated charts. Series names are array keys — adding two series with the same name overwrites the first.
Alpha is 0–100, not 0–127. Color arrays use ['R' => 0, 'G' => 0, 'B' => 0, 'Alpha' => 50]. Passing GD-style alpha values will produce unexpectedly solid or invisible elements.
Caching requires invalidation discipline. The file cache is keyed on chart parameters, but if your inputs or styling change and the key does not, you will serve stale charts. Clear cache directories on deploy if necessary, and ensure the cache directory is writable by the PHP process. For most Laravel apps, wrapping rendering in Laravel's cache with your own data-derived key is simpler and more predictable.
Failures are often quiet. Many legacy methods return false or emit PHP warnings instead of throwing. After rendering, check file_exists($path) and getimagesize($path), and log failures. Do not assume a chart was produced because no exception was thrown.
Large datasets and memory. GD holds the entire image in memory. Wide/time-series charts with thousands of points can hit memory limits and become slow. Downsample or aggregate before plotting, cap chart dimensions, and avoid generating charts synchronously on high-traffic pages.
Method availability varies by chart type. drawScale() options, legend methods, and graph-area behavior differ between line, pie, gauge, and stock charts. Check the concrete class rather than assuming the line-chart workflow applies. method_exists() checks are useful when building a generic renderer.
PHP 8 type strictness. Newer majors add stricter type hints. Legacy pChart code that passed numeric strings can trigger TypeErrors. Cast values to int/float at the boundary of your data layer.
No interactivity or responsiveness. This is a server-side raster renderer. For dashboards, tooltips, and responsive charts, use a JavaScript library or a service like QuickChart. Use c-pchart where a static image is genuinely required: PDFs, emails, reports, thumbnails, and server-generated exports.
Security and maintenance. Never pass user-controlled strings into file paths or font paths. Sanitize labels rendered onto images. The package has a small maintainer base and a long legacy tail; pin a version, keep tests around chart generation, and be prepared to swap it out if maintenance stalls.
How can I help you explore Laravel packages today?