index.html
<!DOCTYPE html>
<meta charset="utf-8">
<script src="./d3.js"></script>
<script src="./moment.js"></script>
<script src="./helpers.js"></script>
<script src="./line-chart.js"></script>
<link href="./line-chart.css" rel="stylesheet" />
<body>
<div id="chart"></div>
<script>
var datasets = ["Any_Bedrooms", "One_Bedroom", "Studios", "Two_Bedrooms", "Three_Bedrooms_Or_More"];
var selection = d3.select("#select-data")
.on("input", function(d) {
d3.csv("./sales_inventory_time_series_counts_All_Types_" + this.value + ".csv", ready);
})
selection.selectAll("option")
.data(datasets)
.enter().append("option")
.attr("value", function(d) { return d })
.text(function(d) { return d });
d3.csv("./sales_inventory_time_series_counts_All_Types_Any_Bedrooms.csv", ready);
function ready(error, data) {
console.log(data);
var columns = data.columns.filter(function(d) { return d != "Area" || d != "Boro" || d != "AreaType" })
, flatData = []
, excludeAreas = ["Area","NYC","Manhattan","All Downtown","All Midtown","All Upper West Side","All Upper East Side","All Upper Manhattan","Bronx","Brooklyn","North Brooklyn","Northwest Brooklyn","East Brooklyn","Prospect Park","South Brooklyn","Queens","Staten Island"];
data = data.filter(function(d) { return excludeAreas.indexOf(d["Area"]) == -1 })
.filter(function(d) { return d["Boro"] == "Brooklyn" })
data.forEach(function(d) {
columns.forEach(function(col) {
d["date"] = moment(col, "YYYY-MM-DD");
if(+d[col] > 0) { flatData.push({"date": d["date"], "val": +d[col], "area": d["Area"] }); }
})
});
var lines = lineChart()
.x("date")
.y("val")
.margin({top: 20, right: 20, bottom: 120, left: 60})
.width(parseInt(d3.select("#chart").style("width"),10))
.height(650 - 50 - 100)
.nest("area");
d3.select("#chart")
.datum(flatData)
.call(lines);
}
</script>
line-chart.js
function lineChart() {
var margin = {top: 20, right: 20, bottom: 20, left: 20}
, width = 760
, height = 120
, xValue = "date"
, yValue = "val"
, yText = "Number of Listings"
, nestValue = "area"
, colorScale = d3.scaleSequential(d3.interpolateRainbow)
, xScale = d3.scaleTime()
, yScale = d3.scaleLinear()
, xAxis = d3.axisBottom(xScale).tickSize(6, 0)
, yAxis = d3.axisLeft(yScale)
, line = d3.line().curve(d3.curveMonotoneX).x(X).y(Y)
, voronoi = d3.voronoi().x(X).y(Y)
, hoverText;
function chart(selection) {
selection.each(function(data) {
voronoi.extent([[-margin.left, -margin.top], [width + margin.right, height-margin.bottom]]);
xScale
.domain(d3.extent(data, function(d) { return d[xValue]; }))
.range([0, width - margin.left - margin.right]);
yScale
.domain([0, d3.max(data, function(d) { return d[yValue]; })])
.range([height - margin.top - margin.bottom, 0]);
if(yScale.domain()[1] <= 1) {
yAxis.tickFormat(d3.format(".0%"))
}
yAxis.tickSize(-(width-margin.left-margin.right));
xAxis.tickSize(-(height));
colorScale
.domain([0, d3.set(data.map(function(d) { return d[nestValue] })).values().length])
var nestedData = d3.nest()
.key(function(d) { return d[nestValue] })
.entries(data);
var svg = d3.select(this).selectAll("svg").data([data]);
var gEnter = svg.enter().append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
gEnter.append("g")
.attr("class", "y axis")
.transition()
.call(yAxis);
gEnter.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + yScale.range()[0] + ")")
.transition()
.call(xAxis);
gEnter.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
gEnter.append("text")
.attr("class", "y-axis-text")
.attr("text-anchor", "middle")
.attr("transform", "translate("+ (-margin.left/2) +","+((height-margin.bottom)/2)+")rotate(-90)")
.text(yText);
hoverText = gEnter.append("text")
.attr("class", "hover-text")
.attr("text-anchor", "end")
.attr("x", width - margin.left - margin.right);
svg.attr("width", width)
.attr("height", height);
var g = svg.select("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
gEnter.append("g")
.attr("class", "lines")
.selectAll(".line")
.data(nestedData, function(d) { return d.key })
.enter().append("path")
.attr("class", "line")
.style("clip-path", "url(#clip)")
.style("stroke", function(d, i) { d.color = colorScale(i); return d.color })
.attr("d", function(d) { return line(d.values) })
var lines = svg.select("g").select(".lines").selectAll(".line")
.data(nestedData, function(d) { return d.key });
lines.enter().append("path")
.attr("class", "line")
.style("clip-path", "url(#clip)")
.style("stroke", function(d, i) { d.color = colorScale(i); return d.color })
.attr("d", function(d) { return line(d.values) });
lines.exit().remove();
lines
.style("stroke", function(d, i) { d.color = colorScale(i); return d.color })
.transition()
.attr("d", function(d) { return line(d.values) });
g.select(".x")
.attr("transform", "translate(0," + yScale.range()[0] + ")")
.transition()
.call(xAxis);
g.select(".y")
.transition()
.call(yAxis);
var margin2 = {top: height-margin.bottom + 10, right: margin.right, bottom: 80, left: margin.left}
, height2 = height - margin2.top - margin2.bottom
, width2 = width - margin.left - margin.right
, x2 = d3.scaleTime().range([0, width - margin.left - margin.right]).domain(xScale.domain())
, y2 = d3.scaleLinear().range([height2, 0]).domain(yScale.domain())
, xAxis2 = d3.axisBottom(x2)
, contextLine = d3.line().curve(d3.curveMonotoneX).x(X2).y(Y2);
var brush = d3.brushX()
.extent([[0, 0], [width2, height2]])
.on("brush end", brushed);
var context = gEnter.append("g")
.attr("class", "context")
.attr("transform", "translate(0," + margin2.top + ")");
context.append("rect")
.attr("class","background-rect")
.attr("width", width2)
.attr("height", height2)
context.append("text")
.attr("class", "brush-text")
.attr("transform","translate(0," + (height2 + 35) + ")")
.text("Click and drag this section to select a timeframe.");
context.append("g")
.attr("class", "axis axis--grid")
.attr("transform", "translate(0," + height2 + ")")
.call(d3.axisBottom(x2)
.ticks(d3.timeMonth, 12)
.tickSize(-height2)
.tickFormat(function() { return null; }))
.selectAll(".tick")
.classed("tick--minor", function(d) { return d.getMonth(); });
context.append("g")
.attr("class", "context-x axis")
.attr("transform", "translate(0," + height2 + ")");
context.append("g")
.attr("class", "context-brush brush");
d3.select(".context-x").call(xAxis2);
d3.select(".context-brush").call(brush);
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return;
var s = d3.event.selection || x2.range();
xScale.domain(s.map(x2.invert, x2));
d3.selectAll(".line")
.attr("d", function(d) { return line(d.values) });
d3.selectAll(".voronoi-path")
.data(voronoi.polygons(d3.merge(nestedData.map(function(d) { return d.values; }))))
.attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; });
d3.select(".x").call(xAxis.ticks(6));
}
gEnter.append("g")
.attr("class", "voronoi")
.selectAll("path")
.data(voronoi.polygons(d3.merge(nestedData.map(function(d) { return d.values; }))))
.enter().append("path")
.attr("class", "voronoi-path")
.attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; })
.on("mouseenter", mouseover)
.on("mouseleave", mouseout);
var voronoiSelection = svg.select("g").select(".voronoi").selectAll(".voronoi-path")
.data(voronoi.polygons(d3.merge(nestedData.map(function(d) { return d.values; }))));
voronoiSelection.enter().append("path")
.attr("class", "voronoi-path")
.attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; })
.on("mouseenter", mouseover)
.on("mouseleave", mouseout);
voronoiSelection.exit().remove();
voronoiSelection
.attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; });
});
}
function mouseover(d) {
console.log(d)
d3.selectAll(".line")
.filter(function(e) { return d.data[nestValue] == e.key })
.moveToFront()
.transition()
.style("stroke-width", 4)
.style("stroke", "#000");
hoverText.text(d.data[nestValue]);
}
function mouseout(d) {
d3.selectAll(".line")
.filter(function(e) { return d.data[nestValue] == e.key })
.transition()
.style("stroke-width", 1)
.style("stroke", function(d, i) { return d.color });
hoverText.text("");
}
function X(d) {
return xScale(d[xValue]);
}
function Y(d) {
return yScale(d[yValue]);
}
function X2(d) {
return x2(d[xValue]);
}
function Y2(d) {
return y2(d[yValue]);
}
chart.margin = function(_) {
if (!arguments.length) return margin;
margin = _;
return chart;
};
chart.width = function(_) {
if (!arguments.length) return width;
width = _;
return chart;
};
chart.height = function(_) {
if (!arguments.length) return height;
height = _;
return chart;
};
chart.x = function(_) {
if (!arguments.length) return xValue;
xValue = _;
return chart;
};
chart.y = function(_) {
if (!arguments.length) return yValue;
yValue = _;
return chart;
};
chart.yText = function(_) {
if (!arguments.length) return yText;
yText = _;
return chart;
};
chart.nest = function(_) {
if (!arguments.length) return nestValue;
nestValue = _;
return chart;
};
return chart;
}
queue.min.js
!function(){function n(n){function e(){for(;i=a<c.length&&n>p;){var u=a++,e=c[u],o=t.call(e,1);o.push(l(u)),++p,e[0].apply(null,o)}}function l(n){return function(u,t){--p,null==s&&(null!=u?(s=u,a=d=0/0,o()):(c[n]=t,--d?i||e():o()))}}function o(){null!=s?m(s):f?m(s,c):m.apply(null,[s].concat(c))}var r,i,f,c=[],a=0,p=0,d=0,s=null,m=u;return n||(n=1/0),r={defer:function(){return s||(c.push(arguments),++d,e()),r},await:function(n){return m=n,f=!1,d||o(),r},awaitAll:function(n){return m=n,f=!0,d||o(),r}}}function u(){}var t=[].slice;n.version="1.0.7","function"==typeof define&&define.amd?define(function(){return n}):"object"==typeof module&&module.exports?module.exports=n:this.queue=n}();