Skip to content

Instantly share code, notes, and snippets.

@jdutta
Last active August 29, 2015 14:12
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 jdutta/a3ae3755c4a61dbd7e2b to your computer and use it in GitHub Desktop.
Save jdutta/a3ae3755c4a61dbd7e2b to your computer and use it in GitHub Desktop.
people-projects
{"description":"people-projects","endpoint":"","display":"svg","public":true,"require":[],"fileconfigs":{"inlet.js":{"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},"s_action.csv":{"default":true,"vim":false,"emacs":false,"fontSize":12},"inlet.css":{"default":true,"vim":false,"emacs":false,"fontSize":12},"c_ip.csv":{"default":true,"vim":false,"emacs":false,"fontSize":12},"chart_data.csv":{"default":true,"vim":false,"emacs":false,"fontSize":12},"style.css":{"default":true,"vim":false,"emacs":false,"fontSize":12},"foo.csv":{"default":true,"vim":false,"emacs":false,"fontSize":12},"foo2.json":{"default":true,"vim":false,"emacs":false,"fontSize":12},"data.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/17M6lV0.png"}
{
"categoriesMap": {
"project1": {
"name": "Project1"
},
"project2": {
"name": "Project2"
},
"project3": {
"name": "Project3"
},
"project4": {
"name": "Project4"
}
},
"locationsMap": {
"sfo": {
"name": "San Francisco"
},
"sjc": {
"name": "San Jose"
}
},
"people": [
{
"name": "Fname1 Lname1",
"cats": ["project1"],
"locid": "sjc",
"imgUrl": ""
},
{
"name": "Fname2 Lname2",
"cats": ["project3"],
"locid": "sjc",
"imgUrl": ""
},
{
"name": "Fname3 Lname3",
"cats": ["project1"],
"locid": "sfo",
"imgUrl": ""
},
{
"name": "Fname4 Lname4",
"cats": ["project4"],
"locid": "sfo",
"imgUrl": ""
},
{
"name": "Fname5 Lname5",
"cats": ["project2"],
"locid": "sjc",
"imgUrl": ""
},
{
"name": "Fname6 Lname6",
"cats": ["project1"],
"locid": "sfo",
"imgUrl": ""
}
]
}
// Custom viz for people and projects
// Show projects in the middle and teams/people on either side
// Configurable params
// Click on the number and see a magic slider appears to tweak it.
var config = {
width: 700,
height: 600,
categoryRectWidth: 150,
categoryRectHeight: 30,
personRectWidth: 120,
personRectHeight: 20,
personRectGap: 25,
imageSize: 40,
paddingForImage: 50,
defaultImgUrl: 'https://assets3.assembla.com/assets/avatars/big/8-b25e82266d6568a6bcd262c5630fe562.png'
};
var data = tributary.data;
function isLeftColPerson(p) {
return (p.locid === 'sjc');
}
function processDataForViz(data, config) {
var catList = [];
Object.keys(data.categoriesMap).forEach(function (k, i) {
var cat = data.categoriesMap[k];
cat.id = k;
cat.index = i;
cat.x = config.width / 2 - config.categoryRectWidth / 2;
cat.y = i * (config.categoryRectHeight + 5);
catList.push(cat);
});
var locList = [];
Object.keys(data.locationsMap).forEach(function (k) {
var loc = data.locationsMap[k];
loc.id = k;
locList.push(loc);
});
var links = [];
var indexForLoc = {};
data.people.forEach(function (p, i) {
p.index = i;
indexForLoc[p.locid] = indexForLoc[p.locid] || 0;
p.x = config.paddingForImage;
if (!isLeftColPerson(p)) {
p.x = config.width - config.personRectWidth - config.paddingForImage;
}
p.y = indexForLoc[p.locid] * (config.personRectHeight + config.personRectGap);
indexForLoc[p.locid]++;
p.cats.forEach(function (catId) {
var cat = data.categoriesMap[catId];
// link connection points at the edge of person nodes (rectangles)
var source = {
index: p.index,
x: isLeftColPerson(p) ? p.x + config.personRectWidth : p.x,
y: p.y + config.personRectHeight / 2
};
var target = {
index: cat.index,
x: isLeftColPerson(p) ? cat.x : cat.x + config.categoryRectWidth,
y: cat.y + config.categoryRectHeight / 2
};
links.push({source: source, target: target});
});
});
data.categoriesList = catList;
data.locationsList = locList;
data.links = links;
}
processDataForViz(data, config);
console.log(data);
visualize(data);
function visualize(data) {
var svg = d3.select('svg');
var gRoot = svg.append('svg:g')
.attr('transform', 'translate(20, 100)');
var diagonal = d3.svg.diagonal();
var gCat = gRoot.selectAll('.category-node')
.data(data.categoriesList)
.enter()
.append('svg:g')
.attr('class', function (d) {
return 'category-node category-'+d.index;
})
.attr('transform', function (d, i) {
return 'translate('+[d.x, d.y]+')';
})
.on('mouseenter', function (d) {
d3.select(this).classed('active', true);
d3.selectAll('path.link-to-' + d.index)
.classed('active', true)
.moveToFront();
data.links.forEach(function (link) {
if (link.target.index === d.index) {
d3.select('.person-'+link.source.index).classed('active', true);
}
});
})
.on('mouseleave', function (d) {
d3.selectAll('.person-node').classed('active', false);
d3.selectAll('.category-node').classed('active', false);
d3.selectAll('path.link-to-' + d.index)
.classed('active', false)
.moveToFront();
});
gCat
.append('svg:rect')
.attr('width', config.categoryRectWidth)
.attr('height', config.categoryRectHeight);
gCat
.append('svg:text')
.attr('x', 5)
.attr('y', 16)
.text(function (d) {
return d.name;
});
var gPerson = gRoot.selectAll('.person-node')
.data(data.people)
.enter()
.append('svg:g')
.attr('class', function (d) {
return 'person-node person-'+d.index;
})
.attr('transform', function (d, i) {
return 'translate('+[d.x, d.y]+')';
})
.on('mouseenter', function (d) {
d3.select(this).classed('active', true);
d3.selectAll('path.link-from-' + d.index).classed('active', true);
data.links.forEach(function (link) {
if (link.source.index === d.index) {
d3.select('.category-'+link.target.index).classed('active', true);
}
});
})
.on('mouseleave', function (d) {
d3.selectAll('.person-node').classed('active', false);
d3.selectAll('.category-node').classed('active', false);
d3.selectAll('path.link-from-' + d.index).classed('active', false);
});
gPerson
.append('svg:rect')
.attr('width', config.personRectWidth)
.attr('height', config.personRectHeight);
gPerson
.append('svg:text')
.attr('x', 5)
.attr('y', 11)
.text(function (d) {
return d.name;
});
gPerson.append('svg:image')
.attr('xlink:href', function (d) {
return d.imgUrl || config.defaultImgUrl
})
.attr('x', function (d) {
return isLeftColPerson(d) ? -config.imageSize - 3 : config.personRectWidth + 3;
})
.attr('y', -(config.imageSize - config.personRectHeight)/2)
.attr('width', config.imageSize)
.attr('height', config.imageSize)
var gLink = gRoot.selectAll('.link')
.data(data.links)
.enter()
.append('svg:path')
.attr('d', diagonal)
.attr('class', function (d) {
return 'link link-from-' + d.source.index + ' link-to-' + d.target.index;
});
}
d3.selection.prototype.moveToFront = function() {
return this.each(function() {
this.parentNode.appendChild(this);
});
};
text {
font-size: 12px;
alignment-baseline: middle;
}
g.category-node rect,
g.person-node rect {
stroke: #aaa;
stroke-opacity: 0.3;
fill: #eee;
}
g.category-node.active rect,
g.person-node.active rect,
path.link.active {
stroke: #1ca8dd;
stroke-opacity: 0.8;
stroke-width: 2;
}
path.link {
stroke: #aaa;
stroke-opacity: 0.3;
fill: none;
}
g.person-node image {
display: none;
}
g.person-node.active image {
display: block;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment