structures.js |
|
---|---|
Copyright 2013 Allen Institute for Brain Science Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. |
|
A an example visualization of connectivity data using the d3 partition and bundle layouts. |
|
Hard-coded links to the data files to be used in this visualization |
var structuresFile = "all.structures.json";
var connFile = "all.connections.json";
if (!Modernizr.svg){
$('#chart').append(
$('<div>').addClass('svgError')
.html("This demo requires SVG support. " +
"Click <a href='http://caniuse.com/#cats=SVG'>here</a> " +
"to see which browsers support SVG."));
throw 'SVG not supported';
} |
Constants that control properties of the visualization:
|
var w = $("#chart").width();
var h = $("#chart").height();
var r = Math.min(w, h) / 2;
var outerPct = .30;
var colorRange = ["green","blue"];
var opacityRange = [.1,0.2];
var fadedOpacityRange = [0, .05];
var highlightOpacityRange = [.4,0.7];
var tensionRange = [0,.99];
var transitionTime = 1000;
var fastTransitionTime = 200;
var rootStructureId = 8; |
Scales for interpolating a range of colors/angles/values/etc
|
var x = d3.scale.linear().range([0, 2 * Math.PI]);
var y = d3.scale.linear().range([r, r*(1.0 - outerPct)]);
var iy = d3.scale.linear().range([0, r*(1.0 - outerPct)]);
var distScale = d3.scale.linear().clamp(true)
.domain([-1,-.5,0,.5,1]).range([0,.5,0,.5,0]);
var connColorScale = d3.scale.linear().clamp(true).range(colorRange);
var connOpacityScale = d3.scale.linear().clamp(true).range(opacityRange);
var connFadedOpacityScale = d3.scale.linear().clamp(true).range(fadedOpacityRange);
var connHighlightOpacityScale = d3.scale.linear().clamp(true).range(highlightOpacityRange);
var connTensionScale = d3.scale.sqrt().clamp(true).domain([0,.5]).range(tensionRange); |
jQuery’d variables representing html buttons and labels. |
var structureLabel = $("#structureLabel");
var volumeLabel = $("#volumeLabel");
var corrLabel = $("#corrLabel");
var connectionFromLabel = $("#connectionFromLabel");
var connectionToLabel = $("#connectionToLabel");
var scaleButtonContainer = $("#scaleButtons"); |
Fill the |
var scaleOptions = [{ name: 'volume', id: "#volumeButton", fn: volumeValue },
{ name: 'uniform', id: "#uniformButton", fn: uniformValue }];
$.each(scaleOptions, function(index, button) {
var name = button.name;
scaleButtonContainer.append($(document.createElement('input'))
.attr('type','radio')
.attr('id',name+'Button')
.attr('name','scaleRadio')
.attr('value',name)
.attr('checked',name=='uniform'));
scaleButtonContainer.append($(document.createElement('label'))
.attr('for',name+'Button')
.html(name));
});
scaleButtonContainer.buttonset();
scaleButtonContainer.css("font-size",8);
$("#homeButton").button({ icons: { primary: "ui-icon-home" }}); |
These are the functions used to scale the structure arcs. |
function volumeValue(d) { return d.volume; }
function uniformValue(d) { return 1; } |
Construct the basic chart element, append an svg element and transform it. |
var vis = d3.select("#chart").append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")"); |
Construct the d3 visualization algorithm objections. The The |
var partition = d3.layout.partition()
.sort(function(a,b) { return b.graph_order - a.graph_order; })
.value(uniformValue);
var bundle = d3.layout.bundle();
var arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
.innerRadius(function(d) { return Math.max(0, y(d.y)); })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
var connectionLine = d3.svg.line.radial()
.interpolate("bundle")
.radius(function(d) { return d.terminal ?
Math.max(0, y(d.node.y + d.node.dy)) :
Math.max(0, iy(d.node.y + d.node.dy)); })
.angle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.node.x + .5*d.node.dx))); }); |
initialize some globals that will be defined once the data is loaded. |
var nodes = null;
var links = null;
var bundledLinks = null;
var connections = null;
var arcs = null;
var lines = null; |
Load the data and lay out the SVG elements.This is asynchronous, so the second argument to each |
d3.json(structuresFile, function(structures) { |
Build a hash from structure id to structure. Make parent-child relationships explicit. |
var structureHash = {};
for (var i=0; i<structures.length; i++) {
var s = structures[i];
s.children = [];
structureHash[s.id] = s;
}
for (var i = 0; i < structures.length; i++) {
var s = structures[i];
var numParents = s.structure_id_path.length - 1;
if (numParents > 0) {
var parentId = s.structure_id_path[numParents-1];
structureHash[parentId].children.push(s);
}
} |
This is the root node of the ontology. |
var structureTree = structureHash[rootStructureId]; |
Read in the connection file. |
d3.json(connFile, function(connData) { |
Transform the connections so that source and target actually point to structures rather than structure IDs. |
connections = connData.map(function(c) {
return {
source: structureHash[c.source],
target: structureHash[c.target],
corr: c.corr
};
}); |
Compute the minimum and maximum correlation values and update the scales accordingly. |
updateCorrelationDomain(
connections.reduce(function(r,c) {
return [Math.min(c.corr,r[0]), Math.max(c.corr,r[1])]
}, [10,-10])); |
Filter out connections from child to parent or vice-versa. |
connections = connections.filter(function(c) {
return !childOf(c.source,c.target) && !childOf(c.target,c.source)
}); |
Supply the partition algorithm with the ontology, add an svg group to the chart to hold the structure arcs, and draw them. |
nodes = partition.nodes(structureTree);
vis.append("svg:g").attr("class","arc");
redrawArcs(); |
Add a property to the connections — angular distance on the plot. |
for (var i=0; i<connections.length; i++) {
var c = connections[i];
var src = c.source;
var trg = c.target;
c.dist = distScale((src.x+.5*src.dx) - (trg.x+.5*trg.dx));
} |
Flatten the ontology into arrays separated by ontology depth, then |
vis.append("svg:g").attr("class","line");
redrawLines(); |
Register Click event handlers for buttons that determine if structure arcs are scaled uniformly or by their volume. |
$.each(scaleOptions, function(index, button) {
$(button.id).click(function() {
arcs.data(partition.value(button.fn).nodes(structureTree))
.style("fill", arcColor)
.style("stroke", arcStroke)
.style("stroke-width", arcStrokeWidth)
.transition()
.duration(transitionTime)
.attrTween("d",dataTween);
redrawLines();
});
});
});
}); |
A function handler for interpolating between the scales. Currently not used. |
function arcTween(d) {
var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]);
return function(d, i) {
return i ?
function(t) { return arc(d); } :
function(t) { x.domain(xd(t)); return arc(d); };
};
} |
Add properties to an element that cache their current position and width. |
function stash(d) {
d.x0 = d.x;
d.dx0 = d.dx;
} |
Interpolate the arcs in data space. |
function dataTween(d) {
var i = d3.interpolate({x: d.x0, dx: d.dx0}, d);
return function(t) {
var b = i(t);
d.x0 = b.x;
d.dx0 = b.dx;
return arc(b);
};
} |
Methods for drawing linesDraw the connection curves. |
function redrawLines() { |
If there are no bundled links yet, they need to be built. |
if (!bundledLinks) {
bundledLinks = bundle(connections); |
This is called a “data join.” Within the group ‘line’, path elements
are joined to data supplied in the |
lines = vis.selectAll("g.line").selectAll("path")
.data(bundledLinks)
.attr("d", lineLayout)
.style("opacity",0);
lines.transition()
.duration(fastTransitionTime)
.style("opacity",lineOpacity); |
The |
lines.enter().append("svg:path")
.attr("d", lineLayout)
.style("fill","none")
.style("opacity", 0)
.style("stroke", lineColor)
.style("stroke-width", 1)
.on("mouseover", lineMouseOver)
.on("mouseout", lineMouseOut)
.transition()
.duration(fastTransitionTime)
.style("opacity",lineOpacity); |
The ‘exit’ method is called whenever data elements are removed (which happens when the number of nodes has changed). The transition fades them out before they actually get removed from the DOM. |
lines.exit().transition()
.duration(fastTransitionTime)
.style("opacity",0)
.remove();
} else { |
this is a transition. d3 makes this very easy, for CSS properties that are easy to interpolate, just supply the data, call for a transition, and tell it what property is changing. |
vis.selectAll("g.line").selectAll("path")
.data(bundledLinks)
.transition()
.duration(transitionTime)
.attr("d", lineLayout)
.style("stroke", lineColor)
.style("opacity",lineOpacity);
}
} |
This function takes a connection ( |
function lineLayout(d,i) {
var c = connections[i];
if (c.source.id == c.target.id)
return null;
var tension = connTensionScale(c.dist);
td = d.map(function(di) { return { node: di, terminal: di == c.source || di == c.target }; });
return connectionLine.tension(tension)(td);
} |
Map a connection to a color |
function lineColor(d,i) {
return connColorScale(connections[i].corr);
} |
Map a connection to an opacity |
function lineOpacity(d,i) {
return connOpacityScale(connections[i].corr);
} |
Map a connection to an opacity, fade out elements that are not children of the supplied parent. |
function lineStructureHighlightOpacity(d,parent,i) {
if (childOf(d[0],parent) || childOf(d[d.length-1],parent))
return connHighlightOpacityScale(connections[i].corr);
else
return connFadedOpacityScale(connections[i].corr);
} |
Determine if a function is a child of another node by moving up the ontology. |
function childOf(node,parent) {
while (node) {
if (node.id == parent.id)
return true;
node = node.parent;
}
return false;
} |
Map a connection to an opacity, fade out lines that don’t start and end at the same location as the supplied line. |
function lineHighlightOpacity(d,line,i) {
if (d[0].id == line[0].id &&
d[d.length-1].id == line[line.length-1].id)
return connHighlightOpacityScale(connections[i].corr);
else
return connFadedOpacityScale(connections[i].corr);
} |
Highlight a line as the user mouses over it. |
function lineMouseOver(d,i) {
connectionFromLabel.html(d[0].name);
connectionToLabel.html(d[d.length-1].name);
corrLabel.html(connections[i].corr);
lines.transition().duration(fastTransitionTime)
.style("opacity", function(datum,i) { return lineHighlightOpacity(datum, d, i); });
} |
Go back to regular opacity when not mousing over a line. |
function lineMouseOut(d) {
connectionFromLabel.html("");
connectionToLabel.html("");
corrLabel.html("");
lines.transition().duration(fastTransitionTime)
.style("opacity",lineOpacity);
} |
Methods for drawing arcsRemove all existing structure arcs and draw thm again. |
function redrawArcs() { |
lay out the structure arcs |
vis.selectAll("g.arc").selectAll("path").remove();
arcs = vis.selectAll("g.arc").selectAll("path")
.data(nodes)
.enter().append("svg:path")
.attr("d", arc)
.style("fill", arcColor)
.style("stroke", arcStroke)
.style("stroke-width", arcStrokeWidth)
.on("mouseover", arcMouseOver)
.on("mouseout", arcMouseOut)
.each(stash);
} |
Clear all highlight information when the mouse leaves an arc. |
function arcMouseOut(d) {
structureLabel.html("");
volumeLabel.html("");
if (lines) {
lines.transition().duration(fastTransitionTime)
.style("opacity",lineOpacity);
}
} |
When the mouse is over an arc, describe its corresponding structure and highlight all of the lines entering or leaving it and its children. |
function arcMouseOver(d) {
structureLabel.html(d.name);
volumeLabel.html(d.volume);
if (lines) {
lines.transition().duration(fastTransitionTime)
.style("opacity",function(datum,i) { return lineStructureHighlightOpacity(datum, d,i); })
}
} |
Map a structure to a color. |
function arcColor(d) {
if (d.depth > 0)
return "#" + d.color_hex_triplet;
else
return "#fff";
} |
Map a sturcture to an opacity. |
function arcOpacity(d) {
return 1;
} |
White outlines for the structure arcs. |
function arcStroke(d) {
return "#fff";
} |
How wide of an outline to draw for each arc. |
function arcStrokeWidth(d) {
return "1px";
} |
Update the domain of all of the interpolation scales. |
function updateCorrelationDomain(corrDomain) {
connColorScale.domain(corrDomain);
connOpacityScale.domain(corrDomain);
connFadedOpacityScale.domain(corrDomain);
connHighlightOpacityScale.domain(corrDomain);
} |