Initial Montage Review for people to take a look at

pull/961/head
Linwood-F 2015-07-22 13:51:31 -04:00
parent a6a33fa77f
commit 84ed491765
2 changed files with 693 additions and 1 deletions

View File

@ -210,7 +210,11 @@ if ( canView( 'Stream' ) && $cycleCount > 1 )
{
$cycleGroup = isset($_COOKIE['zmGroup'])?$_COOKIE['zmGroup']:0;
?>
<div id="cycleMontage"><?php echo makePopupLink( '?view=cycle&amp;group='.$cycleGroup, 'zmCycle'.$cycleGroup, array( 'cycle', $cycleWidth, $cycleHeight ), translate('Cycle'), $running ) ?>&nbsp;/&nbsp;<?php echo makePopupLink( '?view=montage&amp;group='.$cycleGroup, 'zmMontage'.$cycleGroup, 'montage', translate('Montage'), $running ) ?></div>
<div id="cycleMontage">
<?php echo makePopupLink( '?view=cycle&amp;group='.$cycleGroup, 'zmCycle'.$cycleGroup, array( 'cycle', $cycleWidth, $cycleHeight ), translate('Cycle'), $running ) ?>&nbsp;/&nbsp;
<?php echo makePopupLink( '?view=montage&amp;group='.$cycleGroup, 'zmMontage'.$cycleGroup, 'montage', translate('Montage'), $running ) ?>&nbsp;/&nbsp;
<?php echo makePopupLink( '?view=montagereview&amp;group='.$cycleGroup, 'zmMontage'.$cycleGroup, 'montagereview', translate('Montage Review'), $running ) ?>
</div>
<?php
}
else

View File

