diff --git a/deep_sequencing_unit/source/core-plugins/ngs-sample-overview-graph/1/as/webapps/sample-graph/html/webapp.js b/deep_sequencing_unit/source/core-plugins/ngs-sample-overview-graph/1/as/webapps/sample-graph/html/webapp.js index 084cdc184687ffa34526e0106e854738d99f8479..61ceeafe374cd4eaed5bb6bb7f511d3863e2b9d3 100644 --- a/deep_sequencing_unit/source/core-plugins/ngs-sample-overview-graph/1/as/webapps/sample-graph/html/webapp.js +++ b/deep_sequencing_unit/source/core-plugins/ngs-sample-overview-graph/1/as/webapps/sample-graph/html/webapp.js @@ -65,12 +65,16 @@ function SampleGraphNode(sample) { this.arrayIndex = -1; } +SampleGraphNode.prototype.toString = function() { + return "[" + this.sampleType + ", " + this.identifier + ", " + this.permId + "]"; +}; + /** * The link stores the information necessary for drawing connections between nodes */ -function SampleGraphLink(sourceNode, targetNode) { - this.sourceNode = sourceNode; - this.targetNode = targetNode; +function SampleGraphEdge(source, target) { + this.source = source; + this.target = target; } /** Break an identifier into a code and space */ @@ -271,20 +275,12 @@ SampleGraphModel.prototype.coalesceGraphData = function(data, callback) { */ function SampleGraphPresenter(model) { this.model = model; + this.renderer = new SimpleGraphRenderer(); this.didCreateVis = false; this.useBottomUpMode(); this.initializePresenter(); } -function textBBoxForNode(node) { - var bbox = presenter.columns.selectAll("text.sample")[node.col][node.visibleIndex].getBBox(); - // Correct for the column - bbox.x += node.colOffset; - return bbox; -} - -var yLinkOffset = LINE_HEIGHT * 0.33; - /** * Create the DOM elements to store the visualization (tree + inspectors) */ @@ -296,19 +292,6 @@ SampleGraphPresenter.prototype.initializePresenter = function() this.root = d3.select("#root"); this.rootLabel = d3.select("#root-label"); this.rootLabel.text(this.model.sampleIdentifier); - - // Function used to draw paths between elements - function source(d) { - // Find the associated text node in the DOM and use that as a basis for creating the links - var bbox = textBBoxForNode(d.sourceNode); - return { x : bbox.x + bbox.width + 7, y : bbox.y + yLinkOffset }; - } - function target(d) { - var bbox = textBBoxForNode(d.targetNode); - return { x : bbox.x, y : bbox.y + yLinkOffset } - } - - this.useLineLinkPath(source, target); this.didCreateVis = true; } @@ -356,27 +339,6 @@ SampleGraphPresenter.prototype.useTopDownMode = function() { this.calcuateVisibleColumnOffsets(); }; -/** - * Draw links using the diagonal function - */ -SampleGraphPresenter.prototype.useDiagonalLinkPath = function(source, target) { - var diagonal = d3.svg.diagonal(); - diagonal.source(source); - diagonal.target(target); - this.path = diagonal; -} - -/** - * Draw links using the line function - */ -SampleGraphPresenter.prototype.useLineLinkPath = function(source, target) { - var line = d3.svg.line(); - this.path = function(d) { - var src = source(d); - var dst = target(d); - return line([[src.x, src.y], [dst.x, dst.y]]); } -} - /** * Return a function that gives the outgoing edges for a sample. * @@ -412,6 +374,8 @@ SampleGraphPresenter.prototype.initializeGraphSamples = function() sampleData.color = (!oneToOne) ? colors(row) : oneToOneColor; sampleData.userEdgesVisible = null; sampleData.edgesVisible = col + 1 < FIRST_COLLAPSED_COLUMN; + sampleData.width = width; + sampleData.height = LINE_HEIGHT; } } this.allNodes = nodes; @@ -448,7 +412,7 @@ SampleGraphPresenter.prototype.updateLinks = function() { this.allNodes.forEach(function(samps) { samps.forEach(function(d) { if (!d.visible) return; - outEdgesGetter(d).forEach(function(c) { if (c.visible) links.push(new SampleGraphLink(d, c))}); + outEdgesGetter(d).forEach(function(c) { if (c.visible) links.push(new SampleGraphEdge(d, c))}); }) }); @@ -474,13 +438,106 @@ SampleGraphPresenter.prototype.updateState = function() */ SampleGraphPresenter.prototype.draw = function() { - this.updateState(); - var nodes = this.nodes; - var vizWidth = this.vizWidth; - var vizHeight = this.vizHeight; + this.renderer.draw(); +} + + +SampleGraphPresenter.prototype.toggleExpand = function(svgNode, d) { + // toggle visiblity + d.userEdgesVisible = (null == d.userEdgesVisible) ? !d.edgesVisible :!d.userEdgesVisible; + this.draw(); +} + +SampleGraphPresenter.prototype.openSample = function(svgNode, d) { + var url = "/openbis/index.html?viewMode=SIMPLE#entity=SAMPLE&permId=" + d.permId; + window.open(url, '_blank'); +} + +function displayActiveMode(active, inactive) { + active.parent().addClass("active"); + inactive.parent().removeClass("active"); +} + +function clickedBottomUp() { + if (presenter.bottomUpMode) return; + + displayActiveMode($('#bottom-up'), $('#top-down')); + presenter.useBottomUpMode(); + presenter.initializeGraphSamples(); + presenter.draw(); +} + +function clickedTopDown() { + if (!presenter.bottomUpMode) return; + + displayActiveMode($('#top-down'), $('#bottom-up')); + presenter.useTopDownMode(); + presenter.initializeGraphSamples(); + presenter.draw(); +} + + +function textBBoxForNode(node) { + var bbox = presenter.renderer.columns.selectAll("text.sample")[node.col][node.visibleIndex].getBBox(); + // Correct for the column + bbox.x += node.colOffset; + return bbox; +} + +var yLinkOffset = LINE_HEIGHT * 0.33; + + +/** + * A class that renders the graph + */ +function SimpleGraphRenderer(presenter) { + // Function used to draw paths between elements + function source(d) { + // Find the associated text node in the DOM and use that as a basis for creating the links + var bbox = textBBoxForNode(d.source); + return { x : bbox.x + bbox.width + 7, y : bbox.y + yLinkOffset }; + } + function target(d) { + var bbox = textBBoxForNode(d.target); + return { x : bbox.x, y : bbox.y + yLinkOffset } + } + + this.useLineLinkPath(source, target); +} + +/** + * Draw links using the diagonal function + */ +SimpleGraphRenderer.prototype.useDiagonalLinkPath = function(source, target) { + var diagonal = d3.svg.diagonal(); + diagonal.source(source); + diagonal.target(target); + this.path = diagonal; +} + +/** + * Draw links using the line function + */ +SimpleGraphRenderer.prototype.useLineLinkPath = function(source, target) { + var line = d3.svg.line(); + this.path = function(d) { + var src = source(d); + var dst = target(d); + return line([[src.x, src.y], [dst.x, dst.y]]); } +} + +/** + * Display the sample nodes. + */ +SimpleGraphRenderer.prototype.draw = function() +{ + presenter.updateState(); + var nodes = presenter.nodes; + var vizWidth = presenter.vizWidth; + var vizHeight = presenter.vizHeight; // Display the graph in an SVG element - this.viz = this.root.selectAll("svg").data([nodes]); + this.viz = presenter.root.selectAll("svg").data([nodes]); // Code under enter is run if there is no HTML element for a data element this.viz.enter().append("svg:svg").attr("class", "viz"); this.viz.attr("width", vizWidth); @@ -489,8 +546,7 @@ SampleGraphPresenter.prototype.draw = function() this.columns = this.viz.selectAll("g").data(function(d) { return d }); this.columns.enter().append("svg:g").attr("class", "column"); this.columns.exit().remove(); - var lexicalParent = this; - this.columns.attr("transform", function(d, i) { return "translate(" + lexicalParent.visibleColumns[i].xOffset + ", 0)"}); + this.columns.attr("transform", function(d, i) { return "translate(" + presenter.visibleColumns[i].xOffset + ", 0)"}); this.drawHeaders(); this.drawNodes(); this.drawLinks(); @@ -499,10 +555,9 @@ SampleGraphPresenter.prototype.draw = function() /** * Draw the headers */ -SampleGraphPresenter.prototype.drawHeaders = function() +SimpleGraphRenderer.prototype.drawHeaders = function() { - var lexicalParent = this; - var header = this.columns.selectAll("text.header").data(function(d, i) { return [lexicalParent.visibleColumns[i]] }); + var header = this.columns.selectAll("text.header").data(function(d, i) { return [presenter.visibleColumns[i]] }); header.enter().append("svg:text") .attr("class", "header") .attr("x", "0") @@ -516,9 +571,9 @@ SampleGraphPresenter.prototype.drawHeaders = function() /** * Draw the nodes */ -SampleGraphPresenter.prototype.drawNodes = function() +SimpleGraphRenderer.prototype.drawNodes = function() { - var outEdgesGetter = this.outEdgesFunction(); + var outEdgesGetter = presenter.outEdgesFunction(); var sample = this.columns.selectAll("text.sample").data(function(d) { return d }); sample.enter().append("svg:text") .attr("class", "sample") @@ -563,9 +618,9 @@ SampleGraphPresenter.prototype.drawNodes = function() /** * Draw the links */ -SampleGraphPresenter.prototype.drawLinks = function() +SimpleGraphRenderer.prototype.drawLinks = function() { - var link = this.viz.selectAll("path.link").data(this.links); + var link = this.viz.selectAll("path.link").data(presenter.links); link.enter().append("svg:path") .attr("class", "link") .attr("pointer-events", "none") @@ -577,42 +632,171 @@ SampleGraphPresenter.prototype.drawLinks = function() .transition() .style("opacity", 0).remove(); link - .style("stroke", function(d) { return d.sourceNode.color}) + .style("stroke", function(d) { return d.source.color}) .attr("d", this.path); } -SampleGraphPresenter.prototype.toggleExpand = function(svgNode, d) { - // toggle visiblity - d.userEdgesVisible = (null == d.userEdgesVisible) ? !d.edgesVisible :!d.userEdgesVisible; - this.draw(); +/** + * A class that renders the graph using dagre. + */ +function DagreGraphRenderer(presenter) { + // Function used to draw paths between elements + function source(d) { + // Find the associated text node in the DOM and use that as a basis for creating the links + var bbox = textBBoxForNode(d.source); + return { x : bbox.x + bbox.width + 7, y : bbox.y + yLinkOffset }; + } + function target(d) { + var bbox = textBBoxForNode(d.target); + return { x : bbox.x, y : bbox.y + yLinkOffset } + } + + this.useLineLinkPath(source, target); } -SampleGraphPresenter.prototype.openSample = function(svgNode, d) { - var url = "/openbis/index.html?viewMode=SIMPLE#entity=SAMPLE&permId=" + d.permId; - window.open(url, '_blank'); +/** + * Draw links using the diagonal function + */ +DagreGraphRenderer.prototype.useDiagonalLinkPath = function(source, target) { + var diagonal = d3.svg.diagonal(); + diagonal.source(source); + diagonal.target(target); + this.path = diagonal; } -function displayActiveMode(active, inactive) { - active.parent().addClass("active"); - inactive.parent().removeClass("active"); +/** + * Draw links using the line function + */ +DagreGraphRenderer.prototype.useLineLinkPath = function(source, target) { + var line = d3.svg.line(); + this.path = function(d) { + var src = source(d); + var dst = target(d); + return line([[src.x, src.y], [dst.x, dst.y]]); } } -function clickedBottomUp() { - if (presenter.bottomUpMode) return; +/** + * Display the sample nodes. + */ +DagreGraphRenderer.prototype.draw = function() +{ + presenter.updateState(); - displayActiveMode($('#bottom-up'), $('#top-down')); - presenter.useBottomUpMode(); - presenter.initializeGraphSamples(); - presenter.draw(); + var dagreNodes = [] + this.nodes.forEach(function(nodeGroup) { nodeGroup.forEach(function(d) { dagreNodes = dagreNodes.concat(d) }) }); + + dagre.layout() + .nodeSep(50) + .edgeSep(10) + .rankSep(50) + .nodes(dagreNodes) + .edges(this.links) + .debugLevel(1) + .run(); + console.log(dagreNodes); + + var nodes = presenter.nodes; + var vizWidth = presenter.vizWidth; + var vizHeight = presenter.vizHeight; + + // Display the graph in an SVG element + this.viz = presenter.root.selectAll("svg").data([nodes]); + // Code under enter is run if there is no HTML element for a data element + this.viz.enter().append("svg:svg").attr("class", "viz"); + this.viz.attr("width", vizWidth); + this.viz.attr("height", vizHeight); + // Columns + this.columns = this.viz.selectAll("g").data(function(d) { return d }); + this.columns.enter().append("svg:g").attr("class", "column"); + this.columns.exit().remove(); + this.columns.attr("transform", function(d, i) { return "translate(" + presenter.visibleColumns[i].xOffset + ", 0)"}); + this.drawHeaders(); + this.drawNodes(); + this.drawLinks(); } -function clickedTopDown() { - if (!presenter.bottomUpMode) return; +/** + * Draw the headers + */ +DagreGraphRenderer.prototype.drawHeaders = function() +{ + var header = this.columns.selectAll("text.header").data(function(d, i) { return [presenter.visibleColumns[i]] }); + header.enter().append("svg:text") + .attr("class", "header") + .attr("x", "0") + .attr("y", LINE_HEIGHT) + .attr("text-anchor", "begin") + .style("font-weight", "bold"); + header + .text(function(d) { return d.label }); +} - displayActiveMode($('#top-down'), $('#bottom-up')); - presenter.useTopDownMode(); - presenter.initializeGraphSamples(); - presenter.draw(); +/** + * Draw the nodes + */ +DagreGraphRenderer.prototype.drawNodes = function() +{ + var outEdgesGetter = presenter.outEdgesFunction(); + var sample = this.columns.selectAll("text.sample").data(function(d) { return d }); + sample.enter().append("svg:text") + .attr("class", "sample") + .attr("x", "0") + .attr("y", LINE_HEIGHT) + .attr("text-anchor", "begin") + .style("cursor", "pointer") + .on("click", function(d) { presenter.openSample(this, d) }) + .transition() + .style("opacity", 1); + sample.exit() + .transition() + .style("opacity", 0).remove(); + sample + .attr("x", "0") + .attr("y", function(d, i) { return LINE_HEIGHT * (i+2)}) + + .text(function(d) { return d.identifier }); + + var ring = this.columns.selectAll("circle.ring").data(function(d) { return d }); + ring.enter().append("svg:circle") + .attr("class", "ring") + .attr("pointer-events", "all") + .attr("r", 0) + .style("cursor", "pointer") + .style("stroke-width", "2px") + ring.exit() + .transition() + .style("opacity", 0).remove(); + ring + .attr("cx", function(d) { return textBBoxForNode(d).width + 7 }) + .attr("cy", function(d, i) { return LINE_HEIGHT * (i+2) - yLinkOffset}) + .style("fill", function(d) { return d.edgesVisible ? "none" : d.color}) + .style("stroke", function(d) { return d.color}) + .on("click", function(d) { presenter.toggleExpand(this, d) }); + ring + .transition() + .style("opacity", function(d) { return outEdgesGetter(d).length > 0 ? 1 : 0 }) + .attr("r", 5); +} + +/** + * Draw the links + */ +DagreGraphRenderer.prototype.drawLinks = function() +{ + var link = this.viz.selectAll("path.link").data(presenter.links); + link.enter().append("svg:path") + .attr("class", "link") + .attr("pointer-events", "none") + .style("fill", "none") + .style("stroke-width", "1.5px") + .transition() + .style("opacity", 1); + link.exit() + .transition() + .style("opacity", 0).remove(); + link + .style("stroke", function(d) { return d.source.color}) + .attr("d", this.path); }