brendt/php-sparkline
Generate lightweight SVG sparklines in PHP from arrays of numbers. brendt/php-sparkline offers a simple API for customizing colors, dimensions, and styles, making it handy for dashboards, reports, and compact data visuals.
composer require brendt/php-sparkline. It's a plain-PHP package: no service provider, no config file, no vendor:publish step. It works fine inside a Laravel app but is not Laravel-specific.src/SparkLine.php and src/Day.php. The public surface is intentionally tiny — a factory on SparkLine, a Day value object, and a render() that returns an SVG string. You can absorb the whole API in a few minutes.Day objects, hand them to SparkLine::from(...), echo the result.use Brendt\SparkLine\Day;
use Brendt\SparkLine\SparkLine;
$svg = SparkLine::from(
Day::make(new DateTimeImmutable('2025-09-01'), 4),
Day::make(new DateTimeImmutable('2025-09-02'), 9),
Day::make(new DateTimeImmutable('2025-09-03'), 2),
// ...
)->render();
return view('dashboard', ['sparkline' => $svg]);
Check src/Day.php for the exact Day::make() signature in your installed version — it may accept a string date rather than a DateTimeInterface — and check SparkLine for the styling setters (width/height/colour) you intend to rely on.
Query → daily series → sparkline. The package does not query anything; you own the aggregation. Group by day in SQL, then map the result.
$rows = DB::table('users')
->where('created_at', '>=', now()->subDays(29)->startOfDay())
->selectRaw('DATE(created_at) as day, COUNT(*) as count')
->groupBy('day')
->pluck('count', 'day');
$days = collect(range(29, 0))
->map(fn ($i) => now()->subDays($i)->toDateString())
->map(fn ($date) => Day::make(
new DateTimeImmutable($date),
(int) ($rows[$date] ?? 0),
))
->all();
$svg = SparkLine::from(...$days)->render();
Wrap it in a small builder class. This is the single highest-leverage pattern. Put the date-range padding, query, and render() in one testable class, and inject it wherever a sparkline is needed (dashboard controller, weekly digest command, Slack notifier). It keeps the "charting" concern out of controllers and gives you one place to change colours or ranges.
Blade component wrapper. Since render() returns raw SVG, render it through a dedicated component so templates stay clean:
{{-- resources/views/components/sparkline.blade.php --}}
<div class="sparkline" role="img" aria-label="{{ $label }}">
{!! $svg !!}
</div>
Caching. SVG output is deterministic and cheap to cache. Cache::remember("sparkline:signups:30d", now()->addMinutes(10), fn () => $builder->render(...)) removes the group-by cost on every page load. Cache the string, not the collection — no hydration cost and no serialization surprises.
Email usage. Inline SVG is stripped or mangled by several clients (notably Gmail in some contexts). For emails, prefer a data URI in an <img> (data:image/svg+xml;base64,), or render to a file and reference a hosted URL. Test in Gmail and Outlook before shipping.
Testing. Assert on coarse properties of the string (str_contains($svg, '<svg'), count of bars/paths) rather than exact path coordinates. For visual regressions, snapshot the SVG and update deliberately when you bump the package.
Other integrations. The same builder works for any count-per-day series: GitHub-style commit activity, error counts, queue throughput, API requests. If you also want a full contribution heatmap, keep this package for the sparkline and don't try to force it into that role.
created_at is typically UTC. DATE(created_at) groups by UTC day, which can shift the apparent peak for users in other zones. Decide consciously: UTC for system metrics, app/user timezone for user-facing charts.{{ $svg }} will HTML-encode the markup and print it as text. Use {!! $svg !!}. For the same reason, don't run the output through any sanitizer that strips tags unless you've verified <svg> and its children survive.null and bad values. LEFT JOIN yields null counts; cast to int with a default. Negative or wildly out-of-range values will distort the normalization — clamp or filter before constructing Day objects.fill= or height= on the returned string. Use the package's setters/constructor options; post-processing is brittle across versions.role="img", <title>, or aria-label for you. Wrap the SVG and provide a readable label — every sparkline in a UI should have one.image/svg+xml, ensure nothing (BOM, stray echo, debug output) precedes the <svg/XML declaration, or the browser will reject the image.SparkLine and Day are simple enough that a thin adapter in your codebase is more maintainable than a fork.composer.json and treat upgrades as a chance to refresh snapshot tests.How can I help you explore Laravel packages today?