Handling Statistics – Pulling data from Yii and displaying with HighCharts

“Facts are stubborn things, but statistics are pliable.” 
― Mark Twain

Sometimes, you might want to display some kind of charts in your Yii project based on the data that you’ve collected in your database. From the front end, there is an awesome way to do so, being a plugin called HighCharts, that is both eye-appealing and free to use if you’re not a commercial website. In this post, we’ll be looking at how to integrate it with a back end running on Yii. For an example of data, we’ll be logging website visits and visitors.

Gathering data

First of all, let’s get some data to start with. Create a simple table in the database that looks like this:

Снимок экрана 2014-08-17 в 17.01.38

and generate a model for it using Gii.

Now, we’ll be adding a mechanism to record the visits. It should be sitewide, so let’s add it to components/Controller.php. It should also determine whether the user is a new visitor or someone who we already have in our records. We’ll be using a permanent cookie for that.

The code is pretty straightforward:

public function init() {

if(isset(Yii::app()->request->cookies['user_identifier']->value)) { // returning visitor $user_identifier = Yii::app()->request->cookies['user_identifier']->value; // try to find if we have anything logged for today $model = Visitors::model()->findByAttributes(array("date"=>date("Y-m-d"), "user_identifier"=>$user_identifier)); if(!$model) { // first time today! $model = new Visitors; $model->date = date("Y-m-d"); $model->user_identifier = $user_identifier; $model->visits = 1; $model->save(); } else { // just adjust the count $model->visits++; $model->save(); } } else { // a new visitor $user_identifier = md5(rand()); // a nice way to generate a random string $cookie = new CHttpCookie('user_identifier', $user_identifier); $cookie->expire = time() + (60*60*24*365*5); // should not expire less than in five years! Yii::app()->request->cookies['user_identifier'] = $cookie; // record the visit $model = new Visitors; $model->date = date("Y-m-d"); $model->user_identifier = $user_identifier; $model->visits = 1; $model->save(); } parent::init(); }

Now, if you get back to the site and open up a few pages, you should get a single record in the database. In my case, it looks like this:

Снимок экрана 2014-08-17 в 17.23.45

Setting up HighCharts

So far, so good. Now, let’s render some actual charts! You can grab the plugin in question at http://highcharts.com and copy all the files to your project, or simply add a few lines to the main layout:

<script src="http://code.jquery.com/jquery-1.11.1.js"></script>
<script src="http://code.highcharts.com/highcharts.js"></script>
<script src="http://code.highcharts.com/modules/exporting.js"></script>

Once that is done, we should tell it to render something! There’s dozens of demos for different kinds of graphs you can have with HighCharts on this page, but we will be using a plain line chart for now.

Take the code from this JSFiddle and add it to a view you want to use. I will be using the site index. Don’t forget to add a container div that you will be rendering in!

And there it goes:

Снимок экрана 2014-08-17 в 17.34.53

Okay, but how do you get the data to be dynamic? Changing it right in the view, according to MVC, is not the proper approach to doing so. Why don’t we try grabbing it with JSON then? Wrap the highcharts init call in a getJSON function:

$.getJSON('http://www.highcharts.com/samples/data/jsonp.php?filename=analytics.csv&callback=?', function (csv) {

Then our code will look like this (let’s also change some labels, while we’re at it!)

<script>

$(function () { $.getJSON('<?php echo $this->createUrl("site/stats"); ?>', function (json) { $('#container').highcharts({ title: { text: 'Site Traffic', x: -20 //center }, xAxis: { categories: json.dates }, yAxis: { title: { text: 'Daily count' }, plotLines: [{ value: 0, width: 1, color: '#808080' }] }, legend: { layout: 'vertical', align: 'right', verticalAlign: 'middle', borderWidth: 0 }, series: [{ name: 'Unique visitors', data: json.visitors }, { name: 'Page visits', data: json.visits }] }); }); }); </script> <div id="container"></div>

This will be initiating an AJAX call to actionStats in our SiteController. Such an action is not yet there, though. Let’s go ahead and create one.

public function actionStats()
{
$response = array();
// collect all the unique dates
$dates = array();
$models=Visitors::model()->findAll(array(
'select'=>'date',
'group'=>'date',
'distinct'=>true,
));
foreach($models as $model)
array_push($dates, $model->date);
// why not also format the dates for display?
$response["dates"] = array();
foreach($dates as $date) {
$datetime = new DateTime($date);
array_push($response["dates"], $datetime->format("M jS"));
}
// count the visitors and the visits for each day
$response["visitors"] = array();
$response["visits"] = array();
foreach($dates as $date) {
$visits = 0;
$visitors = Visitors::model()->findAllByAttributes(array("date"=>$date));
foreach($visitors as $visitor)
$visits += $visitor->visits;
array_push($response["visitors"], count($visitors));
array_push($response["visits"], $visits);
}
echo json_encode($response);
}

And that will give us a JSON response just like this:

{"dates":["2014-08-17"],"visitors":[2],"visits":[17]}

Why don’t we cheat with the statistics now and add a few other dates manually through the database, so we have several?

Then, if we refresh the website, we’ll have our nice-looking graphs right there.

The picture is also easily exported to several formats, including PDF and SVG.

Basing on this, you can create almost any kinds of charts that you need for your website. Just be sure to keep your code clean and follow the MVC practices!

Was this helpful? Share it!
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>