davaxi/sparkline
davaxi/sparkline is a lightweight PHP library for generating compact sparkline charts from numeric data. It offers customizable dimensions, colors, and image output, making it easy to add small inline trend visualizations to Laravel or any PHP app.
davaxi/sparkline is a single-class, GD-based PNG renderer for tiny line charts. There is no service provider, config file, facade, or Blade component — you instantiate Davaxi\Sparkline yourself and feed it an array of numbers.
Install and verify the only hard dependency:
composer require davaxi/sparkline
php -m | grep -i gd # must be present; install php-gd if not
Where to look first:
vendor/davaxi/sparkline/README.md — the entire public API is documented there.vendor/davaxi/sparkline/src/Sparkline.php — one class; read setData(), generate()/show(), save(), and the color/alpha setters to confirm method names for your pinned version.First use case — a trend column on a dashboard. Generate a 200×40 PNG from the last 30 days of an Eloquent aggregate and return it from a route:
use Davaxi\Sparkline;
Route::get('/metrics/orders/sparkline', function () {
$values = Order::query()
->where('created_at', '>=', now()->subDays(30))
->selectRaw('DATE(created_at) as day, COUNT(*) as total')
->groupBy('day')
->orderBy('day')
->pluck('total')
->map(fn ($v) => (float) $v)
->all();
$sparkline = new Sparkline();
$sparkline->setWidth(200);
$sparkline->setHeight(40);
$sparkline->setPadding(2);
$sparkline->setLineThickness(2);
$sparkline->setLineColor('#2563eb');
$sparkline->setFillColor('#2563eb', 0.2);
$sparkline->setBackgroundColor('#ffffff');
$sparkline->setData($values);
$png = $sparkline->generate();
return response($png, 200, [
'Content-Type' => 'image/png',
'Content-Length' => strlen($png),
'Cache-Control' => 'public, max-age=300',
]);
});
If your installed version does not expose a binary-returning method, use save() to a temp path and read it back, or show() behind output buffering — but prefer generate()/save() inside Laravel so the framework owns the response headers.
Wrap it in one application service. Do not scatter new Sparkline() calls across controllers and Blade files. Put defaults (dimensions, palette, padding, thickness) in config/sparkline.php and expose a single renderer:
namespace App\Support;
use Davaxi\Sparkline;
class SparklineRenderer
{
public function png(array $values, array $overrides = []): string
{
$o = array_merge(config('sparkline.defaults'), $overrides);
$sparkline = new Sparkline();
$sparkline->setWidth($o['width']);
$sparkline->setHeight($o['height']);
$sparkline->setPadding($o['padding']);
$sparkline->setLineThickness($o['thickness']);
$sparkline->setLineColor($o['line_color']);
$sparkline->setFillColor($o['fill_color'], $o['fill_alpha']);
$sparkline->setBackgroundColor($o['background']);
$sparkline->setData($this->normalize($values));
return $sparkline->generate();
}
private function normalize(array $values): array
{
$values = array_values(array_filter($values, 'is_numeric'));
return array_map('floatval', $values);
}
}
This gives you one place to change brand colors, add caching, or swap the renderer later.
Inline base64 for dashboard pages. A dashboard with 20 sparklines should not fire 20 additional HTTP requests. Render server-side and embed data URIs:
function sparkline_data_uri(array $values, array $opts = []): string
{
$png = app(SparklineRenderer::class)->png($values, $opts);
return 'data:image/png;base64,' . base64_encode($png);
}
<img src="{{ sparkline_data_uri($values, ['line_color' => '#16a34a']) }}"
width="200" height="40" alt="30-day trend">
Cache the rendered PNG, not just the query. GD rendering and base64 encoding are cheap per image but add up across a dashboard. Key on the data hash and styling so style changes invalidate correctly:
$key = 'sparkline:' . md5(serialize([$values, $options]));
$png = Cache::remember($key, now()->addMinutes(10), fn () =>
app(SparklineRenderer::class)->png($values, $options)
);
For routes, add a cache-busting query string (?v={{ $lastUpdated->timestamp }}) and keep long Cache-Control headers.
Prepare data as a normalized, ordered numeric series. The class plots values left-to-right and auto-scales between the series min and max. There is no date axis, so gaps in your aggregate must be filled in PHP (zero-fill missing days) or the x-axis will be misleading. Cast database pluck() results to float — MySQL often returns aggregates as strings.
One sparkline per series. The package draws a single line; there is no multi-series API. For comparisons, render two images side by side or stack them with a shared legend in HTML.
Saving to disk for exports or queued jobs. Use save() with an absolute path under storage_path():
$sparkline->save(storage_path('app/reports/orders-sparkline.png'));
Create the directory first and ensure the web/queue user can write to it.
Testing. Do not assert exact bytes — GD output can differ across GD versions. Assert the PNG signature and decode dimensions:
$png = app(SparklineRenderer::class)->png([1, 4, 2, 8]);
$this->assertStringStartsWith("\x89PNG\r\n\x1a\n", $png);
$this->assertSame([200, 40], array_slice(getimagesizefromstring($png), 0, 2));
Also test your normalizer: empty input, one value, flat series, numeric strings, and nulls.
Compose with Laravel HTTP responses rather than show(). show() sends Content-Type: image/png and echoes to the output buffer, which conflicts with Laravel middleware, response caching, and test assertions. Return the binary through response().
ext-gd with PNG support must be compiled in. CI containers and minimal PHP images frequently omit it; add an explicit platform requirement in composer.json.show() sends headers directly. Calling it after any output (including a stray newline or debug dump()) produces "headers already sent" warnings and corrupt images. In Laravel, use generate() or save() and return a response.generate(), show(), or only save(). Pin the version in composer.json and confirm against src/Sparkline.php; this package is small and low-activity, so don't assume the README matches the installed tag.null and render a placeholder.2 * padding >= height (or width), the drawable area collapses. Keep padding small relative to the requested size.rgba() CSS strings. Pass plain hex (#2563eb), not named colors.width/height for crisp HiDPI output. Don't try to use this as a general charting library; it has no axes, legends, or hover behavior.memory_limit. Keep images small and cache aggressively; never render a sparkline inside a loop over thousands of records.save() requires a real, writable path. Relative paths resolve against the process working directory, which differs between FPM, queue workers, and tests. Always use storage_path().array<int, int|float> in your renderer, reject or filter everything else. Letting strings with thousands separators ("1,204") reach setData() produces silently wrong charts.How can I help you explore Laravel packages today?