|
<!DOCTYPE html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<style> |
|
body { margin:0;top:0;right:0;bottom:0;left:0; font-family: sans-serif; } |
|
.domain { display: none; } |
|
.tick text { fill: #808080 } |
|
.axis-label { fill: #808080; font-size: 12; } |
|
.tick line { stroke: #808080; shape-rendering: crispEdges; } |
|
.hull { stroke:#808080; stroke-width: 20; stroke-linejoin: round;} |
|
.legend rect, .legend text { cursor: pointer; } |
|
</style> |
|
</head> |
|
|
|
<body> |
|
<select id="user"></select> |
|
|
|
<div id="chart"></div> |
|
|
|
|
|
|
|
<script> |
|
|
|
var maxVideoViews; |
|
var maxSubscribers; |
|
var extentVideoViews; |
|
var extentSubscribers; |
|
var radius = 2; |
|
var mutedOpacity = 0.05; |
|
|
|
function formatBillions(d) { |
|
var roundedNumber = (d/1000000000); |
|
return roundedNumber + "bn"; |
|
}; |
|
|
|
function formatMillions(d) { |
|
var roundedNumber = (d/1000000); |
|
return roundedNumber// + "m"; |
|
}; |
|
|
|
d3.csv("data.csv", convertTextToNumbers, function(error, data) { |
|
if(error) { throw error; }; |
|
|
|
var dropDown = d3.select("#user").on("change", highlightUser); |
|
|
|
dropDown.selectAll("option") |
|
.data(data) |
|
.enter() |
|
.append("option") |
|
.attr("value", function(d) { return d.User; }) |
|
.text(function(d) { return d.User; }); |
|
|
|
maxVideoViews = d3.max(data, function(d){ return d.VideoViews; }); |
|
maxSubscribers = d3.max(data, function(d){ return d.Subscribers; }); |
|
|
|
extentVideoViews = d3.extent(data, function(d){ return d.VideoViews; }); |
|
extentSubscribers = d3.extent(data, function(d){ return d.Subscribers; }); |
|
|
|
drawChart(data); |
|
|
|
}); |
|
|
|
|
|
|
|
function highlightUser() { |
|
|
|
var sel = document.getElementById('user'); |
|
var selectedUser = sel.options[sel.selectedIndex].value; |
|
|
|
d3.selectAll("circle") |
|
.style("opacity", function(d) { return d.User==selectedUser ? 1 : 0.2; }) |
|
.attr("r", function(d) { return d.User==selectedUser ? 5 : radius; }); |
|
|
|
|
|
|
|
}; |
|
|
|
function drawChart(data) { |
|
|
|
var width = 800; |
|
var height = width; |
|
var margin = {"top": 50, "left": 50, "right": 150, "bottom": 50,} |
|
|
|
|
|
var svg = d3.select("#chart").append("svg") |
|
.attr("width", width + margin.right + margin.left) |
|
.attr("height", height + margin.top + margin.bottom); |
|
|
|
var colorScale = d3.scaleOrdinal() |
|
.domain(["A-plus", "A", "A-minus", "B-plus", "B", "B-minus", "C-and-D"]) |
|
.range(["#081d58", "#253494", "#225ea8", "#1d91c0", "#41b6c4", "#7fcdbb", "#c7e9b4"]); |
|
|
|
//var yScale = d3.scaleLinear() |
|
var yScale = d3.scaleLog() |
|
.range([height, 0]) |
|
//.domain([0,maxVideoViews]); |
|
.domain(extentVideoViews); |
|
|
|
var yAxis = d3.axisLeft(yScale).tickFormat(formatBillions); |
|
|
|
//var xScale = d3.scaleLinear() |
|
var xScale = d3.scaleLog() |
|
.range([0,width]) |
|
//.domain([0,maxSubscribers]); |
|
.domain(extentSubscribers); |
|
|
|
var xAxis = d3.axisBottom(xScale).tickFormat(formatMillions); |
|
|
|
svg.append("g") |
|
.attr("transform", "translate(" + (margin.left) + "," + (margin.top + height) + ")") |
|
.call(xAxis); |
|
|
|
svg.append("g") |
|
.attr("transform", "translate(" + (margin.left) + "," + margin.top + ")") |
|
.call(yAxis); |
|
|
|
|
|
|
|
var g = svg.append("g") |
|
.attr("transform", "translate(" + (margin.left) + "," + margin.top + ")"); |
|
|
|
g.append("text") |
|
.text("Views (billions, log scale)") |
|
.attr("class", "axis-label") |
|
.attr("x", -margin.left/2) |
|
.attr("y", yScale(maxVideoViews)); |
|
|
|
g.append("text") |
|
.text("Subscribers (millons, log scale)") |
|
.attr("class", "axis-label") |
|
.attr("x", xScale(maxSubscribers)) |
|
.attr("y", (height - 10)) |
|
.style("text-anchor", "end"); |
|
|
|
var nestedData = d3.nest() |
|
.key(function(d){ return d.Rating; }) |
|
.sortKeys(d3.ascending) |
|
.entries(data); |
|
|
|
nestedData.forEach(function(d) { |
|
|
|
var ratingData = []; |
|
|
|
d.values.forEach(function(d) { |
|
ratingData.push([xScale(d.Subscribers),yScale(d.VideoViews)]) |
|
}) |
|
|
|
var hullData = ratingData.length == 2 ? ratingData : d3.polygonHull(ratingData); |
|
|
|
var hull = g.append("path") |
|
.attr("class", "hull") |
|
.attr("id", "hull-" + d.key) |
|
.datum(hullData) |
|
.attr("d", function(d) { return "M" + d.join("L") + "Z"; }) |
|
.style("fill", colorScale(d.key)) |
|
.style("stroke", colorScale(d.key)) |
|
.style("opacity", mutedOpacity); |
|
|
|
}); |
|
|
|
var circles = g.selectAll("g") |
|
.data(data) |
|
.enter() |
|
.append("g") |
|
.attr("class", "user-circle") |
|
.attr("transform", function(d) { return "translate(" + xScale(d.Subscribers) + "," + yScale(d.VideoViews) +")"; }); |
|
|
|
circles.append("circle") |
|
.attr("r", radius) |
|
.attr("cx", 0) |
|
.attr("cy", 0) |
|
.style("fill", function(d) { return colorScale(d.Rating); }) |
|
.style("fill-opacity", 0.8 ); |
|
|
|
circles.append("text") |
|
.text(function(d){ return d.User; }) |
|
.attr("class", "user-label") |
|
.attr("x", 0) |
|
.attr("y", -5) |
|
.attr("text-anchor", "middle") |
|
.style("visibility", function(d){ |
|
if (d.Rating == "A-plus")/* || d.Subscribers > 14000000 || d.VideoViews > 6000000000)*/ { |
|
return "inherit"; |
|
} else { |
|
return "hidden"; |
|
}; |
|
}); |
|
|
|
d3.selectAll("circle") |
|
.on("mouseover", function(d){ |
|
d3.select("#user").property('value', d.User); |
|
highlightUser(); |
|
}) |
|
.on("mouseout", function(d){ |
|
d3.selectAll("circle").style("opacity", 0.7).attr("r", radius); |
|
}) |
|
|
|
var legendBoxWidth = 20; |
|
|
|
var legend = svg.append("g") |
|
.attr("class", "legend") |
|
.attr("transform", "translate(" + (margin.left + 200) + "," + (margin.top + 200) + ")"); |
|
|
|
|
|
legend.append("text") |
|
.text("Ratings:") |
|
.attr("x", 0) |
|
.attr("y", -5); |
|
|
|
|
|
legend.append("rect") |
|
.attr("class", "legend-border") |
|
.attr("x", -3) |
|
.attr("y", -25) |
|
.attr("width", margin.right - 50) |
|
.attr("height", 180) |
|
.style("fill", "none") |
|
.style("stroke", "grey") |
|
|
|
var legendEntries = legend.selectAll("g") |
|
.data(["A-plus", "A", "A-minus", "B-plus", "B", "B-minus", "C-and-D"]) |
|
.enter() |
|
.append("g") |
|
.attr("transform", function(d, i) { return "translate(0," + (i * (legendBoxWidth + 2)) + ")"; }); |
|
|
|
legendEntries.append("rect") |
|
.attr("x", 0) |
|
.attr("y", 0) |
|
.attr("width", legendBoxWidth) |
|
.attr("height", legendBoxWidth) |
|
.attr("class", "legend-rect") |
|
.attr("id", function(d) { return "legend-rect-" + d; }) |
|
.style("fill", function(d) { return colorScale(d); }) |
|
.style( |
|
"opacity", mutedOpacity) |
|
.on("click", showHideHull); |
|
|
|
legendEntries.append("text") |
|
.text(function(d) { return d; }) |
|
.attr("x", legendBoxWidth + 3) |
|
.attr("y", legendBoxWidth - 3) |
|
.on("click", showHideHull); |
|
|
|
} |
|
|
|
|
|
function convertTextToNumbers(d) { |
|
d.Rank = +d.Rank; |
|
d.Subscribers = +d.Subscribers; |
|
d.VideoViews = +d.VideoViews; |
|
return d; |
|
}; |
|
|
|
function showHideHull(d) { |
|
|
|
var hullID = "#hull-" + d; |
|
var rectID = "#legend-rect-" + d; |
|
|
|
var currentOpacity = d3.select(hullID).style("opacity"); |
|
|
|
//if (currentOpacity == 0) { |
|
|
|
//selectedHullOpacity = 0.6; |
|
|
|
//d3.select(hullID).style("opacity", selectedHullOpacity); |
|
//d3.select(rectID).style("opacity", selectedHullOpacity); |
|
|
|
//} else { |
|
var selectedHullOpacity = currentOpacity == mutedOpacity ? 0.5 : mutedOpacity; |
|
|
|
//var otherHullsOpacity = currentOpacity == mutedOpacity ? 0 : mutedOpacity; |
|
|
|
//d3.selectAll(".hull").style("opacity", otherHullsOpacity); |
|
d3.select(hullID).style("opacity", selectedHullOpacity); |
|
|
|
//d3.selectAll(".legend-rect").style("opacity", otherHullsOpacity); |
|
d3.select(rectID).style("opacity", selectedHullOpacity); |
|
|
|
//}; |
|
|
|
|
|
|
|
|
|
} |
|
|
|
</script> |
|
</body> |