Bubble chart showing 100.000 Kickstarter projects. Based on the data and idea of Polygraph but trying to reorganize the colors. Color represents the project category, size the number of backers.
forked from mbostock's block: Bubble Chart
forked from john-guerra's block: Bubble Chart d3 v4
xxxxxxxxxx
<meta charset="utf-8">
<style>
text {
font: 10px sans-serif;
opacity: 0.8;
pointer-events: none;
}
circle {
stroke-width: 1px;
}
.controls {
font: 18px sans-serif;
}
</style>
<body>
<div class="controls">
<input type="checkbox" id="checkPolygraphColors"> Use Polygraph Colors
<input type="checkbox" id="checkGroupByCategories"> Group by Categories
</div>
<div id="chart"></div>
<script src="//d3js.org/d3.v4.js"></script>
<script>
var diameter = 960,
format = d3.format(",d"),
colorStandard = d3.scaleOrdinal(d3.schemeCategory20c);
var categoriesAll = ['Music', 'Film & Video', 'Publishing', 'Art', 'Games', 'Design',"Theater","Food","Comics","Technology","Fashion","Photography","Dance","Crafts","Journalism"];
var categoryClassesBig = d3.scaleOrdinal().domain(categoriesAll).range(["music", 'film','publishing','art','games','design',"theater","food","comics","technology","fashion","photography","dance","crafts","journalism","other"]);
var categoriesToMap = ['Music', 'Film & Video', 'Publishing', 'Art', 'Games', 'Design', "Theater","Food","Comics","Technology"];
var categoryClassesSmall = d3.scaleOrdinal().domain(categoriesToMap).range(["music", 'film','publishing','art','games','design', "theater","food","comics","technology","other"]);
var categoryColorsRange = ["#F44336", //'Music'
"#ff7f00", //'Film & Video'
"#cab2d6", //'Publishing'
"#268bd0", //'Art'
"#673AB7", //'Games'
"#bbb", //'Design'
"#fdbf6f", //"Theater"
"#b2df8a", //"Food"
"#a6cee3", //"Comics"
"#4CAF50", //"Technology"
"#333" //"other"
];
var colorPolygraph = d3.scaleOrdinal().domain(categoryClassesSmall.range()).range(categoryColorsRange);
var bubble = d3.pack()
.size([diameter, diameter])
.padding(0);
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.attr("class", "bubble");
function aggreativeHierarchy(data, hierarchy) {
var dNodes = {};
var root = createNode("/", "root");
function createNode(id, name, children) {
var node = {
id: id ,
name: name || id.split("/").slice(-1)[0] || '',
children: children || []
};
dNodes[id] = node;
return node;
}
function getParentId(id) {
var path = id.split("/");
return path.slice(0, path.length-1).join("/") || "/";
}
function getOrCreateParent(id) {
if (id in dNodes)
return dNodes[id];
else {
//The parent doesn't exist yet
var node = createNode(id);
var parentId = getParentId(id);
node.parent = getOrCreateParent(parentId);
node.parent.children.push(node)
return node;
}
}
data.forEach(function (d, i) {
var id = hierarchy(d, i , data);
dNodes[id] = d;
var parentId = getParentId(id);
d.parent = getOrCreateParent(parentId);
d.parent.children.push(d);
});
return root;
}
function ready(error,
unique_subs,
// city_data,
city_meta,
project_backers
// uSmap,
// ipInfo
)
{
if (error) throw error;
function update() {
groupCategories = d3.select("#checkGroupByCategories").property("checked");
usePolygraphColors = d3.select("#checkPolygraphColors").property("checked");
var color = usePolygraphColors ? colorPolygraph : colorStandard;
var categoryClasses = usePolygraphColors ? categoryClassesSmall : categoryClassesBig;
var mapSubs = d3.map(unique_subs, function (d) { return d.sub_id;});
var mapSubsCounts = d3.map();
var mapCities = d3.map(city_meta, function (d) { return d.city_id;});
var mapCityCounts = d3.map();
//Preprocess data
project_backers.forEach(function (d) {
d.backers_count = +d.backers_count;
d.city = mapCities.get(d.city_id);
d.sub = mapSubs.get(d.sub_id);
//Aggregate categories
d.subcategoryOther = categoryClasses(d.sub.subcategory);
//Count subCategory totals
if (!mapSubsCounts.has(d.subcategoryOther)) {
mapSubsCounts.set(d.subcategoryOther, 0);
}
mapSubsCounts.set(d.subcategoryOther, mapSubsCounts.get(d.subcategoryOther)+1);
//Count city totals
if (!mapCityCounts.has(d.city_id)) {
mapCityCounts.set(d.city_id, 0);
}
mapCityCounts.set(d.city_id, mapCityCounts.get(d.city_id)+1);
});
//Create a dynamic hierarchy from data;
data = aggreativeHierarchy(project_backers, function (d) {
if (groupCategories){
return "/" + d.city_id +
"/" + d.sub_id +
"/" + d.project_id;
} else {
return "/" + d.city_id +
"/" + d.project_id;
}
});
var root = d3.hierarchy(data)
.sum(function(d) { return d.backers_count; })
.sort(function(a, b) {
var citySort = (a.depth === 1 && b.depth === 1) ?
d3.descending(mapCityCounts.get(a.data.name), mapCityCounts.get(b.data.name)) :
0 ;
//Sort by cities, then by categories that have more projects first, then by count
return citySort ||
d3.descending(mapSubsCounts.get(a.data.subcategoryOther), mapSubsCounts.get(b.data.subcategoryOther)) ||
b.data.backers_count - a.data.backers_count ;
});
bubble(root);
d3.selectAll(".node").remove();
var node = svg.selectAll(".node")
.data(root.descendants()
.sort(function (a, b) { return d3.descending(a.depth, b.depth); }) // To show the labels first
)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("title")
.filter(function (d) { return d.children === undefined; })
.text(function(d) {
return "Project id: " + d.data.project_id +
" Category:" + d.data.sub.subcategory +
" Backers: " + format(d.data.backers_count); });
node.append("circle")
.attr("r", function(d) { return d.r; })
.style("pointer-events", function (d) { return d.children ? "none" : "auto"; }) // Don't want parents to respond to events
.style("fill", function(d) {
return d.children ? "none" : color(d.data.subcategoryOther);
})
.style("stroke", function(d) {
return d.children ? "#ccc" : "none";
});
;
// Only add label to the first level
node
.filter(function (d) { return d.children && d.depth ===1;})
.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.style("font-size", function(d) { return d.r > 50 ? d.r/4 + "px" : "10px"})
.text(function(d) {
return mapCities.get(d.data.name).city_state.substring(0, d.r / 3);
});
}
function redraw() {
d3.selectAll(".node").remove();
update();
}
d3.select("#checkPolygraphColors").on("change", redraw);
d3.select("#checkGroupByCategories").on("change", redraw);
redraw();
}
d3.queue()
.defer(d3.csv, "unique_subs.csv") // 67KB
// .defer(d3.csv, "city_data_2.csv") // 67KB
.defer(d3.csv, "city_map_5.csv") // 23KB
.defer(d3.csv, "project_raw_4.csv") // 2.4MB
// .defer(d3.json, "map.json") //600kb
// .defer(d3.json, 'https://ipinfo.io')
// .defer()
.await(ready);
d3.select(self.frameElement).style("height", diameter + "px");
</script>
https://d3js.org/d3.v4.js