Using a sample of tweets, this scatter plot is an exploratoin for how to visualize various dimensions. The graph was built as part of a workshop for CU Boulder students.
Last active
November 18, 2015 05:28
-
-
Save blehman/1f53e85c3a708f6a216c to your computer and use it in GitHub Desktop.
Interactive scatterplot
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> | |
<style> | |
body { | |
font: 20px sans-serif; | |
} | |
.axis { | |
fill: none; | |
stroke: #999999; | |
shape-rendering: crispEdges; | |
font-size: 15px; | |
} | |
.lines { | |
fill: none; | |
stroke: black; | |
stroke-width: 1.5px; | |
opacity:0.5 | |
} | |
.dot { | |
opacity:0.5; | |
stroke:black | |
} | |
.mouseoverText{ | |
font-size:15; | |
stroke:black; | |
opacity:0.5; | |
} | |
.legend { | |
opacity:0.1; | |
stroke: #191919; | |
stroke-width: 2.5px; | |
fill: #51abff; /* other color options (http://bit.ly/1ymjJKI) */ | |
} | |
.legendShapes { | |
stroke: #000000; | |
stroke-width: 2.5px; | |
} | |
.legendText { | |
font-size:12px; | |
font-family:"Sans Serif"; | |
} | |
.arrowText{ | |
font-size:12px; | |
font-family:"Sans Serif"; | |
fill:black; | |
} | |
</style> | |
</head> | |
<body> | |
<script> | |
// globals: | |
var formatter = d3.format(",") | |
d3.select('body').append('svg') | |
var title = d3.select("svg").append("text").text("Twitter User Analysis").classed("title",true).attr("x",14).attr("y",20) | |
var margin = {top: 36, right: 100, bottom: 100, left: 100}, | |
width = 657 - margin.left - margin.right, | |
height = 420 - margin.top - margin.bottom; | |
// function to transform a domain of date objects into a range of screen coordinates | |
var x = d3.scale.log() | |
.range([0, width]); | |
// function to transform a domain of numeric values into a range of screen coordinates | |
var y = d3.scale.log() | |
.range([height, 0]); | |
// function that thakes an input and transforms it into a specified range. | |
var minSize = 2; | |
var maxSize = 13; | |
var size_scale = d3.scale.linear() | |
.range([minSize,maxSize]); | |
var tweet_type = d3.scale.ordinal() | |
.domain(["Retweet","Tweet","Reply"]) | |
.range([1,2,3]); | |
// function that returns one of 10 colors depending on the integer input | |
var color = d3.scale.category10(); | |
// d3.svg.axis is a predefined and you can change the options | |
var xAxis = d3.svg.axis() | |
.scale(x) | |
.orient("bottom"); | |
// d3.svg.axis is a predefined and you can change the options | |
var yAxis = d3.svg.axis() | |
.scale(y) | |
.orient("left"); | |
// adds an svg element to the DOM | |
var svg = d3.select("svg") | |
.attr("width", width + margin.left + margin.right) | |
.attr("height", height + margin.top + margin.bottom) | |
.append("g") // the 'g' element is just a container; also note: the indent is intentional | |
.classed("graphContainer",true) | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); // repositions the svg to the right 'margin.left' units and down 'margin.top' units | |
// MAIN | |
// scatter plot builder | |
// note: build_graph() takes one argument: the dataset | |
function build_graph(tweetData){ | |
d3.csv(tweetData,function(tweetData){ | |
console.log(tweetData) | |
var legend = d3.select("svg").append("g"); | |
var legend_width = 134; | |
var legend_height = 109; | |
var legend_x_transform = 428; | |
var legend_y_transform = 185; | |
var content_x_transform = 15; | |
var content_y_transform = 6; | |
var legend_content = d3.select("svg").append("g"); | |
var legend_text = d3.select("svg").append("g"); | |
var legend_circles_radius = 5; | |
//var l = d3.scale.linear() | |
// .range([legend_height*0.9,0]) | |
//var legendAxis = d3.svg.axis() | |
// .scale(l) | |
// .orient("left") | |
// set domanin | |
//console.log("[min,max]",d3.extent(tweetData,function(d){return d.listedCount})) | |
//l.domain(d3.extent(tweetData,function(d){return d.listedCount})) | |
// adds the y axis and some added css | |
//legend.append("g") | |
// .attr("class", "y axis") | |
// .call(legendAxis) | |
var fake_data = ["GOOD STUFF","BAD STUFF","REGULAR STUFF","BEST STUFF"]; | |
var color = d3.scale.category10(); | |
var upperY = 14; | |
var lowerY = 0.85*legend_height; | |
console.log("upperY,lowerY:",upperY,lowerY); | |
var xcoords = [-6,26]; | |
var xcoordsLength = xcoords.reduce(function(a, b) {return a + b;}); | |
var arrowY = d3.scale.linear() | |
.domain([minSize-1,maxSize+1]) | |
.range([lowerY,upperY]) | |
// draw lines in legend | |
legend_content.append("line") | |
.classed("lines",true) | |
.attr("x1", xcoordsLength/2).attr("y1", upperY) | |
.attr("x2", xcoordsLength/2).attr("y2", lowerY) | |
.attr("transform","translate(" + legend_x_transform + "," + legend_y_transform+ ")"); | |
legend_content.append("line") | |
.classed("lines",true) | |
.attr("x1", xcoords[0]).attr("y1", upperY) | |
.attr("x2", xcoords[1]).attr("y2", upperY) | |
.attr("transform","translate(" + legend_x_transform + "," + legend_y_transform+ ")"); | |
legend_content.append("line") | |
.classed("lines",true) | |
.attr("x1", xcoords[0]).attr("y1", lowerY) | |
.attr("x2", xcoords[1]).attr("y2", lowerY) | |
.attr("transform","translate(" + legend_x_transform + "," + legend_y_transform+ ")"); | |
legend_content.append("circle") | |
.classed("dot",true) | |
.attr("cx",10) | |
.attr("cy",upperY) | |
.attr("r",maxSize) | |
.style("stroke","red") | |
.attr("transform","translate(" + legend_x_transform + "," + legend_y_transform+ ")"); | |
legend_content.append("circle") | |
.classed("dot",true) | |
.attr("cx",10) | |
.attr("cy",lowerY) | |
.attr("r",minSize) | |
.style("stroke","red") | |
.attr("transform","translate(" + legend_x_transform + "," + legend_y_transform+ ")"); | |
// create legend rectangle | |
legend.append("rect") | |
.classed("legend",true) | |
.attr("height",legend_height) | |
.attr("width",legend_width) | |
.attr("transform","translate(" + legend_x_transform + "," + legend_y_transform+ ")") | |
// adjust content location | |
legend_content.attr("transform","translate(" + content_x_transform + "," + content_y_transform + ")") | |
// print statment often used to test code | |
console.log("building scatterplot.") | |
// format strings | |
tweetData.forEach(function(d) { | |
d.timeStamp = new Date(d.timeStamp); | |
d.followersCount = +d.followersCount; | |
d.followingCount = +d.followingCount; | |
d.listedCount = +d.listedCount; | |
d.statusesCount = +d.statusesCount; | |
d.link = "https://twitter.com/"+d.displayName+"/status/"+d.tweetID | |
}); | |
// set min/max values to define the domain of various functions | |
size_scale.domain(d3.extent(tweetData,function(d) {return d.statusesCount})); | |
x.domain(d3.extent(tweetData, function(d) { return d.followingCount; })); | |
y.domain(d3.extent(tweetData, function(d) { return d.followersCount; })); | |
// adds the x axis | |
svg.append("g") | |
.attr("class", "x axis") // look at the .axis setings in style.css | |
.attr("transform", "translate(0," + height + ")") | |
.call(xAxis) // calls the function that we created to use d3.svg.axis | |
.append("text") | |
.text("Following") | |
.attr("x",width) | |
.attr("dy", "-.5em") | |
.style("stroke","black") | |
.style("text-anchor", "end"); | |
// changes the x axis css that does not include the "Following" label | |
d3.selectAll(".x.axis .tick.major text") | |
.style("text-anchor", "end") | |
.attr("dx", "-0.497664em") | |
.attr("dy", ".1em") | |
.attr("transform", function(d) { | |
return "rotate(-65)" | |
}); | |
// adds the y axis and some added css | |
svg.append("g") | |
.attr("class", "y axis") | |
.call(yAxis) | |
.append("text") | |
.attr("transform", "rotate(-90)") | |
.attr("y", 6) | |
.attr("dy", ".71em") | |
.style("text-anchor", "end") // additional css (http://mzl.la/1rC7zya) | |
.text("Followers") | |
.style("stroke","black"); // we can override the css here | |
// joins the data, creates the circles, classes the circles as 'dot' | |
circles = svg.selectAll(".dot") | |
.data(tweetData) | |
.enter().append("circle") // data binding (http://bost.ocks.org/mike/join/) | |
.classed("dot",true) | |
.attr("r", function(d){ return size_scale(d.statusesCount)}) | |
.attr("cx", function(d) { return x(d.followingCount); }) | |
.attr("cy", function(d) { return y(d.followersCount); }) | |
.attr("fill",function(d) {return color(tweet_type(d.activityType))}); | |
// add click event handler | |
circles.on("click",function(event){ | |
window.open(event.link); | |
}) | |
.on("mouseover",function(event){ | |
var selectedX = this.cx.baseVal.value | |
var selectedY = this.cy.baseVal.value | |
var selectedR = this.r.baseVal.value | |
var selectedText=event.activityType | |
var selectedStatuses=event.statusesCount | |
console.log(d3.select(this)[0][0].attributes.r) | |
console.log("selectedR",selectedR) | |
d3.select(this) | |
.transition() | |
.duration(100) | |
.delay(0) | |
.attr("r",function(d){return 6*size_scale(d.statusesCount)}) | |
// add text for activity type | |
d3.select(".graphContainer").append("text") | |
.classed("mouseoverText",true) | |
.text(event.activityType) | |
.attr("x", selectedX+selectedR*6) | |
.attr("y",selectedY+selectedR) | |
.attr("fill",color(tweet_type(event.activityType))); | |
// create legend arrow | |
legend_content.append("path") | |
.classed("arrow",true) | |
.attr("transform","translate(" + (legend_x_transform+9) + "," + (legend_y_transform+arrowY(selectedR))+")"+" rotate(-60)") | |
.attr("d","M2,2 L2,11 L10,6 L2,2") | |
// create legend text | |
legend_content.append("text") | |
.classed("arrowText",true) | |
.text(formatter(selectedStatuses)+" statuses") | |
.attr({x:0,y:0}) | |
.attr("transform","translate(" + (legend_x_transform+23) + "," + (legend_y_transform+arrowY(selectedR)+3)+")") | |
}) | |
.on("mouseout",function(event){ | |
d3.selectAll(".mouseoverText").remove() | |
d3.selectAll(".arrow").remove() | |
d3.selectAll(".arrowText").remove() | |
d3.select(this).transition() | |
.duration(200) | |
.delay(20) | |
.attr("r",function(d){ return size_scale(d.statusesCount)}) | |
}); | |
console.log("built scatterplot") | |
}) | |
} | |
console.log("loading data") | |
// get data and build graph | |
build_graph("https://raw.githubusercontent.com/blehman/shitty_graphers/master/sample1000_withHeader.csv"); | |
// get data and build graph alternative: | |
// d3.csv("http://public-data-bucket.gnip.com.s3.amazonaws.com/2014-12-04_CU_workshop/sample1000_withHeader.csv", function(error, rawData) { | |
// console.log("loaded data"); | |
// build_graph(rawData); | |
</script> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment