NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages

A trend aggregates a value over time, bucketed by a unit, and returns a trends map keyed by bucket:

use App\Models\User;
use RoundlyConsulting\Metrics\Enums\Period;
use RoundlyConsulting\Metrics\Facades\Metrics;

Metrics::trend()
    ->count(User::query(), 'created_at')
    ->daily()
    ->range(Period::MonthToDate)
    ->toArray();
// result => ['trends' => ['2024-06-01' => 12.0, '2024-06-02' => 0.0, ...]]

Units

Set the unit with unit() — a Unit case or its string — or a convenience helper. The default is DAY unless you change metrics.default_unit; an unknown unit string is ignored and the current unit is kept:

use RoundlyConsulting\Metrics\Enums\Unit;

$metric->unit(Unit::Hour);   // or ->unit('HOUR')
$metric->perMinute();
$metric->hourly();
$metric->daily();            // the default (config: metrics.default_unit)
$metric->weekly();
$metric->monthly();
$metric->yearly();
ValueEnum caseHelperBucket key
MINUTEUnit::MinuteperMinute()Y-m-d H:i:00
HOURUnit::Hourhourly()Y-m-d H:00
DAYUnit::Daydaily()Y-m-d
WEEKUnit::Weekweekly()o-W — ISO year and week, e.g. 2022-52
MONTHUnit::Monthmonthly()Y-m
YEARUnit::Yearyearly()Y

Gap filling

Every bucket in the selected range is present in the output — empty buckets are filled with 0 so charts get a continuous axis. The axis starts at the bucket that holds the range start (the ISO week of January 1st for a weekly YTD, the month of the start for a monthly 90), so the bucket holding “now” is always there. Opt out to return only the buckets that have rows:

Metrics::trend()->count(User::query(), 'created_at')->daily()->withoutGapFilling()->toArray();

$metric->withoutGapFilling();          // only buckets that have rows
$metric->withoutGapFilling(false);     // re-enable filling (the default)

With the ALL range, the span is inferred from the first and last buckets that have data.

Multi-series trends

Split a trend by a dimension with groupBy(). The combined totals stay under trends, and an additive series key holds one bucket set per dimension value — each gap-filled across the same range, so every series shares the same buckets:

Metrics::trend()->count(User::query(), 'created_at')->daily()->groupBy('plan')->toArray();
// result => [
//   'trends' => ['2024-01-01' => 30.0, ...],            // totals across series
//   'series' => [
//     'pro'  => ['2024-01-01' => 20.0, ...],
//     'free' => ['2024-01-01' => 10.0, ...],
//   ],
// ]

Database support

Bucketing uses a database-specific SQL date expression. MySQL, MariaDB, PostgreSQL and SQLite are supported out of the box, so the same trend runs unchanged in development and production. Week buckets are ISO-8601 on every driver — including SQLite, which has no native ISO week. Any other driver throws MissingTrendQueryExpressionException until you map an expression for it in config('metrics.trend_drivers') (see Extending).

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.