Commit 23b58ab7 authored by Robert Lyon's avatar Robert Lyon
Browse files

Bug 853662: Replacing the pear graphing for chartjs graphing.



The system is created so we can either throw pre-generated chartjs json
data at the canvas to show the chart or we can throw correctly structered
array data that is then coverted to chartjs json data.

Also we can override the type of chart after the pre-generated chartjs
json is generated. Eg we can generate the chart as piegraph data and
then later choose to display it as a doughnut graph.

Change-Id: Idc342a14a9efc4af42f15770fa8dfa93f5e9639b
Signed-off-by: Robert Lyon's avatarRobert Lyon <robertl@catalyst.net.nz>
parent 87bf3bd2
......@@ -65,13 +65,14 @@ addLoadEvent(function () {
});
EOF;
$smarty = smarty(array('paginator'));
$smarty = smarty(array('paginator','js/chartjs/Chart.min.js'));
setpageicon($smarty, 'icon-area-chart');
$smarty->assign('PAGEHEADING', TITLE);
$smarty->assign('INLINEJAVASCRIPT', $js);
$smarty->assign('sitedata', $sitedata);
$smarty->assign('type', $type);
$smarty->assign('subpages', $subpages);
......
......@@ -100,7 +100,7 @@ addLoadEvent(function() {
});
EOF;
$smarty = smarty(array('paginator'));
$smarty = smarty(array('paginator','js/chartjs/Chart.min.js'));
setpageicon($smarty, 'icon-university');
$smarty->assign('PAGEHEADING', TITLE);
......
......@@ -1027,3 +1027,71 @@ var is_page_ready = false;
jQuery(document).ready(function() {
is_page_ready = true;
});
/**
* Calls statistical data from the db and returns Chart.js structured json
*
* @param object opts Any options we need to pass in to get correct data
* Can contain:
* id - the id of the canvas to put the graph in. The legend id should be id + 'legend'
* type - the type of graph we want to display, eg line/bar/pie etc
* graph - the name of the function to fetch the data from, eg 'group_type_graph'
* colours - an array of rgb colours eg "['200,100,37','123,21,103']"
*
* @return object data A json encoded object acceptable to Chart.js
* - see Chart.js for json shape.
*/
var chartobject;
var canvascontext;
function fetch_graph_data(opts) {
if (typeof opts.extradata != 'undefined') {
opts.extradata = JSON.stringify(opts.extradata);
}
if (typeof opts.colours != 'undefined') {
opts.colours = JSON.stringify(opts.colours);
}
if (!document.getElementById(opts.id + 'legend')) {
// We need to add in the legend container
var legend = document.createElement('div');
legend.id = opts.id + 'legend';
legend.className = 'graphlegend';
var canvas = document.getElementById(opts.id);
canvas.parentNode.insertBefore(legend, canvas.nextSibling);
}
if (!document.getElementById(opts.id + 'title')) {
// We need to add in the title container
var title = document.createElement('div');
title.id = opts.id + 'title';
title.className = 'graphtitle';
var canvas = document.getElementById(opts.id);
canvas.parentNode.insertBefore(title, canvas);
}
sendjsonrequest(config.wwwroot + 'json/graphdata.php', opts, 'POST', function (json) {
if (json.data.empty == true) {
document.getElementById(opts.id).style.display = 'none';
}
else {
if (document.getElementById(opts.id + 'legend').hasChildNodes()) {
// We already have a chart with this id so we need to clear its data
chartobject.destroy();
document.getElementById(opts.id + 'legend').innerHTML = '';
document.getElementById(opts.id + 'title').innerHTML = '';
}
else {
canvascontext = document.getElementById(opts.id).getContext("2d");
}
chartobject = new Chart(canvascontext)[json.data.graph](JSON.parse(json.data.datastr));
var legendHolder = document.createElement('div');
legendHolder.innerHTML = chartobject.generateLegend();
document.getElementById(opts.id + 'legend').appendChild(legendHolder.firstChild);
if (json.data.title) {
jQuery('#' + opts.id + 'title').text(json.data.title);
}
}
});
}
<?php
/**
*
* @package mahara
* @subpackage core
* @author Catalyst IT Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*
*/
if (!defined('INTERNAL')) {
define('INTERNAL', 1);
}
require_once(dirname(dirname(__FILE__)) . '/init.php');
if (!defined('CRON')) {
define('JSON', 1);
json_headers();
$validtypes = array('Line', 'Bar', 'Radar', 'PolarArea', 'Pie', 'Doughnut');
$type = ucfirst(param_alphanum('type', false));
if (!in_array($type, $validtypes)) {
json_reply('missingparameter', '\'' . $type . '\' is not a valid graph type');
}
$graph = param_alphanumext('graph', null);
$colours = param_variable('colours', null);
$colours = json_decode($colours);
$extradata = param_variable('extradata', null);
$extradata = json_decode($extradata);
require_once(get_config('libroot') . 'graph.php');
require_once(get_config('libroot') . 'registration.php');
if (!function_exists($graph) || !in_array($graph, allowed_graph_functions())) {
json_reply('invalidparameter', 'Cannot call graph function \'' . $graph . '\'');
}
else {
$data = ($extradata) ? $graph($type, $extradata) : $graph($type);
if (empty($data)) {
$data['empty'] = true;
json_reply(false, array('data' => $data));
}
if (!empty($data['jsondata'])) {
$data['datastr'] = $data['jsondata'];
json_reply(false, array('data' => $data));
}
$graphdata = array();
$data['colours'] = get_graph_colours($data, $colours);
// Now covert it to something Chart.js can understand
switch ($data['graph']) {
case 'Pie':
case 'PolarArea':
case 'Doughnut':
$graphdata = get_circular_graph_json($data, $colours);
break;
case 'Bar':
$graphdata = get_bar_graph_json($data, $colours);
break;
case 'Line':
$graphdata = get_line_graph_json($data, $colours);
break;
default:
}
$data['datastr'] = json_encode($graphdata);
json_reply(false, array('data' => $data));
}
}
......@@ -117,6 +117,7 @@ $string['count_blocks'] = 'Number of blocks';
$string['count_views'] = 'Number of pages';
$string['dbtype'] = 'Database type';
$string['enablenetworking'] = 'Enabled networking';
$string['grouptypes'] = 'Group types';
$string['installation_key'] = 'Installation key';
$string['lang'] = 'Language';
$string['newstats'] = 'New statistics';
......@@ -187,3 +188,9 @@ $string['view_type_grouphomepage'] = 'Number of group homepages';
$string['view_type_portfolio'] = 'Number of portfolio pages';
$string['view_type_profile'] = 'Number of profile pages';
$string['wwwroot'] = 'WWW root';
$string['sitedataweekly'] = 'Weekly site data';
$string['group-count'] = 'Groups';
$string['view-count'] = 'Views';
$string['user-count'] = 'User';
$string['institutiondataweekly'] = 'Institution weekly data';
\ No newline at end of file
<?php
/**
*
* @package mahara
* @subpackage core
* @author Catalyst IT Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*/
defined('INTERNAL') || die();
/**
* Only these functions are allowed to be called by fetch_graph_data().
*/
function allowed_graph_functions() {
return array(
'institution_view_type_graph_render',
'graph_institution_data_weekly',
'user_institution_graph',
'view_type_graph_render',
'group_type_graph_render',
'graph_site_data_weekly',
);
}
/**
* Return the chartjs structured array data for circular graphs
* Circular graphs include pie, doughnut, polar graphs
*
* @param array $data Array of information to graph
* Includes: 'data': associative array of label -> data points
* 'colours': custom colours from the function to use instead of the defaults
* 'labellang': the lang file to find the label string translation
* @param array $colours Custom colours from js to use instead of the defaults
* @param bool $cron If function is called from cron we don't want to reply via js
*
* @return array $graphdata An array structure that can be encoded to json for chartjs
*/
function get_circular_graph_json($data, $colours = null, $cron = false) {
if (empty($data['data'])) {
$data['empty'] = true;
if ($cron) {
return $data;
}
json_reply(false, array('data' => $data));
}
$data['colours'] = get_graph_colours($data, $colours);
$graphdata = array();
$x = 0;
foreach ($data['data'] as $key => $value) {
$dataobj['value'] = (int)$value;
$dataobj['color'] = "rgba(" . $data['colours'][$x] . ",1)";
$dataobj['highlight'] = "rgba(" . $data['colours'][$x] . ",0.6)";
$dataobj['label'] = !empty($data['labellang']) ? get_string($key, $data['labellang']) : $key;
$graphdata[] = $dataobj;
$x = empty($data['colours'][$x+1]) ? 0 : $x + 1;
}
return $graphdata;
}
/**
* Return the chartjs structured array data for a bar graph
*
* @param array $data Array of information to graph
* Includes: 'data': associative array of point label -> data point
* 'labels': labels for the bars
* 'colours': custom colours from the function to use instead of the defaults
* 'labellang': the lang file to find the label string translation
* @param array $colours Custom colours from js to use instead of the defaults
* @param bool $cron If function is called from cron we don't want to reply via js
*
* @return array $graphdata An array structure that can be encoded to json for chartjs
*/
function get_bar_graph_json($data, $colours = null, $cron = false) {
if (empty($data['data'])) {
$data['empty'] = true;
if ($cron) {
return $data;
}
json_reply(false, array('data' => $data));
}
$data['colours'] = get_graph_colours($data, $colours);
$graphdata = array();
$x = 0;
$graphdata['labels'] = $data['labels'];
foreach ($data['data'] as $key => $value) {
$dataobj['fillColor'] = "rgba(" . $data['colours'][$x] . ",0.5)";
$dataobj['strokeColor'] = "rgba(" . $data['colours'][$x] . ",0.8)";
$dataobj['highlightFill'] = "rgba(" . $data['colours'][$x] . ",0.75)";
$dataobj['highlightStroke'] = "rgba(" . $data['colours'][$x] . ",1)";
$dataobj['label'] = !empty($data['labellang']) ? get_string($key, $data['labellang']) : $key;
$dataobj['data'] = is_array($value) ? array_values($value) : array($value);
$graphdata['datasets'][] = $dataobj;
$x = empty($data['colours'][$x+1]) ? 0 : $x + 1;
}
return $graphdata;
}
/**
* Return the chartjs structured array data for a line graph
*
* @param array $data Array of information to graph
* Includes: 'data': associative array of point label -> data point
* 'labels': labels for the lines
* 'colours': custom colours from the function to use instead of the defaults
* 'labellang': the lang file to find the label string translation
* @param array $colours Custom colours from js to use instead of the defaults
* @param bool $cron If function is called from cron we don't want to reply via js
*
* @return array $graphdata An array structure that can be encoded to json for chartjs
*/
function get_line_graph_json($data, $colours = null, $cron = false) {
if (empty($data['data'])) {
$data['empty'] = true;
if ($cron) {
return $data;
}
json_reply(false, array('data' => $data));
}
$data['colours'] = get_graph_colours($data, $colours);
$graphdata = array();
$x = 0;
$graphdata['labels'] = $data['labels'];
foreach ($data['data'] as $key => $value) {
$dataobj['fillColor'] = "rgba(" . $data['colours'][$x] . ",0.2)";
$dataobj['strokeColor'] = "rgba(" . $data['colours'][$x] . ",1)";
$dataobj['pointColor'] = "rgba(" . $data['colours'][$x] . ",1)";
$dataobj['pointStrokeColor'] = "rgba(255,255,255,1)";
$dataobj['pointHighlightFill'] = "rgba(255,255,255,1)";
$dataobj['pointHighlightStroke'] = "rgba(" . $data['colours'][$x] . ",1)";
$dataobj['label'] = !empty($data['labellang']) ? get_string($key, $data['labellang']) : $key;
$dataobj['data'] = is_array($value) ? array_values($value) : array($value);
$graphdata['datasets'][] = $dataobj;
$x = empty($data['colours'][$x+1]) ? 0 : $x + 1;
}
return $graphdata;
}
/**
* Returns an array of rgb colours to use in the graph
* We use rgb colours so to allow the chartjs to use alpha transperency
*
* @param array $data Array of information to graph
* Includes: 'colours': custom colours passed in via the call to the graph function, eg view_type_graph()
* @param array $colours Array of colours passed in via ajax from fetch_graph_data()
*
* @return array The merged set of colours
*/
function get_graph_colours($data, $colours = null) {
// Using colours in rgb format to allow for the use of rgba colours in Chart.js
// 10 defaults: Red, Green, Blue, Yellow, Sky blue, Magenta, Orange, Light blue, Grey, Purple
$defaultcolours = ['255,0,0','0,255,0','0,0,255','255,255,0','0,255,255','255,0,255','255,128,0','0,128,255','128,128,128','128,0,255'];
// We try to set colours in this order:
// passed in by user overides
// passed in by function overides
// defaults
if (is_array($colours)) {
if (!empty($data['colours']) && is_array($data['colours'])) {
$data['colours'] = $colours + $data['colours'] + $defaultcolours;
}
else {
$data['colours'] = $colours + $defaultcolours;
}
}
else if (!empty($data['colours']) && is_array($data['colours'])) {
$data['colours'] = $data['colours'] + $defaultcolours;
}
else {
$data['colours'] = $defaultcolours;
}
return $data['colours'];
}
\ No newline at end of file
......@@ -3618,8 +3618,6 @@ function cron_site_data_weekly() {
'type' => 'view-count',
'value' => $current['views'],
));
graph_site_data_weekly();
}
function cron_site_data_daily() {
......@@ -3702,8 +3700,8 @@ function cron_site_data_daily() {
'value' => $diskusage,
));
}
graph_site_data_daily();
}
function cron_institution_data_weekly() {
......@@ -3726,9 +3724,7 @@ function cron_institution_data_weekly() {
));
$current['name'] = $institution;
graph_institution_data_weekly($current);
}
}
function cron_institution_data_daily() {
......
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
.graphtitle {
font-weight: bold;
}
.graphlegend ul {
list-style: none;
li {
white-space: nowrap;
display: inline-block;
padding-left: 30px;
position: relative;
margin-bottom: 4px;
border-radius: 3px;
padding: 2px 6px 2px 28px;
font-size: 0.9em;
cursor: default;
-webkit-transition: background-color 200ms ease-in-out;
-moz-transition: background-color 200ms ease-in-out;
-o-transition: background-color 200ms ease-in-out;
transition: background-color 200ms ease-in-out;
span {
display: block;
position: absolute;
left: 0;
top: 0;
width: 18px;
height: 100%;
border-radius: 3px;
}
}
}
......@@ -87,7 +87,7 @@
@import "../lib/star";
@import "../lib/tinymce";
@import "../lib/draggable";
@import "../lib/chart";
// Keep these files last to override all other style sheets
@import "../custom";
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -3,7 +3,16 @@
{else}
{if $groupgraph}
<img src="{$groupgraph}" alt="" class="pull-right" />
<div id="site-stats-graph" class="pull-right">
<canvas class="graphcanvas" id="sitestatsgroupgraph" width="265" height="151"></canvas>
<script type="application/javascript">
{literal}
jQuery(function() {
fetch_graph_data({'id':'sitestatsgroupgraph','type':'doughnut','graph':'group_type_graph_render'});
});
{/literal}
</script>
</div>
{/if}
<div>
<h4>{str tag=groupcountsbytype section=admin}:</h4>
......
{if $viewcount == 0}
<p>{str tag=noviews section=view}</p>
{/if}
{if $blocktypecounts}
<p>{str tag=blockcountsbytype section=admin}:
<ul>
{foreach from=$blocktypecounts item=item}
<li>{str tag=title section=blocktype.$item->langsection}: {$item->blocks}</li>
{/foreach}
</ul>
</p>
{/if}
{if $viewtypes}
<div id="site-stats-graph">
<canvas class="graphcanvas" id="sitestatsviewtypesgraph" width="300" height="200"></canvas>
<script type="application/javascript">
{literal}
jQuery(function() {
fetch_graph_data({'id':'sitestatsviewtypesgraph',
'type':'doughnut',
'graph':'institution_view_type_graph_render',
'extradata': {'institution': '{/literal}{$institution}{literal}'}
});
});
{/literal}
</script>
</div>
{/if}
{if $sitedata.weekly}
<div id="site-stats-graph" class="panel-body">
<img src="{$sitedata.weekly}" alt="" />
<canvas class="graphcanvas" id="sitestatsgraph" width="265" height="151"></canvas>
<script type="application/javascript">
{literal}
jQuery(function() {
fetch_graph_data({'id':'sitestatsgraph','type':'bar','graph':'graph_site_data_weekly'});
});
{/literal}
</script>
</div>
{/if}
<table class="table">
......
{if $institutiondata.weekly}
<div id="site-stats-graph">
<img src="{$institutiondata.weekly}" alt="" />
<canvas class="graphcanvas" id="sitestatsgraph" width="265" height="151"></canvas>
<script type="application/javascript">
{literal}
jQuery(function() {
fetch_graph_data({'id':'sitestatsgraph','type':'bar','graph':'graph_institution_data_weekly',
'extradata': {'institution': '{/literal}{$institutiondata.institution}{literal}'}
});
});
{/literal}
</script>
</div>
{/if}
<table class="table">
......@@ -46,4 +55,4 @@
{$institutiondata.diskusage|display_size}
</td>
{/if}
</table>
\ No newline at end of file
</table>
......@@ -5,6 +5,15 @@
<li class="list-group-item">{$data.strmaxgroups|safe}</li>
<li class="list-group-item">{$data.strmaxquotaused|safe}</li>
</ul>
{if $data.institutions}
<img src="{$data.institutions}" alt="" class="pull-right" />
{if $data}
<div id="site-stats-graph" class="pull-right">
<canvas class="graphcanvas" id="sitestatsusersgraph" width="300" height="300"></canvas>
<script type="application/javascript">
{literal}
jQuery(function() {
fetch_graph_data({'id':'sitestatsusersgraph','type':'bar','graph':'user_institution_graph'});
});
{/literal}
</script>
</div>
{/if}
......@@ -4,12 +4,20 @@
{if $blocktypecounts}
<h4>{str tag=blockcountsbytype section=admin}: </h4>
{if $viewtypes}
<img src="{$viewtypes}" alt="" class="pull-right" />
<div id="site-stats-graph" class="pull-right">
<canvas class="graphcanvas" id="sitestatsviewtypesgraph" width="300" height="200"></canvas>
<script type="application/javascript">
{literal}
jQuery(function() {
fetch_graph_data({'id':'sitestatsviewtypesgraph','type':'doughnut','graph':'view_type_graph_render'});
});
{/literal}
</script>
</div>
{/if}
<ul class="list-group unstyled pull-left">
{foreach from=$blocktypecounts item=item}
<li class="list-group-item">{str tag=title section=blocktype.$item->langsection}: {$item->blocks}</li>
{/foreach}
</ul>
{/if}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment