forked from d3noob's block: Leanpub Book Download Scatterplot
xxxxxxxxxx
<meta charset="utf-8">
<style>
body { font: 12px sans-serif; }
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
.dot { stroke: none; }
.grid .tick { stroke: lightgrey; opacity: 0.7; }
.grid path { stroke-width: 0;}
path {
stroke: #1f78b4;
stroke-width: 1.5;
fill: none;
}
text.shadow {
stroke: white;
stroke-width: 2.5px;
opacity: 0.9;
}
</style>
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
// functions to parse the date / time formats
parseDate = d3.time.format("%Y-%m-%d").parse;
parseTime = d3.time.format("%H:%M:%S").parse;
formatDate = d3.time.format("%d-%b"),
formatTime = d3.time.format("%H:%M"),
bisectDate = d3.bisector(function(d) { return d.date; }).left;
// Load the raw data
d3.json("downloads.json", function(error, events) {
// parse and format all the event data
events.forEach(function(d) {
d.dtg = d.dtg.slice(0,-4)+'0:00'; // get the 10 minute block
dtgSplit = d.dtg.split(" "); // split on the space
d.date = dtgSplit[0]; // get the date seperatly
d.time = dtgSplit[1]; // format the time
d.number_downloaded = 1; // Number of downloads
});
// get the scatterplot data and nest the data by date/time
var data = d3.nest()
.key(function(d) { return d.dtg;})
.rollup(function(d) {
return d3.sum(d,function(g) {return g.number_downloaded; });
})
.entries(events);
// format the date/time data
data.forEach(function(d) {
d.dtg = d.key; // get the 10 minute block
dtgSplit = d.dtg.split(" "); // split on the space
d.date = parseDate(dtgSplit[0]); // get the date seperatly
d.time = parseTime(dtgSplit[1]); // format the time
d.number_downloaded = d.values; // Number of downloads
});
// nest the data by date for the daily graph
var dataDate = d3.nest()
.key(function(d) { return d.date;})
.rollup(function(d) {
return d3.sum(d,function(g) {return g.number_downloaded; });
})
.entries(events);
// format the date data
dataDate.forEach(function(d) {
d.date = parseDate(d.key); // format the date
d.close = d.values; // Number of downloads
});
// nest the data by 10 minute intervals for the time graph
var dataTime = d3.nest()
.key(function(d) { return d.time;})
.sortKeys(d3.ascending)
.rollup(function(d) {
return d3.sum(d,function(g) {return g.number_downloaded; });
})
.entries(events);
// format the time data
dataTime.forEach(function(d) {
d.time = d.key; // get the 10 minute block
d.time = parseTime(d.time); // get the date seperatly
d.close = d.values; // Number of downloads
});
// Get number of days in date range to calculate scatterplotWidth
var oneDay = 24*60*60*1000; // hours*minutes*seconds*milliseconds
var dateStart = d3.min(data, function(d) { return d.date; });
var dateFinish = d3.max(data, function(d) { return d.date; });
var numberDays = Math.round(Math.abs((dateStart.getTime() -
dateFinish.getTime())/(oneDay)));
var margin = {top: 20, right: 20, bottom: 20, left: 50},
scatterplotHeight = 520,
scatterplotWidth = numberDays * 1.5,
dateGraphHeight = 220,
timeGraphWidth = 220;
// Set the dimensions of the canvas / graph
var height = scatterplotHeight + dateGraphHeight,
width = scatterplotWidth + timeGraphWidth;
// ************* draw the scatterplot ****************
var formatDay_Time = d3.time.format("%H:%M"); // tooltip time
var formatWeek_Year = d3.time.format("%d-%m-%Y"); // tooltip date
var x = d3.time.scale().range([0, scatterplotWidth]);
var y = d3.time.scale().range([0, scatterplotHeight]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(7);
var yAxis = d3.svg.axis()
.scale(y)
.orient("right")
.ticks(12,0,0)
.tickFormat(d3.time.format("%H:%M"));
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + ","
+ margin.top + ")");
// State the functions for the grid
function make_x_axis() {
return d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(12)
}
// State the functions for the grid
function make_y_axis() {
return d3.svg.axis()
.scale(y)
.orient("right")
.ticks(8)
}
// Set the domains
y.domain([new Date(1899, 12, 02, 0, 0, 0),
new Date(1899, 12, 01, 0, 0, 1)]);
x.domain(d3.extent(data, function(d) { return d.date; }));
// tickSize: Get or set the size of major, minor and end ticks
svg.append("g").classed("grid x_grid", true)
.attr("transform", "translate(0,"
+ (scatterplotHeight + dateGraphHeight) + ")")
.style("stroke-dasharray", ("3, 3, 3"))
.call(make_x_axis()
.tickSize(-scatterplotHeight - dateGraphHeight, 0, 0)
.tickFormat(""))
// tickSize: Get or set the size of major, minor and end ticks
svg.append("g").classed("grid y_grid", true)
.attr("transform", "translate(0,0)")
.style("stroke-dasharray", ("3, 3, 3"))
.call(make_y_axis()
.tickSize(+scatterplotWidth+timeGraphWidth, 0, 0)
.tickFormat(""))
// Draw the Axes and the tick labels
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + scatterplotHeight + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "middle");
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + scatterplotWidth + ",0)")
.call(yAxis)
.selectAll("text");
// draw the plotted circles
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", function(d) { return d.number_downloaded*1.5; })
.style("opacity", 0.3)
.style("fill", "#e31a1c" )
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.time); })
;
// ************* draw the date graph *****************
// Set the ranges
var xDate = d3.time.scale().range([0, scatterplotWidth]);
var yDate = d3.scale.linear().range([dateGraphHeight, 0]);
var yAxisDate = d3.svg.axis().scale(yDate)
.orient("right").ticks(5);
// Define the line
var valueline = d3.svg.line()
.interpolate("basis")
.x(function(d) { return xDate(d.date); })
.y(function(d) { return yDate(d.close); } );
// Scale the range of the data
xDate.domain(d3.extent(dataDate, function(d) { return d.date; }));
yDate.domain([0,d3.max(dataDate, function(d) { return d.close; })]);
// Add the valueline path.
svg.append("path")
.attr("class", "line")
.attr("transform", "translate(0," + scatterplotHeight + ")")
.attr("d", valueline(dataDate));
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate("
+ scatterplotWidth + "," + scatterplotHeight + ")")
.call(yAxisDate);
// ************* draw the time graph **************
// Set the ranges
var yTime = d3.time.scale().range([0, scatterplotHeight]);
var xTime = d3.scale.linear().range([0, timeGraphWidth]);
// Define the axes
var xAxisTime = d3.svg.axis().scale(xTime)
.orient("bottom").ticks(5);
// Define the line
var valuelineTime = d3.svg.line()
.x(function(d) { return xTime(d.close); })
.y(function(d) { return yTime(d.time); });
// Scale the range of the data
yTime.domain([new Date(1899, 12, 02, 0, 0, 0),
new Date(1899, 12, 01, 0, 0, 1)]);
xTime.domain([1e-6, d3.max(dataTime,
function(d) { return d.close; }
)]);
// Add the valueline path.
svg.append("path")
.attr("class", "line")
.attr("transform", "translate(" + scatterplotWidth + ",0)")
.attr("d", valuelineTime(dataTime));
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate("
+ scatterplotWidth + "," + scatterplotHeight + ")")
.call(xAxisTime);
// *********** place the mouse movement information **************
var focus = svg.append("g")
.style("display", "none");
// append the x line
focus.append("line")
.attr("class", "x")
.style("stroke", "#33a02c")
.style("stroke-dasharray", "3,3")
.style("opacity", 1)
.style("shape-rendering", "crispEdges");
// append the y line
focus.append("line")
.attr("class", "y")
.style("stroke", "#33a02c")
.style("stroke-dasharray", "3,3")
.style("opacity", 1)
.style("shape-rendering", "crispEdges");
// place the value at the intersection
focus.append("text")
.attr("class", "y1")
.style("stroke", "white")
.style("stroke-width", "3.5px")
.style("opacity", 0.8)
.attr("dx", 8)
.attr("dy", "-.3em");
focus.append("text")
.attr("class", "y2")
.attr("dx", 8)
.attr("dy", "-.3em");
// place the date at the intersection
focus.append("text")
.attr("class", "y3")
.style("stroke", "white")
.style("stroke-width", "3.5px")
.style("opacity", 0.8)
.attr("dx", 8)
.attr("dy", "1em");
focus.append("text")
.attr("class", "y4")
.attr("dx", 8)
.attr("dy", "1em");
// place the Daily Downloads dynamic text
focus.append("text")
.attr("class", "y5")
.style("stroke", "white")
.style("stroke-width", "3.5px")
.style("opacity", 0.8)
.attr("dy", "1em");
focus.append("text")
.attr("class", "y6")
.attr("dy", "1em");
// place the time Downloads dynamic text
focus.append("text")
.attr("class", "y7")
.style("stroke", "white")
.style("stroke-width", "3.5px")
.style("opacity", 0.8)
.attr("dy", ".35em");
focus.append("text")
.attr("class", "y8")
.attr("dy", ".35em");
// append the rectangle to capture mouse
svg.append("rect")
.attr("width", scatterplotWidth)
.attr("height", scatterplotHeight)
.style("fill", "none")
.style("pointer-events", "all")
.on("mouseover", function() { focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);
// The conversion ratio to change x position of cursor to
// the index of the date array
var convertDate = dataDate.length/scatterplotWidth;
var convertTime = dataTime.length/scatterplotHeight;
// interactive mouse function
function mousemove() {
var xpos = d3.mouse(this)[0],
x0 = x.invert(xpos),
y0 = d3.mouse(this)[1],
y1 = y.invert(y0),
date1 = d3.mouse(this)[0];
// Place the intersection date text
focus.select("text.y1")
.attr("transform",
"translate(" + (date1 - 50) + "," + (y0+20) + ")")
.text(formatDate(x0));
focus.select("text.y2")
.attr("transform",
"translate(" + (date1 - 50) + "," + (y0+20) + ")")
.text(formatDate(x0));
// Place the intersection time text
focus.select("text.y3")
.attr("transform",
"translate(" + (date1) + "," + (y0-15) + ")")
.text(formatTime(y1).substring(0,4)+'0');
focus.select("text.y4")
.attr("transform",
"translate(" + (date1) + "," + (y0-15) + ")")
.text(formatTime(y1).substring(0,4)+'0');
// Place the dynamic daily downloads text
focus.select("text.y5")
.attr("transform",
"translate("
+ (date1) + ","
+ (scatterplotHeight+dateGraphHeight) + ")")
.attr("text-anchor", "middle")
.text(dataDate[parseInt(xpos*convertDate)].close);
focus.select("text.y6")
.attr("transform",
"translate("
+ (date1) + ","
+ (scatterplotHeight+dateGraphHeight) + ")")
.attr("text-anchor", "middle")
.text(dataDate[parseInt(xpos*convertDate)].close);
// Place the dynamic time downloads text
focus.select("text.y7")
.attr("transform",
"translate("
+ (scatterplotWidth+timeGraphWidth) + ","
+ (y0) + ")")
.attr("text-anchor", "start")
.text(dataTime[144-parseInt(y0*convertTime)].close);
focus.select("text.y8")
.attr("transform",
"translate("
+ (scatterplotWidth+timeGraphWidth) + ","
+ (y0) + ")")
.attr("text-anchor", "start")
.text(dataTime[144-parseInt(y0*convertTime)].close);
focus.select(".x")
.attr("transform",
"translate(" + date1 + "," + (0) + ")")
.attr("y2", height );
focus.select(".y")
.attr("transform",
"translate(0," + y0 + ")")
.attr("x2", width );
}
// axis labels
svg.append("text")
.attr("transform", "translate("
+ scatterplotWidth + ","
+ scatterplotHeight + "), rotate(-90)")
.attr("y", 25)
.attr("x",-dateGraphHeight )
.attr("dy", "1em")
.style("text-anchor", "start")
.attr("class", "shadow")
.text("Daily Downloads");
svg.append("text")
.attr("transform", "translate("
+ scatterplotWidth + ","
+ scatterplotHeight + "), rotate(-90)")
.attr("y", 25)
.attr("x",-dateGraphHeight )
.attr("dy", "1em")
.style("text-anchor", "start")
.text("Daily Downloads");
svg.append("text")
.attr("transform", "translate("
+ scatterplotWidth + ","
+ scatterplotHeight + ")")
.attr("y", 20)
.attr("x",timeGraphWidth )
.attr("dy", "1em")
.style("text-anchor", "end")
.attr("class", "shadow")
.text("Downloads in 10 Minute Blocks");
svg.append("text")
.attr("transform", "translate("
+ scatterplotWidth + ","
+ scatterplotHeight + ")")
.attr("y", 20)
.attr("x",timeGraphWidth )
.attr("dy", "1em")
.style("text-anchor", "end")
.text("Downloads in 10 Minute Blocks");
});
</script>
</body>
Modified http://d3js.org/d3.v3.min.js to a secure url
https://d3js.org/d3.v3.min.js