Skip to content

Instantly share code, notes, and snippets.

@dmann99
Created April 8, 2014 01:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dmann99/10081819 to your computer and use it in GitHub Desktop.
Save dmann99/10081819 to your computer and use it in GitHub Desktop.
Node Upon Arcs
{"description":"Node Upon Arcs","endpoint":"","display":"svg","public":true,"require":[],"fileconfigs":{"inlet.js":{"default":true,"vim":false,"emacs":false,"fontSize":12},"style.css":{"default":true,"vim":false,"emacs":false,"fontSize":12},"_.md":{"default":true,"vim":false,"emacs":false,"fontSize":12},"config.json":{"default":true,"vim":false,"emacs":false,"fontSize":12},"data.json":{"default":true,"vim":false,"emacs":false,"fontSize":12},"mydata.json":{"default":true,"vim":false,"emacs":false,"fontSize":12}},"fullscreen":false,"play":false,"loop":false,"restart":false,"autoinit":true,"pause":true,"loop_type":"pingpong","bv":false,"nclones":15,"clone_opacity":0.4,"duration":3000,"ease":"linear","dt":0.01,"ajax-caching":true,"thumbnail":"http://i.imgur.com/67gwtvF.png"}
// David Mann
// @ba6dotus
// david@ba6.us
// This graph was created to show:
// o A Child (Center ring), 1..n Parents (Middle ring), 1..n Grandparents (Outer ring)
// o Ability to show relationships with some addl data mixed in,
// Each member had multiple types of data thus the nested arcs
// o Node overlay was added to convey the ancestral relationships
// Original idea from Scott Hayman.
// Things I would like to do:
// o Incorporate more data items into myTree
// o Proportional arc sizes to show more info about members
// I would like to have the size of the primary arc convey
// the proportion of the makeup of each ring
// o Basic ToolTips, ToolTips from additional data items
// See http://jsfiddle.net/kingernest/YDQR4/1/ for rollover stuff
// o Smooth animation?
// http://bl.ocks.org/mbostock/1346410
// http://bl.ocks.org/mbostock/5872848
// o Provide some real world data examples
var debug=false;
// Input JSON Object that holds hierarchy
var myTree = tributary.mydata;
// ---------------
// -- Constants --
// ---------------
// Container
var width=800;
var height=800
// Center of graphic
var xCenter = width/2;
var yCenter = height/2;
var SpacerPx = 18; // Space between nested arcs in pixels
var SpacerAng = 5; // Angle separator between nested arcs in degrees
var LevelWidth = 100; // Width of each donut level in pixels
var pi = Math.PI; // Constant for a yummy dessert
var lineWidth = 4; // Line thickness for node graph elements
// Color related code
// Some Triplets of pleasing colors
// Outside / Middle / Inside
var colorData = [
{"arccolor2" : "#B8B800", "arccolor1" : "#FFFF00", "arccolor0" : "#FFFF99"},
{"arccolor2" : "#993300", "arccolor1" : "#AD5C33", "arccolor0" : "#BD7D5C"},
{"arccolor2" : "#000066", "arccolor1" : "#333385", "arccolor0" : "#5C5C9D"},
{"arccolor2" : "#006600", "arccolor1" : "#338533", "arccolor0" : "#5C9D5C"},
{"arccolor2" : "#990033", "arccolor1" : "#AC3059", "arccolor0" : "#BD597A"},
{"arccolor2" : "#3D3D5C", "arccolor1" : "#64647D", "arccolor0" : "#838397"},
{"arccolor2" : "#CC0099", "arccolor1" : "#D633AD", "arccolor0" : "#DE5CBD"},
{"arccolor2" : "#6600FF", "arccolor1" : "#8533FF", "arccolor0" : "#9D5CFF"},
{"arccolor2" : "#FF0000", "arccolor1" : "#FF3300", "arccolor0" : "#FFA375"},
{"arccolor2" : "#33CC33", "arccolor1" : "#00FF00", "arccolor0" : "#B2FFB2"},
{"arccolor2" : "#000000", "arccolor1" : "#666666", "arccolor0" : "#B2B2B2"}
];
var colorCounter = 0;
// Set color of object, rotate through predefined palette
function setColors(myObj) {
myObj.fillcolor2 = colorData[colorCounter%colorData.length].arccolor2;
myObj.fillcolor1 = colorData[colorCounter%colorData.length].arccolor1;
myObj.fillcolor0 = colorData[colorCounter%colorData.length].arccolor0;
colorCounter++;
}
// Output to renderer
var data = new Array();
// Using current level and existance of parents
// calculate parent arc info
function setArcInfo(myObj) {
// We are at the root label leaf at targetDepth, analyze it
if (myObj.level === 0) {
myObj.arcstart=0;
myObj.arclength=360;
}
// Check for parents, if exist set their Arc Info
if (myObj.parents) {
var sliceSizeDegrees = myObj.arclength / myObj.parents.length;
// Step through parents
for (pCounter=0;pCounter<myObj.parents.length;pCounter++) {
myObj.parents[pCounter].arcstart = myObj.arcstart + (pCounter*sliceSizeDegrees);
myObj.parents[pCounter].arclength = sliceSizeDegrees;
// Determine centroid angle for this piece.
myObj.parents[pCounter].arcmid = myObj.parents[pCounter].arcstart + (sliceSizeDegrees/2);
if (debug) {
console.log(myObj.parents[pCounter].label + " | arcstart " + myObj.parents[pCounter].arcstart +
" | arclength " + myObj.parents[pCounter].arclength + "<BR>");
}
}
}
}
// Function to visit all nodes starting at center (child) node
// Pass it
function traverse(myObj, myFunc, depth ) {
// start at level zero
var depth = depth || 0;
// Separation between nodes
if (debug) {
console.log("<HR>");
}
// Step through this node's keys
for (var i in myObj) {
if (typeof(myObj[i])=="object") {
// Found an object, step down into the object tree
traverse(myObj[i], myFunc, depth+0.5);
} else if (i == 'label') {
// We are at a leaf, process it
myObj.level=depth;
if (debug) {
console.log("Depth : " + myObj.level + " KEY : " + i + " / VALUE = "+myObj[i]+"<BR>");
}
// Executed the passed in function with myObj as a parameter
myFunc(myObj);
}
}
}
// Prepare output array
function setData(myObj) {
data[ (data.length===0 ? 0 : data.length) ] = {"label": myObj.label, "level": myObj.level, "arcstart": myObj.arcstart, "arclen": myObj.arclength, "LevSpacer":0, "arccolor" : myObj.fillcolor2};
data[ (data.length===0 ? 0 : data.length) ] = {"label": myObj.label, "level": myObj.level, "arcstart": myObj.arcstart, "arclen": myObj.arclength, "LevSpacer":1, "arccolor" : myObj.fillcolor1};
data[ (data.length===0 ? 0 : data.length) ] = {"label": myObj.label, "level": myObj.level, "arcstart": myObj.arcstart, "arclen": myObj.arclength, "LevSpacer":2, "arccolor" : myObj.fillcolor0};
}
// Draw node marker
// Using angle in degrees (0 degrees is top center, increasing clockwise)
// and Level 0,1,2, draw
function SetCentroidPos(myObj) {
var angle = myObj.arcmid;
var level = myObj.level;
// Find the middle of the arc in radians
var arcmidrad = (angle/360)*2*pi;
arcmidrad = arcmidrad - (0.5*pi);
// Find the desired distance from center
var arcpos = (level*LevelWidth)+(LevelWidth*0.5);
if (level === 0) {
xPos = xCenter;
yPos = yCenter;
myObj.centroidx = xPos;
myObj.centroidy = yPos;
} else {
xPos = xCenter + (arcpos) * Math.cos((arcmidrad));
yPos = yCenter + (arcpos) * Math.sin((arcmidrad));
myObj.centroidx = xPos;
myObj.centroidy = yPos;
}
}
// Draw node marker
// Using angle in degrees (0 degrees is top center, increasing clockwise)
// and Level 0,1,2, draw
function DrawNode(myObj) {
var myDotRadius=LevelWidth/10;
svg.append("circle")
.attr("r",myDotRadius)
.attr("cx",myObj.centroidx)
.attr("cy",myObj.centroidy)
.attr("class","node")
.on("mouseover",mouseOverNode);
}
function mouseOverNode() {
// Todo: Make this work!
var myCircle = d3.select(this);
myCircle.attr("fill","gray");
// d3.select(this).attr("fill", "black"); //Color circle green
console.log("mouseOverNode");
// tooltip.html( //Populate tooltip text
// "Username: " + d3.select(this).attr("username") + "<br/>" +
// "Session ID: " + d3.select(this).attr("sessionid") + "<br/>" +
// "Impact CPU: " + d3.select(this).attr("impact")
// )
// .transition()
// .duration(250)
// .style("opacity", .7);
}
function DrawEdges(myObj) {
// Build edges
// For each edge build from the child to the parent
// In getData, use this info to determine a node's source and parent coords for an edge
// Check for parents, if exist draw lines to them
if (myObj.parents) {
// Step through parents
for (pCounter=0;pCounter<myObj.parents.length;pCounter++) {
svg.append("line")
.attr("x1", myObj.centroidx)
.attr("y1", myObj.centroidy)
.attr("x2", myObj.parents[pCounter].centroidx)
.attr("y2", myObj.parents[pCounter].centroidy)
.style("stroke", "black")
.style("stroke-width", lineWidth);
}
}
}
// definitions for arc, col, title callbacks
var arc = d3.svg.arc()
.innerRadius(function(d,i) {
if (d.level===0) {
// Level 0 = inner circle, has to be handled differently
return 0;
} else {
return (LevelWidth*(d.level)+(d.LevSpacer*SpacerPx));
}
})
.outerRadius(function(d,i) {return (LevelWidth*(d.level+1)-(d.LevSpacer*SpacerPx)); })
.startAngle(function(d,i) {
var myretval;
if (d.level===0) {
// Level 0 = inner circle, has to be handled differently
myretval = ( (d.arcstart) * pi / 180 );
} else {
myretval = ( (d.arcstart+(d.LevSpacer*SpacerAng)) * pi / 180 );
}
return myretval;
})
.endAngle(function(d) {
var myretval;
if (d.level===0) {
// Level 0 = inner circle, has to be handled differently
myretval = ( ( (d.arcstart+d.arclen)) * pi / 180);
} else {
myretval = ( ( (d.arcstart+d.arclen-(d.LevSpacer*SpacerAng))) * pi / 180);
}
return myretval;
});
var col = function(d) { return ( d.arccolor ); };
var mytitle= function(d) { return ( d.label ); };
// Create container and draw in it
var svg=d3.select("svg");
//var svg = d3.select("body")
// .append("svg:svg")
// .attr("width", width)
// .attr("height", height);
var chartContainer = svg.append("g")
.attr('class', 'some_class')
.attr("transform", "translate(400, 400)");
// Draw Donuts
// Make multiple passes at the data by traversing tree
// and executing these functions against the nodes
traverse( myTree, setArcInfo );
traverse( myTree, setColors );
traverse( myTree, setData );
// Now that data is in place, build the arcs
chartContainer.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("d", arc)
.attr("fill",col)
.attr("class","bar")
.attr('title',function(d){
return d.label;
});
// Build the node graph
traverse( myTree, SetCentroidPos );
traverse( myTree, DrawEdges );
traverse( myTree, DrawNode );
{
"label" : "LEV0-1of1",
"parents": [
{
"label": "LEV1-1of2",
"parents": [
{"label": "LEV2-1of5"},
{"label": "LEV2-2of5"},
{"label": "LEV2-3of5"},
{"label": "LEV2-4of5"} ]
},
{
"label": "LEV1-2of2",
"parents": [
{"label": "LEV2-1of3"},
{"label": "LEV2-2of3"},
{"label": "LEV2-3of3"}
]
},
{
"label": "LEV1-3of3",
"parents": [
{"label": "LEV2-1of3"},
{"label": "LEV2-2of3"}
]
}
]
}
.bar {
display:inline-block;
width: 3px; border:1px outset #69f;
background: #369; border-bottom:1px solid #000;
}
.bar:hover { fill:#EEE;
stroke : #AAA;}
/*
.bar:hover { fill:#EEE;
stroke : #EEE;
stroke-width=10px}
*/
.node {
stroke:#000;
stroke-width:3;
stroke-color:#000;
fill : #EEE;
}
.node:hover { fill:#AAA; }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment