Built with blockbuilder.org
xxxxxxxxxx
<head>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet" type="text/css">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="heatmap.js"></script>
</head>
<body>
<script>
/* helper method to make translating easier */
var translate = function(x, y) {
return "translate(" + x + "," + y + ")";
};
// container for configuration parameters
var config = {};
config.svg = {
width: 960,
height: 500
};
config.margin = {
top: 50,
right: 10,
bottom: 20,
left: 40
};
config.plot = {
width: config.svg.width - config.margin.right - config.margin.left,
height: config.svg.height - config.margin.top - config.margin.bottom
};
config.legend = {
width: 200,
height: 15
};
// create svg based on config
var svg = d3.select("body").append("svg")
.attr("width", config.svg.width)
.attr("height", config.svg.height);
// create plot area based on config
var plot = svg.append("g")
.attr("id", "plot")
.attr("transform", translate(config.margin.left, config.margin.top));
// create the scales and set ranges
// https:github.com/d3/d3-scale/blob/master/README.md#scaleBand
// we only need the plot dimensions for the range!
// we will set the domain later when we load the data
var xScale = d3.scaleBand()
.range([0, config.plot.width]);
var yScale = d3.scaleBand()
.range([config.plot.height, 0], 0, 0);
// create color scale for each cell
// https://github.com/d3/d3-scale/blob/master/README.md#scaleSequential
var colorScale = d3.scaleSequential(d3.interpolateViridis);
// formatter to parse dates
// https://github.com/mbostock/d3/wiki/Time-Formatting
// must match format used by csv
// converts string to date
var dateParse = d3.timeParse("%x %I:%M:%S %p");
// used to nest data and output values nicely
// converts date to string
var yearFormat = d3.timeFormat("%Y");
var monthFormat = d3.timeFormat("%b");
// placeholder for data
var data = [];
var file = "Monthly_Property_Crime_2005_to_2015.csv";
d3.csv(file, rowAccessor, dataCallback);
function rowAccessor(d) {
var row = {};
row.date = dateParse(d["Date"]);
row.year = yearFormat(row.date);
row.month = monthFormat(row.date);
row.type = d["Category"];
row.count = +d["IncidntNum"];
return row;
}
function dataCallback(error, rows) { // callback
if (error) {
console.warn(error);
}
// we can use fancy d3 operations to group our data by year and month
// https://github.com/d3/d3-collection/blob/master/README.md#nest
data = d3.nest()
.key(function(d) {
return d.year;
})
// group by year first
.key(function(d) {
return d.month;
})
// group by month second
.rollup(function(values) {
// sum together all of the categories
return d3.sum(values, function(d) {
return d.count;
})
})
.map(rows, d3.map); // return as nested d3.maps
console.log("rows", rows);
console.log("nest", data);
// pull out years and months
// use to set domain of x and y scales
var years = data.keys();
var months = data.get(years[0]).keys();
xScale.domain(months);
yScale.domain(years);
/*
* how will we map our values to those colors?
* our first step is to calculate the min and max
* the issue now is our sums are nested
*/
// un-nesting these values is a little strange looking
// debug each step in the console so you know what is going on
// this returns all the values in our map
// in this case, our values are themselves maps
var values = data.values();
// now we have an array of maps
// grab the map values for each inner map
values = values.map(function(d) {
return d.values();
});
// now we have an array of arrays
// merge arrays into a single array
values = d3.merge(values);
// now we can calculate the extent our counts
var extent = d3.extent(values);
/*
* is this efficient?
* goodness no! but if you cared, why are you doing
* data processing in javascript!
*/
// use these values as domain for our color scale
colorScale.domain(extent);
// finally, draw stuff
drawBackground();
drawAxes();
drawHeatmap();
drawTitle();
drawLegend();
}
</script>
</body>
https://d3js.org/d3.v4.min.js