'
});
// Loop over each option
for (key in args) {
var name = key,
type = args[key].type,
label = args[key].label,
values = args[key].values,
defaultValue = args[key]['default'];
// Update default value
updateActiveArgument(container, name, defaultValue);
// Filter out from UI some pre-defined args
if(EXCLUDE_ARGS.hasOwnProperty(key)) {
continue;
}
// Build widget if needed
if(values.length > 1) {
htmlBuffer.push(WidgetFactory[type](name, label, values, defaultValue));
}
}
// Add control panel to UI
htmlBuffer.sort();
$('
', {
html: '
' + htmlBuffer.join('
') + '
'
}).appendTo($('.parameters', controlContainer));
controlContainer.appendTo(container);
// Attache listeners
initializeListeners(container);
}
// ----------------------------------------------------------------------
function attachTouchListener(container) {
var current_button = null, posX, posY, defaultDragButton = 1,
isZooming = false, isDragging = false, mouseAction = 'up', target;
function mobileTouchInteraction(evt) {
evt.gesture.preventDefault();
switch(evt.type) {
case 'drag':
if(isZooming) {
return;
}
current_button = defaultDragButton;
if(mouseAction === 'up') {
mouseAction = "down";
target = evt.gesture.target;
isDragging = true;
} else {
mouseAction = "move";
}
posX = evt.gesture.touches[0].pageX;
posY = evt.gesture.touches[0].pageY;
break;
case 'hold':
if(defaultDragButton === 1) {
defaultDragButton = 2;
//container.html("Pan mode").css('color','#FFFFFF');
} else {
defaultDragButton = 1;
//container.html("Rotation mode").css('color','#FFFFFF');
}
break;
case 'release':
//container.html('');
current_button = 0;
mouseAction = "up";
isZooming = false;
isDragging = false;
break;
case 'doubletap':
container.trigger('resetCamera');
return;
case 'pinch':
if(isDragging) {
return;
}
current_button = 3;
if(mouseAction === 'up') {
mouseAction = 'down';
posX = 0;
posY = container.height();
target = evt.gesture.target;
isZooming = true;
} else {
mouseAction = 'move';
posY = container.height() * (1+(evt.gesture.scale-1)/2);
}
break;
}
// Trigger event
container.trigger({
type: 'mouse',
action: mouseAction,
current_button: current_button,
charCode: '',
altKey: false,
ctrlKey: false,
shiftKey: false,
metaKey: false,
delegateTarget: target,
pageX: posX,
pageY: posY
});
}
// Bind listener to UI container
container.hammer({
prevent_default : true,
prevent_mouseevents : true,
transform : true,
transform_always_block : true,
transform_min_scale : 0.03,
transform_min_rotation : 2,
drag : true,
drag_max_touches : 1,
drag_min_distance : 10,
swipe : false,
hold : true // To switch from rotation to pan
}).on("doubletap pinch drag release hold", mobileTouchInteraction);
}
// ------------------------------------------------------------------------
function createZoomableCanvasObject(container, img, canvas, pixelZoomRatio) {
// First set up some variables we will need
var modeRotation = 1, // when dragging, it's a rotation
modePan = 2, // when dragging, it's a pan
modeZoom = 3, // when dragging, it's a zoom
modeNone = 0, // No mouse move handling
mouseMode = modeNone, // Current mode
dzScale = 0.005, // scaling factor to control how fast we zoom in and out
wheelZoom = 0.05, // amount to change zoom with each wheel event
drawingCenter = [0,0], // Drawing parameters
zoomLevel = 1.0, //
maxZoom = pixelZoomRatio, // limit how far we can zoom in
minZoom = 1 / maxZoom, // limit how far we can zoom out
lastLocation = [0,0], // Last place mouse event happened
// Rotation management vars
thetaValues, phiValues, stepPhi, stepTheta, currentArgs;
/*
* Adds mouse event handlers so that we can pan and zoom the image
*/
function setupEvents() {
var element = canvas;
// Needed this to override context menu behavior
element.bind('contextmenu', function(evt) { evt.preventDefault(); });
// Wheel should zoom across browsers
element.bind('DOMMouseScroll mousewheel', function (evt) {
var x = (-evt.originalEvent.wheelDeltaY || evt.originalEvent.detail);
lastLocation = getRelativeLocation(canvas, evt);
handleZoom((x > 0 ? wheelZoom : x < 0 ? -wheelZoom : 0));
evt.preventDefault();
// Redraw the image in the canvas
redrawImage();
});
// Handle mobile
attachTouchListener(element);
element.bind('mouse', function(e){
// action: mouseAction,
// current_button: current_button,
// charCode: '',
// altKey: false,
// ctrlKey: false,
// shiftKey: false,
// metaKey: false,
// delegateTarget: target,
// pageX: posX,
// pageY: posY
var action = e.action,
altKey = e.altKey,
shiftKey = e.shiftKey,
ctrlKey = e.ctrlKey,
x = e.pageX,
y = e.pageY,
current_button = e.current_button;
if(action === 'down') {
if (e.altKey) {
current_button = 2;
e.altKey = false;
} else if (e.shiftKey) {
current_button = 3;
e.shiftKey = false;
}
// Detect interaction mode
switch(current_button) {
case 2: // middle mouse down = pan
mouseMode = modePan;
break;
case 3: // right mouse down = zoom
mouseMode = modeZoom;
break;
default:
mouseMode = modeRotation;
break;
}
// Store mouse location
lastLocation = [x, y];
e.preventDefault();
} else if(action === 'up') {
mouseMode = modeNone;
e.preventDefault();
} else if(action === 'move') {
if(mouseMode != modeNone) {
var loc = [x,y];
// Can NOT use switch as (modeRotation == modePan) is
// possible when Pan should take over rotation as
// rotation is not possible
if(mouseMode === modePan) {
handlePan(loc);
} else if (mouseMode === modeZoom) {
var deltaY = loc[1] - lastLocation[1];
handleZoom(deltaY * dzScale);
// Update mouse location
lastLocation = loc;
} else {
handleRotation(loc);
}
// Redraw the image in the canvas
redrawImage();
}
}
});
// Zoom and pan events with mouse buttons and drag
element.bind('mousedown', function(evt) {
var current_button = evt.which;
// alt+click simulates center button, shift+click simulates right
if (evt.altKey) {
current_button = 2;
evt.altKey = false;
} else if (evt.shiftKey) {
current_button = 3;
evt.shiftKey = false;
}
// Detect interaction mode
switch(current_button) {
case 2: // middle mouse down = pan
mouseMode = modePan;
break;
case 3: // right mouse down = zoom
mouseMode = modeZoom;
break;
default:
mouseMode = modeRotation;
break;
}
// Store mouse location
lastLocation = getRelativeLocation(canvas, evt);
evt.preventDefault();
});
// Send mouse movement event to the forwarding function
element.bind('mousemove', function(e) {
if(mouseMode != modeNone) {
var loc = getRelativeLocation(canvas, e);
// Can NOT use switch as (modeRotation == modePan) is
// possible when Pan should take over rotation as
// rotation is not possible
if(mouseMode === modePan) {
handlePan(loc);
} else if (mouseMode === modeZoom) {
var deltaY = loc[1] - lastLocation[1];
handleZoom(deltaY * dzScale);
// Update mouse location
lastLocation = loc;
} else {
handleRotation(loc);
}
// Redraw the image in the canvas
redrawImage();
}
});
// Stop any zoom or pan events
element.bind('mouseup', function(evt) {
mouseMode = modeNone;
evt.preventDefault();
});
// Update rotation handler if possible
modeRotation = container.data('info').arguments.hasOwnProperty('phi') ? modeRotation : modePan;
if(modeRotation != modePan) {
thetaValues = container.data('info').arguments.theta.values;
phiValues = container.data('info').arguments.phi.values;
stepPhi = phiValues[1] - phiValues[0];
stepTheta = thetaValues[1] - thetaValues[0];
currentArgs = container.data('active-args');
}
}
/*
* If the data can rotate
*/
function handleRotation(loc) {
var currentPhi = currentArgs.phi,
currentTheta = currentArgs.theta,
currentPhiIdx = phiValues.indexOf(currentPhi),
currentThetaIdx = thetaValues.indexOf(currentTheta)
deltaPhi = (loc[0] - lastLocation[0]),
deltaTheta = (loc[1] - lastLocation[1]),
changeDetected = false;
if(Math.abs(deltaPhi) > stepPhi) {
changeDetected = true;
currentPhiIdx += (deltaPhi > 0) ? 1 : -1;
if(currentPhiIdx >= phiValues.length) {
currentPhiIdx -= phiValues.length;
} else if(currentPhiIdx < 0) {
currentPhiIdx += phiValues.length;
}
currentArgs['phi'] = phiValues[currentPhiIdx];
}
if(Math.abs(deltaTheta) > stepTheta) {
currentThetaIdx += (deltaTheta > 0) ? 1 : -1;
if(currentThetaIdx >= thetaValues.length) {
currentThetaIdx = thetaValues.length - 1;
} else if(currentThetaIdx < 0) {
currentThetaIdx = 0;
}
if(currentArgs['theta'] !== thetaValues[currentThetaIdx]) {
currentArgs['theta'] = thetaValues[currentThetaIdx];
changeDetected = true;
}
}
if(changeDetected) {
fireLoadImage(container);
container.trigger('invalidate-viewport');
// Update mouse location
lastLocation = loc;
}
}
/*
* Does the actual image panning. Panning should not mess with the
* source width or source height, those are fixed by the current zoom
* level. Panning should only update the source origin (the x and y
* coordinates of the upper left corner of the source rectangle).
*/
function handlePan(loc) {
// Update the source rectangle origin, but afterwards, check to
// make sure we're not trying to look outside the image bounds.
drawingCenter[0] += (loc[0] - lastLocation[0]);
drawingCenter[1] += (loc[1] - lastLocation[1]);
// Update mouse location
lastLocation = loc;
}
/*
* Does the actual image zooming. Zooming first sets what the source width
* and height should be based on the zoom level, then adjusts the source
* origin to try and maintain the source center point. However, zooming
* must also not try to view outside the image bounds, so the center point
* may be changed as a result of this.
*/
function handleZoom(inOutAmount) {
var beforeZoom = zoomLevel,
afterZoom = beforeZoom + inOutAmount;
// Disallow zoomLevel outside allowable range
if (afterZoom < minZoom) {
afterZoom = minZoom;
} else if (afterZoom > maxZoom) {
afterZoom = maxZoom;
}
if(beforeZoom != afterZoom) {
zoomLevel = afterZoom;
// FIXME ----------------------------------------------------------------
// zoom by keeping location of "lastLocation" in the same screen position
// FIXME ----------------------------------------------------------------
}
}
/*
* Convenience function to draw the image. As a reminder, we always fill
* the entire viewport. Also, we always use the source origin and source
* dimensions that we have calculated and maintain internally.
*/
function redrawImage() {
var ctx = canvas[0].getContext("2d"),
w = container.width(),
h = container.height(),
iw = img[0].naturalWidth,
ih = img[0].naturalHeight;
if(iw === 0) {
setTimeout(redrawImage, 100);
} else {
canvas.attr("width", w);
canvas.attr("height", h);
ctx.clearRect(0, 0, w, h);
var tw = Math.floor(iw*zoomLevel),
th = Math.floor(ih*zoomLevel),
tx = drawingCenter[0] - (tw/2),
ty = drawingCenter[1] - (th/2),
dx = (tw > w) ? (tw - w) : (w - tw),
dy = (th > h) ? (th - h) : (h - th),
centerBounds = [ (w-dx)/2 , (h-dy)/2, (w+dx)/2, (h+dy)/2 ];
if( drawingCenter[0] < centerBounds[0] || drawingCenter[0] > centerBounds[2]
|| drawingCenter[1] < centerBounds[1] || drawingCenter[1] > centerBounds[3] ) {
drawingCenter[0] = Math.min( Math.max(drawingCenter[0], centerBounds[0]), centerBounds[2] );
drawingCenter[1] = Math.min( Math.max(drawingCenter[1], centerBounds[1]), centerBounds[3] );
tx = drawingCenter[0] - (tw/2);
ty = drawingCenter[1] - (th/2);
}
ctx.drawImage(img[0],
0, 0, iw, ih, // Source image [Location,Size]
tx, ty, tw, th); // Traget drawing [Location,Size]
}
}
/*
* Make sure the image will fit inside container as ZoomOut
*/
function resetCamera() {
var w = container.width(),
h = container.height(),
iw = img[0].naturalWidth,
ih = img[0].naturalHeight;
if(iw === 0) {
setTimeout(resetCamera, 100);
} else {
zoomLevel = minZoom = Math.min( w / iw, h / ih );
drawingCenter[0] = w/2;
drawingCenter[1] = h/2;
redrawImage();
}
}
// Now do some initialization
setupEvents();
resetCamera();
// Just expose a couple of methods that need to be called from outside
return {
'resetCamera': resetCamera,
'imageLoaded': redrawImage
};
}
// ------------------------------------------------------------------------
function createImageViewer(container, func) {
var imageContainer = $('', { class: 'image-viewer' }),
imageCanvas = $('', { class: 'image-canvas' }),
currentFileToRender = null;
imageContainer.appendTo(imageCanvas);
imageCanvas.appendTo(container);
// Add zoom manager
var manipMgr = createZoomableCanvasObject(container, imageContainer, imageCanvas, 10);
container.bind('invalidate-size', function() {
manipMgr.resetCamera();
});
imageContainer.bind('onload load', function(){
manipMgr.imageLoaded();
container.trigger('image-render');
});
container.bind('image-loaded', function(event){
if(currentFileToRender === null || event.url.indexOf(currentFileToRender) != -1) {
imageContainer.attr('src', event.url);
}
});
container.bind('load-image', function(event){
currentFileToRender = event.filename;
});
return imageCanvas;
}
// ========================================================================
// JQuery
// ========================================================================
/**
* jQuery catalyst view constructor.
*
* @member jQuery.vtkCatalystViewer
* @param basePath
* Root directory for data to visualize
*/
$.fn.vtkCatalystViewer = function(dataBasePath, preload) {
return this.each(function() {
var me = $(this).empty().addClass('vtk-catalyst-viewer small'); //.unbind();
// Get meta-data
$.ajax({
url: dataBasePath + '/info.json',
dataType: 'json',
success: function( data ) {
// Store metadata
me.data('info', data);
me.data('active-args', {});
me.data('base-path', dataBasePath);
me.data('preload', (preload ? true : false));
// Create download manager
createDownloadManager(me, 5, dataBasePath);
// Create Control UI
createControlPanel(me, data.arguments);
// Create interactive viewer
createImageViewer(me);
// Load default image
fireLoadImage(me);
},
error: function(error) {
console.log("error");
console.log(error);
}
});
});
}
}(jQuery, window));