Laravel and Highcharts graph
Hello developers, In this article I am will show you how to display your data with highcharts graph. I am using Laravel/PHP for this.
I will use this graph:https://www.highcharts.com/demo/line-labels/grid-light
to show active and trial users of our database.
Lets start...
step1:
first let's get highchart library and include it in layouts/app.blade.php file of Laravel project.
<script src="https://code.highcharts.com/highcharts.js"></script>
step2:
Let's make a controller and view file to show the graph.
To make controller I typed this command in terminal
php artisan make:controller ChartController
and make a view file inside
resources/views
folder
let's give name as
graph.blade.php
and now will make a Route in web.php
This is my route
Route::get('/chart', [App\Http\Controllers\Chartcontroller::class, 'index']);
Now the last step is to make a index method in ChartController and return a view as below
ChartController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ChartController extends Controller
{
public function index()
{
return view('graph');
}
}
my graph.blade.php looks like this
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-12">
<div class="card">
<div class="card-header">Active and Trial Users
</div>
<div class="card-body">
graph
</div>
</div>
</div>
</div>
</div>
@endsection
and this is view page looks like: A simple page with graph
as a message
Step3:
Visit this link https://www.highcharts.com/demo/line-labels/grid-light and get all the code and paste it in your graph.blade.php with <script>
tag as show below:
Make sure you have <div id="container></div>
to load graph in this div
graph.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-12">
<div class="card">
<div class="card-header">Active and Trial Users
</div>
<div class="card-body">
<div id="container>
//here we will load the chart
</div>
</div>
</div>
</div>
</div>
</div>
<script>
//highchart code here
</script>
@endsection
Finally our graph.blade.php should looks like this:
Here I have changed the graph title to Active and Trail users, title of y-axis to Users , name of series to Active and Trial and aasigned whole chart to variable const chart
as below.
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-12">
<div class="card">
<div class="card-header">Active and Trial Users
</div>
<div class="card-body">
<div id="container"></div>
</div>
</div>
</div>
</div>
</div>
<script>
const chart = Highcharts.chart('container', {
chart: {
type: 'line'
},
title: {
text: 'Active and Trail users'
},
subtitle: {
text: ''
},
xAxis: {
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
'Oct', 'Nov', 'Dec'
]
},
yAxis: {
title: {
text: 'Users'
}
},
plotOptions: {
line: {
dataLabels: {
enabled: true
},
enableMouseTracking: false
}
},
series: [{
name: 'Active',
data: [7.0, 6.9, 9.5, 14.5, 18.4, 21.5, 25.2, 26.5, 23.3, 18.3, 13.9,
9.6
]
}, {
name: 'Trial',
data: [3.9, 4.2, 5.7, 8.5, 11.9, 15.2, 17.0, 16.6, 14.2, 10.3, 6.6, 4.8]
}]
});
</script>
@endsection
Now if you view this graph in browser, it should look like this
Step4:
Now it's time to setup our database structure.I have all the users informations in users
table
My users table has id ,name,email,password,status,plan,createad_at,updated_at
Your table must have status
and plan
. Here status
is the users status which can be either plan
or trial/null
. and plan
is user premium plan such as "business plan","enterprises plan" etc.
so now we can easily determine whether user has active account ot trial account.
Active account
if users status is active and user has not trail/null plan and
Trial account
if user has trail/null plan
Step5:
In step4 I showed you users table. Now we will wrtite the logic to determine if the user is Active or Trail based on step4 scenario.
We will make scope in User.php
file. If you are not familiar with scope then please check the laravel documentation. Laravel scopes
I will make two scopes. One is to get Active users and other one is to get Trial users
/**
* get all paid users
*/
public function scopeAllPremiumUsers($query)
{
$premiumUser = $query->where('status', 'active')->where(function ($query) {
$query->where([
['plan', '!=', 'trial'],
['plan', '!=', null]
]);
});
return $premiumUser;
}
/**
* get all Trail users
*/
public function scopeAllTrailUsers($query)
{
return $query->where('plan', 'trial')->orWhere('plan', null);
}
Step6:
Now we will make two more routes in web.php
one route will return all active users and the other route will return all the trrial users
Route::get('/chart', [App\Http\Controllers\Chartcontroller::class, 'index']);
//New route
Route::get('/chart/active/users', [App\Http\Controllers\Chartcontroller::class, 'activeUsers']);
Route::get('/chart/trial/users', [App\Http\Controllers\Chartcontroller::class, 'trialusers']);
Step7:
Now we will work on our controller and we will implement the scopes we have defined in our User.php
Now here I want to hardcode date. Later we will make the date dynamic.
I want to show active and trail users of date2021-01-01
year because I have some active and trial users in this date.
Before that I can get trial users as
User::allTrailUser()->get();
and active users as
User::allPremiumUser()->get();
allTrailUser()
and allPremiumUser()
are the two methods which we have in User.php
These are the steps I am going to do now to get Active and Trial users in required format
/**
* get trial users (user with plan trial or null)
* group by created_at date
* count number of user based on month
* start from index 1 to 12 for 12 month,fill with 0 values of particluar key(month)that doesn't exist in collection
* sort arrays
* convert collection to array
*/
I am grouping the users and counting number of users. In some case we may not have particular month of date in our database. for example for year 2021
. For example If don't have record for month March
. So in this case I still have to show users of 12 months,not 11 months. So for march
I will set number of users 0
and month index as 3
(jan is 1,feb is 2,march 3,april 4.....Dec 12)
Which can be done by this
union(array_fill(1, 12, 0))
sortKeys()
This are my two methods:
public function activeUsers()
{
$activeUser = User::allPremiumUser()->orderBy('created_at')->whereYear('created_at','2021')->get()
->groupBy(function ($date) {
return $date->created_at->month;
})
->map(function ($group) {
return $group->count();
})
->union(array_fill(1, 12, 0))
->sortKeys()
->toArray();
return (array_values($activeUser));
}
public function trialUsers()
{
$trialUser = User::allTrailUser()->orderBy('created_at')->whereYear('created_at','2021')->get()
->groupBy(function ($date) {
return $date->created_at->month;
})
->map(function ($group) {
return $group->count();
})
->union(array_fill(1, 12, 0))
->sortKeys()
->toArray();
return (array_values($trialUser));
}
}
Now lets call this two methods in Index method of same controller
public function index()
{
$trialUserForChart = $this->trialUsers();
$activeUserForChart = $this->activeUsers();
return view('graph',compact('trialUserForChart','activeUserForChart'));
}
Now our ChartController.php
looks like this
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
class ChartController extends Controller
{
public function index()
{
$trialUserForChart = $this->trialUsers();
$activeUserForChart = $this->activeUsers();
return view('graph',compact('trialUserForChart','activeUserForChart'));
}
public function activeUsers()
{
$activeUser = User::allPremiumUser()->orderBy('created_at')->whereYear('created_at','2021')->get()
->groupBy(function ($date) {
return $date->created_at->month;
})
->map(function ($group) {
return $group->count();
})
->union(array_fill(1, 12, 0))
->sortKeys()
->toArray();
return (array_values($activeUser));
}
public function trialUsers()
{
$trialUser = User::allTrailUser()->orderBy('created_at')->whereYear('created_at','2021')->get()
->groupBy(function ($date) {
return $date->created_at->month;
})
->map(function ($group) {
return $group->count();
})
->union(array_fill(1, 12, 0))
->sortKeys()
->toArray();
return (array_values($trialUser));
}
}
If you visit each route /chart/active/users
or /chart/trial/users
it returns the arrays of 12 data for 12 months as shown in the picture. The image below shows trial users data.
So as you see, the first record is of Jan, second is Feb and so on. O means we don't have record for that month. You may have differnet data.
Step8:
Now lets pass index method varibale $activeUserForChart
and $trialUserForChart
to graph.blade.php
and convert it into JSON with json_encode
json_encode($activeUserForChart);
json_encode($trialUserForChart);
Now our graph.balde.php
looks like below
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-12">
<div class="card">
<div class="card-header">Active and Trial Users
</div>
<div class="card-body">
<div id="container"></div>
</div>
</div>
</div>
</div>
</div>
<script>
const chart = Highcharts.chart('container', {
chart: {
type: 'line'
},
title: {
text: 'Active and Trail users'
},
subtitle: {
text: ''
},
xAxis: {
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
'Oct', 'Nov', 'Dec'
]
},
yAxis: {
title: {
text: 'Users'
}
},
plotOptions: {
line: {
dataLabels: {
enabled: true
},
enableMouseTracking: false
}
},
series: [{
name: 'Active',
data: <?php echo json_encode($activeUserForChart); ?>
}, {
name: 'Trial',
data: <?php echo json_encode($trialUserForChart); ?>
}]
});
</script>
@endsection
Based on this data my graph looks like below
Step9:
From this step we will make our chart dynamic. We will remove the hardcoded year and we will send the year from dropdown list and refresh the chart with the particular year data.
Let's make a dropdown in graph.blade.php
. I want to show year from 2013 to cuurent year.
<select name="year" id="year" class="float-right border-5">
@foreach (range(date('Y'), 2013) as $year)
<option value="{{ $year }}">{{ $year }}</option>
@endforeach
</select>
Now will make end point which will return the active and trial users.
in web.php let's register new route
Route::get('/chart/active-trial', [App\Http\Controllers\Chartcontroller::class, 'activeTrail']);
In Chartcontroller we will make a method that will return active and trial users for given year.
Lets assume we are reciving year form ajax request and we receive year in our method as request()->year
public function activeTrail()
{
$year = request()->year;
return [
'active'=>$this->activeUsers($year),
'trial'=> $this->trialUsers($year)
];
}
So now out activeUsers() and trialUsers() accepts year as parameter. So we have to modify our methods accordingly.
Our final ChartController looks as below.
Chartcontroller.php
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
class ChartController extends Controller
{
public function index()
{
$year = date('Y');
$trialUserForChart = $this->trialUsers($year);
$activeUserForChart = $this->activeUsers($year);
return view('graph',compact('trialUserForChart','activeUserForChart'));
}
public function activeUsers($year)
{
$activeUser = User::allPremiumUser()->orderBy('created_at')->whereYear('created_at',$year)->get()
->groupBy(function ($date) {
return $date->created_at->month;
})
->map(function ($group) {
return $group->count();
})
->union(array_fill(1, 12, 0))
->sortKeys()
->toArray();
return (array_values($activeUser));
}
public function trialUsers($year)
{
$trialUser = User::allTrailUser()->orderBy('created_at')->whereYear('created_at',$year)->get()
->groupBy(function ($date) {
return $date->created_at->month;
})
->map(function ($group) {
return $group->count();
})
->union(array_fill(1, 12, 0))
->sortKeys()
->toArray();
return (array_values($trialUser));
}
public function activeTrail()
{
$year = request()->year;
return [
'active'=>$this->activeUsers($year),
'trial'=> $this->trialUsers($year)
];
}
}
In index() method I have $year = date('Y');
so when page loads we wil show the chart with recent year data
step10:
lets include ajax. Make sure you have jQuery library in your project. When user change the year from dropdown list we will send this year to our endpoint and grap the data of that particular year.
$(document).on('change', '#name', function() {
var value = this.value;
$.ajax({
url: "/chart/active-trial",
type: "GET",
data: {
year: value,
},
success: function(response) {
chart.series[0].update({
data: response.active
});
chart.series[1].update({
data: response.trial
});
},
});
});
I have included this in graph.balde.php
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-12">
<div class="card">
<select name="select" id="select" class="float-right border-5">
@foreach (range(date('Y'), 2013) as $year)
<option value="{{ $year }}">{{ $year }}</option>
@endforeach
</select>
<div class="card-header">Active and Trial Users
</div>
<div class="card-body">
<div id="container"></div>
</div>
</div>
</div>
</div>
</div>
<script>
const chart= Highcharts.chart('container', {
chart: {
type: 'line'
},
title: {
text: 'Active and Trail users'
},
subtitle: {
text: ''
},
xAxis: {
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
'Oct', 'Nov', 'Dec'
]
},
yAxis: {
title: {
text: 'Users'
}
},
plotOptions: {
line: {
dataLabels: {
enabled: true
},
enableMouseTracking: false
}
},
series: [{
name: 'Active',
data: <?php echo json_encode($activeUserForChart); ?>
}, {
name: 'Trial',
data: <?php echo json_encode($trialUserForChart); ?>
}]
});
$(document).on('change', '#name', function() {
var value = this.value;
$.ajax({
url: "/chart/active-trial",
type: "GET",
data: {
year: value,
},
success: function(response) {
chart.series[0].update({
data: response.active
});
chart.series[1].update({
data: response.trial
});
},
});
});
</script>
@endsection
Now you should able to display active trial users in graph for different date