diff --git a/airtime_mvc/application/configs/navigation.php b/airtime_mvc/application/configs/navigation.php index e2df2e3f5..59ce874fe 100644 --- a/airtime_mvc/application/configs/navigation.php +++ b/airtime_mvc/application/configs/navigation.php @@ -87,7 +87,7 @@ $pages = array( 'resource' => 'playouthistory' ), array( - 'label' => 'Listener Stat', + 'label' => 'Listener Stats', 'module' => 'default', 'controller' => 'listenerstat', 'action' => 'index', diff --git a/airtime_mvc/application/controllers/ListenerstatController.php b/airtime_mvc/application/controllers/ListenerstatController.php index d0af81593..648a88dfe 100644 --- a/airtime_mvc/application/controllers/ListenerstatController.php +++ b/airtime_mvc/application/controllers/ListenerstatController.php @@ -18,6 +18,7 @@ class ListenerstatController extends Zend_Controller_Action $baseUrl = $request->getBaseUrl(); $this->view->headScript()->appendFile($baseUrl.'/js/flot/jquery.flot.js?'.$CC_CONFIG['airtime_version'],'text/javascript'); + $this->view->headScript()->appendFile($baseUrl.'/js/flot/jquery.flot.crosshair.js?'.$CC_CONFIG['airtime_version'],'text/javascript'); $this->view->headScript()->appendFile($baseUrl.'/js/airtime/listenerstat/listenerstat.js?'.$CC_CONFIG['airtime_version'],'text/javascript'); $offset = date("Z") * -1; @@ -46,9 +47,6 @@ class ListenerstatController extends Zend_Controller_Action 'his_time_end' => $end->format("H:i") )); - $allMPs = Application_Model_ListenerStat::getAllMPNames(); - - $this->view->mps = $allMPs; $this->view->date_form = $form; } diff --git a/airtime_mvc/application/models/ListenerStat.php b/airtime_mvc/application/models/ListenerStat.php index 95ea1584d..f5567fed5 100644 --- a/airtime_mvc/application/models/ListenerStat.php +++ b/airtime_mvc/application/models/ListenerStat.php @@ -7,33 +7,44 @@ class Application_Model_ListenerStat public static function getDataPointsWithinRange($p_start, $p_end) { $sql = <<=:p1 AND ts.timestamp <= :p2) - ORDER BY mount_name, timestamp +WHERE (ts.timestamp >=:p1 AND ts.timestamp <=:p2) +group by mount_name SQL; - $data = Application_Common_Database::prepareAndExecute($sql, - array('p1'=>$p_start, 'p2'=>$p_end)); + $data = Application_Common_Database::prepareAndExecute($sql, + array('p1'=>$p_start, 'p2'=>$p_end)); $out = array(); foreach ($data as $d) { - $out[$d['mount_name']][] = $d; - } - - return $out; - } - - public static function getAllMPNames() { - $sql = <<=:p1 AND ts.timestamp <= :p2) AND mount_name=:p3) as temp +WHERE (temp.rownum%:p4) = :p5; SQL; - $mps = Application_Common_Database::prepareAndExecute($sql, array()); - $out = array(); - foreach ($mps as $mp) { - $out[] = $mp['mount_name']; + $result = Application_Common_Database::prepareAndExecute($sql, + array('p1'=>$p_start, 'p2'=>$p_end, 'p3'=>$d['mount_name'], 'p4'=>$jump, 'p5'=>$remainder)); + foreach ($result as $r) { + $t = new DateTime($r['timestamp'], new DateTimeZone("UTC")); + $t->setTimezone(new DateTimeZone(date_default_timezone_get())); + // tricking javascript so it thinks the server timezone is in UTC + $dt = new DateTime($t->format("Y-m-d H:i:s"), new DateTimeZone("UTC")); + + $r['timestamp'] = $dt->format("U"); + $out[$r['mount_name']][] = $r; + } } return $out; } @@ -41,42 +52,42 @@ SQL; public static function insertDataPoints($p_dataPoints) { - $timestamp_sql = "INSERT INTO cc_timestamp (timestamp) VALUES + $timestamp_sql = "INSERT INTO cc_timestamp (timestamp) VALUES (:ts::TIMESTAMP) RETURNING id;"; - $mount_name_check_sql = "SELECT id from cc_mount_name WHERE + $mount_name_check_sql = "SELECT id from cc_mount_name WHERE mount_name = :mn;"; - $mount_name_insert_sql = "INSERT INTO cc_mount_name (mount_name) VALUES + $mount_name_insert_sql = "INSERT INTO cc_mount_name (mount_name) VALUES (:mn) RETURNING id;"; - $stats_sql = "INSERT INTO cc_listener_count (timestamp_id, - listener_count, mount_name_id) VALUES (:timestamp_id, + $stats_sql = "INSERT INTO cc_listener_count (timestamp_id, + listener_count, mount_name_id) VALUES (:timestamp_id, :listener_count, :mount_name_id)"; foreach ($p_dataPoints as $dp) { $timestamp_id = Application_Common_Database::prepareAndExecute( - $timestamp_sql, - array('ts'=> $dp['timestamp']), + $timestamp_sql, + array('ts'=> $dp['timestamp']), "column"); $mount_name_id = Application_Common_Database::prepareAndExecute( $mount_name_check_sql, - array('mn' => $dp['mount_name']), + array('mn' => $dp['mount_name']), "column"); if (strlen($mount_name_id) == 0) { //there is a race condition here where theoretically the row - //with value "mount_name" could appear, but this is *very* + //with value "mount_name" could appear, but this is *very* //unlikely and won't break anything even if it happens. $mount_name_id = Application_Common_Database::prepareAndExecute( $mount_name_insert_sql, - array('mn' => $dp['mount_name']), - "column"); + array('mn' => $dp['mount_name']), + "column"); } Application_Common_Database::prepareAndExecute($stats_sql, - array('timestamp_id' => $timestamp_id, + array('timestamp_id' => $timestamp_id, 'listener_count' => $dp["num_listeners"], 'mount_name_id' => $mount_name_id, ) diff --git a/airtime_mvc/application/views/scripts/listenerstat/index.phtml b/airtime_mvc/application/views/scripts/listenerstat/index.phtml index af1c2c21d..9f3568869 100644 --- a/airtime_mvc/application/views/scripts/listenerstat/index.phtml +++ b/airtime_mvc/application/views/scripts/listenerstat/index.phtml @@ -1,13 +1,7 @@
- Timestamp vs Listener Count
- + Listener Count Over Time
- +
date_form; ?>
\ No newline at end of file diff --git a/airtime_mvc/public/js/airtime/listenerstat/listenerstat.js b/airtime_mvc/public/js/airtime/listenerstat/listenerstat.js index 3911dbad3..a410acf62 100644 --- a/airtime_mvc/public/js/airtime/listenerstat/listenerstat.js +++ b/airtime_mvc/public/js/airtime/listenerstat/listenerstat.js @@ -12,43 +12,148 @@ $(document).ready(function() { endTimestamp = AIRTIME.utilities.fnGetTimestamp(dateEndId, timeEndId); getDataAndPlot(startTimestamp, endTimestamp); }); - - listenerstat_content.find("#all_mps").change(function(){ - var mpName = $(this).val(); - startTimestamp = AIRTIME.utilities.fnGetTimestamp(dateStartId, timeStartId); - endTimestamp = AIRTIME.utilities.fnGetTimestamp(dateEndId, timeEndId); - getDataAndPlot(startTimestamp, endTimestamp); - getDataAndPlot(startTimestamp, endTimestamp, mpName); - }) }); -function getDataAndPlot(startTimestamp, endTimestamp, mountName){ +function getDataAndPlot(startTimestamp, endTimestamp){ // get data $.get('/Listenerstat/get-data', {startTimestamp: startTimestamp, endTimestamp: endTimestamp}, function(data){ data = JSON.parse(data); - out = new Array(); + out = new Object(); $.each(data, function(mpName, v){ - plotData = new Array(); - if (mountName != null && mpName != mountName){ - console.log(mountName); - return true; - } + plotData = new Object(); + plotData.data = new Array(); $.each(v, function(i, ele){ - temp = new Array(); - temp[0] = new Date(ele.timestamp.replace(/-/g,"/")); - temp[1] = ele.listener_count; - plotData.push(temp); + plotData.label = mpName; + var d = new Date(0); + d.setUTCSeconds(ele.timestamp); + plotData.data.push([d, ele.listener_count]); }) - out.push(plotData); + out[mpName] = plotData; }); - if (out.length == 0) { - out.push(new Array()); - } plot(out); }) } -function plot(d){ +function plot(datasets){ + var plot; + data = null; + function plotByChoice(doAll) + { + // largest date object that you can set + firstTimestamp = new Date(8640000000000000); + // smallest + lastTimestamp = new Date(0); + + data = []; + if (doAll != null) + { + $.each(datasets, function(key, val) { + if (firstTimestamp.getTime() > val.data[0][0].getTime()) { + firstTimestamp = val.data[0][0]; + } + if (lastTimestamp.getTime() < val.data[val.data.length-1][0].getTime()) { + lastTimestamp = val.data[val.data.length-1][0]; + } + data.push(val); + }); + } + else + { + $('#legend .legendCB').each( + function(){ + if (this.checked) + { + data.push(datasets[this.id]); + if (firstTimestamp.getTime() > datasets[this.id].data[0][0].getTime()) { + firstTimestamp = datasets[this.id].data[0][0]; + } + if (lastTimestamp.getTime() < datasets[this.id].data[datasets[this.id].data.length-1][0].getTime()) { + lastTimestamp = datasets[this.id].data[datasets[this.id].data.length-1][0]; + } + } + else + { + data.push({label: this.id, data: []}) + } + } + ); + } + + numOfTicks = 10; + tickSize = (lastTimestamp.getTime() - firstTimestamp.getTime())/1000/numOfTicks; + + plot = $.plot($("#flot_placeholder"), data, { + yaxis: { min: 0, tickDecimals: 0 }, + xaxis: { mode: "time", timeformat:"%y/%m/%0d %H:%M", tickSize: [tickSize, "second"] }, + grid: { + hoverable: true, + backgroundColor: { colors: ["#888888", "#999999"] } + }, + series: { + lines: { + show: true, + fill: 0.3 + }, + points: { show: true } + }, + legend: { + container: $('#legend'), + noColumns: 5, + labelFormatter: function (label, series) { + var cb = ' 0){ + cb += 'checked="true" '; + } + cb += 'id="'+label+'" /> '; + cb += label; + return cb; + } + } + }); + + function showTooltip(x, y, contents) { + $('
' + contents + '
').css( { + position: 'absolute', + display: 'none', + top: y + 5, + left: x + 5, + border: '1px solid #fdd', + padding: '2px', + 'background-color': '#fee', + opacity: 0.80 + }).appendTo("body").fadeIn(200); + } + + var previousPoint = null; + $("#flot_placeholder").bind("plothover", function (event, pos, item) { + if (item) { + if (previousPoint != item.dataIndex) { + previousPoint = item.dataIndex; + + $("#tooltip").remove(); + var y = item.datapoint[1].toFixed(2); + + showTooltip(item.pageX, item.pageY, + "Listener Count on '"+item.series.label + "': " + Math.floor(y)); + } + } + else { + $("#tooltip").remove(); + previousPoint = null; + } + }); + + $("#placeholder").bind("plotclick", function (event, pos, item) { + if (item) { + $("#clickdata").text("You clicked point " + item.dataIndex + " in " + item.series.label + "."); + plot.highlight(item.series, item.datapoint); + } + }); + + $('#legend').find("input").click(function(){setTimeout(plotByChoice,100);}); + } + + plotByChoice(true); oBaseDatePickerSettings = { dateFormat: 'yy-mm-dd', onSelect: function(sDate, oDatePicker) { @@ -67,7 +172,4 @@ function plot(d){ listenerstat_content.find(timeStartId).timepicker(oBaseTimePickerSettings); listenerstat_content.find(dateEndId).datepicker(oBaseDatePickerSettings); listenerstat_content.find(timeEndId).timepicker(oBaseTimePickerSettings); - - $.plot($("#flot_placeholder"), d, { xaxis: { mode: "time", timeformat: "%y/%m/%0d %H:%M:%S" } }); - - } \ No newline at end of file +} \ No newline at end of file