Skip to content
Snippets Groups Projects
Commit fe2be7bd authored by Jared Hancock's avatar Jared Hancock
Browse files

Next iteration of the reports

Most things work, still outstanding
  - the table needs to support filtering like the graphs,
  - the bootstrap.css file needs to be culled of what isn't used for now,
  - g.raphael needs to be re-minned after a issue is filed with g.raphael
    for the snapEnds() function not picking reasonable graph axes.
  - split dashboard.php into several smaller js, css, etc., respective files
parent 55ddff32
No related branches found
No related tags found
No related merge requests found
......@@ -29,7 +29,9 @@ include_once(INCLUDE_DIR.'class.ticket.php');
class OverviewReportAjaxAPI extends AjaxController {
function enumTabularGroups() {
return $this->encode(array("dept"=>"Department", "topic"=>"Topics",
"staff"=>"Staff"));
# XXX: This will be relative to permissions based on the
# logged-in-staff. For basic staff, this will be 'My Stats'
"staff"=>"Staff"));
}
function getData() {
......@@ -51,6 +53,8 @@ class OverviewReportAjaxAPI extends AjaxController {
"fields" => "T1.topic",
"headers" => array('Help Topic')
),
# XXX: This will be relative to permissions based on the
# logged-in-staff
"staff" => array(
"table" => STAFF_TABLE,
"pk" => 'staff_id',
......@@ -65,27 +69,43 @@ class OverviewReportAjaxAPI extends AjaxController {
$res = db_query(
'SELECT ' . $info['fields'] . ','
.' COUNT(A1.ticket_id) AS Opened,'
.' COUNT(A2.ticket_id) AS Closed,'
.' COUNT(A3.ticket_id) AS Reopened'
.'(SELECT COUNT(A1.ticket_id) FROM '.TICKET_TABLE
.' A1 WHERE A1.'.$info['pk'].' = T1.'.$info['pk']
.' AND A1.status=\'open\') AS Open,'
.'(SELECT COUNT(A1.ticket_id) FROM '.TICKET_TABLE
.' A1 WHERE A1.'.$info['pk'].' = T1.'.$info['pk']
.' AND (A1.staff_id > 0 OR A1.team_id > 0)'
.' AND A1.status=\'open\') AS Assigned,'
.'(SELECT COUNT(A1.ticket_id) FROM '.TICKET_TABLE
.' A1 WHERE A1.'.$info['pk'].' = T1.'.$info['pk']
.' AND (A1.staff_id = 0 AND A1.team_id = 0)'
.' AND A1.status=\'open\') AS Unassigned,'
.'(SELECT COUNT(A1.ticket_id) FROM '.TICKET_TABLE
.' A1 WHERE A1.'.$info['pk'].' = T1.'.$info['pk']
.' AND A1.isanswered = 0'
.' AND A1.status=\'open\') AS Unanswered,'
.'(SELECT COUNT(A1.ticket_id) FROM '.TICKET_TABLE
.' A1 WHERE A1.'.$info['pk'].' = T1.'.$info['pk']
.' AND A1.isoverdue = 1'
.' AND A1.status=\'open\') AS Overdue,'
.'(SELECT COUNT(A1.ticket_id) FROM '.TICKET_TABLE
.' A1 WHERE A1.'.$info['pk'].' = T1.'.$info['pk']
.' AND A1.status=\'closed\') AS Closed,'
.'(SELECT COUNT(A1.ticket_id) FROM '.TICKET_TABLE
.' A1 WHERE A1.'.$info['pk'].' = T1.'.$info['pk']
.' AND A1.reopened is not null) AS Reopened,'
.'(SELECT FORMAT(AVG(DATEDIFF(A1.closed, A1.created)),1) FROM '.TICKET_TABLE
.' A1 WHERE A1.'.$info['pk'].' = T1.'.$info['pk']
.' AND A1.status=\'closed\') AS ServiceTime'
.' FROM ' . $info['table'] . ' T1'
.' LEFT JOIN ' . TICKET_EVENT_TABLE
. ' A1 ON (T1.' . $info['pk'] . ' = A1.' . $info['pk']
.' AND A1.state=\'opened\' AND NOT A1.annulled)'
.' LEFT JOIN ' . TICKET_EVENT_TABLE
. ' A2 ON (T1.' . $info['pk'] . ' = A2.' . $info['pk']
.' AND A2.state=\'closed\' AND NOT A2.annulled)'
.' LEFT JOIN ' . TICKET_EVENT_TABLE
. ' A3 ON (T1.' . $info['pk'] . ' = A3.' . $info['pk']
.' AND A3.state=\'reopened\' AND NOT A3.annulled)'
.' GROUP BY '.$info['fields']
);
$rows = array();
while ($row = db_fetch_row($res)) {
$rows[] = $row;
}
return array("columns" => array_merge($info['headers'],
array('Opened','Closed','Reopened')),
array('Open','Assigned','Unassigned','Unanswered',
'Overdue','Closed','Reopened','Service Time')),
"data" => $rows);
}
......@@ -95,21 +115,23 @@ class OverviewReportAjaxAPI extends AjaxController {
function downloadTabularData() {
$data = $this->getData();
$csv = array($data['columns']);
$csv = array_merge($csv, $data['data']);
$csv = '"' . implode('","',$data['columns']) . '"';
foreach ($data['data'] as $row)
$csv .= "\n" . '"' . implode('","', $row) . '"';
Http::download(
sprintf('%s-report.csv', $this->get('group', 'Department')),
Format::implode_array(',', "\n", $csv));
'text/csv', $csv);
}
function getPlotData() {
$start = $this->get('start', strtotime('last month'));
$stop = $this->get('stop', time());
$start = strtotime($this->get('start', 'last month'));
$stop = strtotime($this->get('stop', 'now'));
# Fetch all types of events over the timeframe
$res = db_query('SELECT DISTINCT(state) FROM '.TICKET_EVENT_TABLE
.' WHERE timestamp BETWEEN FROM_UNIXTIME('.db_input($start)
.') AND FROM_UNIXTIME('.db_input($stop).')');
.') AND FROM_UNIXTIME('.db_input($stop)
.') ORDER BY 1');
$events = array();
while ($row = db_fetch_row($res)) $events[] = $row[0];
......@@ -120,7 +142,9 @@ class OverviewReportAjaxAPI extends AjaxController {
.' FROM '.TICKET_EVENT_TABLE
.' WHERE timestamp BETWEEN FROM_UNIXTIME('.db_input($start)
.') AND FROM_UNIXTIME('.db_input($stop)
.') GROUP BY state, DATE_FORMAT(timestamp, \'%Y-%m-%d\')');
.') AND NOT annulled'
.' GROUP BY state, DATE_FORMAT(timestamp, \'%Y-%m-%d\')'
.' ORDER BY 2, 1');
# Initialize array of plot values
$plots = array();
foreach ($events as $e) { $plots[$e] = array(); }
......@@ -133,18 +157,21 @@ class OverviewReportAjaxAPI extends AjaxController {
# New time (and not the first), figure out which events did
# not have any tickets associated for this time slot
if ($time !== null) {
# Not the first record
foreach (array_diff($events, $slots) as $slot) {
# Not the first record -- add zeros all the arrays that
# did not have at least one entry for the timeframe
foreach (array_diff($events, $slots) as $slot)
$plots[$slot][] = 0;
}
}
$slots = array();
$times[] = $time = $row_time;
}
# Keep track of states for this timeframe
if (!in_array($row[0], $slots)) $slots[] = $row[0];
$slots[] = $row[0];
$plots[$row[0]][] = (int)$row[2];
}
return $this->encode(array("times" => $times, "plots" => $plots));
foreach (array_diff($events, $slots) as $slot)
$plots[$slot][] = 0;
return $this->encode(array("times" => $times, "plots" => $plots,
"events"=>$events));
}
}
......@@ -49,6 +49,7 @@ $dispatcher = patterns('',
# Send
url_get('^graph$', 'getPlotData'),
url_get('^table/groups$', 'enumTabularGroups'),
url_get('^table/export$', 'downloadTabularData'),
url_get('^table$', 'getTabularData')
)),
url_get('^/users$', array('ajax.users.php:UsersAjaxAPI', 'search')),
......
This diff is collapsed.
......@@ -17,6 +17,360 @@ require('staff.inc.php');
$nav->setTabActive('dashboard');
require(STAFFINC_DIR.'header.inc.php');
//require(STAFFINC_DIR.$page);
echo "Staff's dashboard";
?>
<script type="text/javascript" src="js/raphael-min.js"></script>
<script type="text/javascript" src="js/g.raphael.js"></script>
<script type="text/javascript" src="js/g.line-min.js"></script>
<script type="text/javascript" src="js/g.dot-min.js"></script>
<script type="text/javascript" src="js/bootstrap-tab.js"></script>
<link rel="stylesheet" type="text/css" href="css/bootstrap.css"/>
<style type="text/css">
#line-chart-here {
padding: 0.4em;
margin-bottom: 1em;
border-radius: 0.3em;
border: 0.2em solid #ccc;
background: rgb(246,248,249); /* Old browsers */
background: -moz-linear-gradient(top, rgba(246,248,249,1) 0%,
rgba(229,235,238,1) 50%, rgba(215,222,227,1) 51%, rgba(245,247,249,1) 100%);
/* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom,
color-stop(0%,rgba(246,248,249,1)), color-stop(50%,rgba(229,235,238,1)),
color-stop(51%,rgba(215,222,227,1)), color-stop(100%,rgba(245,247,249,1)));
/* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, rgba(246,248,249,1)
0%,rgba(229,235,238,1) 50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1)
100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, rgba(246,248,249,1)
0%,rgba(229,235,238,1) 50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1)
100%); /* Opera 11.10+ */
background: -ms-linear-gradient(top, rgba(246,248,249,1)
0%,rgba(229,235,238,1) 50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1)
100%); /* IE10+ */
background: linear-gradient(top, rgba(246,248,249,1) 0%,rgba(229,235,238,1)
50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1) 100%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f6f8f9',
endColorstr='#f5f7f9',GradientType=0 ); /* IE6-9 */
}
#line-chart-here tspan {
font-family: Monaco;
font-size: 8pt;
}
#line-chart-legend {
margin: 0.6em;
line-height: 140%;
}
span.label.disabled {
opacity: 0.5;
background-color: #555 !important;
}
span.label {
cursor: pointer;
}
</style>
<form class="well form-inline" id="timeframe-form">
<label>
Report timeframe:
<input type="text" class="dp input-medium search-query"
name="start" placeholder="Last month"/>
</label>
<button class="btn" type="submit">Refresh</button>
</form>
<!-- Create a graph and fetch some data to create pretty dashboard -->
<div style="position:relative">
<div id="line-chart-here" style="height:300px"></div>
<div style="position:absolute;right:0;top:0" id="line-chart-legend"></div>
</div>
<script type="text/javascript">
var r, previous_data;
function refresh() {
$('#line-chart-here').empty();
$('#line-chart-legend').empty();
var r = new Raphael('line-chart-here');
var width = $('#line-chart-here').width()
var height = $('#line-chart-here').height()
$.ajax({
method: 'GET',
url: 'ajax.php/report/overview/graph',
data: ((this.start && this.start.value) ? {'start': this.start.value} : {}),
dataType: 'json',
success: function(json) {
var previous_data = json,
times = [],
smtimes = Array.prototype.concat.apply([], json.times),
plots = [],
max = 0,
steps = 0,
primes = [2,3,5,7,9];
// Convert the timestamp to number of whole days after the
// unix epoch, and try and find an exact multiple of the
// number of days across the query that is less than 13 for
// the number of dates to place across the bottom.
for (key in smtimes) {
smtimes[key] = Math.floor(smtimes[key] / 86400);
}
steps = smtimes[smtimes.length-1] - smtimes[0] + 1;
while (steps > 12) {
for (var i in primes) {
if (steps % primes[i] === 0) {
steps /= primes[i];
break;
}
}
}
for (key in json.events) {
e = json.events[key];
if (json.plots[e] === undefined) continue;
$('<span>').append(e)
.attr({class:'label','style':'margin-left:0.5em'})
.appendTo($('#line-chart-legend'));
$('<br>').appendTo('#line-chart-legend');
times.push(smtimes);
plots.push(json.plots[e]);
max = Math.max(max, Math.max.apply(Math, json.plots[e]));
}
console.log(json.times, smtimes, steps, max);
m = r.linechart(10, 10, width - 80, height - 20,
times, plots, {
gutter: 10,
width: 1.6,
nostroke: false,
shade: false,
axis: "0 0 1 1",
axisxstep: steps,
axisystep: max,
symbol: "circle",
smooth: false
}).hoverColumn(function () {
this.tags = r.set();
var slots = [];
for (var i = 0, ii = this.y.length; i < ii; i++) {
if (this.values[i] === 0) continue;
if (this.symbols[i].node.style.display == "none") continue;
var angle = 160;
for (var j = 0, jj = slots.length; j < jj; j++) {
if (slots[j][0] == this.x && slots[j][1] == this.y[i]) {
angle = 20;
break;
}
}
slots.push([this.x, this.y[i]]);
this.tags.push(r.tag(this.x, this.y[i],
this.values[i], angle,
10).insertBefore(this).attr([
{ fill: '#eee' },
{ fill: this.symbols[i].attr('fill') }]));
}
}, function () {
this.tags && this.tags.remove();
});
// Change axis labels from Unix epoch
$('tspan', $('#line-chart-here')).each(function(e) {
var text = this.firstChild.textContent;
if (parseInt(text) > 10000)
this.firstChild.textContent =
$.datepicker.formatDate('mm-dd-yy',
new Date(parseInt(text) * 86400000));
});
// Dear aspiring API writers, please consider making [easy]
// things simpler than this...
$('span.label').each(function(i, e) {
e = $(e);
e.click(function() {
e.toggleClass('disabled');
if (e.hasClass('disabled')) {
m.symbols[i].hide();
m.lines[i].hide();
} else {
m.symbols[i].show();
m.lines[i].show();
}
});
});
$('span.label', '#line-chart-legend').css(
'background-color', function(i) {
return Raphael.color(m.symbols[i][0].attr('fill')).hex;
});
}
});
return false;
}
$(refresh);
$('#timeframe-form').submit(refresh);
function refresh_dots() {
var width = $('#line-chart-here').width()
$.ajax({
method: 'GET',
url: 'ajax.php/report/overview/graph',
data: {'start': "May-01-2012"},
dataType: 'json',
success: function(json) {
var xs = [], ys = [], data = [], axisx = [], axisy = [], y = 0;
for (var key in json.plots) {
y++;
axisy.push(key);
for (var x in json.plots[key]) {
xs.push(parseInt(x));
ys.push(y);
data.push(json.plots[key][x]);
}
}
width = Math.min(json.times.length * 75 + 50, width - 50);
r.dotchart(10, 10 , width, 200, xs, ys, data, {
symbol: "o", max: 10, heat: true, axis: "0 0 1 1",
axisxstep: json.times.length-1,
axisystep: axisy.length-1,
axisxlabels: json.times, axisxtype: " ",
axisytype: " ", axisylabels: axisy
}).hover(
function () {
this.marker = this.marker
|| r.tag(this.x, this.y, this.value, 0, this.r + 2)
.insertBefore(this);
this.marker.show();
},
function () {
this.marker && this.marker.hide();
});
}
});
}
//$(refresh_dots);
</script>
<ul class="nav nav-tabs" id="tabular-navigation">
</ul>
<div id="table-here"></div>
<script type="text/javascript">
$(function() {
$('tabular-navigation').tab();
});
// Add tabs for the tabular display
$(function() {
$.ajax({
url: 'ajax.php/report/overview/table/groups',
dataType: 'json',
success: function(json) {
var first=true;
for (key in json) {
$('#tabular-navigation')
.append($('<li>').attr((first) ? {class:"active"} : {})
.append($('<a>')
.click(build_table)
.attr({'table-group':key,'href':'#'})
.append(json[key])));
first=false;
}
build_table.apply($('#tabular-navigation li:first-child a'))
}
});
});
function build_table(e) {
$('#table-here').empty();
$(this).tab('show');
var group = $(this).attr('table-group')
$.ajax({
method: 'GET',
dataType: 'json',
url: 'ajax.php/report/overview/table',
data: {group: group},
success: function(json) {
var q = $('<table>').attr({class:'table table-condensed table-striped'});
var h = $('<tr>').appendTo($('<thead>').appendTo(q));
var pagesize = 25;
for (var c in json.columns)
h.append($('<th>').append(json.columns[c]));
for (var i in json.data) {
if (i % pagesize === 0)
b = $('<tbody>').attr({'page':i/pagesize+1}).appendTo(q);
row = json.data[i];
tr = $('<tr>').appendTo(b);
for (var j in row)
tr.append($('<td>').append(row[j]));
}
$('#table-here').append(q);
// ----------------------> Pagination <---------------------
function goabs(e) {
$('tbody', q).addClass('hide');
if (e.target) {
page = e.target.text;
$('tbody[page='+page+']', q).removeClass('hide');
} else {
e.removeClass('hide');
page = e.attr('page')
}
enable_next_prev(page);
}
function goprev() {
current = $('tbody:not(.hide)', q).attr('page');
page = Math.max(1, parseInt(current) - 1);
goabs($('tbody[page='+page+']', q));
}
function gonext() {
current = $('tbody:not(.hide)', q).attr('page');
page = Math.min(Math.floor(json.data.length / pagesize) + 1,
parseInt(current) + 1);
goabs($('tbody[page='+page+']', q));
}
function enable_next_prev(page) {
$('#table-here div.pagination li[page]').removeClass('active');
$('#table-here div.pagination li[page='+page+']').addClass('active');
if (page == 1) $('#report-page-prev').addClass('disabled');
else $('#report-page-prev').removeClass('disabled');
if (page == Math.floor(json.data.length / pagesize) + 1)
$('#report-page-next').addClass('disabled');
else $('#report-page-next').removeClass('disabled');
}
var p = $('<ul>')
.appendTo($('<div>').attr({'class':'pagination'})
.appendTo($('#table-here')));
$('<a>').click(goprev).attr({'href':'#'})
.append('&laquo;').appendTo($('<li>').attr({'id':'report-page-prev'})
.appendTo(p));
$('tbody', q).each(function() {
page = $(this).attr('page');
$('<a>').click(goabs).attr({'href':'#'}).append(page)
.appendTo($('<li>').attr({'page':page})
.appendTo(p));
});
$('<a>').click(gonext).attr({'href':'#'})
.append('&raquo;').appendTo($('<li>').attr({'id':'report-page-next'})
.appendTo(p));
// ------------------------> Export <-----------------------
$('<a>').attr({'href':'ajax.php/report/overview/table/export?group='
+group}).append('Export')
.appendTo($('<li>')
.appendTo(p));
gonext();
}
});
return false;
}
</script>
<?
include(STAFFINC_DIR.'footer.inc.php');
?>
/* ========================================================
* bootstrap-tab.js v2.0.4
* http://twitter.github.com/bootstrap/javascript.html#tabs
* ========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ======================================================== */
!function ($) {
"use strict"; // jshint ;_;
/* TAB CLASS DEFINITION
* ==================== */
var Tab = function ( element ) {
this.element = $(element)
}
Tab.prototype = {
constructor: Tab
, show: function () {
var $this = this.element
, $ul = $this.closest('ul:not(.dropdown-menu)')
, selector = $this.attr('data-target')
, previous
, $target
, e
if (!selector) {
selector = $this.attr('href')
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
}
if ( $this.parent('li').hasClass('active') ) return
previous = $ul.find('.active a').last()[0]
e = $.Event('show', {
relatedTarget: previous
})
$this.trigger(e)
if (e.isDefaultPrevented()) return
$target = $(selector)
this.activate($this.parent('li'), $ul)
this.activate($target, $target.parent(), function () {
$this.trigger({
type: 'shown'
, relatedTarget: previous
})
})
}
, activate: function ( element, container, callback) {
var $active = container.find('> .active')
, transition = callback
&& $.support.transition
&& $active.hasClass('fade')
function next() {
$active
.removeClass('active')
.find('> .dropdown-menu > .active')
.removeClass('active')
element.addClass('active')
if (transition) {
element[0].offsetWidth // reflow for transition
element.addClass('in')
} else {
element.removeClass('fade')
}
if ( element.parent('.dropdown-menu') ) {
element.closest('li.dropdown').addClass('active')
}
callback && callback()
}
transition ?
$active.one($.support.transition.end, next) :
next()
$active.removeClass('in')
}
}
/* TAB PLUGIN DEFINITION
* ===================== */
$.fn.tab = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('tab')
if (!data) $this.data('tab', (data = new Tab(this)))
if (typeof option == 'string') data[option]()
})
}
$.fn.tab.Constructor = Tab
/* TAB DATA-API
* ============ */
$(function () {
$('body').on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
e.preventDefault()
$(this).tab('show')
})
})
}(window.jQuery);
\ No newline at end of file
/*!
* g.Raphael 0.5 - Charting library, based on Raphaël
*
* Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com)
* Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
*/
(function(){var b=function(g,f,e,d){return"hsb("+[Math.min((1-g/f)*0.4,1),e||0.75,d||0.75]+")"};function a(e,N,M,d,j,B,A,t,I){var v=this;function U(g){+g[0]&&(g[0]=v.axis(N+s,M+s,d-2*s,E,p,I.axisxstep||Math.floor((d-2*s)/20),2,I.axisxlabels||null,I.axisxtype||"t",null,e));+g[1]&&(g[1]=v.axis(N+d-s,M+j-s,j-2*s,D,o,I.axisystep||Math.floor((j-2*s)/20),3,I.axisylabels||null,I.axisytype||"t",null,e));+g[2]&&(g[2]=v.axis(N+s,M+j-s+H,d-2*s,E,p,I.axisxstep||Math.floor((d-2*s)/20),0,I.axisxlabels||null,I.axisxtype||"t",null,e));+g[3]&&(g[3]=v.axis(N+s-H,M+j-s,j-2*s,D,o,I.axisystep||Math.floor((j-2*s)/20),1,I.axisylabels||null,I.axisytype||"t",null,e))}I=I||{};var z=v.snapEnds(Math.min.apply(Math,B),Math.max.apply(Math,B),B.length-1),E=z.from,p=z.to,s=I.gutter||10,L=v.snapEnds(Math.min.apply(Math,A),Math.max.apply(Math,A),A.length-1),D=L.from,o=L.to,C=Math.max(B.length,A.length,t.length),w=e[I.symbol]||"circle",J=e.set(),u=e.set(),G=I.max||100,r=Math.max.apply(Math,t),q=[],Q=Math.sqrt(r/Math.PI)*2/G;for(var S=0;S<C;S++){q[S]=Math.min(Math.sqrt(t[S]/Math.PI)*2/Q,G)}s=Math.max.apply(Math,q.concat(s));var F=e.set(),H=Math.max.apply(Math,q);if(I.axis){var n=(I.axis+"").split(/[,\s]+/);U.call(v,n);var T=[],V=[];for(var S=0,K=n.length;S<K;S++){var W=n[S].all?n[S].all.getBBox()[["height","width"][S%2]]:0;T[S]=W+s;V[S]=W}s=Math.max.apply(Math,T.concat(s));for(var S=0,K=n.length;S<K;S++){if(n[S].all){n[S].remove();n[S]=1}}U.call(v,n);for(var S=0,K=n.length;S<K;S++){if(n[S].all){F.push(n[S].all)}}J.axis=F}var P=(d-s*2)/((p-E)||1),O=(j-s*2)/((o-D)||1);for(var S=0,K=A.length;S<K;S++){var h=e.raphael.is(w,"array")?w[S]:w,m=N+s+(B[S]-E)*P,l=M+j-s-(A[S]-D)*O;h&&q[S]&&u.push(e[h](m,l,q[S]).attr({fill:I.heat?b(q[S],H):v.colors[0],"fill-opacity":I.opacity?q[S]/G:1,stroke:"none"}))}var f=e.set();for(var S=0,K=A.length;S<K;S++){var m=N+s+(B[S]-E)*P,l=M+j-s-(A[S]-D)*O;f.push(e.circle(m,l,H).attr(v.shim));I.href&&I.href[S]&&f[S].attr({href:I.href[S]});f[S].r=+q[S].toFixed(3);f[S].x=+m.toFixed(3);f[S].y=+l.toFixed(3);f[S].X=B[S];f[S].Y=A[S];f[S].value=t[S]||0;f[S].dot=u[S]}J.covers=f;J.series=u;J.push(u,F,f);J.hover=function(i,g){f.mouseover(i).mouseout(g);return this};J.click=function(g){f.click(g);return this};J.each=function(k){if(!e.raphael.is(k,"function")){return this}for(var g=f.length;g--;){k.call(f[g])}return this};J.href=function(x){var k;for(var g=f.length;g--;){k=f[g];if(k.X==x.x&&k.Y==x.y&&k.value==x.value){k.attr({href:x.href})}}};return J}var c=function(){};c.prototype=Raphael.g;a.prototype=new c;Raphael.fn.dotchart=function(e,k,g,d,j,i,f,h){return new a(this,e,k,g,d,j,i,f,h)}})();
\ No newline at end of file
/*!
* g.Raphael 0.5 - Charting library, based on Raphaël
*
* Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com)
* Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
*/
(function(){function a(g,n){var f=g.length/n,h=0,e=f,m=0,i=[];while(h<g.length){e--;if(e<0){m+=g[h]*(1+e);i.push(m/f);m=g[h++]*-e;e+=f}else{m+=g[h++]}}return i}function d(f,e,p,n,k,j){var h=(p-f)/2,g=(k-p)/2,q=Math.atan((p-f)/Math.abs(n-e)),o=Math.atan((k-p)/Math.abs(n-j));q=e<n?Math.PI-q:q;o=j<n?Math.PI-o:o;var i=Math.PI/2-((q+o)%(Math.PI*2))/2,s=h*Math.sin(i+q),m=h*Math.cos(i+q),r=g*Math.sin(i+o),l=g*Math.cos(i+o);return{x1:p-s,y1:n+m,x2:p+r,y2:n+l}}function b(f,P,O,e,h,A,z,J){var s=this;J=J||{};if(!f.raphael.is(A[0],"array")){A=[A]}if(!f.raphael.is(z[0],"array")){z=[z]}var q=J.gutter||10,B=Math.max(A[0].length,z[0].length),t=J.symbol||"",S=J.colors||s.colors,v=null,p=null,ad=f.set(),T=[];for(var ac=0,L=z.length;ac<L;ac++){B=Math.max(B,z[ac].length)}var ae=f.set();for(ac=0,L=z.length;ac<L;ac++){if(J.shade){ae.push(f.path().attr({stroke:"none",fill:S[ac],opacity:J.nostroke?1:0.3}))}if(z[ac].length>e-2*q){z[ac]=a(z[ac],e-2*q);B=e-2*q}if(A[ac]&&A[ac].length>e-2*q){A[ac]=a(A[ac],e-2*q)}}var W=Array.prototype.concat.apply([],A),U=Array.prototype.concat.apply([],z),u=s.snapEnds(Math.min.apply(Math,W),Math.max.apply(Math,W),A[0].length-1),E=u.from,o=u.to,N=s.snapEnds(Math.min.apply(Math,U),Math.max.apply(Math,U),z[0].length-1),C=N.from,n=N.to,Z=(e-q*2)/((o-E)||1),V=(h-q*2)/((n-C)||1);var G=f.set();if(J.axis){var m=(J.axis+"").split(/[,\s]+/);+m[0]&&G.push(s.axis(P+q,O+q,e-2*q,E,o,J.axisxstep||Math.floor((e-2*q)/20),2,f));+m[1]&&G.push(s.axis(P+e-q,O+h-q,h-2*q,C,n,J.axisystep||Math.floor((h-2*q)/20),3,f));+m[2]&&G.push(s.axis(P+q,O+h-q,e-2*q,E,o,J.axisxstep||Math.floor((e-2*q)/20),0,f));+m[3]&&G.push(s.axis(P+q,O+h-q,h-2*q,C,n,J.axisystep||Math.floor((h-2*q)/20),1,f))}var M=f.set(),aa=f.set(),r;for(ac=0,L=z.length;ac<L;ac++){if(!J.nostroke){M.push(r=f.path().attr({stroke:S[ac],"stroke-width":J.width||2,"stroke-linejoin":"round","stroke-linecap":"round","stroke-dasharray":J.dash||""}))}var g=Raphael.is(t,"array")?t[ac]:t,H=f.set();T=[];for(var ab=0,w=z[ac].length;ab<w;ab++){var l=P+q+((A[ac]||A[0])[ab]-E)*Z,k=O+h-q-(z[ac][ab]-C)*V;(Raphael.is(g,"array")?g[ab]:g)&&H.push(f[Raphael.is(g,"array")?g[ab]:g](l,k,(J.width||2)*3).attr({fill:S[ac],stroke:"none"}));if(J.smooth){if(ab&&ab!=w-1){var R=P+q+((A[ac]||A[0])[ab-1]-E)*Z,F=O+h-q-(z[ac][ab-1]-C)*V,Q=P+q+((A[ac]||A[0])[ab+1]-E)*Z,D=O+h-q-(z[ac][ab+1]-C)*V,af=d(R,F,l,k,Q,D);T=T.concat([af.x1,af.y1,l,k,af.x2,af.y2])}if(!ab){T=["M",l,k,"C",l,k]}}else{T=T.concat([ab?"L":"M",l,k])}}if(J.smooth){T=T.concat([l,k,l,k])}aa.push(H);if(J.shade){ae[ac].attr({path:T.concat(["L",l,O+h-q,"L",P+q+((A[ac]||A[0])[0]-E)*Z,O+h-q,"z"]).join(",")})}!J.nostroke&&r.attr({path:T.join(",")})}function K(an){var ak=[];for(var al=0,ap=A.length;al<ap;al++){ak=ak.concat(A[al])}ak.sort();var aq=[],ah=[];for(al=0,ap=ak.length;al<ap;al++){ak[al]!=ak[al-1]&&aq.push(ak[al])&&ah.push(P+q+(ak[al]-E)*Z)}ak=aq;ap=ak.length;var ag=an||f.set();for(al=0;al<ap;al++){var Y=ah[al]-(ah[al]-(ah[al-1]||P))/2,ao=((ah[al+1]||P+e)-ah[al])/2+(ah[al]-(ah[al-1]||P))/2,x;an?(x={}):ag.push(x=f.rect(Y-1,O,Math.max(ao+1,1),h).attr({stroke:"none",fill:"#000",opacity:0}));x.values=[];x.symbols=f.set();x.y=[];x.x=ah[al];x.axis=ak[al];for(var aj=0,am=z.length;aj<am;aj++){aq=A[aj]||A[0];for(var ai=0,y=aq.length;ai<y;ai++){if(aq[ai]==ak[al]){x.values.push(z[aj][ai]);x.y.push(O+h-q-(z[aj][ai]-C)*V);x.symbols.push(ad.symbols[aj][ai])}}}an&&an.call(x)}!an&&(v=ag)}function I(al){var ah=al||f.set(),x;for(var aj=0,an=z.length;aj<an;aj++){for(var ai=0,ak=z[aj].length;ai<ak;ai++){var ag=P+q+((A[aj]||A[0])[ai]-E)*Z,am=P+q+((A[aj]||A[0])[ai?ai-1:1]-E)*Z,y=O+h-q-(z[aj][ai]-C)*V;al?(x={}):ah.push(x=f.circle(ag,y,Math.abs(am-ag)/2).attr({stroke:"none",fill:"#000",opacity:0}));x.x=ag;x.y=y;x.value=z[aj][ai];x.line=ad.lines[aj];x.shade=ad.shades[aj];x.symbol=ad.symbols[aj][ai];x.symbols=ad.symbols[aj];x.axis=(A[aj]||A[0])[ai];al&&al.call(x)}}!al&&(p=ah)}ad.push(M,ae,aa,G,v,p);ad.lines=M;ad.shades=ae;ad.symbols=aa;ad.axis=G;ad.hoverColumn=function(j,i){!v&&K();v.mouseover(j).mouseout(i);return this};ad.clickColumn=function(i){!v&&K();v.click(i);return this};ad.hrefColumn=function(Y){var ag=f.raphael.is(arguments[0],"array")?arguments[0]:arguments;if(!(arguments.length-1)&&typeof Y=="object"){for(var j in Y){for(var y=0,X=v.length;y<X;y++){if(v[y].axis==j){v[y].attr("href",Y[j])}}}}!v&&K();for(y=0,X=ag.length;y<X;y++){v[y]&&v[y].attr("href",ag[y])}return this};ad.hover=function(j,i){!p&&I();p.mouseover(j).mouseout(i);return this};ad.click=function(i){!p&&I();p.click(i);return this};ad.each=function(i){I(i);return this};ad.eachColumn=function(i){K(i);return this};return ad}var c=function(){};c.prototype=Raphael.g;b.prototype=new c;Raphael.fn.linechart=function(f,k,g,e,j,i,h){return new b(this,f,k,g,e,j,i,h)}})();
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment