Created
December 5, 2012 07:15
-
-
Save biovisualize/4213367 to your computer and use it in GitHub Desktop.
Reusable Circular Network with Editable Data Table
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var Table = function module() { | |
var dispatch = d3.dispatch("edit"); | |
function exports(_selection) { | |
_selection.each(function (_dataset) { | |
//________________________________________________ | |
// Data | |
//________________________________________________ | |
var data = _dataset[0]; | |
var columnNames = _dataset[1]; | |
//________________________________________________ | |
// Table | |
//________________________________________________ | |
var table = d3.select(this).selectAll("table").data([0]); | |
table.enter().append('table') | |
.append('tr') | |
.attr('class', 'header-row') | |
.selectAll('th') | |
.data(columnNames) | |
.enter().append('th') | |
.attr('class', 'header .no-select') | |
.text(function(d, i){return d;}); | |
var rows = table.selectAll('tr.row') | |
.data(data); | |
rows.enter().append('tr') | |
.attr('class', 'row'); | |
var cells = rows.selectAll('td.cell') | |
.data(function(d, i){return d;}) | |
cells.enter().append('td') | |
.attr({class: 'cell', contenteditable: true}); | |
cells.text(function(d, i){return d;}) | |
.on("keyup", function(d, i){ | |
var newData = []; | |
d3.select('.table').selectAll('tr.row').selectAll('td') | |
.each(function(d, i, pI){ | |
var text = d3.select(this).text(); | |
if (typeof newData[pI] == 'undefined') newData[pI] = []; | |
newData[pI].push(text) | |
}); | |
dispatch.edit(newData); | |
}); | |
}); | |
} | |
d3.rebind(exports, dispatch, "on"); | |
return exports; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var CircularNetwork = function module() { | |
var opts = { | |
width: 200, | |
height: 200, | |
margins: {top:30, right:30, bottom:30, left:30}, | |
fillColorList: [ | |
"#3182bd", "#6baed6", "#9ecae1", "#c6dbef", | |
"#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", | |
"#31a354", "#74c476", "#a1d99b", "#c7e9c0", | |
"#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", | |
"#636363", "#969696", "#bdbdbd", "#d9d9d9" | |
], | |
enableTooltips: true, | |
enableBringToFront: true, | |
labelOffset: 5 | |
}; | |
function exports(_selection) { | |
_selection.each(function (_dataset) { | |
//________________________________________________ | |
// Data | |
//________________________________________________ | |
var dataset = d3.transpose(_dataset); | |
var start = dataset[0]; | |
var end = dataset[1]; | |
var weight = (dataset[2] == undefined) | |
? start.map(function(d, i){return 1;}) | |
: dataset[2].map(function(d){return parseInt(d);}); | |
//________________________________________________ | |
// Data transform | |
//________________________________________________ | |
var transformedData = d3.transpose([start, end, weight]); | |
// list of unique values to use as index | |
var unique = []; | |
transformedData.forEach(function(d, i){ | |
if(unique.indexOf(d[0]) == -1) unique.push(d[0]); | |
if(unique.indexOf(d[1]) == -1) unique.push(d[1]); | |
}); | |
// init square matrix | |
var matrix = d3.range(unique.length).map(function(){ | |
return d3.range(unique.length).map(function(){return 0;}); | |
}); | |
// compute matrix | |
transformedData.forEach(function(d, i){ | |
var row = unique.indexOf(d[1]); | |
var col = unique.indexOf(d[0]); | |
matrix[col][row] = d[2]; | |
}); | |
//________________________________________________ | |
// DOM selection | |
//________________________________________________ | |
var chartW = Math.max(opts.width - opts.margins.left - opts.margins.right, 0.1); | |
var chartH = Math.max(opts.height - opts.margins.top - opts.margins.bottom, 0.1); | |
var svg = d3.select(this).selectAll("svg").data([0]); | |
svg.enter().append("svg").attr({width: opts.width, height: opts.height}) | |
.append("g").attr({class: "vis-group", | |
transform: "translate(" + (opts.margins.left + chartW/2) + "," + (opts.margins.top + chartH/2) + ")"}); | |
var chartSVG = d3.select("g.vis-group"); | |
//________________________________________________ | |
// Circular network | |
//________________________________________________ | |
var fill = function(i) { | |
return opts.fillColorList[i % opts.fillColorList.length]; | |
} | |
var chord = d3.layout.chord().padding(.05).matrix(matrix); | |
var r1 = Math.min(chartW, chartH) / 2; | |
var r0 = r1 * 0.9; | |
// draw arcs | |
var groupSVG = chartSVG.selectAll("path.arc") | |
.data(chord.groups); | |
groupSVG.enter().append("path") | |
.attr("class", "arc") | |
groupSVG.attr("d", d3.svg.arc() | |
.innerRadius(r0) | |
.outerRadius(r1)) | |
.style("fill", function(d){ return fill(d.index);}); | |
groupSVG.exit().remove(); | |
// draw chords | |
var chordSVG = chartSVG.selectAll("path.link") | |
.data(chord.chords); | |
chordSVG.enter() | |
.append("path") | |
.attr("class", "link") | |
.call(highlight); | |
chordSVG.attr("d", d3.svg.chord().radius(r0)) | |
.style("fill",function (d) {return fill(d.target.index);}); | |
chordSVG.exit().remove(); | |
if(opts.enableBringToFront) chordSVG.call(bringToFront); | |
function highlight(_selection){ | |
_selection | |
.on("mouseover.highlight", function(d, i) { | |
d3.select(this).classed({highlighted: true}); | |
}) | |
.on("mouseout.highlight", function(d, i){ | |
d3.select(this).classed({highlighted: false}); | |
}); | |
} | |
function bringToFront(_selection){ | |
_selection.on("mouseover.toFront", function() { | |
var dragTarget = event.target; | |
dragTarget.parentNode.appendChild( dragTarget ); | |
}); | |
} | |
//________________________________________________ | |
// Labels | |
//________________________________________________ | |
var labelSVG = chartSVG.selectAll("text.label") | |
.data(chord.groups); | |
labelSVG.enter().append("text") | |
.attr("class", "label"); | |
labelSVG.each(function(d){ d.angle = (d.startAngle + d.endAngle) / 2; }) | |
.attr("text-anchor", function(d){ return d.angle > Math.PI ? "end" : null; }) | |
.attr("transform", function(d){ | |
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")" | |
+ "translate(" + (r1 + opts.labelOffset) + ")" | |
+ (d.angle > Math.PI ? "rotate(180)" : ""); | |
}) | |
.text(function(d, i){ return unique[i];}); | |
labelSVG.exit().remove(); | |
//________________________________________________ | |
// Tooltips | |
//________________________________________________ | |
if(opts.enableTooltips){ | |
var groupTooltip = tooltip() | |
.accessor(function(d, i){ | |
return "<strong>start</strong>: " + unique[i]; }); | |
var chordTooltip = tooltip() | |
.accessor(function(d, i){ | |
var tipText = "<strong>start</strong>: " + unique[d.source.index] + "<br/> " | |
+ "<strong>weight</strong>: " + d.source.value + "<br/> " | |
+ "to<br/>" | |
+ "<strong>end</strong>: " + unique[d.target.index] + "<br/>" | |
+ "<strong>weight</strong>: " + d.target.value; | |
return tipText; | |
}); | |
groupSVG.call(groupTooltip); | |
chordSVG.call(chordTooltip); | |
} | |
}); | |
} | |
exports.opts = opts; | |
createAccessors(exports); | |
return exports; | |
}; | |
var tooltip = function module(){ | |
var opts = { | |
accessor: function(d, i){return d;} | |
}; | |
function exports(selection){ | |
var tooltipDiv; | |
var body = d3.select("body"); | |
selection.on("mouseover.tooltip", function(d, i){ | |
d3.select("body").selectAll("div.tooltip").remove(); | |
tooltipDiv = body.append("div").attr("class", "tooltip"); | |
var absoluteMousePos = d3.mouse(body.node()); | |
tooltipDiv.style("left", (absoluteMousePos[0] + 10)+"px") | |
.style("top", (absoluteMousePos[1] - 15)+"px") | |
.style("position", "absolute") | |
.style("z-index", 1001); | |
var tooltipText = opts.accessor(d, i) || ""; | |
tooltipDiv.html(tooltipText); | |
}) | |
.on("mousemove.tooltip", function(d, i) { | |
var absoluteMousePos = d3.mouse(body.node()); | |
tooltipDiv.style("left", (absoluteMousePos[0] + 10)+"px") | |
.style("top", (absoluteMousePos[1] - 15)+"px"); | |
}) | |
.on("mouseout.tooltip", function(d, i){ | |
tooltipDiv.remove(); | |
}); | |
}; | |
exports.opts = opts; | |
createAccessors(exports); | |
return exports; | |
}; | |
function createAccessors(visExport) { | |
for (var n in visExport.opts) { | |
if (!visExport.opts.hasOwnProperty(n)) continue; | |
visExport[n] = (function(n) { | |
return function(v) { | |
return arguments.length ? (visExport.opts[n] = v, this) : visExport.opts[n]; | |
} | |
})(n); | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.js"></script> | |
<script type="text/javascript" src="./circular_network.js"></script> | |
<script type="text/javascript" src="./basic-editable-table.js"></script> | |
<style> | |
.reset-button{ | |
width: 50px; | |
height: 25px; | |
border: 1px solid black; | |
background-color: white; | |
cursor: pointer; | |
text-align: center; | |
position: absolute; | |
top: 260px; | |
} | |
.reset-button:hover{ | |
background-color: #eee; | |
} | |
.reset-button:active{ | |
background-color: #ccc; | |
} | |
.no-select{ | |
-webkit-touch-callout: none; | |
-webkit-user-select: none; | |
-khtml-user-select: none; | |
-moz-user-select: none; | |
-ms-user-select: none; | |
user-select: none; | |
} | |
table{ | |
border-collapse: collapse; | |
} | |
.table{ | |
position: absolute; | |
top: 100px; | |
} | |
.container{ | |
position: absolute; | |
top: 0; | |
left: 250px; | |
} | |
td, th{ | |
border: 1px solid black; | |
background-color: white; | |
width: 70px; | |
padding: 5px; | |
text-align: left; | |
} | |
th.header{ | |
cursor: default; | |
} | |
.tooltip{ | |
background-color: aliceblue; | |
pointer-events: none; | |
margin-left: 10px; | |
padding: 10px; | |
opacity: 0.8; | |
} | |
svg text.label{ | |
fill: black; | |
font: 10px Verdana; | |
} | |
svg path.arc{ | |
stroke: grey; | |
} | |
svg path.link{ | |
stroke: grey; | |
opacity: 0.9; | |
} | |
.highlighted{ | |
opacity: 1 !important; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="table"></div> | |
<div class="reset-button no-select">Reset</div> | |
<div class="container"></div> | |
<script type="text/javascript"> | |
// Data | |
var data = [ | |
['page0', 'page1', 200], | |
['page1', 'page0', 50], | |
['page2', 'page0', 100], | |
['page0', 'page2', 100] | |
]; | |
var columnNames = ['Start', 'End', 'Weight'] | |
// Charts | |
var table = Table() | |
.on('edit', function(d){ update(d) }); | |
var circularNetwork = CircularNetwork() | |
.width(500) | |
.height(500) | |
.margins({top:50, right:50, bottom:50, left:50}) | |
.enableTooltips(true) | |
.labelOffset(5); | |
// Bind charts to DOM | |
function update(_data){ | |
d3.select('.container') | |
.datum(_data) | |
.call(circularNetwork); | |
} | |
function updateTable(_data){ | |
d3.select('.table') | |
.datum(_data) | |
.call(table); | |
} | |
update(data); | |
updateTable([data, columnNames]) | |
// Update charts on reset | |
d3.select('.reset-button') | |
.on("mouseup", function(d, i){ | |
update(data); | |
updateTable([data, columnNames]) | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment