Skip to content

Instantly share code, notes, and snippets.

@blehman
Last active November 18, 2015 05:28
Show Gist options
  • Save blehman/1f53e85c3a708f6a216c to your computer and use it in GitHub Desktop.
Save blehman/1f53e85c3a708f6a216c to your computer and use it in GitHub Desktop.
Interactive scatterplot

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.

<!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