diff --git a/eu_basynthec/source/html/browser/basynthec-dashboard.html b/eu_basynthec/source/html/browser/basynthec-dashboard.html new file mode 100644 index 0000000000000000000000000000000000000000..5c1c7c5a34fdd4ea026038a4016a4ebb7de13675 --- /dev/null +++ b/eu_basynthec/source/html/browser/basynthec-dashboard.html @@ -0,0 +1,481 @@ +<html> +<head> + <title>openBIS BaSynthec Browser</title> + <link type="text/css" rel="stylesheet" href="basynthec-style.css" /> + <link type="text/css" rel="stylesheet" href="button.css" /> + <script type="text/javascript" src="d3.js"></script> + <script type="text/javascript" src="d3.layout.js"></script> + <script type="text/javascript" src="d3.time.js"></script> + <script type="text/javascript" src="jquery.js"></script> + <script type="text/javascript" src="openbis-utils.js"></script> + <script type="text/javascript" src="openbis.js"></script> + <script type="text/javascript" src="openbis-basynthec.js"></script> + <!-- To speed development, cache the requests --> + <!-- <script type="text/javascript" src="openbis-request-cache.js"></script> --> + <script> + +//basynthec = new openbis_basynthec('https://basynthec.ethz.ch/openbis/openbis', 'https://basynthec.ethz.ch/datastore_server'); +basynthec = new openbis_basynthec('http://localhost:8888/openbis/openbis', 'http://localhost:8889/datastore_server'); + +// The elements for each of the views +var vis, od600View, metabolomicsView, transcriptomicsView, proteomicsView; +// The height for each of the tables -- anything beyond is is overflowed +var viewTableHeight = "320px"; + +// A map holding data sets by type +var dataSetsByType = { }; + +// prefixes of strain names to be grouped togehter +var STRAIN_GROUP_PREFIXES = [ "JJS-DIN", "JJS-MGP" ]; + +// The names to show the user for the strain groups +var STRAIN_GROUP_PREFIXES_DISPLAY_NAME = {"JJS-DIN" : "JJS-DIn", "JJS-MGP" : "JJS-MGP" }; + +// groups of strains to be displayed +var strainGroups = []; + +var IGNORED_DATASET_TYPES = [ "EXCEL_ORIGINAL", "TSV_MULTISTRAIN_EXPORT", "TSV_EXPORT", "UNKNOWN" ]; + +//The inspected strains +var inspected = []; + +//The node inspectors +var inspectors; + +// A date formatter used to display data sets +var timeformat = d3.time.format("%Y-%m-%d %H:%M"); + +/** + * Shows the list of data sets retrieved from the openBIS server. + */ +function showDataSets(bisDataSets) +{ + if (null == bisDataSets) return; + + basynthec.dataSetList = bisDataSets.filter(function(dataSet) { + return IGNORED_DATASET_TYPES.indexOf(dataSet.dataSetTypeCode) == -1; + }); + + // sort data sets + var sortByTypeAndRegistration = function(a, b) { + if (a.dataSetTypeCode == b.dataSetTypeCode) { + return a.registrationDetails.registrationDate - b.registrationDetails.registrationDate; + } + return (a.dataSetTypeCode < b.dataSetTypeCode) ? -1 : 1; + }; + + basynthec.dataSetList.sort(sortByTypeAndRegistration); + + // Group data sets by type + dataSetsByType = basynthec.dataSetList.reduce( + function(result, dataSet) { + var listForType = result[dataSet.dataSetTypeCode]; + if (listForType == null) { + listForType = []; + result[dataSet.dataSetTypeCode] = listForType; + } + listForType.push(dataSet); + return result; + }, {}); + refreshDataSetTypeTables(); +} + +function createStrainGroups(strains) { + + var groups = STRAIN_GROUP_PREFIXES.map( + function(strainPrefix) { + var filtered = strains.filter(function(strain) { + return strain.indexOf(strainPrefix) >= 0 + }); + var groupStrains = filtered.map(function(strain) { + return { name : strain, label : strain.substring(strainPrefix.length)}; + }); + + return {groupName : STRAIN_GROUP_PREFIXES_DISPLAY_NAME[strainPrefix], strains : groupStrains}; + }); + + var otherStrains = strains.filter(function(strain) { + return false == STRAIN_GROUP_PREFIXES.some(function(prefix) { return strain.indexOf(prefix) >=0; } ); + }); + otherStrains = otherStrains.map(function(strain) { return {name:strain, label:strain}}); + groups.push({groupName : "Other strains", strains : otherStrains}); + + var sortFunction = sortByProp("name") + groups.forEach(function(group) { group.strains.sort(sortFunction); }); + + // only return groups that have strains + return groups.filter(function(group) { return group.strains.length > 0 }); +} + +function refreshDataSetTypeTables() { + var strains = [] + for (strainName in dataSetsByType) { + strains.push(strainName) + } + strainGroups = createStrainGroups(strains); + + createVis(); + updateDiagram(1000); +} + +function createDataSetSummaryView(group, floatSide, type, id) +{ + var container, result; + container = + group.append("div") + .style("width", "47%") + .style("float", floatSide); + container.append("span").text(type); + + result = + container.append("div") + .attr("id", id) + .attr("class", "datasummaryview"); + + return result; +} + + +var didCreateVis = false; + +function createVis() +{ + if (didCreateVis) return; + + vis = d3.select("#main").append("div").style("position", "relative").style("top", "20px"); + + // Group the views + var group1 = vis.append("div"); + group1 + .attr("class", "datasetsummarygroup") + .style("padding-bottom", "5px"); + var group2 = vis.append("div"); + group2 + .attr("class", "datasetsummarygroup"); + + od600View = createDataSetSummaryView(group1, "left", "OD600", "od600"); + metabolomicsView = createDataSetSummaryView (group1, "right", "Metabolomics", "metabolomics") + transcriptomicsView = createDataSetSummaryView (group2, "left", "Transcriptomics", "transcriptomics") + proteomicsView = createDataSetSummaryView (group2, "right", "Proteomics", "proteomics") + + // An element for the inspectors. + inspectors = vis.append("span") + .style("width", "500px") + .style("position", "relative") + .style("overflow", "auto") + .style("float", "right") + .style("left", "20px"); + + didCreateVis = true; +} + +function updateView(aView, type) +{ + var dataSetsForType = dataSetsByType[type]; + + if (dataSetsForType == null) { + aView.selectAll("p") + .data(["No Data"]) + .enter() + .append("p") + .text("No Data"); + return; + } + + aView.selectAll("table") + .data([dataSetsForType]) + .enter() + .append("table") + .attr("class", "datasetsummarytable") + .selectAll("tr") + .data(function (d) { return d}) + .enter() + .append("tr") + .selectAll("td") + .data(function (d) { + var dateString = timeformat(new Date(d.registrationDetails.registrationDate)); + var strainString = "" + d.properties["STRAIN_NAMES"].split(",").length + " strain(s)"; + return [dateString, d.registrationDetails.userEmail, strainString] + }) + .enter() + .append("td") + .style("width", "33%") + .text(function (d) { return d}); +} + + +function updateDiagram(duration) +{ + // Update the OD600 View + updateView(od600View, "OD600"); + updateView(metabolomicsView, "METABOLITE_INTENSITIES"); + updateView(transcriptomicsView, "TRANSCRIPTOMICS"); + updateView(proteomicsView, "PROTEIN_QUANTIFICATIONS"); +} + + +/** + * Draw / update node inspectors + */ +function updateInspectors(duration) +{ + var inspector = inspectors.selectAll("div.inspector").data(inspected, function (d) { return d.name }); + + var box = inspector.enter().append("div") + .attr("class", "inspector") + .text(function(d) { return d.name }); + + box.append("span") + .attr("class", "close") + .on("click", toggle_inspected) + .text("x"); + + var dataSetList = inspector.selectAll("ul").data(function (d) { return [d] }); + dataSetList.enter() + .append("ul") + .attr('class', 'dataSets'); + + + var dataSetElt = dataSetList.selectAll("li").data(function (d) { return d.dataSets }); + dataSetElt.enter() + .append("li") + .text(function(d) { return dataSetLabel(d) }); + + var dataSetDetailsElt = dataSetElt.selectAll("div.dataSetDetails").data(function(d) { return [d]; }); + dataSetDetailsElt + .enter() + .append("div") + .attr("class", "dataSetDetails"); + + var propsTable = dataSetDetailsElt.selectAll("table.properties").data(function(d) {return [d]}); + + propsTable.enter() + .append("table") + .attr("class", "properties"); + + propsTable.selectAll("tr").data(function(d) { return props_to_pairs(d.bis.properties) }) + .enter() + .append("tr") + .selectAll("td").data(function(d) { return d } ).enter() + .append("td") + .attr("class", function(d, i) { return (i == 0) ? "propkey" : "propvalue"}) + .style("opacity", "0") + .text(function(d) { return d }) + .transition() + .style("opacity", "1"); + + var downloadTable = dataSetDetailsElt.selectAll("table.downloads").data(function(d) { return [d] }); + + downloadTable + .enter() + .append("table") + .attr("class", "downloads") + + // Add a caption, but make sure there is just one (this does not work with select()) + downloadTable.selectAll("caption").data(["Files"]) + .enter() + .append("caption").text(function(d) { return d; }); + + // We just want to see non-directories here + var downloadTableRow = downloadTable.selectAll("tr").data(filesForDataSet, function(d) { return d.pathInDataSet }); + downloadTableRow + .enter() + .append("tr") + .append("td") + .on("click", downloadTableFile) + .text(function(d) { return d.pathInListing }); + downloadTableRow + .exit() + .transition() + .duration(duration) + .style("opacity", "0") + .remove(); + + inspector.exit().transition() + .duration(duration) + .style("opacity", "0") + .remove(); +} + +function downloadTableFile(d) +{ + // If there is no dataset, this is just a marker for loading + if (!d.dataset) return; + + var action = function(data) { + try { + document.location.href = data.result + } catch (err) { + // just ignore errors + } + }; + basynthec.server.getDownloadUrlForFileForDataSet(d.dataset.bis.code, d.pathInDataSet, action); +} + +function dataSetLabel(d) { + return d.bis.dataSetTypeCode + " registered on " + timeformat(new Date(d.bis.registrationDetails.registrationDate)); +} + +function classForNode(d) { + return (d.inspected) ? "inspected" : ""; +} + +function toggle_inspected(d) { + if (d.inspected) { + var index = inspected.indexOf(d) + if (index > -1) inspected.splice(index, 1); + d.inspected = false; + } else { + d.inspected = true; + d.strainNode = this; + inspected.push(d); + if (!d.dataSets) { + d.dataSets = dataSetsByType[d.name].map(function(ds){ return {bis : ds} }); + retrieveFilesForDataSets(d.dataSets); + } + } + + d3.select(d.strainNode).attr("class", classForNode(d)) + updateInspectors(500); +} + +function filesForDataSet(d) +{ + if (d.loadingFiles) return [{ pathInListing : "Loading..." }]; + + var fileFilter = function(file) { + if (!file.isDirectory) { + if (endsWith(file.pathInDataSet, "xls")) { + return true; + } + if (endsWith(file.pathInDataSet, "xls.tsv")) { + return true; + } + } + return false; + }; + + return (d.files) ? d.files.filter(fileFilter) : []; +} + +function retrieveFilesForDataSets(dataSets) +{ + dataSets.forEach(function(ds) { + retrieveFilesForDataSet(ds); + }); +} + +function retrieveFilesForDataSet(ds) +{ + if (ds.files) { + //already retrieved + return; + } + + ds.loadingFiles = true; + ds.files = []; + + basynthec.server.listFilesForDataSet(ds.bis.code, "/", true, function(data) { + if (!data.result) { + return; + } + data.result.forEach(function (file) { file.dataset = ds }); + ds.files = ds.files.concat(data.result); + + ds.loadingFiles = false; + updateInspectors(500); + }); +} + +function shouldRenderProperty(prop, value) { + if (prop == STRAIN_PROP_NAME) { + // strain properties are dealt with separately + return false; + } + if (!value) { + // do not show properties with no values + return false; + } + return true; +} + +/** + * Convert properties to pairs + */ +function props_to_pairs(d) +{ + var pairs = []; + + var dataSetStrains = basynthec.getStrains({properties:d}); + var strainGroups = createStrainGroups(dataSetStrains); + + strainGroups.forEach(function(group) { + var shortedStrains = group.strains.map(function(elt) { return elt.label; }); + shortedStrains = uniqueElements(shortedStrains.sort()) + var pair = [ group.groupName, shortedStrains.join(" ") ]; + pairs.push(pair) + }); + + for (var prop in d) { + if (shouldRenderProperty(prop, d[prop])) { + var pair = [prop, d[prop]]; + pairs.push(pair); + } + } + pairs.sort(function(a, b) { + if (a[0] == b[0]) return 0; + // Sort in reverse lexicographical + return (a[0] < b[0]) ? -1 : 1; + }); + return pairs; +} + +function enterApp() +{ + $("#login-form-div").hide(); + $("#main").show(); + basynthec.listAllDataSets(function(data) { + showDataSets(data.result); + }); + + $('#openbis-logo').height(50) + +} + +$(document).ready(function() { + $("#main").hide() + + $('#logout-button').click(function() { basynthec.server.logout(function(data) { + window.location.reload(); + }) + }); + + $('#login-form').submit(function() { + basynthec.server.login( $('#username').val(), $('#password').val(), function(data) { enterApp() }) + }); + + basynthec.server.ifRestoredSessionActive(function(data) { enterApp() }); + }); + + </script> +</head> +<body> +<img id="openbis-logo" src="images/openBIS_Logo.svg" alt="openBIS" height="100px" style="float: right;"/> +<div id="login-form-div"> +<h1>openBIS BaSynthec</h1> + +<form id="login-form" action="javascript:"> +<input id="username" type="text"> <input id="password" type="password"> <button id="login-button">Login</button> +</form> +</div> + +<div id="main"> + +<div id="button-group"> + <button id="logout-button">Logout</button> +</div> + +</div> + +</body> +</html> \ No newline at end of file diff --git a/eu_basynthec/source/html/browser/basynthec-style.css b/eu_basynthec/source/html/browser/basynthec-style.css index c2a29de976b4ff2cb2a70e1102fda87a2015d7dc..f16734ad61c4f83cb1dd91ee7e654251df23d0c1 100644 --- a/eu_basynthec/source/html/browser/basynthec-style.css +++ b/eu_basynthec/source/html/browser/basynthec-style.css @@ -27,6 +27,19 @@ h3 { font: 12px; } padding: 2px; } +.datasetsummarygroup { + width: 75%; + height: 320px; +} + +div.datasummaryview { + height: 300px; + overflow: auto; +} + +.datasetsummarytable { + font-size: 12px; +} .sequenced text { fill: black;