Merge branch 'montagereview_rework' of ../ZoneMinder.master into montagereview_rework
This commit is contained in:
commit
6b2e23e1a5
|
@ -0,0 +1,720 @@
|
||||||
|
function evaluateLoadTimes() {
|
||||||
|
// Only consider it a completed event if we load ALL monitors, then zero all and start again
|
||||||
|
var start=0;
|
||||||
|
var end=0;
|
||||||
|
if ( liveMode != 1 && currentSpeed == 0 ) return; // don't evaluate when we are not moving as we can do nothing really fast.
|
||||||
|
for ( var i = 0; i < monitorIndex.length; i++ ) {
|
||||||
|
if ( monitorName[i] > "" ) {
|
||||||
|
if ( monitorLoadEndTimems[i] ==0 ) return; // if we have a monitor with no time yet just wait
|
||||||
|
if ( start == 0 || start > monitorLoadStartTimems[i] ) start = monitorLoadStartTimems[i];
|
||||||
|
if ( end == 0 || end < monitorLoadEndTimems[i] ) end = monitorLoadEndTimems[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( start == 0 || end == 0 ) return; // we really should not get here
|
||||||
|
for ( var i=0; i < numMonitors; i++ ) {
|
||||||
|
var monId = monitorPtr[i];
|
||||||
|
monitorLoadStartTimems[monId] = 0;
|
||||||
|
monitorLoadEndTimems[monId] = 0;
|
||||||
|
}
|
||||||
|
freeTimeLastIntervals[imageLoadTimesEvaluated++] = 1 - ((end - start)/currentDisplayInterval);
|
||||||
|
if( imageLoadTimesEvaluated < imageLoadTimesNeeded ) return;
|
||||||
|
var avgFrac=0;
|
||||||
|
for ( var i=0; i < imageLoadTimesEvaluated; i++ )
|
||||||
|
avgFrac += freeTimeLastIntervals[i];
|
||||||
|
avgFrac = avgFrac / imageLoadTimesEvaluated;
|
||||||
|
// The larger this is(positive) the faster we can go
|
||||||
|
if (avgFrac >= 0.9) currentDisplayInterval = (currentDisplayInterval * 0.50).toFixed(1); // we can go much faster
|
||||||
|
else if (avgFrac >= 0.8) currentDisplayInterval = (currentDisplayInterval * 0.55).toFixed(1);
|
||||||
|
else if (avgFrac >= 0.7) currentDisplayInterval = (currentDisplayInterval * 0.60).toFixed(1);
|
||||||
|
else if (avgFrac >= 0.6) currentDisplayInterval = (currentDisplayInterval * 0.65).toFixed(1);
|
||||||
|
else if (avgFrac >= 0.5) currentDisplayInterval = (currentDisplayInterval * 0.70).toFixed(1);
|
||||||
|
else if (avgFrac >= 0.4) currentDisplayInterval = (currentDisplayInterval * 0.80).toFixed(1);
|
||||||
|
else if (avgFrac >= 0.35) currentDisplayInterval = (currentDisplayInterval * 0.90).toFixed(1);
|
||||||
|
else if (avgFrac >= 0.3) currentDisplayInterval = (currentDisplayInterval * 1.00).toFixed(1);
|
||||||
|
else if (avgFrac >= 0.25) currentDisplayInterval = (currentDisplayInterval * 1.20).toFixed(1);
|
||||||
|
else if (avgFrac >= 0.2) currentDisplayInterval = (currentDisplayInterval * 1.50).toFixed(1);
|
||||||
|
else if (avgFrac >= 0.1) currentDisplayInterval = (currentDisplayInterval * 2.00).toFixed(1);
|
||||||
|
else currentDisplayInterval = (currentDisplayInterval * 2.50).toFixed(1);
|
||||||
|
currentDisplayInterval=Math.min(Math.max(currentDisplayInterval, 30),10000); // limit this from about 30fps to .1 fps
|
||||||
|
imageLoadTimesEvaluated=0;
|
||||||
|
setSpeed(speedIndex);
|
||||||
|
$('fps').innerHTML="Display refresh rate is " + (1000 / currentDisplayInterval).toFixed(1) + " per second, avgFrac=" + avgFrac.toFixed(3) + ".";
|
||||||
|
}
|
||||||
|
|
||||||
|
function SetImageSource( monId, val ) {
|
||||||
|
if ( liveMode == 1 ) {
|
||||||
|
return monitorImageObject[monId].src.replace(/rand=\d+/i, 'rand='+Math.floor((Math.random() * 1000000) ));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
for ( var i=0, eIdlength = eId.length; i < eIdlength; i++ ) {
|
||||||
|
// Search for a match
|
||||||
|
if ( eMonId[i] == monId && val >= eStartSecs[i] && val <= eEndSecs[i] ) {
|
||||||
|
var frame = parseInt((val - eStartSecs[i])/(eEndSecs[i]-eStartSecs[i])*eventFrames[i])+1;
|
||||||
|
return "index.php?view=image&eid=" + eId[i] + '&fid='+frame + "&width=" + monitorCanvasObj[monId].width + "&height=" + monitorCanvasObj[monId].height;
|
||||||
|
}
|
||||||
|
} // end for
|
||||||
|
return "no data";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// callback when loading an image. Will load itself to the canvas, or draw no data
|
||||||
|
function imagedone( obj, monId, success ) {
|
||||||
|
if ( success ) {
|
||||||
|
var canvasCtx = monitorCanvasCtx[monId];
|
||||||
|
var canvasObj = monitorCanvasObj[monId];
|
||||||
|
|
||||||
|
canvasCtx.drawImage( monitorImageObject[monId], 0, 0, canvasObj.width, canvasObj.height );
|
||||||
|
var iconSize=(Math.max(canvasObj.width, canvasObj.height) * 0.10);
|
||||||
|
canvasCtx.font = "600 " + iconSize.toString() + "px Arial";
|
||||||
|
canvasCtx.fillStyle = "white";
|
||||||
|
canvasCtx.globalCompositeOperation = "difference";
|
||||||
|
canvasCtx.fillText( "+", iconSize*0.2, iconSize*1.2 );
|
||||||
|
canvasCtx.fillText( "-", canvasObj.width - iconSize*1.2, iconSize*1.2 );
|
||||||
|
canvasCtx.globalCompositeOperation = "source-over";
|
||||||
|
monitorLoadEndTimems[monId] = new Date().getTime(); // elapsed time to load
|
||||||
|
evaluateLoadTimes();
|
||||||
|
}
|
||||||
|
monitorLoading[monId] = false;
|
||||||
|
if ( ! success ) {
|
||||||
|
// if we had a failrue queue up the no-data image
|
||||||
|
//loadImage2Monitor(monId,"no data"); // leave the staged URL if there is one, just ignore it here.
|
||||||
|
loadNoData( monId );
|
||||||
|
} else {
|
||||||
|
if ( monitorLoadingStageURL[monId] == "" ) {
|
||||||
|
console.log("Not showing image for " + monId );
|
||||||
|
// This means that there wasn't a loading image placeholder.
|
||||||
|
// So we weren't actually loading an image... which seems weird.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//loadImage2Monitor(monId,monitorLoadingStageURL[monId] );
|
||||||
|
//monitorLoadingStageURL[monId]="";
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadNoData( monId ) {
|
||||||
|
if ( monId ) {
|
||||||
|
var canvasCtx = monitorCanvasCtx[monId];
|
||||||
|
var canvasObj = monitorCanvasObj[monId];
|
||||||
|
canvasCtx.fillStyle="white";
|
||||||
|
canvasCtx.fillRect(0, 0, canvasObj.width, canvasObj.height);
|
||||||
|
var textSize=canvasObj.width * 0.15;
|
||||||
|
var text="No Data";
|
||||||
|
canvasCtx.font = "600 " + textSize.toString() + "px Arial";
|
||||||
|
canvasCtx.fillStyle="black";
|
||||||
|
var textWidth = canvasCtx.measureText(text).width;
|
||||||
|
canvasCtx.fillText(text,canvasObj.width/2 - textWidth/2,canvasObj.height/2);
|
||||||
|
} else {
|
||||||
|
console.log("No monId in loadNoData");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either draws the
|
||||||
|
function loadImage2Monitor( monId, url ) {
|
||||||
|
if ( monitorLoading[monId] && monitorImageObject[monId].src != url ) {
|
||||||
|
// never queue the same image twice (if it's loading it has to be defined, right?
|
||||||
|
monitorLoadingStageURL[monId] = url; // we don't care if we are overriting, it means it didn't change fast enough
|
||||||
|
} else {
|
||||||
|
if ( monitorImageObject[monId].src == url ) return; // do nothing if it's the same
|
||||||
|
if ( url == 'no data' ) {
|
||||||
|
loadNoData( monId );
|
||||||
|
} else {
|
||||||
|
monitorLoading[monId] = true;
|
||||||
|
monitorLoadStartTimems[monId] = new Date().getTime();
|
||||||
|
monitorImageObject[monId].src = url; // starts a load but doesn't refresh yet, wait until ready
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function timerFire() {
|
||||||
|
// See if we need to reschedule
|
||||||
|
if(currentDisplayInterval != timerInterval || currentSpeed == 0) {
|
||||||
|
// zero just turn off interrupts
|
||||||
|
clearInterval(timerObj);
|
||||||
|
timerInterval=currentDisplayInterval;
|
||||||
|
if(currentSpeed>0 || liveMode!=0) timerObj=setInterval(timerFire,timerInterval); // don't fire out of live mode if speed is zero
|
||||||
|
}
|
||||||
|
|
||||||
|
if (liveMode) outputUpdate(currentTimeSecs); // In live mode we basically do nothing but redisplay
|
||||||
|
else if (currentTimeSecs + playSecsperInterval >= maxTimeSecs) // beyond the end just stop
|
||||||
|
{
|
||||||
|
setSpeed(0);
|
||||||
|
outputUpdate(currentTimeSecs);
|
||||||
|
}
|
||||||
|
else outputUpdate(currentTimeSecs + playSecsperInterval);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawSliderOnGraph(val) {
|
||||||
|
var sliderWidth=10;
|
||||||
|
var sliderLineWidth=1;
|
||||||
|
var sliderHeight=cHeight;
|
||||||
|
|
||||||
|
if(liveMode==1) {
|
||||||
|
val=Math.floor( Date.now() / 1000);
|
||||||
|
}
|
||||||
|
// Set some sizes
|
||||||
|
|
||||||
|
var labelpx = Math.max( 6, Math.min( 20, parseInt(cHeight * timeLabelsFractOfRow / (numMonitors+1)) ) );
|
||||||
|
var labbottom=parseInt(cHeight * 0.2 / (numMonitors+1)).toString() + "px"; // This is positioning same as row labels below, but from bottom so 1-position
|
||||||
|
var 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;
|
||||||
|
}
|
||||||
|
if(liveMode==0) // we get rid of the slider if we switch to live (since it may not be in the "right" place)
|
||||||
|
{
|
||||||
|
// 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 = $('scruboutput');
|
||||||
|
if(liveMode==1)
|
||||||
|
{
|
||||||
|
o.innerHTML="Live Feed @ " + (1000 / currentDisplayInterval).toFixed(1) + " fps";
|
||||||
|
o.style.color="red";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
o.innerHTML=secs2dbstr(val);
|
||||||
|
o.style.color="blue";
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
var x;
|
||||||
|
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 = $('scrubleft');
|
||||||
|
o.innerHTML=secs2dbstr(minTimeSecs);
|
||||||
|
o.style.position="absolute";
|
||||||
|
o.style.bottom=labbottom;
|
||||||
|
o.style.font=labfont;
|
||||||
|
o.style.left="5px";
|
||||||
|
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 + 10 > sliderX || cWidth < len * 4 ) // that last check is for very narrow browsers
|
||||||
|
o.style.display="none";
|
||||||
|
else
|
||||||
|
{
|
||||||
|
o.style.display="inline";
|
||||||
|
o.style.display="inline-flex"; // safari won't take this but will just ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
var o = $('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 - 15).toString() + "px";
|
||||||
|
if(sliderX > cWidth - len - 20 || cWidth < len * 4 )
|
||||||
|
o.style.display="none";
|
||||||
|
else
|
||||||
|
{
|
||||||
|
o.style.display="inline";
|
||||||
|
o.style.display="inline-flex";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawGraph()
|
||||||
|
{
|
||||||
|
var divWidth=$('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(eId.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<eId.length; i++) // Display all we loaded
|
||||||
|
{
|
||||||
|
var x1=parseInt( (eStartSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth) ; // round low end down
|
||||||
|
var x2=parseInt( (eEndSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ) ; // round high end up to be sure consecutive ones connect
|
||||||
|
ctx.fillStyle=monitorColour[eMonId[i]];
|
||||||
|
ctx.globalAlpha = 0.2; // light color for background
|
||||||
|
ctx.clearRect(x1,monitorIndex[eMonId[i]]*rowHeight,x2-x1,rowHeight); // Erase any overlap so it doesn't look artificially darker
|
||||||
|
ctx.fillRect (x1,monitorIndex[eMonId[i]]*rowHeight,x2-x1,rowHeight);
|
||||||
|
}
|
||||||
|
for(var i=0; (i<fScore.length) && (maxScore>0); i++) // Now put in scored frames (if any)
|
||||||
|
{
|
||||||
|
var x1=parseInt( (fTimeFromSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth) ; // round low end down
|
||||||
|
var x2=parseInt( (fTimeToSecs[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[fMonId[i]];
|
||||||
|
ctx.globalAlpha = 0.4 + 0.6 * (1 - fScore[i]/maxScore); // Background is scaled but even lowest is twice as dark as the background
|
||||||
|
ctx.fillRect(x1,monitorIndex[fMonId[i]]*rowHeight,x2-x1,rowHeight);
|
||||||
|
}
|
||||||
|
for(var i=0; i<numMonitors; i++) // Note that this may be a sparse array
|
||||||
|
{
|
||||||
|
ctx.font= parseInt(rowHeight * timeLabelsFractOfRow).toString() + "px Georgia";
|
||||||
|
ctx.fillStyle="Black";
|
||||||
|
ctx.globalAlpha=1;
|
||||||
|
ctx.fillText(monitorName[monitorPtr[i]], 0, (i + 1 - (1 - timeLabelsFractOfRow)/2 ) * rowHeight ); // This should roughly center font in row
|
||||||
|
}
|
||||||
|
underSlider=undefined; // flag we don't have a slider cached
|
||||||
|
drawSliderOnGraph(currentTimeSecs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function redrawScreen()
|
||||||
|
{
|
||||||
|
if(fitMode==0) // if we fit, then monitors were absolutely positioned already (or will be) otherwise release them to float
|
||||||
|
{
|
||||||
|
for(var i=0; i<numMonitors; i++)
|
||||||
|
monitorCanvasObj[monitorPtr[i]].style.position="";
|
||||||
|
$('monitors').setStyle('height',"auto");
|
||||||
|
}
|
||||||
|
if(liveMode==1) // if we are not in live view switch to history -- this has to come before fit in case we re-establish the timeline
|
||||||
|
{
|
||||||
|
$('SpeedDiv').style.display="none";
|
||||||
|
$('timelinediv').style.display="none";
|
||||||
|
$('live').innerHTML="History";
|
||||||
|
$('zoomin').style.display="none";
|
||||||
|
$('zoomout').style.display="none";
|
||||||
|
$('panleft').style.display="none";
|
||||||
|
$('panright').style.display="none";
|
||||||
|
|
||||||
|
}
|
||||||
|
else // switch out of liveview mode
|
||||||
|
{
|
||||||
|
$('SpeedDiv').style.display="inline";
|
||||||
|
$('SpeedDiv').style.display="inline-flex";
|
||||||
|
$('timelinediv').style.display=null;
|
||||||
|
$('live').innerHTML="Live";
|
||||||
|
$('zoomin').style.display="inline";
|
||||||
|
$('zoomin').style.display="inline-flex";
|
||||||
|
$('zoomout').style.display="inline";
|
||||||
|
$('zoomout').style.display="inline-flex";
|
||||||
|
$('panleft').style.display="inline";
|
||||||
|
$('panleft').style.display="inline-flex";
|
||||||
|
$('panright').style.display="inline";
|
||||||
|
$('panright').style.display="inline-flex";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(fitMode==1)
|
||||||
|
{
|
||||||
|
$('ScaleDiv').style.display="none";
|
||||||
|
$('fit').innerHTML="Scale";
|
||||||
|
var vh=window.innerHeight;
|
||||||
|
var vw=window.innerWidth;
|
||||||
|
var pos=$('monitors').getPosition();
|
||||||
|
var mh=(vh - pos.y - $('fps').getSize().y);
|
||||||
|
$('monitors').setStyle('height',mh.toString() + "px"); // leave a small gap at bottom
|
||||||
|
if(maxfit2($('monitors').getSize().x,$('monitors').getSize().y) == 0) /// if we fail to fix we back out of fit mode -- ??? This may need some better handling
|
||||||
|
fitMode=1-fitMode;
|
||||||
|
}
|
||||||
|
else // switch out of fit mode
|
||||||
|
{
|
||||||
|
$('ScaleDiv').style.display="inline";
|
||||||
|
$('ScaleDiv').style.display="inline-flex";
|
||||||
|
$('fit').innerHTML="Fit";
|
||||||
|
setScale(currentScale);
|
||||||
|
}
|
||||||
|
drawGraph();
|
||||||
|
outputUpdate(currentTimeSecs);
|
||||||
|
timerFire(); // force a fire in case it's not timing
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function outputUpdate(val)
|
||||||
|
{
|
||||||
|
drawSliderOnGraph(val);
|
||||||
|
for(var i=0; i<numMonitors; i++)
|
||||||
|
{
|
||||||
|
loadImage2Monitor(monitorPtr[i],SetImageSource(monitorPtr[i],val));
|
||||||
|
}
|
||||||
|
var currentTimeMS = new Date(val*1000);
|
||||||
|
currentTimeSecs=val;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFit(value)
|
||||||
|
{
|
||||||
|
fitMode=value;
|
||||||
|
redrawScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showScale(newscale) // updates slider only
|
||||||
|
{
|
||||||
|
$('scaleslideroutput').innerHTML = parseFloat(newscale).toFixed(2).toString() + " x";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setScale(newscale) // makes actual change
|
||||||
|
{
|
||||||
|
showScale(newscale);
|
||||||
|
for(var i=0; i<numMonitors; i++)
|
||||||
|
{
|
||||||
|
monitorCanvasObj[monitorPtr[i]].width=monitorWidth[monitorPtr[i]]*monitorNormalizeScale[monitorPtr[i]]*monitorZoomScale[monitorPtr[i]]*newscale;
|
||||||
|
monitorCanvasObj[monitorPtr[i]].height=monitorHeight[monitorPtr[i]]*monitorNormalizeScale[monitorPtr[i]]*monitorZoomScale[monitorPtr[i]]*newscale;
|
||||||
|
}
|
||||||
|
currentScale=newscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSpeed(val) // updates slider only
|
||||||
|
{
|
||||||
|
$('speedslideroutput').innerHTML = parseFloat(speeds[val]).toFixed(2).toString() + " x";
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSpeed(val) // Note parameter is the index not the speed
|
||||||
|
{
|
||||||
|
var t;
|
||||||
|
if(liveMode==1) return; // we shouldn't actually get here but just in case
|
||||||
|
currentSpeed=parseFloat(speeds[val]);
|
||||||
|
speedIndex=val;
|
||||||
|
playSecsperInterval = currentSpeed * currentDisplayInterval / 1000;
|
||||||
|
showSpeed(val);
|
||||||
|
if( timerInterval != currentDisplayInterval || currentSpeed == 0 ) timerFire(); // if the timer isn't firing we need to trigger it to update
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLive(value)
|
||||||
|
{
|
||||||
|
liveMode=value;
|
||||||
|
redrawScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
|
||||||
|
// The section below are to reload this program with new parameters
|
||||||
|
|
||||||
|
function clicknav(minSecs,maxSecs,arch,live) {// we use the current time if we can
|
||||||
|
var now = new Date() / 1000;
|
||||||
|
var minStr="";
|
||||||
|
var maxStr="";
|
||||||
|
var currentStr="";
|
||||||
|
if ( minSecs > 0 ) {
|
||||||
|
if(maxSecs > now)
|
||||||
|
maxSecs = parseInt(now);
|
||||||
|
maxStr="&maxTime=" + secs2dbstr(maxSecs);
|
||||||
|
}
|
||||||
|
if ( maxSecs > 0 )
|
||||||
|
minStr="&minTime=" + secs2dbstr(minSecs);
|
||||||
|
if ( maxSecs == 0 && minSecs == 0 ) {
|
||||||
|
minStr="&minTime=01/01/1950 12:00:00";
|
||||||
|
maxStr="&maxTime=12/31/2035 12:00:00";
|
||||||
|
}
|
||||||
|
var intervalStr="&displayinterval=" + currentDisplayInterval.toString();
|
||||||
|
if ( minSecs && maxSecs ) {
|
||||||
|
if ( currentTimeSecs > minSecs && currentTimeSecs < maxSecs ) // make sure time is in the new range
|
||||||
|
currentStr="¤t=" + secs2dbstr(currentTimeSecs);
|
||||||
|
}
|
||||||
|
|
||||||
|
var liveStr="&live=0";
|
||||||
|
if ( live == 1 )
|
||||||
|
liveStr="&live=1";
|
||||||
|
|
||||||
|
var fitStr="&fit=0";
|
||||||
|
if ( fitMode == 1 )
|
||||||
|
fitStr="&fit=1";
|
||||||
|
|
||||||
|
var zoomStr="";
|
||||||
|
for ( var i=0; i < numMonitors; i++ )
|
||||||
|
if ( monitorZoomScale[monitorPtr[i]] < 0.99 || monitorZoomScale[monitorPtr[i]] > 1.01 ) // allow for some up/down changes and just treat as 1 of almost 1
|
||||||
|
zoomStr += "&z" + monitorPtr[i].toString() + "=" + monitorZoomScale[monitorPtr[i]].toFixed(2);
|
||||||
|
|
||||||
|
var uri = "?view=" + currentView + fitStr + groupStr + minStr + maxStr + currentStr + intervalStr + liveStr + zoomStr + "&scale=" + document.getElementById("scaleslider").value + "&speed=" + speeds[$j("#speedslider").value];
|
||||||
|
window.location = uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lastHour() {
|
||||||
|
var now = new Date() / 1000;
|
||||||
|
clicknav(now - 3600 + 1, now,1,0);
|
||||||
|
}
|
||||||
|
function lastEight() {
|
||||||
|
var now = new Date() / 1000;
|
||||||
|
clicknav(now - 3600*8 + 1, now,1,0);
|
||||||
|
}
|
||||||
|
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,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,0);
|
||||||
|
}
|
||||||
|
function panleft() {
|
||||||
|
minTimeSecs = parseInt(minTimeSecs - rangeTimeSecs/2);
|
||||||
|
maxTimeSecs = minTimeSecs + rangeTimeSecs - 1;
|
||||||
|
clicknav(minTimeSecs,maxTimeSecs,1,0);
|
||||||
|
}
|
||||||
|
function panright() {
|
||||||
|
minTimeSecs = parseInt(minTimeSecs + rangeTimeSecs/2);
|
||||||
|
maxTimeSecs = minTimeSecs + rangeTimeSecs - 1;
|
||||||
|
clicknav(minTimeSecs,maxTimeSecs,1,0);
|
||||||
|
}
|
||||||
|
function allof() {
|
||||||
|
clicknav(0,0,1,0);
|
||||||
|
}
|
||||||
|
function allnon() {
|
||||||
|
clicknav(0,0,0,0);
|
||||||
|
}
|
||||||
|
/// >>>>>>>>>>>>>>>>> handles packing different size/aspect monitors on screen <<<<<<<<<<<<<<<<<<<<<<<<
|
||||||
|
|
||||||
|
function compSize(a, b) { // sort array by some size parameter - height seems to work best. A semi-greedy algorithm
|
||||||
|
var a_value = monitorHeight[a] * monitorWidth[a] * monitorNormalizeScale[a] * monitorZoomScale[a] * monitorNormalizeScale[a] * monitorZoomScale[a];
|
||||||
|
var b_value = monitorHeight[b] * monitorWidth[b] * monitorNormalizeScale[b] * monitorZoomScale[b] * monitorNormalizeScale[b] * monitorZoomScale[b];
|
||||||
|
|
||||||
|
if ( a_value > b_value ) return -1;
|
||||||
|
else if ( a_value == b_value ) return 0;
|
||||||
|
else return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function maxfit2(divW, divH) {
|
||||||
|
var bestFitX=[]; // how we arranged the so-far best match
|
||||||
|
var bestFitX2=[];
|
||||||
|
var bestFitY=[];
|
||||||
|
var bestFitY2=[];
|
||||||
|
var bestFitScale;
|
||||||
|
|
||||||
|
var minScale=0.05;
|
||||||
|
var maxScale=5.00;
|
||||||
|
var bestFitArea=0;
|
||||||
|
|
||||||
|
var borders=-1;
|
||||||
|
|
||||||
|
monitorPtr.sort(compSize);
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
if( maxScale - minScale < 0.01 ) break;
|
||||||
|
var thisScale = (maxScale + minScale) / 2;
|
||||||
|
var allFit=1;
|
||||||
|
var thisArea=0;
|
||||||
|
var thisX=[]; // top left
|
||||||
|
var thisY=[];
|
||||||
|
var thisX2=[]; // bottom right
|
||||||
|
var thisY2=[];
|
||||||
|
|
||||||
|
for ( var m = 0; m < numMonitors; m++ ) {
|
||||||
|
// this loop places each monitor (if it can)
|
||||||
|
var monId = monitorPtr[m];
|
||||||
|
|
||||||
|
function doesItFit(x,y,w,h,d) { // does block (w,h) fit at position (x,y) relative to edge and other nodes already done (0..d)
|
||||||
|
if(x+w>=divW) return 0;
|
||||||
|
if(y+h>=divH) return 0;
|
||||||
|
for(var i=0; i<=d; i++)
|
||||||
|
if( !( thisX[i]>x+w-1 || thisX2[i] < x || thisY[i] > y+h-1 || thisY2[i] < y ) ) return 0;
|
||||||
|
return 1; // it's OK
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( borders <= 0 )
|
||||||
|
borders=$("Monitor"+monId).getStyle("border").toInt() * 2; // assume fixed size border, and added to both sides and top/bottom
|
||||||
|
// try fitting over first, then down. Each new one must land at either upper right or lower left corner of last (try in that order)
|
||||||
|
// Pick the one with the smallest Y, then smallest X if Y equal
|
||||||
|
var fitX = 999999999;
|
||||||
|
var fitY = 999999999;
|
||||||
|
for ( adjacent = 0; adjacent < m; adjacent ++ ) {
|
||||||
|
// try top right of adjacent
|
||||||
|
if ( doesItFit(thisX2[adjacent]+1, thisY[adjacent], monitorWidth[monId] * thisScale * monitorNormalizeScale[monId] * monitorZoomScale[monId] + borders, monitorHeight[monId] * thisScale * monitorNormalizeScale[monId] * monitorZoomScale[monId] + borders, m-1) == 1 ) {
|
||||||
|
if ( thisY[adjacent]<fitY || ( thisY[adjacent] == fitY && thisX2[adjacent]+1 < fitX ) ) {
|
||||||
|
fitX = thisX2[adjacent] + 1;
|
||||||
|
fitY = thisY[adjacent];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// try bottom left
|
||||||
|
if ( doesItFit(thisX[adjacent], thisY2[adjacent]+1, monitorWidth[monId] * thisScale * monitorNormalizeScale[monId] * monitorZoomScale[monId] + borders, monitorHeight[monId] * thisScale * monitorNormalizeScale[monId] * monitorZoomScale[monId] + borders, m-1) == 1 ) {
|
||||||
|
if ( thisY2[adjacent]+1 < fitY || ( thisY2[adjacent]+1 == fitY && thisX[adjacent] < fitX ) ) {
|
||||||
|
fitX = thisX[adjacent];
|
||||||
|
fitY = thisY2[adjacent] + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( m == 0 ) { // note for the very first one there were no adjacents so the above loop didn't run
|
||||||
|
if ( doesItFit(0,0,monitorWidth[monId] * thisScale * monitorNormalizeScale[monId] * monitorZoomScale[monId] + borders, monitorHeight[monId] * thisScale * monitorNormalizeScale[monId] * monitorZoomScale[monId] + borders, -1) == 1 ) {
|
||||||
|
fitX = 0;
|
||||||
|
fitY = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( fitX == 999999999 ) {
|
||||||
|
allFit = 0;
|
||||||
|
break; // break out of monitor loop flagging we didn't fit
|
||||||
|
}
|
||||||
|
thisX[m] =fitX;
|
||||||
|
thisX2[m]=fitX + monitorWidth[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders;
|
||||||
|
thisY[m] =fitY;
|
||||||
|
thisY2[m]=fitY + monitorHeight[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders;
|
||||||
|
thisArea += (thisX2[m] - thisX[m])*(thisY2[m] - thisY[m]);
|
||||||
|
}
|
||||||
|
if ( allFit == 1 ) {
|
||||||
|
minScale=thisScale;
|
||||||
|
if(bestFitArea<thisArea) {
|
||||||
|
bestFitArea=thisArea;
|
||||||
|
bestFitX=thisX;
|
||||||
|
bestFitY=thisY;
|
||||||
|
bestFitX2=thisX2;
|
||||||
|
bestFitY2=thisY2;
|
||||||
|
bestFitScale=thisScale;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// didn't fit
|
||||||
|
maxScale=thisScale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( bestFitArea > 0 ) { // only rearrange if we could fit -- otherwise just do nothing, let them start coming out, whatever
|
||||||
|
for ( m = 0; m < numMonitors; m++ ) {
|
||||||
|
c = $("Monitor" + monitorPtr[m]);
|
||||||
|
c.style.position="absolute";
|
||||||
|
c.style.left=bestFitX[m].toString() + "px";
|
||||||
|
c.style.top=bestFitY[m].toString() + "px";
|
||||||
|
c.width = bestFitX2[m] - bestFitX[m] + 1 - borders;
|
||||||
|
c.height= bestFitY2[m] - bestFitY[m] + 1 - borders;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// >>>>>>>>>>>>>>>> Handles individual monitor clicks and navigation to the standard event/watch display
|
||||||
|
|
||||||
|
function showOneMonitor(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
|
||||||
|
var url;
|
||||||
|
if ( liveMode != 0 )
|
||||||
|
url="?view=watch&mid=" + monId.toString();
|
||||||
|
else
|
||||||
|
for ( var i=0, len=eId.length; i<len; i++ ) {
|
||||||
|
if ( eMonId[i] == monId && currentTimeSecs >= eStartSecs[i] && currentTimeSecs <= eEndSecs[i] )
|
||||||
|
url="?view=event&eid=" + eId[i] + '&fid=' + parseInt(Math.max(1, Math.min(eventFrames[i], eventFrames[i] * (currentTimeSecs - eStartSecs[i]) / (eEndSecs[i] - eStartSecs[i] + 1) ) ));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
createPopup(url, 'zmEvent', 'event', monitorWidth[eMonId[i]], monitorHeight[eMonId[i]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoom(monId,scale) {
|
||||||
|
var lastZoomMonPriorScale = monitorZoomScale[monId];
|
||||||
|
monitorZoomScale[monId] *= scale;
|
||||||
|
if ( redrawScreen() == 0 ) {// failure here is probably because we zoomed too far
|
||||||
|
monitorZoomScale[monId] = lastZoomMonPriorScale;
|
||||||
|
alert("You can't zoom that far -- rolling back");
|
||||||
|
redrawScreen(); // put things back and hope it works
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clickMonitor(event,monId) {
|
||||||
|
var monitor_element = $("Monitor"+monId.toString());
|
||||||
|
var pos_x = event.offsetX ? (event.offsetX) : event.pageX - monitor_element.offsetLeft;
|
||||||
|
var pos_y = event.offsetY ? (event.offsetY) : event.pageY - monitor_element.offsetTop;
|
||||||
|
if ( pos_x < monitor_element.width/4 && pos_y < monitor_element.height/4 )
|
||||||
|
zoom(monId,1.15);
|
||||||
|
else if ( pos_x > monitor_element.width * 3/4 && pos_y < monitor_element.height/4 )
|
||||||
|
zoom(monId,1/1.15);
|
||||||
|
else
|
||||||
|
showOneMonitor(monId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// >>>>>>>>> Initialization that runs on window load by being at the bottom
|
||||||
|
|
||||||
|
function initPage() {
|
||||||
|
canvas = $("timeline");
|
||||||
|
ctx = canvas.getContext('2d');
|
||||||
|
for ( var i = 0, len = monitorPtr.length; i < len; i += 1 ) {
|
||||||
|
var monId = monitorPtr[i];
|
||||||
|
if ( ! monId ) continue;
|
||||||
|
monitorCanvasObj[monId] = $('Monitor'+monId );
|
||||||
|
if ( ! monitorCanvasObj[monId] ) {
|
||||||
|
alert("Couldn't find DOM element for Monitor"+monId + "monitorPtr.length="+len);
|
||||||
|
} else {
|
||||||
|
monitorCanvasCtx[monId] = monitorCanvasObj[monId].getContext('2d');
|
||||||
|
var imageObject = monitorImageObject[monId] = new Image();
|
||||||
|
imageObject.monId = monId;
|
||||||
|
imageObject.onload = function() {imagedone(this, this.monId, true )};
|
||||||
|
imageObject.onerror = function() {imagedone(this, this.monId, false )};
|
||||||
|
loadImage2Monitor( monId, monitorImageURL[monId] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drawGraph();
|
||||||
|
setSpeed(speedIndex);
|
||||||
|
setFit(fitMode); // will redraw
|
||||||
|
setLive(liveMode); // will redraw
|
||||||
|
}
|
||||||
|
window.addEventListener("resize",redrawScreen);
|
||||||
|
// Kick everything off
|
||||||
|
window.addEvent( 'domready', initPage );
|
|
@ -0,0 +1,194 @@
|
||||||
|
|
||||||
|
var currentScale=<?php echo $defaultScale?>;
|
||||||
|
var liveMode=<?php echo $initialModeIsLive?>;
|
||||||
|
console.log("Live mode?"+liveMode);
|
||||||
|
var fitMode=<?php echo $fitMode?>;
|
||||||
|
var currentSpeed=<?php echo $speeds[$speedIndex]?>; // slider scale, which is only for replay and relative to real time
|
||||||
|
var speedIndex=<?php echo $speedIndex?>;
|
||||||
|
var currentDisplayInterval=<?php echo $initialDisplayInterval?>; // will be set based on performance, this is the display interval in milliseconds for history, and fps for live, and dynamically determined (in ms)
|
||||||
|
var playSecsperInterval=1; // How many seconds of recorded image we play per refresh determined by speed (replay rate) and display interval; (default=1 if coming from live)
|
||||||
|
var timerInterval; // milliseconds between interrupts
|
||||||
|
var timerObj; // object to hold timer interval;
|
||||||
|
var freeTimeLastIntervals=[]; // Percentage of current interval used in loading most recent image
|
||||||
|
var imageLoadTimesEvaluated=0; // running count
|
||||||
|
var imageLoadTimesNeeded=15; // and how many we need
|
||||||
|
var timeLabelsFractOfRow = 0.9;
|
||||||
|
var eMonId = [];
|
||||||
|
var eId = [];
|
||||||
|
var eStartSecs = [];
|
||||||
|
var eEndSecs = [];
|
||||||
|
var eventFrames = []; // this is going to presume all frames equal durationlength
|
||||||
|
var groupStr=<?php if($group=="") echo '""'; else echo "\"&group=$group\""; ?>;
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
|
||||||
|
// This builds the list of events that are eligible from this range
|
||||||
|
|
||||||
|
$index=0;
|
||||||
|
$anyAlarms=false;
|
||||||
|
|
||||||
|
foreach( dbFetchAll( $eventsSql ) as $event ) {
|
||||||
|
if ( $minTimeSecs > $event['StartTimeSecs'] ) $minTimeSecs = $event['StartTimeSecs'];
|
||||||
|
if ( $maxTimeSecs < $event['CalcEndTimeSecs'] ) $maxTimeSecs = $event['CalcEndTimeSecs'];
|
||||||
|
echo "
|
||||||
|
eMonId[$index]=" . $event['MonitorId'] . ";
|
||||||
|
eId[$index]=" . $event['Id'] . ";
|
||||||
|
eStartSecs[$index]=" . $event['StartTimeSecs'] . ";
|
||||||
|
eEndSecs[$index]=" . $event['CalcEndTimeSecs'] . ";
|
||||||
|
eventFrames[$index]=" . $event['Frames'] . ";
|
||||||
|
|
||||||
|
";
|
||||||
|
|
||||||
|
$index = $index + 1;
|
||||||
|
if ( $event['MaxScore'] > 0 )
|
||||||
|
$anyAlarms = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there is no data set the min/max to the passed in values
|
||||||
|
if ( $index == 0 ) {
|
||||||
|
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 = time() + 86400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, but consolidated down to (nearly) contiguous segments
|
||||||
|
// comparison in else governs how aggressively it consolidates
|
||||||
|
|
||||||
|
echo "var fMonId = [];\n";
|
||||||
|
echo "var fTimeFromSecs = [];\n";
|
||||||
|
echo "var fTimeToSecs = [];\n";
|
||||||
|
echo "var fScore = [];\n";
|
||||||
|
$maxScore=0;
|
||||||
|
$index=0;
|
||||||
|
$mId=-1;
|
||||||
|
$fromSecs=-1;
|
||||||
|
$toSecs=-1;
|
||||||
|
$maxScore=-1;
|
||||||
|
|
||||||
|
if ( $anyAlarms ) {
|
||||||
|
foreach( dbFetchAll ($frameSql) as $frame ) {
|
||||||
|
if ( $mId < 0 ) {
|
||||||
|
$mId = $frame['MonitorId'];
|
||||||
|
$fromSecs = $frame['TimeStampSecs'];
|
||||||
|
$toSecs = $frame['TimeStampSecs'];
|
||||||
|
$maxScore = $frame['Score'];
|
||||||
|
} else if ( $mId != $frame['MonitorId'] || $frame['TimeStampSecs'] - $toSecs > 10 ) {
|
||||||
|
// dump this one start a new
|
||||||
|
$index++;
|
||||||
|
echo "
|
||||||
|
fMonId[$index]= $mId;
|
||||||
|
fTimeFromSecs[$index]= $fromSecs;
|
||||||
|
fTimeToSecs[$index]= $toSecs;
|
||||||
|
fScore[$index]= $maxScore;
|
||||||
|
";
|
||||||
|
$mId = $frame['MonitorId'];
|
||||||
|
$fromSecs = $frame['TimeStampSecs'];
|
||||||
|
$toSecs = $frame['TimeStampSecs'];
|
||||||
|
$maxScore = $frame['Score'];
|
||||||
|
} else {
|
||||||
|
// just add this one on
|
||||||
|
$toSecs = $frame['TimeStampSecs'];
|
||||||
|
if ( $maxScore < $frame['Score'] ) $maxScore = $frame['Score'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( $mId > 0 ) {
|
||||||
|
echo "
|
||||||
|
fMonId[$index]= $mId;
|
||||||
|
fTimeFromSecs[$index]= $fromSecs;
|
||||||
|
fTimeToSecs[$index]= $toSecs;
|
||||||
|
fScore[$index]= $maxScore;
|
||||||
|
";
|
||||||
|
}
|
||||||
|
|
||||||
|
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 monitorImageURL = [];\n";
|
||||||
|
echo "var monitorLoadingStageURL = [];\n";
|
||||||
|
echo "var monitorLoadStartTimems = [];\n";
|
||||||
|
echo "var monitorLoadEndTimems = [];\n";
|
||||||
|
echo "var monitorColour = [];\n";
|
||||||
|
echo "var monitorWidth = [];\n";
|
||||||
|
echo "var monitorHeight = [];\n";
|
||||||
|
echo "var monitorIndex = [];\n";
|
||||||
|
echo "var monitorNormalizeScale = [];\n";
|
||||||
|
echo "var monitorZoomScale = [];\n";
|
||||||
|
echo "var monitorCanvasObj = [];\n"; // stash location of these here so we don't have to search
|
||||||
|
echo "var monitorCanvasCtx = [];\n";
|
||||||
|
echo "var monitorPtr = []; // monitorName[monitorPtr[0]] is first monitor\n";
|
||||||
|
|
||||||
|
|
||||||
|
$numMonitors=0; // this array is indexed by the monitor ID for faster access later, so it may be sparse
|
||||||
|
$avgArea=floatval(0); // Calculations the normalizing scale
|
||||||
|
|
||||||
|
foreach ( $monitors as $m ) {
|
||||||
|
$avgArea = $avgArea + floatval($m->Width() * $m->Height());
|
||||||
|
$numMonitors++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $numMonitors > 0 ) $avgArea = $avgArea / $numMonitors;
|
||||||
|
|
||||||
|
$numMonitors = 0;
|
||||||
|
foreach ( $monitors as $m ) {
|
||||||
|
echo " monitorLoading[" . $m->Id() . "]=false;\n";
|
||||||
|
echo " monitorImageURL[" . $m->Id() . "]='".$m->getStreamSrc( array('mode'=>'single','scale'=>$defaultScale*100), '&' )."';\n";
|
||||||
|
echo " monitorLoadingStageURL[" . $m->Id() . "] = '';\n";
|
||||||
|
echo " monitorColour[" . $m->Id() . "]=\"" . $m->WebColour() . "\";\n";
|
||||||
|
echo " monitorWidth[" . $m->Id() . "]=" . $m->Width() . ";\n";
|
||||||
|
echo " monitorHeight[" . $m->Id() . "]=" . $m->Height() . ";\n";
|
||||||
|
echo " monitorIndex[" . $m->Id() . "]=" . $numMonitors . ";\n";
|
||||||
|
echo " monitorName[" . $m->Id() . "]=\"" . $m->Name() . "\";\n";
|
||||||
|
echo " monitorLoadStartTimems[" . $m->Id() . "]=0;\n";
|
||||||
|
echo " monitorLoadEndTimems[" . $m->Id() . "]=0;\n";
|
||||||
|
echo " monitorNormalizeScale[" . $m->Id() . "]=" . sqrt($avgArea / ($m->Width() * $m->Height() )) . ";\n";
|
||||||
|
$zoomScale=1.0;
|
||||||
|
if(isset($_REQUEST[ 'z' . $m->Id() ]) )
|
||||||
|
$zoomScale = floatval( validHtmlStr($_REQUEST[ 'z' . $m->Id() ]) );
|
||||||
|
echo " monitorZoomScale[" . $m->Id() . "]=" . $zoomScale . ";\n";
|
||||||
|
echo " monitorPtr[" . $numMonitors . "]=" . $m->Id() . ";\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 + $maxTimeSecs)/2 . ";\n";
|
||||||
|
|
||||||
|
echo 'var speeds=[';
|
||||||
|
for ($i=0; $i<count($speeds); $i++)
|
||||||
|
echo (($i>0)?', ':'') . $speeds[$i];
|
||||||
|
echo "];\n";
|
||||||
|
?>
|
||||||
|
|
||||||
|
var scrubAsObject=$('scrub');
|
||||||
|
var cWidth; // save canvas width
|
||||||
|
var cHeight; // save canvas height
|
||||||
|
var canvas; // global canvas definition so we don't have to keep looking it up
|
||||||
|
var ctx;
|
||||||
|
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)
|
||||||
|
|
|
@ -95,20 +95,20 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
if ( !canView( 'Events' ) ) {
|
if ( !canView( 'Events' ) ) {
|
||||||
$view = 'error';
|
$view = 'error';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
require_once( 'includes/Monitor.php' );
|
require_once( 'includes/Monitor.php' );
|
||||||
|
|
||||||
# FIXME THere is no way to select group at this time.
|
# FIXME THere is no way to select group at this time.
|
||||||
if ( !empty($_REQUEST['group']) ) {
|
if ( !empty($_REQUEST['group']) ) {
|
||||||
$group = $_REQUEST['group'];
|
$group = $_REQUEST['group'];
|
||||||
$row = dbFetchOne( 'SELECT * FROM Groups WHERE Id = ?', NULL, array($_REQUEST['group']) );
|
$row = dbFetchOne( 'SELECT * FROM Groups WHERE Id = ?', NULL, array($_REQUEST['group']) );
|
||||||
$monitorsSql = "SELECT * FROM Monitors WHERE Function != 'None' AND find_in_set( Id, '".$row['MonitorIds']."' ) ";
|
$monitorsSql = "SELECT * FROM Monitors WHERE Function != 'None' AND find_in_set( Id, '".$row['MonitorIds']."' ) ";
|
||||||
} else {
|
} else {
|
||||||
$monitorsSql = "SELECT * FROM Monitors WHERE Function != 'None'";
|
$monitorsSql = "SELECT * FROM Monitors WHERE Function != 'None'";
|
||||||
$group = '';
|
$group = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that this finds incomplete events as well, and any frame records written, but still cannot "see" to the end frame
|
// Note that this finds incomplete events as well, and any frame records written, but still cannot "see" to the end frame
|
||||||
|
@ -222,7 +222,7 @@ $monitors = array();
|
||||||
$monitorsSql .= ' ORDER BY Sequence ASC';
|
$monitorsSql .= ' ORDER BY Sequence ASC';
|
||||||
$index=0;
|
$index=0;
|
||||||
foreach( dbFetchAll( $monitorsSql ) as $row ) {
|
foreach( dbFetchAll( $monitorsSql ) as $row ) {
|
||||||
$monitors[$index] = $row;
|
$monitors[$index] = new Monitor( $row );
|
||||||
$index = $index + 1;
|
$index = $index + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,921 +270,15 @@ input[type=range]::-ms-tooltip {
|
||||||
<span id="scrubright"></span>
|
<span id="scrubright"></span>
|
||||||
<span id="scruboutput"></span>
|
<span id="scruboutput"></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="monitors">
|
||||||
<?php
|
<?php
|
||||||
// Monitor images - these had to be loaded after the monitors used were determined (after loading events)
|
// Monitor images - these had to be loaded after the monitors used were determined (after loading events)
|
||||||
|
|
||||||
echo '<div id="monitors">';
|
|
||||||
foreach ($monitors as $m) {
|
foreach ($monitors as $m) {
|
||||||
echo '<canvas width="' . $m['Width'] * $defaultScale . 'px" height="' . $m['Height'] * $defaultScale . 'px" id="Monitor' . $m['Id'] . '" style="border:3px solid ' . $m['WebColour'] . '" onclick="clickMonitor(event,' . $m['Id'] . ')">No Canvas Support!!</canvas>';
|
echo '<canvas width="' . $m->Width() * $defaultScale . 'px" height="' . $m->Height() * $defaultScale . 'px" id="Monitor' . $m->Id() . '" style="border:3px solid ' . $m->WebColour() . '" onclick="clickMonitor(event,' . $m->Id() . ')">No Canvas Support!!</canvas>';
|
||||||
}
|
}
|
||||||
echo "</div>\n";
|
|
||||||
echo "<p id=\"fps\">evaluating fps</p>\n";
|
|
||||||
echo "<script type=\"text/javascript\">\n";
|
|
||||||
?>
|
?>
|
||||||
|
</div>
|
||||||
var currentScale=<?php echo $defaultScale?>;
|
<p id="fps">evaluating fps</p>
|
||||||
var liveMode=<?php echo $initialModeIsLive?>;
|
|
||||||
var fitMode=<?php echo $fitMode?>;
|
|
||||||
var currentSpeed=<?php echo $speeds[$speedIndex]?>; // slider scale, which is only for replay and relative to real time
|
|
||||||
var speedIndex=<?php echo $speedIndex?>;
|
|
||||||
var currentDisplayInterval=<?php echo $initialDisplayInterval?>; // will be set based on performance, this is the display interval in milliseconds for history, and fps for live, and dynamically determined (in ms)
|
|
||||||
var playSecsperInterval=1; // How many seconds of recorded image we play per refresh determined by speed (replay rate) and display interval; (default=1 if coming from live)
|
|
||||||
var timerInterval; // milliseconds between interrupts
|
|
||||||
var timerObj; // object to hold timer interval;
|
|
||||||
var freeTimeLastIntervals=[]; // Percentage of current interval used in loading most recent image
|
|
||||||
var imageLoadTimesEvaluated=0; // running count
|
|
||||||
var imageLoadTimesNeeded=15; // and how many we need
|
|
||||||
var timeLabelsFractOfRow = 0.9;
|
|
||||||
var eMonId = [];
|
|
||||||
var eId = [];
|
|
||||||
var eStartSecs = [];
|
|
||||||
var eEndSecs = [];
|
|
||||||
var ePath = [];
|
|
||||||
var eventFrames = []; // this is going to presume all frames equal durationlength
|
|
||||||
|
|
||||||
<?php
|
|
||||||
|
|
||||||
// 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");
|
|
||||||
|
|
||||||
// This builds the list of events that are eligible from this range
|
|
||||||
|
|
||||||
$index=0;
|
|
||||||
$anyAlarms=false;
|
|
||||||
|
|
||||||
foreach( dbFetchAll( $eventsSql ) as $event ) {
|
|
||||||
if( $minTimeSecs > $event['StartTimeSecs']) $minTimeSecs = $event['StartTimeSecs'];
|
|
||||||
if( $maxTimeSecs < $event['CalcEndTimeSecs']) $maxTimeSecs = $event['CalcEndTimeSecs'];
|
|
||||||
echo "eMonId[$index]=" . $event['MonitorId'] . ";
|
|
||||||
eId[$index]=" . $event['Id'] . ";
|
|
||||||
eStartSecs[$index]=" . $event['StartTimeSecs'] . ";
|
|
||||||
eEndSecs[$index]=" . $event['CalcEndTimeSecs'] . ";
|
|
||||||
eventFrames[$index]=" . $event['Frames'] . "; ";
|
|
||||||
|
|
||||||
// NO GOOD, need to use view=image
|
|
||||||
if ( ZM_USE_DEEP_STORAGE )
|
|
||||||
echo "ePath[$index] = \"events/" . $event['MonitorId'] . "/" . strftime("%y/%m/%d/%H/%M/%S", $event['StartTimeSecs']) . "/\";" ;
|
|
||||||
else
|
|
||||||
echo "ePath[$index] = \"events/" . $event['MonitorId'] . "/" . $event['Id'] . "/\";" ;
|
|
||||||
$index = $index + 1;
|
|
||||||
if($event['MaxScore']>0)
|
|
||||||
$anyAlarms = true;
|
|
||||||
echo "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there is no data set the min/max to the passed in values
|
|
||||||
if($index == 0) {
|
|
||||||
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, but consolidated down to (nearly) contiguous segments
|
|
||||||
// comparison in else governs how aggressively it consolidates
|
|
||||||
|
|
||||||
echo "var fMonId = [];\n";
|
|
||||||
echo "var fTimeFromSecs = [];\n";
|
|
||||||
echo "var fTimeToSecs = [];\n";
|
|
||||||
echo "var fScore = [];\n";
|
|
||||||
$maxScore=0;
|
|
||||||
$index=0;
|
|
||||||
$mId=-1;
|
|
||||||
$fromSecs=-1;
|
|
||||||
$toSecs=-1;
|
|
||||||
$maxScore=-1;
|
|
||||||
|
|
||||||
if($anyAlarms) {
|
|
||||||
foreach( dbFetchAll ($frameSql) as $frame ) {
|
|
||||||
if($mId<0) {
|
|
||||||
$mId=$frame['MonitorId'];
|
|
||||||
$fromSecs=$frame['TimeStampSecs'];
|
|
||||||
$toSecs=$frame['TimeStampSecs'];
|
|
||||||
$maxScore=$frame['Score'];
|
|
||||||
} else if ($mId != $frame['MonitorId'] || $frame['TimeStampSecs'] - $toSecs > 10) {
|
|
||||||
// dump this one start a new
|
|
||||||
$index++;
|
|
||||||
echo " fMonId[$index]=" . $mId . ";";
|
|
||||||
echo " fTimeFromSecs[$index]=" . $fromSecs . ";";
|
|
||||||
echo " fTimeToSecs[$index]=" . $toSecs . ";";
|
|
||||||
echo " fScore[$index]=" . $maxScore . ";\n";
|
|
||||||
$mId=$frame['MonitorId'];
|
|
||||||
$fromSecs=$frame['TimeStampSecs'];
|
|
||||||
$toSecs=$frame['TimeStampSecs'];
|
|
||||||
$maxScore=$frame['Score'];
|
|
||||||
} else {
|
|
||||||
// just add this one on
|
|
||||||
$toSecs=$frame['TimeStampSecs'];
|
|
||||||
if($maxScore < $frame['Score']) $maxScore=$frame['Score'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if($mId>0) {
|
|
||||||
echo " fMonId[$index]=" . $mId . ";";
|
|
||||||
echo " fTimeFromSecs[$index]=" . $fromSecs . ";";
|
|
||||||
echo " fTimeToSecs[$index]=" . $toSecs . ";";
|
|
||||||
echo " fScore[$index]=" . $maxScore . ";\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 monitorLoadStartTimems = [];\n";
|
|
||||||
echo "var monitorLoadEndTimems = [];\n";
|
|
||||||
echo "var monitorColour = [];\n";
|
|
||||||
echo "var monitorWidth = [];\n";
|
|
||||||
echo "var monitorHeight = [];\n";
|
|
||||||
echo "var monitorIndex = [];\n";
|
|
||||||
echo "var monitorNormalizeScale = [];\n";
|
|
||||||
echo "var monitorZoomScale = [];\n";
|
|
||||||
echo "var monitorCanvasObj = [];\n"; // stash location of these here so we don't have to search
|
|
||||||
echo "var monitorCanvasCtx = [];\n";
|
|
||||||
echo "var monitorPtr = []; // monitorName[monitorPtr[0]] is first monitor\n";
|
|
||||||
|
|
||||||
|
|
||||||
$numMonitors=0; // this array is indexed by the monitor ID for faster access later, so it may be sparse
|
|
||||||
$avgArea=floatval(0); // Calculations the normalizing scale
|
|
||||||
|
|
||||||
foreach ($monitors as $m) {
|
|
||||||
$avgArea = $avgArea + floatval($m['Width'] * $m['Height']);
|
|
||||||
$numMonitors++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if($numMonitors>0) $avgArea= $avgArea / $numMonitors;
|
|
||||||
|
|
||||||
$numMonitors=0;
|
|
||||||
foreach ($monitors as $m) {
|
|
||||||
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 " monitorLoadStartTimems[" . $m['Id'] . "]=0; ";
|
|
||||||
echo " monitorLoadEndTimems[" . $m['Id'] . "]=0; ";
|
|
||||||
echo " monitorCanvasObj[" . $m['Id'] . "]=document.getElementById('Monitor" . $m['Id'] . "'); ";
|
|
||||||
echo " monitorCanvasCtx[" . $m['Id'] . "]=monitorCanvasObj[" . $m['Id'] . "].getContext('2d'); ";
|
|
||||||
echo " monitorNormalizeScale[" . $m['Id'] . "]=" . sqrt($avgArea / ($m['Width'] * $m['Height'] )) . "; ";
|
|
||||||
$zoomScale=1.0;
|
|
||||||
if(isset($_REQUEST[ 'z' . $m['Id'] ]) )
|
|
||||||
$zoomScale = floatval( validHtmlStr($_REQUEST[ 'z' . $m['Id'] ]) );
|
|
||||||
echo " monitorZoomScale[" . $m['Id'] . "]=" . $zoomScale . ";";
|
|
||||||
echo " monitorPtr[" . $numMonitors . "]=" . $m['Id'] . ";\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 + $maxTimeSecs)/2 . ";\n";
|
|
||||||
|
|
||||||
echo "var speeds=[";
|
|
||||||
for ($i=0; $i<count($speeds); $i++)
|
|
||||||
echo (($i>0)?", ":"") . $speeds[$i];
|
|
||||||
echo "];\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 evaluateLoadTimes() {
|
|
||||||
// Only consider it a completed event if we load ALL monitors, then zero all and start again
|
|
||||||
var start=0;
|
|
||||||
var end=0;
|
|
||||||
if(liveMode!=1 && currentSpeed==0) return; // don't evaluate when we are not moving as we can do nothing really fast.
|
|
||||||
for(var i=0; i<monitorIndex.length; i++) {
|
|
||||||
if( monitorName[i]>"") {
|
|
||||||
if( monitorLoadEndTimems[i]==0) return; // if we have a monitor with no time yet just wait
|
|
||||||
if( start == 0 || start > monitorLoadStartTimems[i] ) start = monitorLoadStartTimems[i];
|
|
||||||
if( end == 0 || end < monitorLoadEndTimems[i] ) end = monitorLoadEndTimems[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(start==0 || end==0) return; // we really should not get here
|
|
||||||
for(var i=0; i<numMonitors; i++) {
|
|
||||||
monitorLoadStartTimems[monitorPtr[i]]=0;
|
|
||||||
monitorLoadEndTimems[monitorPtr[i]]=0;
|
|
||||||
}
|
|
||||||
freeTimeLastIntervals[imageLoadTimesEvaluated++] = 1 - ((end - start)/currentDisplayInterval);
|
|
||||||
if( imageLoadTimesEvaluated < imageLoadTimesNeeded ) return;
|
|
||||||
var avgFrac=0;
|
|
||||||
for(var i=0; i<imageLoadTimesEvaluated; i++)
|
|
||||||
avgFrac += freeTimeLastIntervals[i];
|
|
||||||
avgFrac = avgFrac / imageLoadTimesEvaluated;
|
|
||||||
// The larger this is(positive) the faster we can go
|
|
||||||
if (avgFrac >= 0.9) currentDisplayInterval = (currentDisplayInterval * 0.50).toFixed(1); // we can go much faster
|
|
||||||
else if (avgFrac >= 0.8) currentDisplayInterval = (currentDisplayInterval * 0.55).toFixed(1);
|
|
||||||
else if (avgFrac >= 0.7) currentDisplayInterval = (currentDisplayInterval * 0.60).toFixed(1);
|
|
||||||
else if (avgFrac >= 0.6) currentDisplayInterval = (currentDisplayInterval * 0.65).toFixed(1);
|
|
||||||
else if (avgFrac >= 0.5) currentDisplayInterval = (currentDisplayInterval * 0.70).toFixed(1);
|
|
||||||
else if (avgFrac >= 0.4) currentDisplayInterval = (currentDisplayInterval * 0.80).toFixed(1);
|
|
||||||
else if (avgFrac >= 0.35) currentDisplayInterval = (currentDisplayInterval * 0.90).toFixed(1);
|
|
||||||
else if (avgFrac >= 0.3) currentDisplayInterval = (currentDisplayInterval * 1.00).toFixed(1);
|
|
||||||
else if (avgFrac >= 0.25) currentDisplayInterval = (currentDisplayInterval * 1.20).toFixed(1);
|
|
||||||
else if (avgFrac >= 0.2) currentDisplayInterval = (currentDisplayInterval * 1.50).toFixed(1);
|
|
||||||
else if (avgFrac >= 0.1) currentDisplayInterval = (currentDisplayInterval * 2.00).toFixed(1);
|
|
||||||
else currentDisplayInterval = (currentDisplayInterval * 2.50).toFixed(1);
|
|
||||||
currentDisplayInterval=Math.min(Math.max(currentDisplayInterval, 30),10000); // limit this from about 30fps to .1 fps
|
|
||||||
imageLoadTimesEvaluated=0;
|
|
||||||
setSpeed(speedIndex);
|
|
||||||
$('fps').innerHTML="Display refresh rate is " + (1000 / currentDisplayInterval).toFixed(1) + " per second, avgFrac=" + avgFrac.toFixed(3) + ".";
|
|
||||||
}
|
|
||||||
|
|
||||||
function SetImageSource(monId,val) {
|
|
||||||
if(liveMode==1) {
|
|
||||||
// This uses the standard php routine to set up the url and authentication, but because it is called repeatedly the built in random number is not usable, so one is appended below for two total (yuck)
|
|
||||||
var effectiveScale = (100.0 * monitorCanvasObj[monId].width) / monitorWidth[monId];
|
|
||||||
var $x = "<?php echo getStreamSrc( array("mode=single"),"&" )?>" + "&monitor=" + monId.toString() + "&scale=" + effectiveScale + Math.random().toString() ;
|
|
||||||
return $x;
|
|
||||||
} else {
|
|
||||||
var zeropad = <?php echo sprintf("\"%0" . ZM_EVENT_IMAGE_DIGITS . "d\"",0); ?>;
|
|
||||||
for(var i=0, eIdlength = eId.length; i<eIdlength; i++) {
|
|
||||||
// Search for a match
|
|
||||||
if(eMonId[i]==monId && val >= eStartSecs[i] && val <= eEndSecs[i]) {
|
|
||||||
var frame=parseInt((val - eStartSecs[i])/(eEndSecs[i]-eStartSecs[i])*eventFrames[i])+1;
|
|
||||||
img = "index.php?view=image&eid=" + eId[i] + '&fid='+frame + "&width=" + monitorCanvasObj[monId].width + "&height=" + monitorCanvasObj[monId].height;
|
|
||||||
return img;
|
|
||||||
}
|
|
||||||
} // end for
|
|
||||||
return "no data";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function imagedone(obj, monId, success) {
|
|
||||||
if(success) {
|
|
||||||
monitorCanvasCtx[monId].drawImage( monitorImageObject[monId], 0, 0, monitorCanvasObj[monId].width, monitorCanvasObj[monId].height);
|
|
||||||
var iconSize=(Math.max(monitorCanvasObj[monId].width,monitorCanvasObj[monId].height) * 0.10);
|
|
||||||
monitorCanvasCtx[monId].font = "600 " + iconSize.toString() + "px Arial";
|
|
||||||
monitorCanvasCtx[monId].fillStyle="white";
|
|
||||||
monitorCanvasCtx[monId].globalCompositeOperation="difference";
|
|
||||||
monitorCanvasCtx[monId].fillText("+",iconSize*0.2, iconSize*1.2);
|
|
||||||
monitorCanvasCtx[monId].fillText("-",monitorCanvasObj[monId].width - iconSize*1.2, iconSize*1.2);
|
|
||||||
monitorCanvasCtx[monId].globalCompositeOperation="source-over";
|
|
||||||
monitorLoadEndTimems[monId] = new Date().getTime(); // elapsed time to load
|
|
||||||
evaluateLoadTimes();
|
|
||||||
}
|
|
||||||
monitorLoading[monId]=false;
|
|
||||||
if(!success) {
|
|
||||||
// if we had a failrue queue up the no-data image
|
|
||||||
loadImage2Monitor(monId,"no data"); // leave the staged URL if there is one, just ignore it here.
|
|
||||||
} else {
|
|
||||||
if(monitorLoadingStageURL[monId]=="") return;
|
|
||||||
loadImage2Monitor(monId,monitorLoadingStageURL[monId]);
|
|
||||||
monitorLoadingStageURL[monId]="";
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadImage2Monitor(monId,url) {
|
|
||||||
if(monitorLoading[monId] && monitorImageObject[monId].src != url ) {
|
|
||||||
// never queue the same image twice (if it's loading it has to be defined, right?
|
|
||||||
monitorLoadingStageURL[monId]=url; // we don't care if we are overriting, it means it didn't change fast enough
|
|
||||||
} else {
|
|
||||||
var skipthis=0;
|
|
||||||
if( typeof monitorImageObject[monId] !== "undefined" && monitorImageObject[monId] != null && monitorImageObject[monId].src == url ) return; // do nothing if it's the same
|
|
||||||
if( monitorImageObject[monId] == null ) {
|
|
||||||
monitorImageObject[monId]=new Image();
|
|
||||||
monitorImageObject[monId].onload = function() {imagedone(this, monId,true )};
|
|
||||||
monitorImageObject[monId].onerror = function() {imagedone(this, monId,false)};
|
|
||||||
}
|
|
||||||
if(url=='no data') {
|
|
||||||
monitorCanvasCtx[monId].fillStyle="white";
|
|
||||||
monitorCanvasCtx[monId].fillRect(0,0,monitorCanvasObj[monId].width,monitorCanvasObj[monId].height);
|
|
||||||
var textSize=monitorCanvasObj[monId].width * 0.15;
|
|
||||||
var text="No Data";
|
|
||||||
monitorCanvasCtx[monId].font = "600 " + textSize.toString() + "px Arial";
|
|
||||||
monitorCanvasCtx[monId].fillStyle="black";
|
|
||||||
var textWidth = monitorCanvasCtx[monId].measureText(text).width;
|
|
||||||
monitorCanvasCtx[monId].fillText(text,monitorCanvasObj[monId].width/2 - textWidth/2,monitorCanvasObj[monId].height/2);
|
|
||||||
} else {
|
|
||||||
monitorLoading[monId]=true;
|
|
||||||
monitorLoadStartTimems[monId]=new Date().getTime();
|
|
||||||
monitorImageObject[monId].src=url; // starts a load but doesn't refresh yet, wait until ready
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function timerFire() {
|
|
||||||
// See if we need to reschedule
|
|
||||||
if(currentDisplayInterval != timerInterval || currentSpeed == 0) {
|
|
||||||
// zero just turn off interrupts
|
|
||||||
clearInterval(timerObj);
|
|
||||||
timerInterval=currentDisplayInterval;
|
|
||||||
if(currentSpeed>0 || liveMode!=0) timerObj=setInterval(timerFire,timerInterval); // don't fire out of live mode if speed is zero
|
|
||||||
}
|
|
||||||
|
|
||||||
if (liveMode) outputUpdate(currentTimeSecs); // In live mode we basically do nothing but redisplay
|
|
||||||
else if (currentTimeSecs + playSecsperInterval >= maxTimeSecs) // beyond the end just stop
|
|
||||||
{
|
|
||||||
setSpeed(0);
|
|
||||||
outputUpdate(currentTimeSecs);
|
|
||||||
}
|
|
||||||
else outputUpdate(currentTimeSecs + playSecsperInterval);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawSliderOnGraph(val) {
|
|
||||||
var sliderWidth=10;
|
|
||||||
var sliderLineWidth=1;
|
|
||||||
var sliderHeight=cHeight;
|
|
||||||
|
|
||||||
if(liveMode==1) {
|
|
||||||
val=Math.floor( Date.now() / 1000);
|
|
||||||
}
|
|
||||||
// Set some sizes
|
|
||||||
|
|
||||||
var labelpx = Math.max( 6, Math.min( 20, parseInt(cHeight * timeLabelsFractOfRow / (numMonitors+1)) ) );
|
|
||||||
var labbottom=parseInt(cHeight * 0.2 / (numMonitors+1)).toString() + "px"; // This is positioning same as row labels below, but from bottom so 1-position
|
|
||||||
var 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;
|
|
||||||
}
|
|
||||||
if(liveMode==0) // we get rid of the slider if we switch to live (since it may not be in the "right" place)
|
|
||||||
{
|
|
||||||
// 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 = $('scruboutput');
|
|
||||||
if(liveMode==1)
|
|
||||||
{
|
|
||||||
o.innerHTML="Live Feed @ " + (1000 / currentDisplayInterval).toFixed(1) + " fps";
|
|
||||||
o.style.color="red";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
o.innerHTML=secs2dbstr(val);
|
|
||||||
o.style.color="blue";
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
var x;
|
|
||||||
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 = $('scrubleft');
|
|
||||||
o.innerHTML=secs2dbstr(minTimeSecs);
|
|
||||||
o.style.position="absolute";
|
|
||||||
o.style.bottom=labbottom;
|
|
||||||
o.style.font=labfont;
|
|
||||||
o.style.left="5px";
|
|
||||||
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 + 10 > sliderX || cWidth < len * 4 ) // that last check is for very narrow browsers
|
|
||||||
o.style.display="none";
|
|
||||||
else
|
|
||||||
{
|
|
||||||
o.style.display="inline";
|
|
||||||
o.style.display="inline-flex"; // safari won't take this but will just ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
var o = $('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 - 15).toString() + "px";
|
|
||||||
if(sliderX > cWidth - len - 20 || cWidth < len * 4 )
|
|
||||||
o.style.display="none";
|
|
||||||
else
|
|
||||||
{
|
|
||||||
o.style.display="inline";
|
|
||||||
o.style.display="inline-flex";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawGraph()
|
|
||||||
{
|
|
||||||
var divWidth=$('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(eId.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<eId.length; i++) // Display all we loaded
|
|
||||||
{
|
|
||||||
var x1=parseInt( (eStartSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth) ; // round low end down
|
|
||||||
var x2=parseInt( (eEndSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ) ; // round high end up to be sure consecutive ones connect
|
|
||||||
ctx.fillStyle=monitorColour[eMonId[i]];
|
|
||||||
ctx.globalAlpha = 0.2; // light color for background
|
|
||||||
ctx.clearRect(x1,monitorIndex[eMonId[i]]*rowHeight,x2-x1,rowHeight); // Erase any overlap so it doesn't look artificially darker
|
|
||||||
ctx.fillRect (x1,monitorIndex[eMonId[i]]*rowHeight,x2-x1,rowHeight);
|
|
||||||
}
|
|
||||||
for(var i=0; (i<fScore.length) && (maxScore>0); i++) // Now put in scored frames (if any)
|
|
||||||
{
|
|
||||||
var x1=parseInt( (fTimeFromSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth) ; // round low end down
|
|
||||||
var x2=parseInt( (fTimeToSecs[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[fMonId[i]];
|
|
||||||
ctx.globalAlpha = 0.4 + 0.6 * (1 - fScore[i]/maxScore); // Background is scaled but even lowest is twice as dark as the background
|
|
||||||
ctx.fillRect(x1,monitorIndex[fMonId[i]]*rowHeight,x2-x1,rowHeight);
|
|
||||||
}
|
|
||||||
for(var i=0; i<numMonitors; i++) // Note that this may be a sparse array
|
|
||||||
{
|
|
||||||
ctx.font= parseInt(rowHeight * timeLabelsFractOfRow).toString() + "px Georgia";
|
|
||||||
ctx.fillStyle="Black";
|
|
||||||
ctx.globalAlpha=1;
|
|
||||||
ctx.fillText(monitorName[monitorPtr[i]], 0, (i + 1 - (1 - timeLabelsFractOfRow)/2 ) * rowHeight ); // This should roughly center font in row
|
|
||||||
}
|
|
||||||
underSlider=undefined; // flag we don't have a slider cached
|
|
||||||
drawSliderOnGraph(currentTimeSecs);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function redrawScreen()
|
|
||||||
{
|
|
||||||
if(fitMode==0) // if we fit, then monitors were absolutely positioned already (or will be) otherwise release them to float
|
|
||||||
{
|
|
||||||
for(var i=0; i<numMonitors; i++)
|
|
||||||
monitorCanvasObj[monitorPtr[i]].style.position="";
|
|
||||||
$('monitors').setStyle('height',"auto");
|
|
||||||
}
|
|
||||||
if(liveMode==1) // if we are not in live view switch to history -- this has to come before fit in case we re-establish the timeline
|
|
||||||
{
|
|
||||||
$('SpeedDiv').style.display="none";
|
|
||||||
$('timelinediv').style.display="none";
|
|
||||||
$('live').innerHTML="History";
|
|
||||||
$('zoomin').style.display="none";
|
|
||||||
$('zoomout').style.display="none";
|
|
||||||
$('panleft').style.display="none";
|
|
||||||
$('panright').style.display="none";
|
|
||||||
|
|
||||||
}
|
|
||||||
else // switch out of liveview mode
|
|
||||||
{
|
|
||||||
$('SpeedDiv').style.display="inline";
|
|
||||||
$('SpeedDiv').style.display="inline-flex";
|
|
||||||
$('timelinediv').style.display=null;
|
|
||||||
$('live').innerHTML="Live";
|
|
||||||
$('zoomin').style.display="inline";
|
|
||||||
$('zoomin').style.display="inline-flex";
|
|
||||||
$('zoomout').style.display="inline";
|
|
||||||
$('zoomout').style.display="inline-flex";
|
|
||||||
$('panleft').style.display="inline";
|
|
||||||
$('panleft').style.display="inline-flex";
|
|
||||||
$('panright').style.display="inline";
|
|
||||||
$('panright').style.display="inline-flex";
|
|
||||||
}
|
|
||||||
|
|
||||||
if(fitMode==1)
|
|
||||||
{
|
|
||||||
$('ScaleDiv').style.display="none";
|
|
||||||
$('fit').innerHTML="Scale";
|
|
||||||
var vh=window.innerHeight;
|
|
||||||
var vw=window.innerWidth;
|
|
||||||
var pos=$('monitors').getPosition();
|
|
||||||
var mh=(vh - pos.y - $('fps').getSize().y);
|
|
||||||
$('monitors').setStyle('height',mh.toString() + "px"); // leave a small gap at bottom
|
|
||||||
if(maxfit2($('monitors').getSize().x,$('monitors').getSize().y) == 0) /// if we fail to fix we back out of fit mode -- ??? This may need some better handling
|
|
||||||
fitMode=1-fitMode;
|
|
||||||
}
|
|
||||||
else // switch out of fit mode
|
|
||||||
{
|
|
||||||
$('ScaleDiv').style.display="inline";
|
|
||||||
$('ScaleDiv').style.display="inline-flex";
|
|
||||||
$('fit').innerHTML="Fit";
|
|
||||||
setScale(currentScale);
|
|
||||||
}
|
|
||||||
drawGraph();
|
|
||||||
outputUpdate(currentTimeSecs);
|
|
||||||
timerFire(); // force a fire in case it's not timing
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function outputUpdate(val)
|
|
||||||
{
|
|
||||||
drawSliderOnGraph(val);
|
|
||||||
for(var i=0; i<numMonitors; i++)
|
|
||||||
{
|
|
||||||
loadImage2Monitor(monitorPtr[i],SetImageSource(monitorPtr[i],val));
|
|
||||||
}
|
|
||||||
var currentTimeMS = new Date(val*1000);
|
|
||||||
currentTimeSecs=val;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setFit(value)
|
|
||||||
{
|
|
||||||
fitMode=value;
|
|
||||||
redrawScreen();
|
|
||||||
}
|
|
||||||
|
|
||||||
function showScale(newscale) // updates slider only
|
|
||||||
{
|
|
||||||
$('scaleslideroutput').innerHTML = parseFloat(newscale).toFixed(2).toString() + " x";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setScale(newscale) // makes actual change
|
|
||||||
{
|
|
||||||
showScale(newscale);
|
|
||||||
for(var i=0; i<numMonitors; i++)
|
|
||||||
{
|
|
||||||
monitorCanvasObj[monitorPtr[i]].width=monitorWidth[monitorPtr[i]]*monitorNormalizeScale[monitorPtr[i]]*monitorZoomScale[monitorPtr[i]]*newscale;
|
|
||||||
monitorCanvasObj[monitorPtr[i]].height=monitorHeight[monitorPtr[i]]*monitorNormalizeScale[monitorPtr[i]]*monitorZoomScale[monitorPtr[i]]*newscale;
|
|
||||||
}
|
|
||||||
currentScale=newscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showSpeed(val) // updates slider only
|
|
||||||
{
|
|
||||||
$('speedslideroutput').innerHTML = parseFloat(speeds[val]).toFixed(2).toString() + " x";
|
|
||||||
}
|
|
||||||
|
|
||||||
function setSpeed(val) // Note parameter is the index not the speed
|
|
||||||
{
|
|
||||||
var t;
|
|
||||||
if(liveMode==1) return; // we shouldn't actually get here but just in case
|
|
||||||
currentSpeed=parseFloat(speeds[val]);
|
|
||||||
speedIndex=val;
|
|
||||||
playSecsperInterval = currentSpeed * currentDisplayInterval / 1000;
|
|
||||||
showSpeed(val);
|
|
||||||
if( timerInterval != currentDisplayInterval || currentSpeed == 0 ) timerFire(); // if the timer isn't firing we need to trigger it to update
|
|
||||||
}
|
|
||||||
|
|
||||||
function setLive(value)
|
|
||||||
{
|
|
||||||
liveMode=value;
|
|
||||||
redrawScreen();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
|
|
||||||
// The section below are to reload this program with new parameters
|
|
||||||
|
|
||||||
function clicknav(minSecs,maxSecs,arch,live) // we use the current time if we can
|
|
||||||
{
|
|
||||||
var now = new Date() / 1000;
|
|
||||||
var minStr="";
|
|
||||||
var maxStr="";
|
|
||||||
var currentStr="";
|
|
||||||
if(minSecs>0)
|
|
||||||
{
|
|
||||||
if(maxSecs > now)
|
|
||||||
maxSecs = parseInt(now);
|
|
||||||
maxStr="&maxTime=" + secs2dbstr(maxSecs);
|
|
||||||
}
|
|
||||||
if(maxSecs>0)
|
|
||||||
minStr="&minTime=" + secs2dbstr(minSecs);
|
|
||||||
if(maxSecs==0 && minSecs==0)
|
|
||||||
{
|
|
||||||
minStr="&minTime=01/01/1950 12:00:00";
|
|
||||||
maxStr="&maxTime=12/31/2035 12:00:00";
|
|
||||||
}
|
|
||||||
var intervalStr="&displayinterval=" + currentDisplayInterval.toString();
|
|
||||||
if(minSecs && maxSecs)
|
|
||||||
{
|
|
||||||
if(currentTimeSecs > minSecs && currentTimeSecs < maxSecs) // make sure time is in the new range
|
|
||||||
currentStr="¤t=" + secs2dbstr(currentTimeSecs);
|
|
||||||
}
|
|
||||||
|
|
||||||
var liveStr="&live=0";
|
|
||||||
if(live==1)
|
|
||||||
liveStr="&live=1";
|
|
||||||
|
|
||||||
var fitStr="&fit=0";
|
|
||||||
if(fitMode==1)
|
|
||||||
fitStr="&fit=1";
|
|
||||||
|
|
||||||
var zoomStr="";
|
|
||||||
for(var i=0; i<numMonitors; i++)
|
|
||||||
if(monitorZoomScale[monitorPtr[i]] < 0.99 || monitorZoomScale[monitorPtr[i]] > 1.01) // allow for some up/down changes and just treat as 1 of almost 1
|
|
||||||
zoomStr += "&z" + monitorPtr[i].toString() + "=" + monitorZoomScale[monitorPtr[i]].toFixed(2);
|
|
||||||
|
|
||||||
var groupStr=<?php if($group=="") echo '""'; else echo "\"&group=$group\""; ?>;
|
|
||||||
var uri = "?view=" + currentView + fitStr + groupStr + minStr + maxStr + currentStr + intervalStr + liveStr + zoomStr + "&scale=" + document.getElementById("scaleslider").value + "&speed=" + speeds[document.getElementById("speedslider").value];
|
|
||||||
window.location=uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
function lastHour()
|
|
||||||
{
|
|
||||||
var now = new Date() / 1000;
|
|
||||||
clicknav(now - 3600 + 1, now,1,0);
|
|
||||||
}
|
|
||||||
function lastEight()
|
|
||||||
{
|
|
||||||
var now = new Date() / 1000;
|
|
||||||
clicknav(now - 3600*8 + 1, now,1,0);
|
|
||||||
}
|
|
||||||
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,0);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,0);
|
|
||||||
}
|
|
||||||
function panleft()
|
|
||||||
{
|
|
||||||
minTimeSecs = parseInt(minTimeSecs - rangeTimeSecs/2);
|
|
||||||
maxTimeSecs = minTimeSecs + rangeTimeSecs - 1;
|
|
||||||
clicknav(minTimeSecs,maxTimeSecs,1,0);
|
|
||||||
}
|
|
||||||
function panright()
|
|
||||||
{
|
|
||||||
minTimeSecs = parseInt(minTimeSecs + rangeTimeSecs/2);
|
|
||||||
maxTimeSecs = minTimeSecs + rangeTimeSecs - 1;
|
|
||||||
clicknav(minTimeSecs,maxTimeSecs,1,0);
|
|
||||||
}
|
|
||||||
function allof()
|
|
||||||
{
|
|
||||||
clicknav(0,0,1,0);
|
|
||||||
}
|
|
||||||
function allnon()
|
|
||||||
{
|
|
||||||
clicknav(0,0,0,0);
|
|
||||||
}
|
|
||||||
/// >>>>>>>>>>>>>>>>> handles packing different size/aspect monitors on screen <<<<<<<<<<<<<<<<<<<<<<<<
|
|
||||||
|
|
||||||
function compSize(a, b) // sort array by some size parameter - height seems to work best. A semi-greedy algorithm
|
|
||||||
{
|
|
||||||
if ( monitorHeight[a] * monitorWidth[a] * monitorNormalizeScale[a] * monitorZoomScale[a] * monitorNormalizeScale[a] * monitorZoomScale[a] > monitorHeight[b] * monitorWidth[b] * monitorNormalizeScale[b] * monitorZoomScale[b] * monitorNormalizeScale[b] * monitorZoomScale[b]) return -1;
|
|
||||||
else if ( monitorHeight[a] * monitorWidth[a] * monitorNormalizeScale[a] * monitorZoomScale[a] * monitorNormalizeScale[a] * monitorZoomScale[a] == monitorHeight[b] * monitorWidth[b] * monitorNormalizeScale[b] * monitorZoomScale[b] * monitorNormalizeScale[b] * monitorZoomScale[b]) return 0;
|
|
||||||
else return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function maxfit2(divW, divH)
|
|
||||||
{
|
|
||||||
var bestFitX=[]; // how we arranged the so-far best match
|
|
||||||
var bestFitX2=[];
|
|
||||||
var bestFitY=[];
|
|
||||||
var bestFitY2=[];
|
|
||||||
var bestFitScale;
|
|
||||||
|
|
||||||
var minScale=0.05;
|
|
||||||
var maxScale=5.00;
|
|
||||||
var bestFitArea=0;
|
|
||||||
|
|
||||||
var borders=-1;
|
|
||||||
|
|
||||||
monitorPtr.sort(compSize);
|
|
||||||
|
|
||||||
while(1)
|
|
||||||
{
|
|
||||||
if( maxScale - minScale < 0.01 ) break;
|
|
||||||
var thisScale = (maxScale + minScale) / 2;
|
|
||||||
var allFit=1;
|
|
||||||
var thisArea=0;
|
|
||||||
var thisX=[]; // top left
|
|
||||||
var thisY=[];
|
|
||||||
var thisX2=[]; // bottom right
|
|
||||||
var thisY2=[];
|
|
||||||
|
|
||||||
for(var m=0; m<numMonitors; m++)
|
|
||||||
{
|
|
||||||
// this loop places each monitor (if it can)
|
|
||||||
|
|
||||||
function doesItFit(x,y,w,h,d)
|
|
||||||
{ // does block (w,h) fit at position (x,y) relative to edge and other nodes already done (0..d)
|
|
||||||
if(x+w>=divW) return 0;
|
|
||||||
if(y+h>=divH) return 0;
|
|
||||||
for(var i=0; i<=d; i++)
|
|
||||||
if( !( thisX[i]>x+w-1 || thisX2[i] < x || thisY[i] > y+h-1 || thisY2[i] < y ) ) return 0;
|
|
||||||
return 1; // it's OK
|
|
||||||
}
|
|
||||||
|
|
||||||
if(borders<=0) borders=$("Monitor"+monitorPtr[m]).getStyle("border").toInt() * 2; // assume fixed size border, and added to both sides and top/bottom
|
|
||||||
// try fitting over first, then down. Each new one must land at either upper right or lower left corner of last (try in that order)
|
|
||||||
// Pick the one with the smallest Y, then smallest X if Y equal
|
|
||||||
var fitX = 999999999;
|
|
||||||
var fitY = 999999999;
|
|
||||||
for( adjacent=0; adjacent<m; adjacent++)
|
|
||||||
{
|
|
||||||
// try top right of adjacent
|
|
||||||
if( doesItFit(thisX2[adjacent]+1, thisY[adjacent], monitorWidth[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders, monitorHeight[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders, m-1) == 1 )
|
|
||||||
{
|
|
||||||
if(thisY[adjacent]<fitY || ( thisY[adjacent]==fitY && thisX2[adjacent]+1 < fitX ))
|
|
||||||
{
|
|
||||||
fitX=thisX2[adjacent]+1;
|
|
||||||
fitY=thisY[adjacent];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// try bottom left
|
|
||||||
if ( doesItFit(thisX[adjacent], thisY2[adjacent]+1, monitorWidth[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders, monitorHeight[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders, m-1) == 1 )
|
|
||||||
{
|
|
||||||
if(thisY2[adjacent]+1<fitY || ( thisY2[adjacent]+1 == fitY && thisX[adjacent]<fitX ))
|
|
||||||
{
|
|
||||||
fitX=thisX[adjacent];
|
|
||||||
fitY=thisY2[adjacent]+1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(m==0) // note for teh very first one there were no adjacents so the above loop didn't run
|
|
||||||
{
|
|
||||||
if( doesItFit(0,0,monitorWidth[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders, monitorHeight[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders, -1) == 1 )
|
|
||||||
{
|
|
||||||
fitX=0;
|
|
||||||
fitY=0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(fitX==999999999)
|
|
||||||
{
|
|
||||||
allFit=0;
|
|
||||||
break; // break out of monitor loop flagging we didn't fit
|
|
||||||
}
|
|
||||||
thisX[m] =fitX;
|
|
||||||
thisX2[m]=fitX + monitorWidth[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders;
|
|
||||||
thisY[m] =fitY;
|
|
||||||
thisY2[m]=fitY + monitorHeight[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders;
|
|
||||||
thisArea += (thisX2[m] - thisX[m])*(thisY2[m] - thisY[m]);
|
|
||||||
}
|
|
||||||
if(allFit==1)
|
|
||||||
{
|
|
||||||
minScale=thisScale;
|
|
||||||
if(bestFitArea<thisArea)
|
|
||||||
{
|
|
||||||
bestFitArea=thisArea;
|
|
||||||
bestFitX=thisX;
|
|
||||||
bestFitY=thisY;
|
|
||||||
bestFitX2=thisX2;
|
|
||||||
bestFitY2=thisY2;
|
|
||||||
bestFitScale=thisScale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else // didn't fit
|
|
||||||
{
|
|
||||||
maxScale=thisScale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(bestFitArea>0) // only rearrange if we could fit -- otherwise just do nothing, let them start coming out, whatever
|
|
||||||
{
|
|
||||||
for(m=0; m<numMonitors; m++)
|
|
||||||
{
|
|
||||||
c = $("Monitor" + monitorPtr[m]);
|
|
||||||
c.style.position="absolute";
|
|
||||||
c.style.left=bestFitX[m].toString() + "px";
|
|
||||||
c.style.top=bestFitY[m].toString() + "px";
|
|
||||||
c.width = bestFitX2[m] - bestFitX[m] + 1 - borders;
|
|
||||||
c.height= bestFitY2[m] - bestFitY[m] + 1 - borders;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// >>>>>>>>>>>>>>>> Handles individual monitor clicks and navigation to the standard event/watch display
|
|
||||||
|
|
||||||
function showOneMonitor(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
|
|
||||||
var url;
|
|
||||||
if(liveMode!=0) url="?view=watch&mid=" + monId.toString();
|
|
||||||
else
|
|
||||||
for(var i=0; i<eId.length; i++)
|
|
||||||
if(eMonId[i]==monId && currentTimeSecs >= eStartSecs[i] && currentTimeSecs <= eEndSecs[i])
|
|
||||||
url="?view=event&eid=" + eId[i] + '&fid=' + parseInt(Math.max(1, Math.min(eventFrames[i], eventFrames[i] * (currentTimeSecs - eStartSecs[i]) / (eEndSecs[i] - eStartSecs[i] + 1) ) ));
|
|
||||||
createPopup(url, 'zmEvent', 'event', monitorWidth[eMonId[i]], monitorHeight[eMonId[i]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function zoom(monId,scale)
|
|
||||||
{
|
|
||||||
var lastZoomMonPriorScale=monitorZoomScale[monId];
|
|
||||||
monitorZoomScale[monId] *= scale;
|
|
||||||
if(redrawScreen()==0) // failure here is probably because we zoomed too far
|
|
||||||
{
|
|
||||||
monitorZoomScale[monId]=lastZoomMonPriorScale;
|
|
||||||
alert("You can't zoom that far -- rolling back");
|
|
||||||
redrawScreen(); // put things back and hope it works
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function clickMonitor(event,monId)
|
|
||||||
{
|
|
||||||
var pos_x = event.offsetX ? (event.offsetX) : event.pageX - $("Monitor"+monId.toString()).offsetLeft;
|
|
||||||
var pos_y = event.offsetY ? (event.offsetY) : event.pageY - $("Monitor"+monId.toString()).offsetTop;
|
|
||||||
if(pos_x < $("Monitor"+monId.toString()).width/4 && pos_y < $("Monitor"+monId.toString()).height/4) zoom(monId,1.15);
|
|
||||||
else if(pos_x > $("Monitor"+monId.toString()).width * 3/4 && pos_y < $("Monitor"+monId.toString()).height/4) zoom(monId,1/1.15);
|
|
||||||
else showOneMonitor(monId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// >>>>>>>>> Initialization that runs on window load by being at the bottom
|
|
||||||
|
|
||||||
drawGraph();
|
|
||||||
setSpeed(speedIndex);
|
|
||||||
setFit(fitMode); // will redraw
|
|
||||||
setLive(liveMode); // will redraw
|
|
||||||
window.addEventListener("resize",redrawScreen);
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
Loading…
Reference in New Issue