Creating bar graphs with AJAX and Morris library

If you have an application that runs on Laravel (or any other back end framework) you might like to see some custom analytics and stats such as :

  • How many users signed up last week
  • How many orders were placed last month
  • How many blog posts were created by all users in your system within past 3 months

I used to think that integrating something like that would take me a long time and didn’t bother to experiment until last weekend. Turns out the existing front end libraries and plugins have gotten a lot better and simpler to use in order to build functionality like that. With the help of one of these libraries  you could integrate some good looking, cross-browser bar charts in your applications in a matter of minutes. You can view a demo of one of them in action here:

http://demos.maxoffsky.com/charts/ (Please wait for it to load, the free server sometimes goes to sleep)

How do you integrate a chart like this?

Let’s build a bar chart like that gradually. First we will build a non-AJAX version of a chart with data hard-coded into HTML page and then we will create a small API that will provide the data in JSON format and will enable us to load the data for the chart via AJAX.

Imagine that you are trying to show a total number of orders that were made in your e-commerce website in the last week. Without using the charts, your data would look like a table listing pairs of dates and number of orders for those dates, like this:

  • 04-02-2014 – 3
  • 04-03-2014 – 10
  • 04-04-2014 – 5
  • 04-05-2014 – 17
  • 04-06-2014 – 6
  • etc.

It would be nice if you could take data like that and turn it into a chart without doing much else, right? Well, a charting library like Morris.js has you covered. It allows you to provide tabular data in a format that it could understand, and it will build a chart for you practically without any effort on your side. Let me demonstrate this by including this library into a page and using its example format to create a chart. In your HTML page include the library and its dependencies (jQuery and Raphael) first:

<link rel="stylesheet" href="http://cdn.oesmith.co.uk/morris-0.4.3.min.css">
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<script src="http://cdn.oesmith.co.uk/morris-0.4.3.min.js"></script>

Now we can use the library and convert the tabular data the we have into a Morris chart. First, define a DIV that would serve as the area where the chart will be displayed, then, add a few lines of JS code that will use the DIV to display a chart using the library. The X axis will represent the date and Y axis – number of orders :

<div id="chart" style="height: 250px;"></div>

<script>
Morris.Bar({
  element: 'chart',
  data: [
    { date: '04-02-2014', value: 3 },
    { date: '04-03-2014', value: 10 },
    { date: '04-04-2014', value: 5 },
    { date: '04-05-2014', value: 17 },
    { date: '04-06-2014', value: 6 }
  ],
  xkey: 'date',
  ykeys: ['value'],
  labels: ['Orders']
});
</script>

Placing this in your HTML will create the following chart (roll over the items to see the number of orders):

Great! From here integration with a back end framework like Laravel is trivial. The data will come from Database using a simple query that I created for my projects. If you have a table called “orders”, the query returning a number of orders for each day for the last 30 days would look like this (using Laravel’s Query Builder):

$range = CarbonCarbon::now()->subDays(30);

$stats = DB::table('orders')
  ->where('created_at', '>=', $range)
  ->groupBy('date')
  ->orderBy('date', 'ASC')
  ->get([
    DB::raw('Date(created_at) as date'),
    DB::raw('COUNT(*) as value')
  ])
  ->toJSON();

Placing that query in a route or a controller will put the tabular data of orders-dates encoded as JSON so that we could use it in the HTML page. Pass the $stats variable to a view in place of the hard coded data we saw earlier and you will have a nice chart in your page using the data from the database. Although if you wanted to load the data for the past 7 days or past 90 days, you would need to refresh the whole page.

AJAX-ifying the chart

Let’s go one step further and eliminate page refresh by using a simple AJAX script and a small API that will just return the data to us as JSON array.  Imagine that you have a URL “/api” relative to application’s root. This will be the route of API that will return the JSON data for the chart. We could pass a single parameter to it: the number of days to show data for. This parameter will define the date range that our application will query for the number of orders. The route for the API will then look like this:

Route::get('api', function(){
  // Get the number of days to show data for, with a default of 7
  $days = Input::get('days', 7);

  $range = CarbonCarbon::now()->subDays($days);

  $stats = DB::table('orders')
    ->where('created_at', '>=', $range)
    ->groupBy('date')
    ->orderBy('date', 'ASC')
    ->get([
      DB::raw('Date(created_at) as date'),
      DB::raw('COUNT(*) as value')
    ]);

  return $stats;
});

The front end will have to load the data from the API and display it using that data. Morris allows updating the chart with the new data by calling “setData(data)” on it. Together with jQuery’s AJAX, loading the chart data and displaying it as a bar chart looks like this:

$(function() {

  // Create a Bar Chart with Morris
  var chart = Morris.Bar({
    // ID of the element in which to draw the chart.
    element: 'stats-container',
    data: [0, 0], // Set initial data (ideally you would provide an array of default data)
    xkey: 'date', // Set the key for X-axis
    ykeys: ['value'], // Set the key for Y-axis
    labels: ['Orders'] // Set the label when bar is rolled over
  });

  // Fire off an AJAX request to load the data
  $.ajax({
      type: "GET",
      dataType: 'json',
      url: "./api", // This is the URL to the API
      data: { days: 7 } // Passing a parameter to the API to specify number of days
    })
    .done(function( data ) {
      // When the response to the AJAX request comes back render the chart with new data
      chart.setData(data);
    })
    .fail(function() {
      // If there is no communication between the server, show an error
      alert( "error occured" );
    });
});

That’s it! Now if you wanted to modify the number of days that your application would display the charts for, you could refactor the AJAX code into a flexible function and then execute this function when the user clicks on buttons with different date ranges. All together the code with HTML and AJAX-enabled chart would look like this:

<ul class="nav nav-pills ranges">
  <li class="active"><a href="#" data-range='7'>7 Days</a></li>
  <li><a href="#" data-range='30'>30 Days</a></li>
  <li><a href="#" data-range='60'>60 Days</a></li>
  <li><a href="#" data-range='90'>90 Days</a></li>
</ul>

<div id="stats-container" style="height: 250px;"></div>

<script>
$(function() {

  // Create a function that will handle AJAX requests
  function requestData(days, chart){
    $.ajax({
      type: "GET",
      dataType: 'json',
      url: "./api", // This is the URL to the API
      data: { days: days }
    })
    .done(function( data ) {
      // When the response to the AJAX request comes back render the chart with new data
      chart.setData(data);
    })
    .fail(function() {
      // If there is no communication between the server, show an error
      alert( "error occured" );
    });
  }

  var chart = Morris.Bar({
    // ID of the element in which to draw the chart.
    element: 'stats-container',
    data: [0, 0], // Set initial data (ideally you would provide an array of default data)
    xkey: 'date', // Set the key for X-axis
    ykeys: ['value'], // Set the key for Y-axis
    labels: ['Orders'] // Set the label when bar is rolled over
  });

  // Request initial data for the past 7 days:
  requestData(7, chart);

  $('ul.ranges a').click(function(e){
    e.preventDefault();

    // Get the number of days from the data attribute
    var el = $(this);
    days = el.attr('data-range');

    // Request the data and render the chart using our handy function
    requestData(days, chart);
  })
});
</script>

Optimization

When using Laravel, for optimal results you could add remember() to the query to cache the results for some time. The source code provided on Github uses that to minimize SQL calls.

Integrating a spinner to show the loading progress is up to you, in fact you could learn all about that and more in my book that’s mentioned on the bottom of this post.

Get the source code for the complete Laravel application demonstrating this technique of generating charts: https://github.com/msurguy/laravel-charts

If you want to learn about other front end libraries and components I use, I have published an e-book that teaches you about many great front end components in detail:

You may also like

  • Pawel

    Hi, I am still quite new to laravel so this might be obvious, but.. is Redis required for this demo to work? I have downloaded it from github and installed locally, but can’t get it to work as I am getting redis related errors?

  • No, redis is not required (I use it on production for caching though). You can just replace the app/config/cache.php setting to file instead of redis and the demo should work.

  • Pingback: Creating bar graphs with AJAX and Morris librar...()

  • Pingback: Laravel: Links and Resources (1) | Angel "Java" Lopez on Blog()

  • Pradeep Kumar

    Hi, I am using morris bar chart, but unable to include caption with chart.
    I want to pass caption with json and show.
    so, any one have any idea then let me explain…

    thanks !

  • maxsurguy

    Please let me know some more details about your setup…

  • Niki Oswald

    So, I have all this code, but the chart just comes up as undefined??

  • jw1540

    did you ever fix this?

  • FelixDesigner

    For people not getting the data in javascript. Use this plugin, it’s super easy: https://github.com/laracasts/PHP-Vars-To-Js-Transformer

  • khudadad

    Hi, I’m getting this error
    Call to a member function toJSON() on a non-object

More in Code Blog
laravel-tutorial-honeypot
Implementing Honeypot spam prevention in Laravel applications

Link to package: https://github.com/msurguy/Honeypot For the past couple months I have been seeing an increase of spam submissions on some of...

Close