|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<title>Reusable Bubble Chart - inspired by @dmesquita</title> |
|
</head> |
|
<body> |
|
<svg></svg> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.6.0/d3.min.js"></script> |
|
<script type="text/javascript"> |
|
"use strict"; |
|
|
|
var bubbleChart = function bubbleChart() { |
|
|
|
var margin = { top: 40, right: 30, bottom: 40, left: 30 }, |
|
width = 960 - margin.right - margin.left, |
|
height = 960 - margin.top - margin.bottom, |
|
colorCategory = "category", |
|
colRadius = "views"; |
|
|
|
// # MODIFIES THE selection GIVEN BY .call() |
|
var chart = function chart(selection) { |
|
|
|
var data = selection.enter().data(); |
|
|
|
var chart = d3.select("svg") |
|
.attr("class", "chart") |
|
.attr("width", width + margin.left + margin.right) |
|
.attr("height", height + margin.top + margin.bottom) |
|
.append("g") |
|
.attr("transform", "translate(" + margin.left + ", " + margin.top + ")"); |
|
|
|
// # CREATE CATEGORICAL AND QUANTITATIVE EXTRA ENCODINGS |
|
var colorCircles = d3.scaleOrdinal(d3.schemeCategory10); |
|
var scaleRadius = d3.scaleLinear().domain([d3.min(data, function (dp) { |
|
return +dp[colRadius]; |
|
}), d3.max(data, function (dp) { |
|
return +dp[colRadius]; |
|
})]).range([5, 21]); |
|
|
|
// # DUE TO REUSABLE CHARTS, ALL 'STYLE' IS DONE WITH JS |
|
var tooltip = selection.append("div") |
|
.style("position", "absolute") |
|
.style("visibility", "visible") |
|
.style("color", "white") |
|
.style("padding", "8px") |
|
.style("background-color", "#626D71") |
|
.style("border-radius", "6px") |
|
.style("text-align", "center") |
|
.style("font-family", "monospace") |
|
.style("width", "400px") |
|
.text(""); |
|
|
|
// # A FORCE ANIMATION FOR EVERY DP |
|
var sim = d3.forceSimulation(data) |
|
.force("charge", d3.forceManyBody().strength([-80])) |
|
.force("x", d3.forceX()) |
|
.force("y", d3.forceY()) |
|
.on("tick", function (e) { |
|
node.attr("cx", function (dp) { |
|
return dp.x; |
|
}).attr("cy", function (dp) { |
|
return dp.y; |
|
}); |
|
}); |
|
|
|
// # TELLS D3 TO CREATE 'CIRCLES' BINDED TO DATA IF NOT BINDED YET, ADD ATTRS |
|
var node = chart.selectAll("circle") |
|
.data(data) |
|
.enter() |
|
.append("circle") |
|
.attr("r", function (dp) { |
|
return scaleRadius(dp[colRadius]); |
|
}).attr("fill", function (dp) { |
|
return colorCircles(dp[colorCategory]); |
|
}).attr("transform", "translate(" + width / 2 + ", " + height / 2 + ")") |
|
.on("mouseover", function (dp) { |
|
tooltip.html(dp[colorCategory] + "<br>" + dp["title"] + "<br>" + dp[colorCategory] + " hearts"); |
|
return tooltip.style("visibility", "visible"); |
|
}).on("mousemove", function () { |
|
return tooltip.style("top", d3.event.pageY - 10 + "px") |
|
.style("left", d3.event.pageX + 10 + "px"); |
|
}).on("mouseout", function () { |
|
return tooltip.style("visibility", "hidden"); |
|
}); |
|
// SOMEHOW THESE 'MOUSE' ACTIONS DON'T WORK |
|
}; |
|
|
|
// APPLIES D3 CONCEPT OF CHAINING METHODS |
|
chart.width = function (val) { |
|
width = val - margin.right - margin.left; |
|
return chart; |
|
}; |
|
|
|
chart.height = function (val) { |
|
height = val - margin.top - margin.bottom; |
|
return chart; |
|
}; |
|
|
|
chart.color = function (column) { |
|
colorCategory = column; |
|
return chart; |
|
}; |
|
|
|
chart.radius = function (column) { |
|
colRadius = column; |
|
return chart; |
|
}; |
|
|
|
return chart; |
|
}; |
|
|
|
d3.csv('medium_january.csv', function (error, data) { |
|
if (error) { |
|
console.error('Error getting or parsing the data.'); |
|
throw error; |
|
} |
|
|
|
// PRE-PROCESS DATA |
|
data.map(function (dp) { |
|
return dp["views"] = +dp["views"]; |
|
}); |
|
|
|
// CHOOSE THE CHART PARAMETERS, THEN CALL chart TO DRAW IT WITH data |
|
var chart = bubbleChart().width(720).height(500); |
|
|
|
d3.select('svg').data(data).call(chart); |
|
}); |
|
</script> |
|
</body> |
|
</html> |