@ -0,0 +1,688 @@
<?php
//
// ZoneMinder web montagereview view file, $Date$, $Revision$
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
// ---------------------
// Montage Review -- show all (visible) monitors, and allow to review time ranges easily for all monitors.
//
// This is similar to the timeline view, but is NOT linked to events directly, and shows all monitors
// It also will do a pseudo play function (one second or more at a time, not less than 1 second resolution)
//
// This is very much a preliminary version, very lightly tested, mostly on Firefox, but should work on IE and Chrome (at least)
// It takes very high bandwidth to the server, and a pretty fast client to keep up with the image rate. To reduce the rate
// change the playback slider to 0 and then it does not try to play at the same time it is scrubbing.
if ( !canView( 'Events' ) )
{
$view = "error";
return;
}
$monitorsSql = "select * from Monitors ";
// Note that this finds incomplete events as well, and any frame records written, but still cannot "see" to the end frame
// if the bulk record has not been written - to get more current reduce bulk frame sizes
$eventsSql = "
select E.Id,E.Name,E.StartTime,max(F.TimeStamp) as CalcEndTime,E.Length,max(F.FrameId) as Frames,E.MaxScore,E.Cause,E.Notes,E.Archived,E.MonitorId
from Events as E
inner join Monitors as M on (E.MonitorId = M.Id)
inner join Frames F on F.EventId=E.Id
where not isnull(E.Frames) and not isnull(StartTime) ";
$frameSql = "
select E.Id as EventId, E.MonitorId, F.TimeStamp, max(F.Score) as Score
from Events as E
inner join Frames as F on (F.EventId = E.Id)
where not isnull(StartTime) and F.Score>0 ";
// This program only calls itself with the time range involved -- it does all monitors (the user can see) all the time
if ( !empty($user['MonitorIds']) )
{
$monFilterSql = ' AND M.Id IN ('.$user['MonitorIds'].')';
$eventsSql .= $monFilterSql;
$monitorsSQL .= $monFilterSql;
$frameSql .= $monFilterSql;
}
// Parse input parameters -- note for future, validate/clean up better in case we don't get called from self.
if ( isset($_REQUEST['minTime']) )
$minTime = validHtmlStr($_REQUEST['minTime']);
if ( isset($_REQUEST['maxTime']) )
$maxTime = validHtmlStr($_REQUEST['maxTime']);
if ( isset($_REQUEST['scale']) )
$defaultScale=validHtmlStr($_REQUEST['scale']);
else
$defaultScale=0.3;
if (isset($_REQUEST['speed']) )
$defaultSpeed=validHtmlStr($_REQUEST['speed']);
else
$defaultSpeed=1;
if (isset($_REQUEST['current']) )
$defaultCurrentTime=validHtmlStr($_REQUEST['current']);
$archive=1;
if (isset($_REQUEST['archive']) )
$archive=validHtmlStr($_REQUEST['archive']);
if ($archive==0)
{
$eventsSql .= " and E.Archived=0 ";
} $frameSql .= " and E.Archived=0 ";
$eventsSql .= "group by E.Id,E.Name,E.StartTime,E.Length,E.Frames,E.MaxScore,E.Cause,E.Notes,E.Archived,E.MonitorId ";
if( isset($minTime) && isset($maxTime) )
{
$eventsSql .= "having CalcEndTime > '" . $minTime . "' and StartTime < '" . $maxTime . "'";
$frameSql .= "
and TimeStamp > '" . $minTime . "' and TimeStamp < '" . $maxTime . "'";
}
$frameSql .= "group by E.Id, E.MonitorId, F.TimeStamp order by E.MonitorId, F.TimeStamp asc";
xhtmlHeaders(__FILE__, translate('montagereview') );
?>
<style>
input[type=range]::-ms-tooltip {
display: none;
}
</style>
</head>
<body>
<div id="page">
<div id="header">
<div id="headerButtons">
<a href="#" onclick="closeWindow();"><?php echo translate('Close') ?></a>
</div>
<h2><?php echo translate('Montage Review') ?></h2>
</div>
<div style='display: inline-flex; border: 1px solid black;'>
<label style='margin:5px;' for=scaleslider>Scale</label>
<input id=scaleslider type=range min=0.10 max=1.00 value=<?php echo $defaultScale ?> step=0.10 width=20% onchange='changescale(this.value)'/>
<output style='margin:5px;' id=scaleslideroutput from=scaleslider><?php echo $defaultScale?>x</output>
</div>
<div style='display: inline-flex; border: 1px solid black;'>
<label style='margin:5px;' for=speedslider>Speed</label>
<input id=speedslider type=range min=0 max=50 value=<?php echo $defaultSpeed ?> step=1 wdth=20% onchange='changespeed(this.value)'/>
<output style='margin:5px;' id=speedslideroutput from=speedslider><?php echo $defaultSpeed ?>x</output>
</div>
<div style='display: inline-flex; border: 1px solid black; flex-flow: row wrap;'>
<button type='button' id=panleft onclick='panleft() '>&lt;&nbsp;Pan&nbsp;Left</button>
<button type='button' id=zoomin onclick='zoomin() '>Zoom&nbsp;In&nbsp;+</button>
<button type='button' id=zoomout onclick='zoomout() '>Zoom&nbsp;Out&nbsp;-</button>
<button type='button' id=lasthour onclick='lasthour()'>LastHour</button>
<button type='button' id=allof onclick='allof() '>All</button>
<button type='button' id=allnon onclick='allnon() '>All&nbsp;Non-Archive</button>
<button type='button' id=panright onclick='panright()'>Pan&nbsp;Right&nbsp;&gt;</button>
</div>
<div id=timelinediv style='position:relative; width:93%;'>
<canvas id=timeline style='border:1px solid;' onmousemove='mmove(event)' ontouchmove='tmove(event)' onmousedown='mdown(event)' onmouseup='mup(event)' onmouseout='mout(event)' ></canvas>
<output id=scrubleft for=scrub></output>
<output id=scrubright for=scrub></output>
<output id=scruboutput for=scrub></output>
</div>
</script>
<?php
// This loads all monitors the user can see -- we will prune this list later based on what we find
$monitors = array();
$monitorsSql .= " order by Sequence asc ";
$index=0;
foreach( dbFetchAll( $monitorsSql ) as $row )
{
$monitors[$index] = $row;
$index = $index + 1;
}
// This builds the list of events that are eligible from this range
echo "<script>\n";
echo "var eventMonitorId = [];\n";
echo "var eventId = [];\n";
echo "var eventStartTimeSecs = [];\n";
echo "var eventEndTimeSecs = [];\n";
echo "var eventPath = [];\n";
echo "var eventFrames = [];\n"; // this is going to presume all frames equal durationlength
// Because we might not have time as the criteria, figure out the min/max time when we run the query
$minTimeSecs = strtotime("2036-01-01 01:01:01");
$maxTimeSecs = strtotime("1950-01-01 01:01:01");
$index=0;
$anyAlarms=false;
$eventsMonitorsFound = array(); // this will just flag which ones found
foreach( dbFetchAll( $eventsSql ) as $event )
{
if( $minTimeSecs > strtotime($event['StartTime'])) $minTimeSecs=strtotime($event['StartTime']);
if( $maxTimeSecs < strtotime($event['CalcEndTime'])) $maxTimeSecs=strtotime($event['CalcEndTime']);
echo "eventMonitorId[$index]=" . $event['MonitorId'] . "; eventId[$index]=" . $event['Id'] . "; ";
echo "eventStartTimeSecs[$index]=" . strtotime($event['StartTime']) . "; eventEndTimeSecs[$index]=" . strtotime($event['CalcEndTime']) . "; ";
echo "eventFrames[$index]=" . $event['Frames'] . "; ";
if ( ZM_USE_DEEP_STORAGE )
echo "eventPath[$index] = \"events/" . $event['MonitorId'] . "/" . strftime("%y/%m/%d/%H/%M/%S", strtotime($event['StartTime'])) . "/\";" ;
else
echo "eventPath[$index] = \"events/" . $event['MonitorId'] . "/" . $event['Id'] . "/\";" ;
$eventsMonitorsFound[$event['MonitorId']] = true;
$index=$index+1;
if($event['MaxScore']>0)
$anyAlarms=true;
echo "\n";
}
if($index == 0) // if there is no data set the min/max to the passed in values
{
if(isset($minTime) && isset($maxTime))
{
$minTimeSecs = strtotime($minTime);
$maxTimeSecs = strtotime($maxTime);
}
else // this is the case of no passed in times AND no data -- just set something arbitrary
{
$minTimeSecs=strtotime('1950-06-01 01:01:01'); // random time so there's something to display
$maxTimeSecs=strtotime('2020-06-02 02:02:02');
}
}
// We only reset the calling time if there was no calling time
if(!isset($minTime) || !isset($maxTime))
{
$maxTime = strftime($maxTimeSecs);
$minTime = strftime($minTimeSecs);
}
else
{
$minTimeSecs = strtotime($minTime);
$maxTimeSecs = strtotime($maxTime);
}
// If we had any alarms in those events, this builds the list of all alarm frames (thought for later - should these be ranges, so as to minimize list if very long?)
echo "var frameMonitorId = [];\n";
echo "var frameTimeStampSecs = [];\n";
echo "var frameScore = [];\n";
$maxScore=0;
$index=0;
if($anyAlarms)
foreach( dbFetchAll ($frameSql) as $frame )
{
echo " frameMonitorId[$index]=" . $frame['MonitorId'] . ";";
echo " frameTimeStampSecs[$index]=" . strtotime($frame['TimeStamp']) . ";";
echo " frameScore[$index]=" . $frame['Score'] . ";\n";
if($maxScore <= $frame['Score'])
$maxScore=$frame['Score'];
$index += 1;
}
// This is where we have to display the canvases -- AFTER determining which monitors we use (above in events) and BEFORE we loop through them to cache the objects
// This splits up the javascript and html a bit, but it's a lot simpler than trying in php to cache one while completing the other
// Monitor images - these had to be loaded after the monitors used were determined (after loading events)
echo "</script>\n";
foreach ($monitors as $m)
{
if(!empty($eventsMonitorsFound[$m['Id']])) // only save the monitor if it's part of these events
{
echo "<canvas width='" . $m['Width'] * $defaultScale . "px' height='" . $m['Height'] * $defaultScale . "px' id='Monitor" . $m['Id'] . "' style='border:2px solid " . $m['WebColour'] . "' onclick='showOneEvent(" . $m['Id'] . ")'>No Canvas Support!!</canvas>\n";
}
}
echo "<script>\n";
echo "var maxScore=$maxScore;\n"; // used to skip frame load if we find no alarms.
echo "var monitorName = [];\n";
echo "var monitorLoading = [];\n";
echo "var monitorImageObject = [];\n";
echo "var monitorLoadingStageURL = [];\n";
echo "var monitorColour = [];\n";
echo "var monitorWidth = [];\n";
echo "var monitorHeight = [];\n";
echo "var monitorIndex = [];\n";
echo "var monitorCanvasObj = [];\n"; // stash location of these here so we don't have to search
echo "var monitorCanvasCtx = [];\n";
// This builds the list of monitors.
$numMonitors=0; // this array is indexed by the monitor ID for faster access later, so it may be sparse
foreach ($monitors as $m)
{
if(!empty($eventsMonitorsFound[$m['Id']])) // only save the monitor if it's part of these events
{
echo " monitorLoading[" . $m['Id'] . "]=false; ";
echo " monitorImageObject[" . $m['Id'] . "]=null; ";
echo " monitorLoadingStageURL[" . $m['Id'] . "] = ''; ";
echo " monitorColour[" . $m['Id'] . "]=\"" . $m['WebColour'] . "\"; ";
echo " monitorWidth[" . $m['Id'] . "]=" . $m['Width'] . "; ";
echo " monitorHeight[" . $m['Id'] . "]=" . $m['Height'] . ";";
echo " monitorIndex[" . $m['Id'] . "]=" . $numMonitors . ";";
echo " monitorName[" . $m['Id'] . "]=\"" . $m['Name'] . "\"; ";
echo " monitorCanvasObj[" . $m['Id'] . "]=document.getElementById('Monitor" . $m['Id'] . "');";
echo " monitorCanvasCtx[" . $m['Id'] . "]=monitorCanvasObj[" . $m['Id'] . "].getContext('2d');\n";
$numMonitors += 1;
}
}
echo "var numMonitors = $numMonitors;\n";
echo "var minTimeSecs=" . $minTimeSecs . ";\n";
echo "var maxTimeSecs=" . $maxTimeSecs . ";\n";
echo "var rangeTimeSecs=" . ( $maxTimeSecs - $minTimeSecs + 1) . ";\n";
if(isset($defaultCurrentTime))
echo "var currentTimeSecs=" . strtotime($defaultCurrentTime) . ";\n";
else
echo "var currentTimeSecs=" . $minTimeSecs . ";\n";
?>
var scrubAsObject=document.getElementById('scrub');
var cWidth; // save canvas width
var cHeight; // save canvas height
var canvas=document.getElementById("timeline"); // global canvas definition so we don't have to keep looking it up
var ctx=canvas.getContext('2d');
var underSlider; // use this to hold what is hidden by the slider
var underSliderX; // Where the above was taken from (left side, Y is zero)
function outputUpdate(val)
{
drawSliderOnGraph(val);
for(var i=0; i<monitorIndex.length; i++)
{
if(monitorName[i]>"")
loadImage2Monitor(i,SetImageSource(i,val));
}
var currentTimeMS = new Date(val*1000);
currentTimeSecs=val;
}
function SetImageSource(monId,val)
{
var zeropad = <?php echo sprintf("\"%0" . ZM_EVENT_IMAGE_DIGITS . "d\"",0); ?>;
for(var i=0; i<eventPath.length; i++) // Search for a match
{
if(eventMonitorId[i]==monId && val >= eventStartTimeSecs[i] && val <= eventEndTimeSecs[i])
{
frame=parseInt((val - eventStartTimeSecs[i])/(eventEndTimeSecs[i]-eventStartTimeSecs[i])*eventFrames[i])+1;
img = eventPath[i] + zeropad.substr(frame.toString().length) + frame.toString() + "-capture.jpg";
i=eventPath.length+1; // force loop exit
return img;
}
}
return "graphics/transparent.gif";
}
function imagedone(monId,success)
{
monitorCanvasCtx[monId].clearRect(0,0,monitorCanvasObj[monId].width,monitorCanvasObj[monId].height); // just in case image is no good
if(success)
monitorCanvasCtx[monId].drawImage(monitorImageObject[monId],0,0,monitorCanvasObj[monId].width,monitorCanvasObj[monId].height); // we ignore errors and just clear the image and keep going
monitorImageObject[monId]=null;
monitorLoading[monId]=false;
if(monitorLoadingStageURL[monId]=="") return;
loadImage2Monitor(monId,monitorLoadingStageURL[monId]);
monitorLoadingStageURL[monId]="";
return;
}
function loadImage2Monitor(monId,url)
{
if(monitorLoading[monId])
monitorLoadingStageURL[monId]=url; // we don't care if we are overriting, it means it didn't change fast enough
else
{
monitorImageObject[monId]=undefined;
monitorImageObject[monId]=new Image();
monitorImageObject[monId].onload = function() {imagedone(monId,true )};
monitorImageObject[monId].onerror = function() {imagedone(monId,false)};
monitorImageObject[monId].src=url; // starts a load but doesn't refresh yet, wait until ready
monitorLoading[monId]=true;
}
}
function changescale(newscale)
{
for(var i=0; i<monitorIndex.length; i++)
{
if(monitorName[i]>"")
{
monitorCanvasObj[i].width=monitorWidth[i]*newscale;
monitorCanvasObj[i].height=monitorHeight[i]*newscale;
}
}
document.getElementById('scaleslideroutput').value = newscale.toString() + " x";
return;
}
var playSecsperSec = 1;
var TimerInterval = 1000; // We never show subsecond frame rates, but if playing faster we play up to 1 per 250ms
var AdvanceByTime = setInterval(TimerFire,TimerInterval);
function changespeed(val)
{
playSecsperSec=parseInt(val);
var t;
if(val==0) t=0;
if(val==1) t=1000; // The speed this can support is likely pretty browser/os dependant
if(val==2) t=500;
if(val==3) t=330;
if(val>3) t=250;
if(t!=TimerInterval)
{
if(AdvanceByTime!=0) clearInterval(AdvanceByTime);
TimerInterval=t;
if(t>0) AdvanceByTime=setInterval(TimerFire,TimerInterval)
else AdvanceByTime=0;
}
document.getElementById('speedslideroutput').innerHTML = val.toString() + " x";
}
function TimerFire()
{
if (currentTimeSecs + playSecsperSec >= maxTimeSecs) return;
outputUpdate(currentTimeSecs + playSecsperSec);
}
function drawSliderOnGraph(val)
{
var sliderWidth=10;
var sliderLineWidth=1;
var sliderHeight=cHeight;
// Set some sizes
labelpx = Math.max( 6, Math.min( 20, parseInt(cHeight * 0.6 / (numMonitors+1)) ) );
labbottom=parseInt(cHeight * 0.2 / (numMonitors+1)).toString() + "px"; // This is positioning same as row labels below, but from bottom so 1-position
labfont=labelpx + "px Georgia"; // set this like below row labels
if(numMonitors>0) // if we have no data to display don't do the slider itself
{
var sliderX=parseInt( (val - minTimeSecs) / rangeTimeSecs * cWidth - sliderWidth/2); // position left side of slider
if(sliderX < 0) sliderX=0;
if(sliderX+sliderWidth > cWidth) sliderX=cWidth-sliderWidth-1;
// If we have data already saved first restore it from LAST time
if(typeof underSlider !== 'undefined')
{
ctx.putImageData(underSlider,underSliderX, 0, 0, 0, sliderWidth, sliderHeight);
underSlider=undefined;
}
// Now save where we are putting it THIS time
underSlider=ctx.getImageData(sliderX, 0, sliderWidth, sliderHeight);
// And add in the slider'
ctx.lineWidth=sliderLineWidth;
ctx.strokeStyle='black';
// looks like strokes are on the outside (or could be) so shrink it by the line width so we replace all the pixels
ctx.strokeRect(sliderX+sliderLineWidth,sliderLineWidth,sliderWidth - 2*sliderLineWidth, sliderHeight - 2*sliderLineWidth);
underSliderX=sliderX;
var o = document.getElementById('scruboutput');
o.innerHTML=secs2dbstr(currentTimeSecs);
o.style.position="absolute";
o.style.bottom=labbottom;
o.style.font=labfont;
// try to get length and then when we get too close to the right switch to the left
var len = o.offsetWidth;
if(sliderX > cWidth/2)
x=sliderX - len - 10;
else
x=sliderX + 10;
o.style.left=x.toString() + "px";
}
// This displays (or not) the left/right limits depending on how close the slider is.
// Because these change widths if the slider is too close, use the slider width as an estimate for the left/right label length (i.e. don't recalculate len from above)
// If this starts to collide increase some of the extra space
var o = document.getElementById('scrubleft');
o.innerHTML=secs2dbstr(minTimeSecs);
o.style.position="absolute";
o.style.bottom=labbottom;
o.style.font=labfont;
o.style.left="3px";
if(numMonitors==0) // we need a len calculation if we skipped the slider
len = o.offsetWidth;
// If the slider will overlay part of this suppress (this is the left side)
if(len + 5 > sliderX)
o.style.display="none";
else
o.style.display="inline";
var o = document.getElementById('scrubright');
o.innerHTML=secs2dbstr(maxTimeSecs);
o.style.position="absolute";
o.style.bottom=labbottom;
o.style.font=labfont;
// If the slider will overlay part of this suppress (this is the right side)
o.style.left=(cWidth - len - 5).toString() + "px";
if(sliderX > cWidth - len - 10)
o.style.display="none";
else
o.style.display="inline";
}
function drawGraph()
{
var divWidth=document.getElementById("timelinediv").clientWidth
canvas.width = cWidth = divWidth; // Let it float and determine width (it should be sized a bit smaller percentage of window)
canvas.height=cHeight = parseInt(window.innerHeight * 0.10);
if(eventId.length==0)
{
ctx.font="40px Georgia";
ctx.fillStyle="Black";
ctx.globalAlpha=1;
var t="No data found in range - choose differently";
var l=ctx.measureText(t).width;
ctx.fillText(t,(cWidth - l)/2, cHeight-10);
underSlider=undefined;
return;
}
var rowHeight=parseInt(cHeight / (numMonitors + 1) ); // Leave room for a scale of some sort
// first fill in the bars for the events (not alarms)
for(var i=0; i<eventId.length; i++) // Display all we loaded
{
var x1=parseInt( (eventStartTimeSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth) ; // round low end down
var x2=parseInt( (eventEndTimeSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ) ; // round high end up to be sure consecutive ones connect
ctx.fillStyle=monitorColour[eventMonitorId[i]];
ctx.globalAlpha = 0.2; // light color for background
ctx.clearRect(x1,monitorIndex[eventMonitorId[i]]*rowHeight,x2-x1,rowHeight); // Erase any overlap so it doesn't look artificially darker
ctx.fillRect (x1,monitorIndex[eventMonitorId[i]]*rowHeight,x2-x1,rowHeight);
}
for(var i=0; (i<frameScore.length) && (maxScore>0); i++) // Now put in scored frames (if any)
{
var x1=parseInt( (frameTimeStampSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth) ; // round low end down
var x2=parseInt( (frameTimeStampSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ) ; // round up
if(x2-x1 < 2) x2=x1+2; // So it is visible make them all at least this number of seconds wide
ctx.fillStyle=monitorColour[frameMonitorId[i]];
ctx.globalAlpha = 0.4 + 0.6 * (1 - frameScore[i]/maxScore); // Background is scaled but even lowest is twice as dark as the background
ctx.fillRect(x1,monitorIndex[frameMonitorId[i]]*rowHeight,x2-x1,rowHeight);
}
for(var i=0; i<monitorName.length; i++) // Note that this may be a sparse array
{
if(monitorName[i]>"")
{
ctx.font= parseInt(rowHeight * 0.6).toString() + "px Georgia";
ctx.fillStyle="Black";
ctx.globalAlpha=1;
ctx.fillText(monitorName[i], 0, (monitorIndex[i] + 0.8) * rowHeight);
}
}
underSlider=undefined; // flag we don't have a slider cached
return;
}
/// Found this here: http://stackoverflow.com/questions/55677/how-do-i-get-the-coordinates-of-a-mouse-click-on-a-canvas-element
function relMouseCoords(event){
var totalOffsetX = 0;
var totalOffsetY = 0;
var canvasX = 0;
var canvasY = 0;
var currentElement = this;
do{
totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft;
totalOffsetY += currentElement.offsetTop - currentElement.scrollTop;
}
while(currentElement = currentElement.offsetParent)
canvasX = event.pageX - totalOffsetX;
canvasY = event.pageY - totalOffsetY;
return {x:canvasX, y:canvasY}
}
HTMLCanvasElement.prototype.relMouseCoords = relMouseCoords;
// These are the functions for mouse movement in the timeline. Note that touch is treated as a mouse move with mouse down
var mouseisdown=false;
function mdown(event) {mouseisdown=true; mmove(event)}
function mup(event) {mouseisdown=false;}
function mout(event) {mouseisdown=false;} // if we go outside treat it as release
function tmove(event) {mouseisdown=true; mmove(event);}
function mmove(event)
{
if(mouseisdown) // only do anything if the mouse is depressed while on the sheet
{
var sec = minTimeSecs + rangeTimeSecs / event.target.width * event.target.relMouseCoords(event).x;
outputUpdate(sec);
}
}
function secs2dbstr (s)
{
var st = (new Date(s * 1000)).format("%Y-%m-%d %H:%M:%S");
return st;
}
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// The sceond below are to reload this program with new parameters
function clicknav(minSecs,maxSecs,arch) // we use the current time if we can
{
var now = new Date() / 1000;
var minStr="";
var maxStr="";
var currentStr="";
var archiveStr="";
if(minSecs>0)
{
if(maxSecs > now)
maxSecs = parseInt(now);
maxStr="&maxTime=" + secs2dbstr(maxSecs);
}
if(maxSecs>0)
minStr="&minTime=" + secs2dbstr(minSecs);
if(arch==0)
archiveStr="&archive=0";
if(minSecs && maxSecs)
{
if(currentTimeSecs > minSecs && currentTimeSecs < maxSecs) // make sure time is in the new range
currentStr="&current=" + secs2dbstr(currentTimeSecs);
}
var uri = "?view=" + currentView + minStr + maxStr + currentStr + "&scale=" + document.getElementById("scaleslider").value + "&speed=" + document.getElementById("speedslider").value + "&archive=" + arch;
window.location=uri;
}
function lasthour()
{
var now = new Date() / 1000;
clicknav(now - 3600 + 1, now,1);
}
function zoomin()
{
rangeTimeSecs = parseInt(rangeTimeSecs / 2);
minTimeSecs = parseInt(currentTimeSecs - rangeTimeSecs/2); // this is the slider current time, we center on that
maxTimeSecs = parseInt(currentTimeSecs + rangeTimeSecs/2);
clicknav(minTimeSecs,maxTimeSecs,1);
}
function zoomout()
{
rangeTimeSecs = parseInt(rangeTimeSecs * 2);
minTimeSecs = parseInt(currentTimeSecs - rangeTimeSecs/2); // this is the slider current time, we center on that
maxTimeSecs = parseInt(currentTimeSecs + rangeTimeSecs/2);
clicknav(minTimeSecs,maxTimeSecs,1);
}
function panleft()
{
minTimeSecs = parseInt(minTimeSecs - rangeTimeSecs/2);
maxTimeSecs = minTimeSecs + rangeTimeSecs - 1;
clicknav(minTimeSecs,maxTimeSecs,1);
}
function panright()
{
minTimeSecs = parseInt(minTimeSecs + rangeTimeSecs/2);
maxTimeSecs = minTimeSecs + rangeTimeSecs - 1;
clicknav(minTimeSecs,maxTimeSecs,1);
}
function allof()
{
clicknav(0,0,1);
}
function allnon()
{
clicknav(0,0,0);
}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ End of handlers for reloading this program ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
function showOneEvent(monId) // link out to the normal view of one event's data
{
// We know the monitor, need to determine the event based on current time
for(var i=0; i<eventId.length; i++) // Display all we loaded
{
if(eventMonitorId[i]==monId && currentTimeSecs >= eventStartTimeSecs[i] && currentTimeSecs <= eventEndTimeSecs[i])
{
var url = '?view=event&eid=' + eventId[i] + '&fid=' + parseInt(Math.max(1, Math.min(eventFrames[i], eventFrames[i] * (currentTimeSecs - eventStartTimeSecs[i]) / (eventEndTimeSecs[i] - eventStartTimeSecs[i] + 1) ) ));
createPopup(url, 'zmEvent', 'event', monitorWidth[eventMonitorId[i]], monitorHeight[eventMonitorId[i]]);
}
}
}
// Do this on load implicitly
drawGraph();
window.addEventListener("resize",drawGraph);
</script>
</div>
</body>
</html>