Skip to content

Instantly share code, notes, and snippets.

@ursulams
Last active June 26, 2020 15:32
Show Gist options
  • Save ursulams/85916753803ea938ee1bc07a8214f373 to your computer and use it in GitHub Desktop.
Save ursulams/85916753803ea938ee1bc07a8214f373 to your computer and use it in GitHub Desktop.
bar chart and responsive nyc choropleth
license: mit
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<!-- <script src="d3.min.js"></script> -->
<script src="https://d3js.org/d3.v5.min.js"></script>
<title>NYC Cuisines Mapped - Ursula Kaczmarek</title>
<style id="jsbin-css">
.zipcode {
stroke: grey;
stroke-width: 1px;
fill: none;
}
.bar {
fill: lightsteelblue;
stroke: grey;
}
.bar:hover {
fill: steelBlue;
}
.axis--y path {
display: none;
}
.label {
fill: black;
font-family: Helvetica;
font-size: 12px;
}
.axislabel {
fill: black;
font-family: Helvetica;
font-size: 12px;
}
.axis--x path {
opacity: 0.0;
}
.axis--x line {
stroke: lightgrey;
opacity: 0.5;
}
.axis--map--caption {
fill: black;
font-family: Helvetica;
font-size: 12px;
text-anchor: start;
font-weight: bold;
}
.tooltip {
position: absolute;
z-index: 10;
visibility: hidden;
background: lightgrey;
padding: 10px;
border: 1px solid grey;
font-family: Helvetica;
font-size: 12px;
}
.scale {
font-family: Helvetica;
font-size: 8px;
}
</style>
</head>
<body>
<div id="chart">
<svg width="1000" height="850"></svg>
</div>
<script id="jsbin-javascript">
var zip_url = "https://raw.githubusercontent.com/hvo/datasets/master/nyc_zip.geojson";
var cuisines_url = "https://raw.githubusercontent.com/hvo/datasets/master/nyc_restaurants_by_cuisine.json";
Promise.all([d3.json(zip_url), d3.json(cuisines_url)])
.then(ready);
function ready(bothSets) {
var zipcodes = bothSets[0];
var cuisines = bothSets[1];
var data = cuisines.map(function(d) {
return [d.cuisine, d.total];
})
.slice(0, 25)
var svg = d3.select("svg");
var gMap = svg.append("g")
.attr("transform", "translate(0, 0)");
var totalValues = d3.max(data, function(d) {
return d[1];
});
var g = svg.append("g");
var x = d3.scaleLinear()
.domain([0, totalValues])
.rangeRound([00, 250]);
var ybar = d3.scaleBand()
.domain(data.map(function(d) {
return d[0];
}))
.rangeRound([55, 525]);
var tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip");
//add cuisine names to barchart
g.append("g")
.attr("class", "axis axis--y")
.attr("transform", "translate(100,0)")
.call(d3.axisLeft(ybar))
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("x", -ybar.range()[1] * 0.2)
.attr("y", -35);
//creating x axis and ticks
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(100, 45)")
.call(d3.axisBottom(x)
.ticks(7)
.tickSize(490)
.tickSizeOuter(0)
.tickFormat(d3.format(".0s")))
.append("text")
.attr("class", "axislabel")
.attr("x", (x.range()[0] + x.range()[1]) * 0.5)
.attr("y", -18)
.text("Number of restaurants");
//adding x ticks below
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(100,40)")
.call(d3.axisBottom(x)
.ticks(7)
.tickSize(0)
.tickSizeOuter(0)
.tickFormat(d3.format(".0s")))
// create barchart
g.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", x(0) + 100)
.attr("y", function(d, i) {
return ybar(d[0]);
})
.attr("width", function(d, i) {
return x(d[1]);
})
.attr("height", ybar.bandwidth() - 2)
.on("mouseover", function(d, i) {
cuisinemap(cuisines[i], gMap, data[i]);
d3.select(this)
.transition().duration(500)
.attr("x", x(0) - 1 + 100)
.attr("y", ybar(d[0]) - 1)
.attr("width", x(d[1]) + 20)
.attr("height", ybar.bandwidth() - 2);
tooltip.text("Number of " + d[0] + " restaurants citywide: " + d[1]);
return tooltip.style("visibility", "visible"); // show tooltip on hover
})
.on("mousemove", function() {
return tooltip
.style("top", (d3.event.pageY + 1) + "px")
.style("left", (d3.event.pageX + 1) + "px");
})
.on("mouseout", function(d) {
d3.select(this)
.transition().duration(500)
.attr("x", x(0) + 100)
.attr("y", ybar(d[0]))
.attr("width", x(d[1]))
.attr("height", ybar.bandwidth() - 2);
return tooltip.style("visibility", "hidden");
});
// create map using function defined below
cuisinemap(cuisines[0], gMap, data[0]);
function cuisinemap(cuisineName, gMap, cuisineLegend) {
var canvasSize = [1200, 850];
var counts = cuisineName.perZip;
var countData = Object.entries(counts);
var maxCount = d3.max(countData, function(d) {
return d[1];
});
var color = d3.scaleThreshold()
.domain(d3.range(0, maxCount, maxCount / 5))
.range(["#f1eef6", "#bdc9e1", "#74a9cf", "#2b8cbe", "#045a8d"]);
var projection = d3.geoMercator()
.scale(Math.pow(2, 10.35 + 5.34))
.center([-73.975, 40.7])
.translate([canvasSize[0] / 2, canvasSize[1] / 3]);
var path = d3.geoPath()
.projection(projection);
var scale = d3.scaleLinear()
.domain([0, 0])
.rangeRound([350, 490]);
gMap.selectAll(".zipcode")
.data(zipcodes.features)
.enter().append("path")
.attr("class", "zipcode")
.attr("d", path);
// update all elements of zipcode class
gMap.selectAll(".zipcode")
.data(countData, function(d) {
return (d[0] ? d[0] : d.properties.zipcode);
})
.style("fill", function(d) {
return color(d[1]);
})
.on("mouseover", function(d, i) {
tooltip.transition()
.duration(500)
.text("Restaurants in zipcode " + d[0] + ": " + d[1]);
return tooltip.style("visibility", "visible"); // show tooltip on hover
})
.on("mousemove", function() {
return tooltip
.style("top", (d3.event.pageY + 1) + "px")
.style("left", (d3.event.pageX + 1) + "px");
})
.on("mouseout", function(d) {
d3.select(this)
.transition().duration(500)
.attr("x", x(0) + 100)
.attr("y", ybar(d[0]))
.attr("width", x(d[1]))
.attr("height", ybar.bandwidth() - 2);
return tooltip.style("visibility", "hidden");
})
.exit().style("fill", color(-1));
g.append("g")
.attr("class", "legend")
.attr("transform", "translate(1,1)")
.append("text")
.attr("class", "axis--map--caption")
.attr("y", -6);
g.append("g")
.attr("class", "grid axis--x")
.attr("transform", "translate(0, 50)")
.call(d3.axisTop(scale).ticks(5).tickSize(-15).tickFormat(""))
g.selectAll(".scale").remove();
g.selectAll(".label").remove();
g.append("text")
.data(cuisineLegend)
.attr("class", "label")
.attr("x", 370)
.attr("y", 45)
.text(function(d) {
return "Number of " + d + " Restaurants";
});
for (i = 0; i < 4; ++i) {
g.append("rect")
.attr("class", "scale")
.attr("x", 370 + i * 35)
.attr("y", 50)
.attr('width', 35)
.attr("height", 7)
.style("fill", color(i / 4 * maxCount));
}
for (i = 0; i < 4; ++i) {
g.append("text")
.attr("class", "scale")
.attr("x", 435 + 33 * (i - 2))
.attr("y", 70)
.text(Math.round(i / 5 * maxCount).toString());
}
}
}
</script>
</body>
</html>
.zipcode {
stroke: grey;
stroke-width: 1px;
fill: none;
}
.bar {
fill: lightsteelblue;
stroke: grey;
}
.bar:hover {
fill: steelBlue;
}
.axis--y path {
display: none;
}
.label {
fill: black;
font-family: Helvetica;
font-size: 12px;
}
.axislabel {
fill: black;
font-family: Helvetica;
font-size: 12px;
}
.axis--x path {
opacity: 0.0;
}
.axis--x line {
stroke: lightgrey;
opacity: 0.5;
}
.axis--map--caption {
fill: black;
font-family: Helvetica;
font-size: 12px;
text-anchor: start;
font-weight: bold;
}
.tooltip {
position: absolute;
z-index: 10;
visibility: hidden;
background: lightgrey;
padding: 10px;
border: 1px solid grey;
font-family: Helvetica;
font-size: 12px;
}
.scale {
font-family: Helvetica;
font-size: 8px;
}
var zip_url = "https://raw.githubusercontent.com/hvo/datasets/master/nyc_zip.geojson";
var cuisines_url = "https://raw.githubusercontent.com/hvo/datasets/master/nyc_restaurants_by_cuisine.json";
Promise.all([d3.json(zip_url), d3.json(cuisines_url)])
.then(ready);
function ready(bothSets) {
var zipcodes = bothSets[0];
var cuisines = bothSets[1];
var data = cuisines.map(function(d) {
return [d.cuisine, d.total];
})
.slice(0, 25)
var svg = d3.select("svg");
var gMap = svg.append("g")
.attr("transform", "translate(0, 0)");
var totalValues = d3.max(data, function(d) {
return d[1];
});
var g = svg.append("g");
var x = d3.scaleLinear()
.domain([0, totalValues])
.rangeRound([00, 250]);
var ybar = d3.scaleBand()
.domain(data.map(function(d) {
return d[0];
}))
.rangeRound([55, 525]);
var tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip");
//add cuisine names to barchart
g.append("g")
.attr("class", "axis axis--y")
.attr("transform", "translate(100,0)")
.call(d3.axisLeft(ybar))
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("x", -ybar.range()[1] * 0.2)
.attr("y", -35);
//creating x axis and ticks
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(100, 45)")
.call(d3.axisBottom(x)
.ticks(7)
.tickSize(490)
.tickSizeOuter(0)
.tickFormat(d3.format(".0s")))
.append("text")
.attr("class", "axislabel")
.attr("x", (x.range()[0] + x.range()[1]) * 0.5)
.attr("y", -18)
.text("Number of restaurants");
//adding x ticks below
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(100,40)")
.call(d3.axisBottom(x)
.ticks(7)
.tickSize(0)
.tickSizeOuter(0)
.tickFormat(d3.format(".0s")))
// create barchart
g.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", x(0) + 100)
.attr("y", function(d, i) {
return ybar(d[0]);
})
.attr("width", function(d, i) {
return x(d[1]);
})
.attr("height", ybar.bandwidth() - 2)
.on("mouseover", function(d, i) {
cuisinemap(cuisines[i], gMap, data[i]);
d3.select(this)
.transition().duration(500)
.attr("x", x(0) - 1 + 100)
.attr("y", ybar(d[0]) - 1)
.attr("width", x(d[1]) + 20)
.attr("height", ybar.bandwidth() - 2);
tooltip.text("Number of " + d[0] + " restaurants citywide: " + d[1]);
return tooltip.style("visibility", "visible"); // show tooltip on hover
})
.on("mousemove", function() {
return tooltip
.style("top", (d3.event.pageY + 1) + "px")
.style("left", (d3.event.pageX + 1) + "px");
})
.on("mouseout", function(d) {
d3.select(this)
.transition().duration(500)
.attr("x", x(0) + 100)
.attr("y", ybar(d[0]))
.attr("width", x(d[1]))
.attr("height", ybar.bandwidth() - 2);
return tooltip.style("visibility", "hidden");
});
// create map using function defined below
cuisinemap(cuisines[0], gMap, data[0]);
function cuisinemap(cuisineName, gMap, cuisineLegend) {
var canvasSize = [1200, 850];
var counts = cuisineName.perZip;
var countData = Object.entries(counts);
var maxCount = d3.max(countData, function(d) {
return d[1];
});
var color = d3.scaleThreshold()
.domain(d3.range(0, maxCount, maxCount / 5))
.range(["#f1eef6", "#bdc9e1", "#74a9cf", "#2b8cbe", "#045a8d"]);
var projection = d3.geoMercator()
.scale(Math.pow(2, 10.35 + 5.34))
.center([-73.975, 40.7])
.translate([canvasSize[0] / 2, canvasSize[1] / 3]);
var path = d3.geoPath()
.projection(projection);
var scale = d3.scaleLinear()
.domain([0, 0])
.rangeRound([350, 490]);
gMap.selectAll(".zipcode")
.data(zipcodes.features)
.enter().append("path")
.attr("class", "zipcode")
.attr("d", path);
// update all elements of zipcode class
gMap.selectAll(".zipcode")
.data(countData, function(d) {
return (d[0] ? d[0] : d.properties.zipcode);
})
.style("fill", function(d) {
return color(d[1]);
})
.on("mouseover", function(d, i) {
tooltip.transition()
.duration(500)
.text("Restaurants in zipcode " + d[0] + ": " + d[1]);
return tooltip.style("visibility", "visible"); // show tooltip on hover
})
.on("mousemove", function() {
return tooltip
.style("top", (d3.event.pageY + 1) + "px")
.style("left", (d3.event.pageX + 1) + "px");
})
.on("mouseout", function(d) {
d3.select(this)
.transition().duration(500)
.attr("x", x(0) + 100)
.attr("y", ybar(d[0]))
.attr("width", x(d[1]))
.attr("height", ybar.bandwidth() - 2);
return tooltip.style("visibility", "hidden");
})
.exit().style("fill", color(-1));
g.append("g")
.attr("class", "legend")
.attr("transform", "translate(1,1)")
.append("text")
.attr("class", "axis--map--caption")
.attr("y", -6);
g.append("g")
.attr("class", "grid axis--x")
.attr("transform", "translate(0, 50)")
.call(d3.axisTop(scale).ticks(5).tickSize(-15).tickFormat(""))
g.selectAll(".scale").remove();
g.selectAll(".label").remove();
g.append("text")
.data(cuisineLegend)
.attr("class", "label")
.attr("x", 370)
.attr("y", 45)
.text(function(d) {
return "Number of " + d + " Restaurants";
});
for (i = 0; i < 4; ++i) {
g.append("rect")
.attr("class", "scale")
.attr("x", 370 + i * 35)
.attr("y", 50)
.attr('width', 35)
.attr("height", 7)
.style("fill", color(i / 4 * maxCount));
}
for (i = 0; i < 4; ++i) {
g.append("text")
.attr("class", "scale")
.attr("x", 435 + 33 * (i - 2))
.attr("y", 70)
.text(Math.round(i / 5 * maxCount).toString());
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment