|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<title>D3 Matrix Example</title> |
|
<script src="http://d3js.org/d3.v2.min.js"></script> |
|
</head> |
|
<style> |
|
body { |
|
font-family:"Lucida Grande","Droid Sans",Arial,Helvetica,sans-serif; |
|
} |
|
.axis path, |
|
.major line { |
|
fill: none; |
|
stroke: black; |
|
stroke-width: 0; |
|
shape-rendering: crispEdges; |
|
} |
|
.minor { |
|
fill: none; |
|
stroke: #C0C0C0; |
|
stroke-width: 1; |
|
shape-rendering: crispEdges; |
|
} |
|
.legend { |
|
border: 1px solid #555555; |
|
border-radius: 5px 5px 5px 5px; |
|
font-size: 0.8em; |
|
margin: 10px; |
|
padding: 8px; |
|
} |
|
.axis text { |
|
font-size: 0.9em; |
|
} |
|
.bld { |
|
font-weight: bold; |
|
} |
|
</style> |
|
<body> |
|
|
|
</body> |
|
<script> |
|
|
|
var raw = '{"responses":[{"newidea":"1","self":"0","grpIndex":"0","quIndex":0},{"newidea":"0","self":"0","grpIndex":"0","quIndex":1},{"newidea":"1","self":"0","grpIndex":"5","quIndex":0},{"newidea":"1","self":"0","grpIndex":"6","quIndex":0},{"newidea":"1","self":"0","grpIndex":"3","quIndex":0},{"newidea":"0","self":"1","grpIndex":"6","quIndex":0},{"newidea":"0","self":"0","grpIndex":"6","quIndex":1},{"newidea":"1","self":"0","grpIndex":"2","quIndex":1},{"newidea":"0","self":"1","grpIndex":"6","quIndex":1},{"newidea":"1","self":"0","grpIndex":"4","quIndex":1},{"newidea":"1","self":"0","grpIndex":"6","quIndex":1},{"newidea":"0","self":"1","grpIndex":"4","quIndex":1},{"newidea":"1","self":"0","grpIndex":"1","quIndex":1},{"newidea":"0","self":"0","grpIndex":"6","quIndex":2},{"newidea":"0","self":"0","grpIndex":"2","quIndex":2},{"newidea":"1","self":"0","grpIndex":"6","quIndex":2},{"newidea":"1","self":"0","grpIndex":"6","quIndex":2},{"newidea":"1","self":"0","grpIndex":"2","quIndex":2},{"newidea":"1","self":"0","grpIndex":"6","quIndex":2}],"groups":[{"group_name":"Bob","type":"g","indx":"0"},{"group_name":"Sally","type":"g","indx":"1"},{"group_name":"Jim","type":"g","indx":"2"},{"group_name":"Susan","type":"g","indx":"3"},{"group_name":"Tom","type":"g","indx":"4"},{"id":"100","group_name":"Frank","type":"u","indx":"5"},{"id":"14","group_name":"Christina","type":"u","indx":"6"}],"questions":[{"indx":0,"title":"Hypothesis"},{"indx":1,"title":"Results"},{"indx":2,"title":"Conclusion"}]}'; |
|
var json =JSON.parse(raw); |
|
var w = 900, |
|
h = 600, |
|
lineheight = 50, |
|
colwidth = 60, |
|
leftpad = 120, |
|
toppad = 50, |
|
axispad = 40, |
|
titlelength = 10; |
|
|
|
// list of all posts |
|
var nodes = json.responses; |
|
|
|
// dynamically calculate height and width based on the input |
|
h = (json.groups.length * lineheight) + toppad; |
|
w = (json.questions.length * colwidth) + leftpad; |
|
|
|
// create svg |
|
var svg = d3.select("body").append("svg:svg") |
|
.attr("width", w + leftpad) |
|
.attr("height", h + toppad); |
|
|
|
// initialize force graph |
|
var force = d3.layout.force() |
|
.nodes(nodes) |
|
.links([]) |
|
.gravity(0) |
|
.charge(-40) |
|
.size([w, h]) |
|
.start(); |
|
|
|
// scale for axis |
|
var xScale = d3.scale.linear() |
|
.domain([0, json.questions.length-1]) |
|
.range([leftpad + axispad, w + axispad]); |
|
var yScale = d3.scale.linear() |
|
.domain([0, json.groups.length-1]) |
|
.range([h, toppad + axispad]); |
|
|
|
// individual axis |
|
var xAxis = d3.svg.axis() |
|
.scale(xScale) |
|
.orient("top") |
|
.ticks(json.questions.length-1) |
|
.tickSubdivide(true) |
|
.tickSize(-h+axispad/2) |
|
.tickFormat(function(d,i){ |
|
return json.questions[i].title.substr(0, titlelength); |
|
}); |
|
|
|
var yAxis = d3.svg.axis() |
|
.scale(yScale) |
|
.orient("left") |
|
.ticks(json.groups.length-1) |
|
.tickSubdivide(true) |
|
.tickSize(-w+axispad) |
|
.tickFormat(function(d,i){ |
|
return json.groups[i].group_name; |
|
}); |
|
|
|
// create the actual circles |
|
var node = svg.selectAll(".node") |
|
.data(json.responses) |
|
.enter().append("circle") |
|
.attr("class", "node") |
|
.attr("cx", function(d) { |
|
return xScale(d.quIndex); |
|
}) |
|
.attr("cy", function(d) { |
|
return yScale(d.grpIndex); |
|
}) |
|
.attr("r", 8) |
|
.style("fill", function(d,i) { |
|
// determines the color |
|
return d.newidea==1 ? 'green' : d.self==1 ? 'yellow' : 'blue'; |
|
}) |
|
.style("stroke", "#555") |
|
.call(force.drag); |
|
|
|
svg.append("g") |
|
.attr("class", "axis") |
|
.attr("transform", "translate(0," + (toppad) + ")") |
|
.call(xAxis); |
|
|
|
svg.append("g") |
|
.attr("class", "axis") |
|
.attr("transform", "translate(" + leftpad + ",0)") |
|
.call(yAxis); |
|
|
|
// text of X axis |
|
svg.append("text") |
|
.attr("y", 15) |
|
.attr("x",w/2 + 50) |
|
.attr("class", "bld") |
|
.text("Prompt"); |
|
|
|
// Text of Y axis, rotate it sideways |
|
svg.append("text") |
|
.attr("transform", "rotate(-90)") |
|
.attr("y", 10) |
|
.attr("x",-h/2-toppad) |
|
.attr("class", "bld") |
|
.attr("dy", "1em") |
|
.style("text-anchor", "middle") |
|
.text("User"); |
|
|
|
|
|
force.on("tick", function(e) { |
|
|
|
// Push nodes toward their designated focus. |
|
var k = .55 * e.alpha; |
|
nodes.forEach(function(o, i) { |
|
// sets the focus of each node to the intersection of the scaled x and y location |
|
// this is what makes the force graph multi-focal |
|
o.y += (yScale(o.grpIndex) - o.y) * k; |
|
o.x += (xScale(o.quIndex) - o.x) * k; |
|
}); |
|
|
|
svg.selectAll("circle.node") |
|
.attr("cx", function(d) { return d.x; }) |
|
.attr("cy", function(d) { return d.y; }); |
|
}); |
|
|
|
</script> |
|
<br/> |
|
<div class="legend" style="width: 400px; float: left;"> |
|
<svg height="20" width="500"> |
|
<circle cx="10" cy="10" r="8" fill="green"/> |
|
<text x="25" y="15">New Ideas</text> |
|
<circle cx="115" cy="10" r="8" fill="blue"/> |
|
<text x="130" y="15">Respond to Others</text> |
|
<circle cx="260" cy="10" r="8" fill="yellow"/> |
|
<text x="275" y="15">Respond to Myself</text> |
|
</svg> |
|
</div> |
|
<br><br> |
|
</html> |