Merge remote-tracking branch 'origin/feature/3-analytics-dashboard' into feature/29-label-photos

This commit is contained in:
Andy Heathershaw 2017-09-10 07:51:10 +01:00
commit aa99d76ae5
6 changed files with 510 additions and 3 deletions

View File

@ -0,0 +1,230 @@
<?php
namespace App\Http\Controllers\Gallery;
use App\Facade\Theme;
use App\Http\Controllers\Controller;
use App\Photo;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class StatisticsController extends Controller
{
public function albumSizeByPhotosChart(Request $request)
{
$stats = DB::table('photos')
->join('albums', 'albums.id', '=', 'photos.album_id')
->groupBy('albums.name')
->select('albums.name', DB::raw('count(photos.id) as photo_count'))
->orderBy('photo_count', 'desc')
->limit(10)
->get();
$labels = [];
$data = [];
foreach ($stats as $stat)
{
$labels[] = $stat->name;
$data[] = $stat->photo_count;
}
return response()->json([
'labels' => $labels,
'backgrounds' => $this->rotateColoursForData($data),
'data' => $data
]);
}
public function albumSizeByPhotoSizeChart(Request $request)
{
$stats = DB::table('photos')
->join('albums', 'albums.id', '=', 'photos.album_id')
->groupBy('albums.name')
->select('albums.name', DB::raw('sum(photos.file_size) as photo_size'))
->orderBy('photo_size', 'desc')
->limit(10)
->get();
$labels = [];
$data = [];
foreach ($stats as $stat)
{
$labels[] = $stat->name;
$data[] = ceil($stat->photo_size / 1024 / 1024);
}
return response()->json([
'labels' => $labels,
'backgrounds' => $this->rotateColoursForData($data),
'data' => $data
]);
}
public function camerasChart(Request $request)
{
$stats = DB::table('photos')
->where([
['camera_make', '!=', ''],
['camera_model', '!=', '']
])
->groupBy('camera_make', 'camera_model')
->select('camera_make', 'camera_model', DB::raw('count(*) as photo_count'))
->orderBy('photo_count', 'desc')
->get();
$labels = [];
$data = [];
foreach ($stats as $stat)
{
// Remove the model from the make if it starts with it
// E.g. CANON - CANON EOS 1200D becomes just CANON EOS 1200D
if (substr($stat->camera_model, 0, strlen($stat->camera_make)) == $stat->camera_make)
{
$stat->camera_make = trim(substr($stat->camera_make, strlen($stat->camera_make)));
}
$labels[] = sprintf('%s %s', $stat->camera_make, $stat->camera_model);
$data[] = $stat->photo_count;
}
return response()->json([
'labels' => $labels,
'backgrounds' => $this->rotateColoursForData($data),
'data' => $data
]);
}
public function fileSizeChart(Request $request)
{
$labels = [
trans('gallery.statistics.file_sizes_legend.small'),
trans('gallery.statistics.file_sizes_legend.medium'),
trans('gallery.statistics.file_sizes_legend.large'),
trans('gallery.statistics.file_sizes_legend.huge')
];
$data = [0, 0, 0, 0];
$stats = DB::table('photos');
$stats->chunk(100, function($photos) use (&$data)
{
foreach ($photos as $photo)
{
if ($photo->file_size < (1 * 1024 * 1024))
{
$data[0]++;
}
else if ($photo->file_size < (3 * 1024 * 1024))
{
$data[1]++;
}
else if ($photo->file_size < (5 * 1024 * 1024))
{
$data[2]++;
}
else if ($photo->file_size >= (5 * 1024 * 1024))
{
$data[3]++;
}
}
});
return response()->json([
'labels' => $labels,
'backgrounds' => $this->rotateColoursForData($data),
'data' => $data
]);
}
public function index(Request $request)
{
return Theme::render('gallery.statistics');
}
public function photosTaken12Months(Request $request)
{
$labels = [];
$data = [];
foreach ($this->last12MonthsDates() as $date)
{
$fromDate = sprintf('%04d-%02d-01 00:00:00', $date[0], $date[1]);
$toDate = sprintf('%04d-%02d-%02d 23:59:59', $date[0], $date[1], cal_days_in_month(CAL_GREGORIAN, $date[1], $date[0]));
$photoCount = Photo::whereBetween('taken_at', array($fromDate, $toDate))->count();
$labels[] = date('M Y', strtotime($fromDate));
$data[] = $photoCount;
}
return response()->json([
'labels' => array_reverse($labels),
'data' => array_reverse($data)
]);
}
public function photosUploaded12Months(Request $request)
{
$labels = [];
$data = [];
foreach ($this->last12MonthsDates() as $date)
{
$fromDate = sprintf('%04d-%02d-01 00:00:00', $date[0], $date[1]);
$toDate = sprintf('%04d-%02d-%02d 23:59:59', $date[0], $date[1], cal_days_in_month(CAL_GREGORIAN, $date[1], $date[0]));
$photoCount = Photo::whereBetween('created_at', array($fromDate, $toDate))->count();
$labels[] = date('M Y', strtotime($fromDate));
$data[] = $photoCount;
}
return response()->json([
'labels' => array_reverse($labels),
'data' => array_reverse($data)
]);
}
private function last12MonthsDates()
{
$year = intval(date('Y'));
$month = intval(date('m'));
$datesNeeded = [];
while (count($datesNeeded) < 12)
{
$datesNeeded[] = [$year, $month];
$month--;
if ($month == 0)
{
$month = 12;
$year--;
}
}
return $datesNeeded;
}
private function rotateColoursForData(array $data = [])
{
$colours = ['#0F2240', '#174E79', '#287598', '#46BBB5', '#35DCAD'];
$result = [];
$lastIndex = 0;
for ($i = 0; $i < count($data); $i++)
{
$result[] = $colours[$lastIndex];
$lastIndex++;
if ($lastIndex >= count($colours))
{
$lastIndex = 0;
}
}
return $result;
}
}

View File

@ -14,5 +14,21 @@ return [
'manage_album_link_2' => 'Manage Album',
'open_album_link' => 'Open Album',
'other_albums_description' => 'You may also be interested in the following albums.',
'other_albums_heading' => 'More Albums in :album_name'
'other_albums_heading' => 'More Albums in :album_name',
'statistics' => [
'album_by_photos' => 'Top 10 largest albums - number of photos',
'album_by_size' => 'Top 10 largest albums - photo size (MB)',
'cameras' => 'Cameras Used',
'file_sizes' => 'Image file sizes',
'file_sizes_legend' => [
'small' => 'Small (<1MB)',
'medium' => 'Medium (<3MB)',
'large' => 'Large (<5MB)',
'huge' => 'Huge (>5MB)'
],
'intro' => 'This page displays some interesting graphs and statistics about :gallery_name.',
'taken_12_months' => 'Photos taken in the last 12 months',
'title' => 'Statistics',
'uploaded_12_months' => 'Photos uploaded in the last 12 months',
]
];

View File

@ -27,6 +27,7 @@ return [
'change_password' => 'Change password',
'login' => 'Login',
'logout' => 'Logout',
'register' => 'Register'
'register' => 'Register',
'statistics' => 'Statistics'
]
];

View File

@ -0,0 +1,250 @@
@extends('themes.base.layout')
@section('title', trans('gallery.statistics.title'))
@section('breadcrumb')
<li class="breadcrumb-item"><a href="{{ route('home') }}"><i class="fa fa-fw fa-home"></i></a></li>
<li class="breadcrumb-item active">@lang('gallery.statistics.title')</li>
@endsection
@section('content')
<div class="container">
<div class="row">
<div class="col">
<h1>@lang('gallery.statistics.title')</h1>
<div class="alert alert-info" style="margin-bottom: 30px;">
<i class="fa fa-fw fa-info"></i> @lang('gallery.statistics.intro', ['gallery_name' => UserConfig::get('app_name')])
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">@lang('gallery.statistics.taken_12_months')</div>
<div class="card-body text-center" id="taken-12m-graph">
<canvas id="taken-12m-chart" style="display: none;"></canvas>
<img class="loading" src="{{ asset('ripple.svg') }}"/>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">@lang('gallery.statistics.uploaded_12_months')</div>
<div class="card-body text-center" id="uploaded-12m-graph">
<canvas id="uploaded-12m-chart" style="display: none;"></canvas>
<img class="loading" src="{{ asset('ripple.svg') }}"/>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">@lang('gallery.statistics.cameras')</div>
<div class="card-body text-center" id="cameras-graph">
<canvas id="cameras-chart" style="display: none;"></canvas>
<img class="loading" src="{{ asset('ripple.svg') }}"/>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">@lang('gallery.statistics.file_sizes')</div>
<div class="card-body text-center" id="filesizes-graph">
<canvas id="filesizes-chart" style="display: none;"></canvas>
<img class="loading" src="{{ asset('ripple.svg') }}"/>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">@lang('gallery.statistics.album_by_photos')</div>
<div class="card-body text-center" id="album-photos-graph">
<canvas id="album-photos-chart" style="display: none;"></canvas>
<img class="loading" src="{{ asset('ripple.svg') }}"/>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">@lang('gallery.statistics.album_by_size')</div>
<div class="card-body text-center" id="album-size-graph">
<canvas id="album-size-chart" style="display: none;"></canvas>
<img class="loading" src="{{ asset('ripple.svg') }}"/>
</div>
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
{{-- TODO: include ChartJS locally --}}
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.bundle.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
/* Photos taken in the last 12 months */
$.get('{{ route('statistics.taken12Months') }}', function(result)
{
$('.loading', '#taken-12m-graph').hide();
$('canvas', '#taken-12m-graph').show();
var myLineChart = new Chart($('#taken-12m-chart'),
{
type: 'line',
data: {
datasets: [
{
borderColor: '#0F2240',
data: result.data,
fill: false
}
],
labels: result.labels
},
options: {
legend: {
display: false
}
}
});
});
/* Photos uploaded in the last 12 months */
$.get('{{ route('statistics.uploaded12Months') }}', function(result)
{
$('.loading', '#uploaded-12m-graph').hide();
$('canvas', '#uploaded-12m-graph').show();
var myLineChart = new Chart($('#uploaded-12m-chart'),
{
type: 'line',
data: {
datasets: [
{
borderColor: '#0F2240',
data: result.data,
fill: false
}
],
labels: result.labels
},
options: {
legend: {
display: false
}
}
});
});
/* Cameras */
$.get('{{ route('statistics.cameras') }}', function(result)
{
$('.loading', '#cameras-graph').hide();
$('canvas', '#cameras-graph').show();
var myPieChart = new Chart($('#cameras-chart'),
{
type: 'doughnut',
data: {
datasets: [
{
backgroundColor: result.backgrounds,
data: result.data
}
],
labels: result.labels
},
options: {
legend: {
display: true,
position: 'bottom'
}
}
});
});
/* File Sizes */
$.get('{{ route('statistics.fileSizes') }}', function(result)
{
$('.loading', '#filesizes-graph').hide();
$('canvas', '#filesizes-graph').show();
var myPieChart = new Chart($('#filesizes-chart'),
{
type: 'doughnut',
data: {
datasets: [
{
backgroundColor: result.backgrounds,
data: result.data
}
],
labels: result.labels
},
options: {
legend: {
display: true,
position: 'bottom'
}
}
});
});
/* Album size by photo count */
$.get('{{ route('statistics.albumSizePhotos') }}', function(result)
{
$('.loading', '#album-photos-graph').hide();
$('canvas', '#album-photos-graph').show();
var myPieChart = new Chart($('#album-photos-chart'),
{
type: 'doughnut',
data: {
datasets: [
{
backgroundColor: result.backgrounds,
data: result.data
}
],
labels: result.labels
},
options: {
legend: {
display: true,
position: 'bottom'
}
}
});
});
/* Album size by photo size */
$.get('{{ route('statistics.albumSizePhotoSize') }}', function(result)
{
$('.loading', '#album-size-graph').hide();
$('canvas', '#album-size-graph').show();
var myPieChart = new Chart($('#album-size-chart'),
{
type: 'doughnut',
data: {
datasets: [
{
backgroundColor: result.backgrounds,
data: result.data
}
],
labels: result.labels
},
options: {
legend: {
display: true,
position: 'bottom'
}
}
});
});
});
</script>
@endpush

View File

@ -9,7 +9,7 @@
@if (count($albums) > 0)
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="{{ url('/') }}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@lang('navigation.navbar.albums')
<i class="fa fa-book"></i> @lang('navigation.navbar.albums')
</a>
<div class="dropdown-menu">
@foreach ($albums as $album)
@ -17,6 +17,9 @@
@endforeach
</div>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('statistics.index') }}"><i class="fa fa-bar-chart"></i> @lang('navigation.navbar.statistics')</a>
</li>
@endif
@if (!Auth::guest() && (Auth::user()->can('admin:access')))

View File

@ -69,6 +69,13 @@ Route::get('/activate/{token}', 'Auth\ActivateController@activate')->name('auth.
Route::get('/password/change', 'Auth\ChangePasswordController@showChangePasswordForm')->name('auth.changePassword');
Route::post('/password/change', 'Auth\ChangePasswordController@processChangePassword')->name('auth.processChangePassword');
Route::get('/sitemap.xml', 'Gallery\DefaultController@sitemapXml');
Route::get('/statistics', 'Gallery\StatisticsController@index')->name('statistics.index');
Route::get('/statistics/album-size-photo-count', 'Gallery\StatisticsController@albumSizeByPhotosChart')->name('statistics.albumSizePhotos');
Route::get('/statistics/album-size-photo-size', 'Gallery\StatisticsController@albumSizeByPhotoSizeChart')->name('statistics.albumSizePhotoSize');
Route::get('/statistics/cameras', 'Gallery\StatisticsController@camerasChart')->name('statistics.cameras');
Route::get('/statistics/file-sizes', 'Gallery\StatisticsController@fileSizeChart')->name('statistics.fileSizes');
Route::get('/statistics/taken-12m', 'Gallery\StatisticsController@photosTaken12Months')->name('statistics.taken12Months');
Route::get('/statistics/uploaded-12m', 'Gallery\StatisticsController@photosUploaded12Months')->name('statistics.uploaded12Months');
Route::get('a/{albumUrlAlias}', 'Gallery\AlbumController@index')
->name('viewAlbum')
->where('albumUrlAlias', '.*');