diff --git a/.idea/laravel-metrics.iml b/.idea/laravel-metrics.iml index 4d75f98..1618cfb 100644 --- a/.idea/laravel-metrics.iml +++ b/.idea/laravel-metrics.iml @@ -54,6 +54,7 @@ + diff --git a/.idea/php.xml b/.idea/php.xml index 9867cfb..f9d643e 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -66,6 +66,7 @@ + diff --git a/README.md b/README.md index bd8aaf2..6d42dbb 100644 --- a/README.md +++ b/README.md @@ -282,6 +282,19 @@ LaravelMetrics::query(...) ->groupByDay() ``` +### Group data (only for ```trends```) +You can group data of a column with multiple values to use it in a dataset for your charts. For example : + +```php +Order::metrics() + ->countByMonth(column: 'status') + ->groupData(['pending', 'delivered', 'cancelled'], Aggregate::SUM->value) + ->fillMissingData() + ->trends(); +``` + +***Note :*** Follow same order in the example to avoid false data. + ## Translations Days and months names are automatically translated using `config(app.locale)` except 'week' period. diff --git a/src/LaravelMetrics.php b/src/LaravelMetrics.php index 47d213c..c7d7d70 100644 --- a/src/LaravelMetrics.php +++ b/src/LaravelMetrics.php @@ -57,6 +57,10 @@ class LaravelMetrics protected string $groupBy; + protected string $groupedData = ''; + + protected array $groupedDataLabels = []; + public function __construct(protected Builder|QueryBuilder $builder) { $this->table = $this->builder->from; @@ -519,7 +523,7 @@ protected function trendsData(): Collection { if (is_array($this->period)) { return $this->builder - ->selectRaw($this->asData().', '.$this->asLabel($this->formatDateColumn(), false)) + ->selectRaw($this->asData().', '.$this->asLabel($this->formatDateColumn(), false).$this->groupedData) ->whereBetween(DB::raw($this->formatDateColumn()), [$this->period[0], $this->period[1]]) ->groupBy('label') ->orderBy('label') @@ -528,7 +532,7 @@ protected function trendsData(): Collection return match ($this->period) { Period::DAY->value => $this->builder - ->selectRaw($this->asData().', '.$this->asLabel(Period::DAY->value)) + ->selectRaw($this->asData().', '.$this->asLabel(Period::DAY->value).$this->groupedData) ->whereYear($this->dateColumn, $this->year) ->whereMonth($this->dateColumn, $this->month) ->when($this->count === 1, function (Builder|QueryBuilder $query) { @@ -542,7 +546,7 @@ protected function trendsData(): Collection ->get(), Period::WEEK->value => $this->builder - ->selectRaw($this->asData().', '.$this->asLabel(Period::WEEK->value)) + ->selectRaw($this->asData().', '.$this->asLabel(Period::WEEK->value).$this->groupedData) ->whereYear($this->dateColumn, $this->year) ->whereMonth($this->dateColumn, $this->month) ->when($this->count === 1, function (Builder|QueryBuilder $query) { @@ -556,7 +560,7 @@ protected function trendsData(): Collection ->get(), Period::MONTH->value => $this->builder - ->selectRaw($this->asData().', '.$this->asLabel(Period::MONTH->value)) + ->selectRaw($this->asData().', '.$this->asLabel(Period::MONTH->value).$this->groupedData) ->whereYear($this->dateColumn, $this->year) ->when($this->count === 1, function (Builder|QueryBuilder $query) { return $query->where(DB::raw($this->formatPeriod(Period::MONTH->value)), $this->month); @@ -569,7 +573,7 @@ protected function trendsData(): Collection ->get(), Period::YEAR->value => $this->builder - ->selectRaw($this->asData().', '.$this->asLabel(Period::YEAR->value)) + ->selectRaw($this->asData().', '.$this->asLabel(Period::YEAR->value).$this->groupedData) ->when($this->count === 1, function (Builder|QueryBuilder $query) { return $query->where(DB::raw($this->formatPeriod(Period::YEAR->value)), $this->year); }) @@ -583,19 +587,33 @@ protected function trendsData(): Collection ->get(), default => $this->builder - ->selectRaw($this->asData().', '.$this->asLabel()) + ->selectRaw($this->asData().', '.$this->asLabel().$this->groupedData) ->groupBy('label') ->orderBy('label') ->get(), }; } - protected function asData(): string + public function groupData(array $dataLabels, string $aggregate): self + { + $this->groupedDataLabels = $dataLabels; + $result = []; + + foreach ($dataLabels as $key => $value) { + $result[] = $aggregate.'('.$this->column.' = "'.$value.'")'." as data$key"; + } + + $this->groupedData = ', '.implode(', ', $result); + + return $this; + } + + protected function asData(string $name = 'data'): string { - return "$this->aggregate($this->column) as data"; + return "$this->aggregate($this->column) as $name"; } - protected function asLabel(?string $label = null, bool $format = true): string + protected function asLabel(string $label = null, bool $format = true): string { if (is_null($this->labelColumn)) { $label = ! $format ? $label : $this->formatPeriod($label); @@ -606,7 +624,7 @@ protected function asLabel(?string $label = null, bool $format = true): string return $label.' as label'; } - protected function populateMissingDataForPeriod(array $data, bool $inPercent = false): array + protected function populateMissingDataForPeriod(array $data, bool $inPercent = false, string $dataLabel = 'data'): array { $dates = $this->getCustomPeriod(); $data = collect($data); @@ -618,7 +636,7 @@ protected function populateMissingDataForPeriod(array $data, bool $inPercent = f if ($dataForDate) { $result[] = [ 'label' => $dataForDate['label'], - 'data' => $dataForDate['data'], + 'data' => (int) $dataForDate[$dataLabel], ]; } else { $result[] = [ @@ -723,36 +741,55 @@ public function metricsWithVariations(int $previousCount, string $previousPeriod return $result; } - /** - * Generate trends data for charts - */ - public function trends(bool $inPercent = false): array + protected function trendsWithMergedData(bool $inPercent = false): array { + $result = []; + $trendsData = $this ->trendsData() ->toArray(); $trendsData = array_map(fn ($datum) => (array) $datum, $trendsData); + $data = [$this->getFormattedTrendsData($trendsData, $inPercent)]; + + foreach ($this->groupedDataLabels as $key => $value) { + $data[] = $this->getFormattedTrendsData($trendsData, $inPercent, "data$key"); + } + + foreach ($data as $key => $value) { + $result['labels'] = $value['labels']; + + if ($key === 0) { + $result['data']['total'] = $value['data']; + } else { + $result['data'][$this->groupedDataLabels[$key - 1]] = $value['data']; + } + } + + return $result; + } + protected function getFormattedTrendsData(array $trendsData, bool $inPercent = false, string $dataLabel = 'data'): array + { if (! $this->fillMissingData) { $trendsData = $this->formatDate($trendsData); - return $this->formatTrends($trendsData, $inPercent); + return $this->formatTrends($trendsData, $inPercent, $dataLabel); } else { if (! is_null($this->labelColumn)) { - $trendsData = $this->formatTrends($trendsData, $inPercent); + $trendsData = $this->formatTrends($trendsData, $inPercent, $dataLabel); return $this->populateMissingData($this->getLabelsData(), $trendsData); } if (is_array($this->period)) { - return $this->populateMissingDataForPeriod($trendsData, $inPercent); + return $this->populateMissingDataForPeriod($trendsData, $inPercent, $dataLabel); } if (is_string($this->period)) { $trendsData = $this->formatDate($trendsData); - return $this->populateMissingData($this->getPeriod(), $this->formatTrends($trendsData, $inPercent)); + return $this->populateMissingData($this->getPeriod(), $this->formatTrends($trendsData, $inPercent, $dataLabel)); } } @@ -762,9 +799,28 @@ public function trends(bool $inPercent = false): array ]; } - protected function formatTrends(array $data, bool $inPercent = false): array + /** + * Generate trends data for charts + */ + public function trends(bool $inPercent = false): array + { + $trendsData = $this + ->trendsData() + ->toArray(); + + $trendsData = array_map(fn ($datum) => (array) $datum, $trendsData); + + if (! empty($this->groupedDataLabels)) { + return $this->trendsWithMergedData($inPercent); + } + + return $this->getFormattedTrendsData($trendsData, $inPercent); + } + + protected function formatTrends(array $data, bool $inPercent = false, string $dataLabel = 'data'): array { $total = 0; + $result = [ 'labels' => [], 'data' => [], @@ -772,8 +828,8 @@ protected function formatTrends(array $data, bool $inPercent = false): array foreach ($data as $datum) { $result['labels'][] = $datum['label']; - $result['data'][] = $datum['data']; - $total += $datum['data']; + $result['data'][] = (int) $datum[$dataLabel]; + $total += $datum[$dataLabel]; } if (! $inPercent) {