(function ($, GLOBAL) { var SELECT_OPTION = '', TEMPLATE_CANVAS = '', TEMPLATE_CONTENT = '
    PIPELINE
Background
  • Time
    0
  • Phi
    0
  • Theta
    0
  • Compute pixel coverage
', PIPELINE_ENTRY = '
  • LABELCONTROL
  • ', DIRECTORY_CONTROL = '', TEMPLATE_SELECTOR = '
    TITLE
    ', TEMPLATE_LAYER_CHECK = '
  • NAME
  • ', // Extract worker url scripts = document.getElementsByTagName('script'), scriptPath = scripts[scripts.length - 1].src.split('/'), workerURL = '/CatalystBrowser/vtkweb-composite-worker.js', NB_RESULT_PER_PAGE = 8; // Compute worker path scriptPath.pop(); workerURL = scriptPath.join('/') + workerURL; // ======================================================================== // Helper method // ======================================================================== function getRelativeLocation(element, mouseEvent) { var parentOffset = element.offset(), x = mouseEvent.pageX || mouseEvent.originalEvent.pageX || mouseEvent.originalEvent.mozMovementX, y = mouseEvent.pageY || mouseEvent.originalEvent.pageY || mouseEvent.originalEvent.mozMovementY, relX = x - parentOffset.left, relY = y - parentOffset.top; return [ relX, relY ]; } // ------------------------------------------------------------------------ function createSearchManager(container, data, basepath) { var layerVisibility = {}, layer_fields = data.metadata.layer_fields, fields = data.metadata.fields, pipeline = data.metadata.pipeline, args = data.arguments, idList = [], dataList = [], result = [], layerOlderInvalid = true, filePattern = data.name_pattern.replace(/{filename}/g, 'query.json'), timeList = args.hasOwnProperty('time') ? args.time.values : ["0"], phiList = args.hasOwnProperty('phi') ? args.phi.values : ["0"], thetaList = args.hasOwnProperty('theta') ? args.theta.values : ["0"], timeCount = timeList.length, phiCount = phiList.length, thetaCount = thetaList.length, workerList = [], roundRobinWorkerId = 0, formulaSTR = "", layerToLabel = {}, nbImages = 1, renderManager = null, layerSeriesData = {}, palette = new Rickshaw.Color.Palette(); // Compute number of images for(var key in layer_fields) { nbImages += layer_fields[key].length; } renderManager = createCompositeManager(container, basepath, data, nbImages); // Compute layer to label mapping function updateLayerToLabel(item) { if(item.type === 'layer') { layerToLabel[item.ids[0]] = item.name; layerSeriesData[item.ids[0]] = []; } else { for(var idx in item.children) { updateLayerToLabel(item.children[idx]); } } } for(var idx in pipeline) { updateLayerToLabel(pipeline[idx]); } // Create workers var nbWorker = 5; while(nbWorker--) { var w = new Worker(workerURL); w.onmessage = processResults; workerList.push(w) } // ------------------------------ function processResults(event) { result.push(event.data); var nb = $('.result-founds', container); nb.html( Number(nb.html()) + 1); if(result.length === idList.length) { updateGraph(); working(false); } } // ------------------------------ function updateWorkers(query) { var wQuery = '_' + query, count = workerList.length; while(count--) { workerList[count].postMessage(wQuery); } } // ------------------------------ function triggerWork() { var count = workerList.length; while(count--) { workerList[count].postMessage('w'); } } // ------------------------------ function sendData(id, fields, orderCount) { workerList[roundRobinWorkerId].postMessage('d' + id + '|' + JSON.stringify(fields) + '|' + JSON.stringify(orderCount)); roundRobinWorkerId = (roundRobinWorkerId + 1) % workerList.length; } // ------------------------------ function sendNumberOfPrixel(number) { var sNumber = 's' + number, count = workerList.length; while(count--) { workerList[count].postMessage(sNumber); } } // ------------------------------ function fetchData(url, fields, count) { $.ajax({ url: url, dataType: 'json', success: function( data ) { sendData(url, fields, data.counts); if(count == 0) { sendNumberOfPrixel(data.dimensions[0] * data.dimensions[1]); } // Fetch next one processDataList(); }, error: function(error) { console.log("error when trying to download " + url); console.log(error); } }); } // ------------------------------ var processIdx = 0, progressBar = $('.progress > div', container); $('.progress', container).show(); function processDataList() { if(processIdx < idList.length) { fetchData(idList[processIdx], dataList[processIdx], processIdx); processIdx++; progressBar.css('width', Math.floor(95*processIdx / idList.length) + '%'); // Update page number and possible results $('.result-count', container).html("Found VALUE results.".replace(/VALUE/g, idList.length)); $('.result-page-number', container).html(Math.floor(idList.length / NB_RESULT_PER_PAGE) + 1); } else { $('.compute-coverage', container).show(); progressBar.parent().hide(); // Update page number and possible results $('.result-count', container).html("Found VALUE results.".replace(/VALUE/g, idList.length)); $('.result-page-number', container).html(Math.floor(idList.length / NB_RESULT_PER_PAGE) + 1); } } // ------------------------------ // Generate image list while(timeCount--) { thetaCount = thetaList.length; var baseURL = basepath + '/' + filePattern.replace(/{time}/g, timeList[timeCount]); while(thetaCount--) { phiCount = phiList.length; var currentURL = baseURL.replace(/{theta}/g, thetaList[thetaCount]); while(phiCount--) { var url = currentURL.replace(/{phi}/g, phiList[phiCount]); dataList.push({ time: timeList[timeCount], phi: phiList[phiCount], theta: thetaList[thetaCount]}); idList.push(url); if(idList.length === 1) { processDataList(); } } } } // ------------------------------ function search() { result = []; working(true); $('.result-founds', container).html('0'); triggerWork(); } // ------------------------------ function updateGraph() { var count = result.length, series = []; // Update serie data for(var layer in layerSeriesData) { layerSeriesData[layer] = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] } while(count--) { var entry = result[count].count; for(var layer in entry) { if(layer !== '+') { var percent = Math.ceil(entry[layer]); layerSeriesData[layer][percent]++; } } } // Find max with no coverage var max = 0; for(var layer in layerSeriesData) { count = 101; while(count-- && layerSeriesData[layer][count] === 0) { // Just wait to find the right index } if(max < count) { max = count; } } max += 1; for(var layer in layerSeriesData) { var name = '(' + layer + ') ' + layerToLabel[layer], data = [], color = palette.color(), hasData = false; for(var x = 1; x < max; ++x) { data.push({x:x, y:layerSeriesData[layer][x]}); if(layerSeriesData[layer][x] > 0) { hasData = true; } } if(hasData) { series.push({ name:name, data:data, color:color }); } } // Create brand new graph var options = { 'renderer': 'area', // Type of chart [line, area, bar, scatterplot] 'stacked' : true, 'series': series, 'axes': [ "bottom", "left", "top"], // Draw axis on border with scale 'chart-padding': [0, 100, 0, 0], // Graph padding [top, right, bottom, left] in px. Useful to save space for legend }; $('.chart-container', container).empty().vtkChart(options); } function applyQuery(userQuery) { $('.toggle-stats', container).removeClass('stats'); // Generate function str // Extract forumla formulaSTR = "var time = Number(obj.fields.time), phi = Number(obj.fields.phi), theta = Number(obj.fields.theta);\n"; for(var layer in layerVisibility) { if(layerVisibility[layer]) { formulaSTR += layer + ' = obj.count.' + layer + ';\n'; } } var sortQuery = $('.sortby-expression', container).val(), sortFunctionSTR = "function extractValue(obj) {" + formulaSTR + "return " + sortQuery + ";}; return extractValue(a) - extractValue(b);"; formulaSTR += "return true"; if(userQuery.trim().length > 0) { formulaSTR += " && " + userQuery + ';'; } else { formulaSTR += ';'; } // Compile formula and run it on results var func = new Function("obj", formulaSTR), count = result.length, found = 0, finalResults = [] sortFunc = new Function(["a","b"], sortFunctionSTR); $('.result-count', container).html('Found 0 result.'); while(count--) { if(func(result[count])) { found++; finalResults.push(result[count]); } } if(sortQuery.length > 0) { finalResults.sort(sortFunc); } $('.result-count', container).html("Found VALUE results.".replace(/VALUE/g, found)); var resultContainer = $('.composite-search-results', container).empty(); count = finalResults.length, nbPages = Math.floor(count / NB_RESULT_PER_PAGE), pages = [], resultNumber = 0; if(count % NB_RESULT_PER_PAGE) { ++nbPages; } $('.result-page-number', container).html(nbPages); for(var idx = 0; idx < nbPages; ++idx) { var page = $('
    ', { "class": 'result-page', "data-page": idx }); page.appendTo(resultContainer); pages.push(page); } pages[0].addClass('active'); while(count--) { addResultToUI(finalResults[count], pages[Math.floor(resultNumber++ / NB_RESULT_PER_PAGE)]); } // Add render callback // $('.composite-result', container).click(function(){ // var me = $(this), fields = me.attr('data-fields').split(':'), // time = fields[0], phi = fields[1], theta = fields[2], // imgURL = drawResult(time, phi, theta); // $('img', me).attr('src', imgURL); // }); $('.composite-result', container).dblclick(function(){ var me = $(this), fields = me.attr('data-fields').split(':'); var colorContainer = $('.color.active', container); if(colorContainer) { container.trigger({ type: "open-view", query: container.data('pipeline-query'), args: { time : fields[0], phi : fields[1], theta : fields[2] }, color: colorContainer.attr('data-color') }); } else { me.trigger({ type: "open-view", query: container.data('pipeline-query'), args: { time : fields[0], phi : fields[1], theta : fields[2] }, }); } }); renderActivePage(); } // ------------------------------ function renderActivePage(){ var processingQueue = []; function renderCompositeResult() { if(processingQueue.length > 0) { // [ container, time, phi, theta ] var item = processingQueue.pop(), time = item[1], phi = item[2], theta = item[3]; if(renderManager.updateFields(time, phi, theta)) { item[0].attr('src', drawResult(time, phi, theta)); item[4].hide(); } else { // Not ready yet processingQueue.push(item); } // Process remaining setTimeout(renderCompositeResult, 50); } } $('.result-page.active .composite-result', container).each(function(){ var me = $(this), fields = me.attr('data-fields').split(':'), time = fields[0], phi = fields[1], theta = fields[2]; processingQueue.push([$('img', me), time, phi, theta, $('ul', me)]); }); processingQueue.reverse(); $('.result-page-index', container).val(1+Number($('.result-page.active').attr('data-page'))); renderCompositeResult(); } // ------------------------------ function addResultToUI(obj, container) { var buffer = [], dataFields = ""; dataFields += obj.fields['time'] + ':' + obj.fields['phi'] + ':' + obj.fields['theta']; for(var field in obj.fields){ buffer.push(field + ': ' + obj.fields[field]); } for(var layer in obj.count){ if(layer != '+' && obj.count[layer] > 0) { buffer.push(layerToLabel[layer] + ': ' + Number(obj.count[layer]).toFixed(2) + ' %'); } } $("
    ", { class: 'composite-result', 'data-fields': dataFields, html: "" }).appendTo(container); } // ------------------------------ function updateFields(time, phi, theta) { //console.log('search updateFields: ' + time + " " + phi + " " + theta); } // ------------------------------ function working(working) { $('.working', container).css('visibility', working ? 'visible' : 'hidden'); if(working) { $('.search-action[data-action="render"]', container).addClass('disabled'); } else { $('.search-action[data-action="render"]', container).removeClass('disabled'); } } // ------------------------------ // function updateResult(found, total) { // var nb = $('.result-founds', container), // tot = $('.result-total', container); // nb.html( Number(nb.html()) + found); // tot.html( Number(tot.html()) + total); // } // ------------------------------ function draw() { //console.log('search draw'); } // ------------------------------ function drawResult(time, phi, theta) { renderManager.updateFields(time, phi, theta); renderManager.draw(); // Extract image and use it inside result return renderManager.toDataURL(); } // ------------------------------ function updatePipeline(query) { // update Layer Visibility var count = query.length; layerOlderInvalid = false; for(var idx = 0; idx < count; idx += 2) { var layer = query[idx], visibility = (query[idx+1] != '_'); if(!layerVisibility.hasOwnProperty(layer) || layerVisibility[layer] != visibility) { layerVisibility[layer] = visibility; layerOlderInvalid = true; } } if(layerOlderInvalid) { updateWorkers(query); $('.chart-container', container).empty(); $('.composite-search-results', container).empty(); result = []; } // Update render manager renderManager.updatePipeline(query); } // ------------------------------ function updateColor(color) { renderManager.updateColor(color); } // ------------------------------ function updatePixelRatio(field, value) { } // ------------------------------ function updatePixelRatioOrder(field, isPlus) { console.log('search updatePixelRatioOrder: ' + field + " " + isPlus); } // ------------------------------ return { updateFields:updateFields, draw:draw, updatePipeline:updatePipeline, updateColor:updateColor, updatePixelRatio:updatePixelRatio, updatePixelRatioOrder:updatePixelRatioOrder, search: search, applyQuery: applyQuery, renderActivePage: renderActivePage, layerToLabel: layerToLabel }; } // ---------------------------------------------------------------------- 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, bgCanvas, frontCanvas, pixelZoomRatio, stepPhi, stepTheta) { // 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 if(stepPhi === 0) { modeRotation = modePan; } /* * Adds mouse event handlers so that we can pan and zoom the image */ function setupEvents() { var element = frontCanvas; // 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(frontCanvas, 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(frontCanvas, evt); evt.preventDefault(); }); // Send mouse movement event to the forwarding function element.bind('mousemove', function(e) { if(mouseMode != modeNone) { var loc = getRelativeLocation(frontCanvas, 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 frontCanvas redrawImage(); } }); // Stop any zoom or pan events element.bind('mouseup', function(evt) { mouseMode = modeNone; evt.preventDefault(); }); } /* * If the data can rotate */ function handleRotation(loc) { var deltaPhi = (loc[0] - lastLocation[0]), deltaTheta = (loc[1] - lastLocation[1]), changeDetected = false; if(Math.abs(deltaPhi) > stepPhi) { changeDetected = true; if(deltaPhi > 0) { $('.phi span[data-action="next"]', container).trigger('click'); } else { $('.phi span[data-action="previous"]', container).trigger('click'); } } if(Math.abs(deltaTheta) > stepTheta) { changeDetected = true; if(deltaTheta > 0) { $('.theta span[data-action="next"]', container).trigger('click'); } else { $('.theta span[data-action="previous"]', container).trigger('click'); } } if(changeDetected) { // 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 frontCtx = frontCanvas[0].getContext("2d"), w = container.width(), h = container.height(), iw = bgCanvas[0].width, ih = bgCanvas[0].height; if(iw === 0) { setTimeout(redrawImage, 100); } else { frontCanvas.attr("width", w); frontCanvas.attr("height", h); frontCtx.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); } frontCtx.drawImage(bgCanvas[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 = bgCanvas[0].width, ih = bgCanvas[0].height; 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, 'paint': redrawImage }; } // ------------------------------------------------------------------------ function createCompositeManager(container, basepath, info, nbImages) { var activeQuery = "", pathPattern = info.name_pattern, activeKey = null, cache = {}, orderMapping = {}, layerOffset = null, offsetMap = info.metadata.offset, singleImageSize = info.metadata.dimensions, fullImageSize = [ singleImageSize[0], singleImageSize[1] * nbImages], bgColor = null; // Add UI components to container $('
    ', { class: 'composite-view', html: TEMPLATE_CANVAS }).appendTo(container); var bgCanvas = $('.back-buffer', container), frontCanvas = $('.single-size-back-buffer', container), bgCTX = bgCanvas[0].getContext('2d'), frontCTX = frontCanvas[0].getContext('2d'); // Update bg canvas size to match image size bgCanvas.attr('width', fullImageSize[0]).attr('height', fullImageSize[1]); frontCanvas.attr('width', singleImageSize[0]).attr('height', singleImageSize[1]); // Create helper methods // ----------------------------------------- function downloadImage(key, url) { var img = new Image(); function onLoad() { cache[key]['image'] = img; if(cache[key].hasOwnProperty('json')) { draw(); } } function onError() { console.log('Error loading image ' + url + ' for key ' + key); } img.onload = onLoad; img.onerror = onError; img.src = url; if (img.complete) { onLoad(); } } // ----------------------------------------- function downloadComposite(key, url) { jQuery.getJSON(url, function(data){ // Process composite var composite = data["pixel-order"].split('+'), count = composite.length; while(count--) { var str = composite[count]; if(str[0] === '@') { composite[count] = Number(str.substr(1)) } else { if(!orderMapping.hasOwnProperty(str)) { // Compute offset orderMapping[str] = computeOffset(str); } } } cache[key]['composite'] = composite; cache[key]['json'] = data; if(cache[key].hasOwnProperty('image')) { draw(); } }); } // ----------------------------------------- function updateColor(color) { bgColor = color; draw(); } // ----------------------------------------- function updateFields(time, phi, theta) { activeKey = pathPattern.replace('{time}', time).replace('{phi}', phi).replace('{theta}', theta); if(!cache.hasOwnProperty(activeKey)) { // Trigger download cache[activeKey] = {}; downloadImage(activeKey, basepath + '/' + activeKey.replace('{filename}', 'rgb.jpg')); downloadComposite(activeKey, basepath + '/' + activeKey.replace('{filename}', 'composite.json')); return false; } else { return draw(); } } // ----------------------------------------- function computeOffset(order) { var count = order.length; for(var i = 0; i < count; ++i) { var offset = layerOffset[order[i]]; if(offset > -1) { return offset; } } return -1; } // ----------------------------------------- function computeLayerOffset(query) { var count = query.length; layerOffset = {}; for(var i = 0; i < count; i += 2) { var layer = query[i], field = query[i+1]; if(field === '_') { layerOffset[layer] = -1; } else { layerOffset[layer] = nbImages - offsetMap[query.substr(i,2)] - 1; } } } // ----------------------------------------- function updatePipeline(query) { if(activeQuery !== query) { activeQuery = query; // Update current offset for each layer computeLayerOffset(query); // Loop over all possible order and compute offset for(var order in orderMapping) { orderMapping[order] = computeOffset(order); } // Render result draw(); } } // ----------------------------------------- function draw() { if(!cache.hasOwnProperty(activeKey) || !cache[activeKey].hasOwnProperty('composite') || !cache[activeKey].hasOwnProperty('image')) { return false; } var composite = cache[activeKey]['composite'], img = cache[activeKey]['image'], localOrder = orderMapping, fullPixelOffset = singleImageSize[0] * singleImageSize[1] * 4, count = composite.length; // Fill buffer with image bgCTX.drawImage(img, 0, 0); var pixelBuffer = bgCTX.getImageData(0, 0, fullImageSize[0], fullImageSize[1]).data, frontBuffer = null, frontPixels = null, pixelIdx = 0, localIdx; // Fill with bg color //if(bgColor) { // frontCTX.fillStyle = bgColor; // frontCTX.fillRect(0,0,singleImageSize[0], singleImageSize[1]); // frontBuffer = frontCTX.getImageData(0, 0, singleImageSize[0], singleImageSize[1]); // frontPixels = frontBuffer.data; //} else { // frontBuffer = bgCTX.getImageData(0, (nbImages - 1) * singleImageSize[1], singleImageSize[0], singleImageSize[1]); // frontPixels = frontBuffer.data; //} // Clear front pixels //frontCTX.fillStyle = "#ffffff"; //frontCTX.fillRect(0,0,singleImageSize[0], singleImageSize[1]); frontCTX.clearRect(0, 0, singleImageSize[0], singleImageSize[1]); frontBuffer = frontCTX.getImageData(0, 0, singleImageSize[0], singleImageSize[1]); frontPixels = frontBuffer.data; for(var i = 0; i < count; ++i) { var order = composite[i]; if(order > 0) { pixelIdx += order; } else { var offset = localOrder[order]; if(offset > -1) { localIdx = 4 * pixelIdx; offset *= fullPixelOffset; offset += localIdx; frontPixels[ localIdx ] = pixelBuffer[ offset ]; frontPixels[ localIdx + 1 ] = pixelBuffer[ offset + 1 ]; frontPixels[ localIdx + 2 ] = pixelBuffer[ offset + 2 ]; frontPixels[ localIdx + 3 ] = 255; } // Move forward ++pixelIdx; } } // Draw buffer to canvas frontCTX.putImageData(frontBuffer, 0, 0); container.trigger('render-bg'); return true; } function toDataURL() { return frontCanvas[0].toDataURL("image/png"); } return { updateFields:updateFields, draw:draw, updatePipeline:updatePipeline, updateColor:updateColor, toDataURL:toDataURL, search:function(){} }; } // ------------------------------------------------------------------------ function createSelectColorBy(name, availableFields, fields, addRatio) { var buffer = [ "
    " ], count = availableFields.length, value = null; if(count < 2) { buffer.push(""); } while(count--) { value = availableFields[count]; buffer.push(SELECT_OPTION.replace(/VALUE/g, value).replace(/NAME/g, fields[value])) } buffer.push(""); // Add % slider for query if(addRatio) { buffer.push("0%".replace(/FIELD/g, name)); } buffer.push("
    "); return buffer.join(''); } // ------------------------------------------------------------------------ function encodeEntry(entry, layer_fields, fields, addRatio) { var controlContent = ""; if(entry['type'] === 'directory') { var array = entry['children'], count = array.length; for(var i = 0; i < count; ++i) { controlContent += encodeEntry(array[i], layer_fields, fields, addRatio).replace(/FRONT_ICON/g, 'vtk-icon-cancel-circled remove'); } controlContent = DIRECTORY_CONTROL.replace(/CHILDREN/g, controlContent); } else { controlContent = createSelectColorBy(entry['ids'][0], layer_fields[entry['ids'][0]], fields, addRatio); } return PIPELINE_ENTRY.replace(/ID/g, entry['ids'].join(':')).replace(/LABEL/g, entry['name']).replace(/CONTROL/g, controlContent); } // ------------------------------------------------------------------------ function createControlPanel(container, pipeline, layer_fields, fields, addRatio) { var pipelineBuffer = [], count = pipeline.length; // Build pipeline content for(var i = 0; i < count; ++i) { pipelineBuffer.push(encodeEntry(pipeline[i], layer_fields, fields, addRatio).replace(/FRONT_ICON/g, 'vtk-icon-eye toggle-eye')); } $('
    ', { class: 'control', html: TEMPLATE_CONTENT.replace(/PIPELINE/g, pipelineBuffer.join('')) }).appendTo(container); $('li > ul > li', container).removeClass('enabled').hide(); } // ------------------------------------------------------------------------ function initializeListeners(container, manager, zoomableRender) { var layers = container.data('layers'), animationWorkIndex = 0, play = $('.play', container), stop = $('.stop', container), keepAnimation = false, toggleLayer = {}; function animate() { $('.active .vcr[data-action="next"]', container).trigger('click'); if(keepAnimation) { setTimeout(animate, 200); } } function updatePipelineUI(query, args, color, sortValue) { var queryObj = {}, count = query.length, queryStr = "", sortStr = sortValue; for(var i = 0; i < count; i += 2) { queryObj[query[i]] = query[i+1]; } // Update pipeline $('.pipeline > ul > li > ul > li', container).hide(); $('.pipeline li', container).each(function(){ var me = $(this), id = me.attr('data-id'), visibleLayer = (queryObj[id] != '_'), selectContainer = $('select', me), iconContainer = $('.toggle-eye', me); selectContainer.val(queryObj[id]); iconContainer.removeClass('vtk-icon-eye vtk-icon-eye-off').addClass(visibleLayer ? 'vtk-icon-eye':'vtk-icon-eye-off'); if(visibleLayer) { me.addClass('show enabled').show(); } else { me.removeClass('enabled show'); } }); // Update args for(var key in args) { var argContainer = $('.'+key, container), values = argContainer.attr('data-values').split(':'), labelContainer = $('.value', argContainer), count = values.length, targetValue = args[key]; while(count--) { if(values[count] == targetValue) { labelContainer.html(targetValue); argContainer.attr('data-index', count); count = 0; } } // Update Query STR queryStr += " && " + key + " == " + targetValue; } queryStr = queryStr.substr(4); // Update color $('.color').removeClass('active'); if(color) { $('.color[data-color="' + color + '"]', container).addClass('active'); } // Render the new content updatePipeline(); updateComposite(); manager.updateColor(color); // Update query if search mode queryExp = $('.query-expression', container); sortExp = $('.sortby-expression', container); if(queryExp) { queryExp.val(queryStr); if(sortStr) { sortExp.val(sortStr); } setTimeout(function(){ $('.compute-coverage', container).trigger('click'); setTimeout(function(){ queryExp.trigger('change'); }, 600); }, 100); } } container.bind('updateControl', function(event){ updatePipelineUI(event.query, event.args, event.color, event.sort); }); function updatePipeline() { var query = ""; for(var i in layers) { layer = layers[i]; query += layer; var layerContainer = $('li[data-id="'+layer+'"]', container); if(layerContainer.hasClass('show') && layerContainer.hasClass('enabled')) { query += $('select:eq(0)', layerContainer).val(); toggleLayer[layer] = true; } else { query += '_'; toggleLayer[layer] = false; } } container.data('pipeline-query', query); manager.updatePipeline(query); } function extractFieldValue(fieldContainer) { return fieldContainer.attr('data-values').split(':')[Number(fieldContainer.attr('data-index'))]; } function updateComposite() { var time = extractFieldValue($('.time', container)), phi = extractFieldValue($('.phi', container)), theta = extractFieldValue($('.theta', container)); manager.updateFields(time, phi, theta); container.data('args', {time:time, phi:phi, theta:theta}); } $('.color', container).click(function(){ var me = $(this), hasColor = me.hasClass('active'), color = me.attr('data-color'); if(!hasColor) { $('.color', me.parent()).removeClass('active'); } me.toggleClass('active'); manager.updateColor(hasColor ? null : color); }); $('.toggle', container).click(function(){ container.toggleClass('small'); }); $('.toggle-active', container).click(function(){ $(this).toggleClass('active'); }); $('.reset', container).click(function(){ if(zoomableRender) { zoomableRender.resetCamera(); } }); $('.toggle-eye', container).click(function(){ var me = $(this), isVisible = me.hasClass('vtk-icon-eye'), all = $('li', me.parent()); me.removeClass('vtk-icon-eye vtk-icon-eye-off') .addClass(isVisible ? 'vtk-icon-eye-off' : 'vtk-icon-eye'); // Update class for pipeline all.removeClass('show'); me.parent().removeClass('show'); if(!isVisible) { all.addClass('show'); me.parent().addClass('show'); } updatePipeline(); // Update query if search mode var queryExp = $('.query-expression', container); if(queryExp) { setTimeout(function(){ $('.compute-coverage', container).trigger('click'); setTimeout(function(){ queryExp.trigger('change'); }, 600); }, 100); } }); $('.remove', container).click(function(){ $(this).parent().removeClass('enabled').hide(); updatePipeline(); }); $('.vcr', container).click(function(){ var me = $(this), action = me.attr('data-action'), root = me.closest('li'), idx = Number(root.attr('data-index')), size = Number(root.attr('data-size')), values = root.attr('data-values').split(':'), valueContainer = $('.value', root), canLoop = root.hasClass('loop'), changeFound = false; root.toggleClass('active'); switch(action) { case 'begin': idx = 0; break; case 'previous': if(canLoop || idx > 0) { idx = (idx + size - 1) % size; changeFound = true; } break; case 'next': if(canLoop || idx + 1 < size) { idx = (idx + 1) % size; changeFound = true; } break; case 'end': idx = size -1; break; } root.attr('data-index', idx); valueContainer.html(values[idx]); if(changeFound) { updateComposite(); } }); $('select', container).change(updatePipeline); $('.select-layer', container).click(function(){ var me = $(this), pipelineContainer = $('.pipeline-container', container), layerSelector = $('.layer-selector', container), title = me.parent().children('span.label:eq(0)').html(), buffer = []; $('span.label', me.parent().children('ul')).each(function(){ var me = $(this); buffer.push(TEMPLATE_LAYER_CHECK.replace(/ID/g, me.parent().attr('data-id')).replace(/NAME/g, me.html()).replace(/CHECKED/g, me.is(":visible") ? "checked=''" : "")); }); layerSelector.empty()[0].innerHTML = TEMPLATE_SELECTOR.replace(/TITLE/g, title).replace(/LIST/g, buffer.join('')); // add listeners $('.validate-layer', layerSelector).click(function(){ pipelineContainer.show(); layerSelector.hide(); updatePipeline(); // Update query if search mode var queryExp = $('.query-expression', container); if(queryExp) { setTimeout(function(){ $('.compute-coverage', container).trigger('click'); setTimeout(function(){ queryExp.trigger('change'); }, 600); }, 100); } }); $('input', layerSelector).change(function(){ var me = $(this), checked = me.is(':checked'), id = me.attr('name'), item = $('li[data-id="' + id + '"]', container); if(checked) { item.addClass("enabled show").show(); } else { item.removeClass("enabled show").hide(); } }); pipelineContainer.hide(); layerSelector.show(); }); play.click(function(){ animationWorkIndex = 0; play.hide(); stop.show(); keepAnimation = true; animate(); }); stop.click(function(){ stop.hide(); play.show(); keepAnimation = false; }); // Forward render call to front buffer container.bind('render-bg', function(){ if(zoomableRender) { zoomableRender.paint(); } }); // Process current config updatePipeline(); updateComposite(); // ===== Search based UI ===== $('.compute-coverage', container).click(function(){ var pipelineContainer = $('.control', container), maxWidth = $(window).width(); $('.chart-container', container).css('height', pipelineContainer.height()).css('width', maxWidth - pipelineContainer.width() - 50); updatePipeline(); manager.search(); }); $('.query-expression', container).bind('change keyup', function(e){ // Apply search if(e.type === 'keyup' && e.keyCode !== 13) { return; } var me = $(this), userQuery = me.val(); manager.applyQuery(userQuery); }); $('.sortby-expression', container).bind('change keyup', function(e){ // Apply search if(e.type === 'keyup' && e.keyCode !== 13) { return; } var me = $('.query-expression', container), userQuery = me.val(); manager.applyQuery(userQuery); }); $('.toggle-stats', container).click(function(){ var me = $(this), shouldShow = me.toggleClass('stats').hasClass('stats'); if(shouldShow) { $('.composite-search-results .composite-result > ul', container).show(); } else { $('.composite-search-results .composite-result > ul', container).hide(); } }); $('.page-result-action', container).bind('click change',function(){ var me = $(this), action = me.attr('data-action'), pages = $('.composite-search-results .result-page', container), activePage = $('.composite-search-results .result-page.active', container), activeIdx = Number(activePage.attr("data-page")), nbPages = pages.length; pages.removeClass('active'); if(action === "first") { $('.result-page[data-page=0]', container).addClass('active'); } else if(action === "previous") { $('.result-page[data-page='+((nbPages + activeIdx - 1)%nbPages)+']', container).addClass('active'); } else if(action === "next") { $('.result-page[data-page='+((activeIdx + 1)%nbPages)+']', container).addClass('active'); } else if(action === "last") { $('.result-page[data-page='+(nbPages - 1)+']', container).addClass('active'); } else if(action === "go-to") { var newIdx = Number($('.result-page-index', container).val()) - 1; $('.result-page[data-page='+newIdx+']', container).addClass('active'); } manager.renderActivePage(); }); // $('.render-all-composites', container).click(function(){ // $('.composite-result', container).trigger('click'); // }); } /** * jQuery catalyst view constructor. * * @member jQuery.vtkCatalystCompositeViewer * @param basePath * Root directory for data to visualize */ $.fn.vtkCatalystCompositeViewer = function(dataBasePath) { return this.each(function() { var me = $(this).unbind().empty().addClass('vtkweb-catalyst-composite small'); // Get meta-data $.ajax({ url: dataBasePath + '/info.json', dataType: 'json', success: function( data ) { // Extract info to local vars var layer_fields = data.metadata.layer_fields, fields = data.metadata.fields, pipeline = data.metadata.pipeline, args = data.arguments, nbImages = 1, deltaPhi = (args.hasOwnProperty('phi')) ? (Number(args.phi.values[1]) - Number(args.phi.values[0])) : 0, deltaTheta = (args.hasOwnProperty('theta')) ? (Number(args.theta.values[1]) - Number(args.theta.values[0])) : 0; // Compute number of images for(var key in layer_fields) { nbImages += layer_fields[key].length; } // Keep data info me.data('basepath', dataBasePath); me.data('layers', data.metadata.layers); // Add control UI createControlPanel(me, pipeline, layer_fields, fields, false); // Add rendering view var manager = createCompositeManager(me, dataBasePath, data, nbImages); me.data('compositeManager', manager); var zoomableRender = createZoomableCanvasObject(me, $('.single-size-back-buffer', me), $('.front-renderer', me), 10, deltaPhi, deltaTheta); me.data('zoomableRender', zoomableRender); // Enable additional fields if any (time, phi, theta) for(var key in args) { var fieldContainer = $('.' + key, me); if(fieldContainer) { fieldContainer.attr('data-values', args[key].values.join(':')).attr('data-index', '0').attr('data-size', args[key].values.length); fieldContainer.show(); } } // Attach interaction listeners initializeListeners(me, manager, zoomableRender); $('.front-renderer', me).dblclick(function(){ var colorContainer = $('.color.active', me); if(colorContainer) { me.trigger({ type: "open-view", query: me.data('pipeline-query'), args: me.data('args'), color: colorContainer.attr('data-color') }); } else { me.trigger({ type: "open-view", query: me.data('pipeline-query'), args: me.data('args') }); } }); }, error: function(error) { console.log("error when trying to download " + dataBasePath + '/info.json'); console.log(error); } }); }); } /** * jQuery catalyst composite search constructor. * * @member jQuery.vtkCatalystCompositeSearch * @param basePath * Root directory for data to visualize */ $.fn.vtkCatalystCompositeSearch = function(dataBasePath) { return this.each(function() { var me = $(this).unbind().empty().addClass('vtkweb-catalyst-analysis-composite-search vtkweb-catalyst-composite'); // Get meta-data $.ajax({ url: dataBasePath + '/info.json', dataType: 'json', success: function( data ) { // Extract info to local vars var layer_fields = data.metadata.layer_fields, fields = data.metadata.fields, pipeline = data.metadata.pipeline, args = data.arguments, searchManager = createSearchManager(me, data, dataBasePath); // Keep data info me.data('basepath', dataBasePath); me.data('layers', data.metadata.layers); // Add control UI createControlPanel(me, pipeline, layer_fields, fields, false); // Enable additional fields if any (time, phi, theta) var helpTxt = "", excludeList = {"filename": true, "field": true}, layerVarNames = "
    "; for(var key in args) { if(key === 'layer') { continue; } var fieldContainer = $('.' + key, me); if(fieldContainer) { // Add documentation var help = "NAME: VALUES
    ".replace(/NAME/g, key), values = args[key]["values"]; if(args[key]['type'] === 'range') { values = "[MIN to MAX % MODULO]".replace(/MIN/g, values[0]).replace(/MAX/g, values[values.length - 1]).replace(/MODULO/g, (Number(values[1]) - Number(values[0]))); } help = help.replace(/VALUES/g, values); if(!excludeList.hasOwnProperty(key)) { helpTxt += help; } fieldContainer.attr('data-values', args[key].values.join(':')).attr('data-index', '0').attr('data-size', args[key].values.length); } } // Create layer labels for(var key in searchManager.layerToLabel) { layerVarNames += "NAME: VALUES
    ".replace(/NAME/g, key).replace(/VALUES/g, searchManager.layerToLabel[key]); } helpTxt += layerVarNames; // Add search/results containers $('
    ', {class: "chart-container"}).appendTo(me); $('
    ', {class: "search-toolbar", html: '
    QuerySort by
    • /
    HELP'.replace(/HELP/g, helpTxt)}).appendTo(me); $('
    ', {class: "composite-search-results"}).appendTo(me); initializeListeners(me, searchManager, null); }, error: function(error) { console.log("error when trying to download " + dataBasePath + '/info.json'); console.log(error); } }); }); } }(jQuery, window));