|
/* |
|
* we assume the following global variables are defined: |
|
* - svg, plot |
|
* - xScale, yScale |
|
* - config |
|
*/ |
|
|
|
/* |
|
* draw white plot background |
|
* useful for debugging before we draw the heatmap |
|
*/ |
|
var drawBackground = function() { |
|
plot.append("rect") |
|
.attr("x", 0) |
|
.attr("y", 0) |
|
.attr("width", config.plot.width) |
|
.attr("height", config.plot.height) |
|
.style("fill", "white"); |
|
}; |
|
|
|
/* |
|
* draws the x and y axis |
|
* https://github.com/mbostock/d3/wiki/SVG-Axes#axis |
|
*/ |
|
var drawAxes = function() { |
|
var xAxis = d3.axisBottom(xScale) |
|
.tickPadding(0); |
|
|
|
var yAxis = d3.axisLeft(yScale) |
|
.tickPadding(0); |
|
|
|
plot.append("g") |
|
.attr("id", "x-axis") |
|
.attr("class", "axis") |
|
.attr("transform", translate(0, config.plot.height)) |
|
.call(xAxis); |
|
|
|
plot.append("g") |
|
.attr("id", "y-axis") |
|
.attr("class", "axis") |
|
.call(yAxis); |
|
}; |
|
|
|
/* |
|
* draws the heatmap |
|
* not too complicated due to how we nested the data |
|
* but, it can be tricky to figure out which scale to use where |
|
*/ |
|
var drawHeatmap = function() { |
|
// create a group per row |
|
var rows = plot.append("g") |
|
.attr("id", "heatmap") |
|
.attr("class", "cell") |
|
.selectAll("g") |
|
.data(data.entries()) |
|
.enter() |
|
.append("g") |
|
.attr("id", function(d) { |
|
return d.key; |
|
}) |
|
.attr("transform", function(d) { |
|
return translate(0, yScale(d.key)); |
|
}); |
|
|
|
// create rect per column |
|
var cells = rows.selectAll("rect") |
|
.data(function(d) { |
|
return d.value.entries(); |
|
}) |
|
.enter() |
|
.append("rect") |
|
.attr("x", function(d) { |
|
return xScale(d.key); |
|
}) |
|
.attr("y", 0) |
|
.attr("width", xScale.bandwidth()) |
|
.attr("height", yScale.bandwidth()) |
|
.style("fill", function(d) { |
|
return colorScale(d.value); |
|
}); |
|
}; |
|
|
|
/* |
|
* draw plot title in upper left margin |
|
* will center the text in the margin |
|
*/ |
|
var drawTitle = function() { |
|
var title = svg.append("text") |
|
.text("SF Monthly Property Crime") |
|
.attr("id", "title") |
|
.attr("x", config.margin.left) |
|
.attr("y", 0) |
|
.attr("dx", 0) |
|
.attr("dy", "18px") |
|
.attr("text-anchor", "left") |
|
.attr("font-size", "18px"); |
|
|
|
// shift text so it is centered in plot area |
|
var bounds = title.node().getBBox(); |
|
var yshift = (config.margin.top - bounds.height) / 2; |
|
title.attr("transform", translate(0, yshift)); |
|
}; |
|
|
|
/* |
|
* draw a color legend at top of plot |
|
* this is ridiculously hard for the amount of pixels we |
|
* are drawing, but it is also ridiculously important |
|
* |
|
* another approach is to threshold our values |
|
* we will leave that for another time |
|
* |
|
* for a great explaination of how this works, see: |
|
* http://www.visualcinnamon.com/2016/05/smooth-color-legend-d3-svg-gradient.html |
|
*/ |
|
var drawLegend = function() { |
|
// our color scale doesn't have an invert() function |
|
// and we need some way of mapping 0% and 100% to our domain |
|
// so we'll create a scale to reverse that mapping |
|
var percentScale = d3.scaleLinear() |
|
.domain([0, 100]) |
|
.range(colorScale.domain()); |
|
|
|
// setup gradient for legend |
|
// http://bl.ocks.org/mbostock/1086421 |
|
svg.append("defs") |
|
.append("linearGradient") |
|
.attr("id", "gradient") |
|
.selectAll("stop") |
|
.data(d3.ticks(0, 100, 5)) |
|
.enter() |
|
.append("stop") |
|
.attr("offset", function(d) { |
|
return d + "%"; |
|
}) |
|
.attr("stop-color", function(d) { |
|
return colorScale(percentScale(d)); |
|
}); |
|
|
|
// create group for legend elements |
|
// will translate it to the appropriate location later |
|
var legend = svg.append("g") |
|
.attr("id", "legend") |
|
.attr("transform", translate( |
|
config.svg.width - config.margin.right - config.legend.width, |
|
(config.margin.top - config.legend.height) / 2)); |
|
|
|
// draw the color rectangle with gradient |
|
legend.append("rect") |
|
.attr("x", 0) |
|
.attr("y", 0) |
|
.attr("width", config.legend.width) |
|
.attr("height", config.legend.height) |
|
.attr("fill", "url(#gradient)"); |
|
|
|
// create another scale so we can easily draw an axis on the color box |
|
var legendScale = d3.scaleLinear() |
|
.domain(colorScale.domain()) |
|
.range([0, config.legend.width]); |
|
|
|
// use an axis generator to draw axis under color box |
|
var legendAxis = d3.axisBottom(legendScale) |
|
// https://github.com/d3/d3-format |
|
.tickFormat(d3.format(",.0s")) |
|
.tickValues(colorScale.domain()) |
|
.tickSize(4); |
|
|
|
// draw it! |
|
legend.append("g") |
|
.attr("id", "color-axis") |
|
.attr("class", "legend") |
|
.attr("transform", translate(0, config.legend.height)) |
|
.call(legendAxis) |
|
|
|
// calculate how much to shift legend group to fit in our plot area nicely |
|
var bounds = legend.node().getBBox(); |
|
var xshift = config.svg.width - bounds.width; |
|
var yshift = (config.margin.top - bounds.height) / 2; |
|
legend.attr("transform", translate(xshift, yshift)); |
|
}; |