Published
Edited
Feb 23, 2022
1 fork
4 stars
Insert cell
# base map + labels + interactivity
Insert cell
d3 = require("d3@7", "d3-geo-scale-bar@1.2", "d3-tile@1")
Insert cell
scaleBar = d3.geoScaleBar()
.top(1.15)
.left(0)
//.label("Yards")
.units({units: "500 Feet", radius: 20902259.664}) // Will do the same as the previous two lines

//.units({units: "350 Yards", radius: 6967419.888})
//.distance(width <= 400 ? 1200 : 1000)
.distance(500)
//.labelAnchor("middle")
//.tickSize(null)
//.tickValues(null)
Insert cell
chart = {
const width = 960,
height = 900;
const svg = d3.create("svg")
.attr("viewBox", [-175, -50, width+200, height+100]);//change x value to give margin on left side, add to width value to increase width of map view

// Use Mercator projection
var projection = d3
.geoMercator()
.fitSize([width - 50, height - 50], bbox);

var path1 = d3.geoPath().projection(projection);
var path2 = d3.geoPath().projection(projection);
var path3 = d3.geoPath().projection(projection);
var path4 = d3.geoPath().projection(projection);
var path5 = d3.geoPath().projection(projection);

var g = svg.append("g").attr("id", "paths");
//i'm not using the boundary lines, so I'm going to comment them out

g.selectAll("path4") //d3 geopath
.data(parks.features) //get data to define path
.enter() //there are more data than elements, this selects them
.append("path") //appends path to data
.attr('class','outlines')
.attr("d", path4) //The d attribute defines a path to be drawn, only applies to appended elements
.style("fill", "green")
.style("fill-opacity", ".3")
.style('stroke-opacity','.4')
.style("stroke-width", '.5')
.style("stroke", "rgb(0,0,0)")

g.selectAll("path2") //d3 geopath
.data(subLns.features) //get data to define path
.enter() //there are more data than elements, this selects them
.append("path") //appends path to data
.attr('class','outlines')
.attr("d", path2) //The d attribute defines a path to be drawn, only applies to appended elements
.style("fill", "none")
.style('stroke-opacity','1')
.style("stroke-width", '.75')
.style("stroke", "rgb(100,100,100)")

g.selectAll("path3") //d3 geopath
.data(bldgs.features) //get data to define path
.enter() //there are more data than elements, this selects them
.append("path") //appends path to data
.attr('class','outlines')
.attr("d", path3) //The d attribute defines a path to be drawn, only applies to appended elements
.style("fill", "rgb(240,240,240)")
.style("fill-opacity", ".5")
.style('stroke-opacity','.4')
.style("stroke-width", '.5')
.style("stroke", "rgb(0,0,0)")

//subway stops
var c = svg.selectAll("circle") //d3 geopath
.data(subSts.features)
.enter() //there are more data than elements, this selects them
.append("circle") //appends path to data
.attr("cx", function(d) {return path1.centroid(d)[0]})
.attr("cy", function(d) {return path1.centroid(d)[1]})
.attr('r',3)
.attr('fill','black')
.style('fill-opacity','1')

//chargers circles
c.enter().append('circle')//use this code for the second and any subsequent circle adds///***
.data(chargers)///***
.enter() //there are more data than elements, this selects them
.append("circle") //appends path to data
.attr("cx", function(d) {return projection([d.long,d.lat])[0]})///***
.attr("cy", function(d) {return projection([d.long,d.lat])[1]})///***
.attr('r',3)
.attr('fill','cyan')
.style('fill-opacity','1')
.style('stroke-width','1')
.style('stroke','black')

//add citibike circles
c.enter().append('circle') //d3 geopath
.data(bikes)
.enter() //there are more data than elements, this selects them
.append("circle") //appends path to data
.attr('class','cb')
.attr("cx", function(d) {return projection([d.long,d.lat])[0]})
.attr("cy", function(d) {return projection([d.long,d.lat])[1]})
.attr('r',4)
.attr('fill','white')
.style('fill-opacity','1')
.style('stroke','black')
.style('stroke-width','.75')
.on('mouseover',bikeText)
.on('mouseout',removeText)

//subway text
var t = svg.selectAll("text")//creating a variable t and adding all text objects to it
.data(subSts.features)
.enter() //there are more data than elements, this selects them
.append("text") //appends path to data
.attr("x", function(d) {return path1.centroid(d)[0]})
.attr("y", function(d) {return path1.centroid(d)[1]-5})
.attr('font-family','helvetica')
.attr('font-size','.5em')
.attr('font-weight','400')
.style("fill", "rgb(110,110,110)")
.attr('text-anchor','middle')
.text(function(d) {return d.properties.name})

//add charger annotation
t.enter().append('text')//use this for second and all subsequent text adds
.data(chargers)
.enter() //there are more data than elements, this selects them
.append("text") //appends path to data
.attr("x", '-10')
.attr("y", function(d,i){return i*30})///****
.attr('font-family','helvetica')
.attr('font-size','.55em')
.attr('font-weight','bold')
.style("fill", "rgb(0,0,0)")
.attr('text-anchor','end')
.text(function(d) {return d.name})//****

t.enter().append('text')//use this for second and all subsequent text adds
.data(chargers)
.enter() //there are more data than elements, this selects them
.append("text") //appends path to data
.attr("x", '-10')
.attr("y", function(d,i){return i*30+10})///****
.attr('font-family','helvetica')
.attr('font-size','.55em')
.attr('font-weight','400')
.style("fill", "rgb(100,100,100)")
.attr('text-anchor','end')
.text(function(d) {return d.description})//****

//annotation lines for chargers
t.enter().append('line')
.data(chargers)
.enter()
.append("line")
.attr('x1','-10')
.attr('y1',function(d,i){return i*30-3})
.attr('x2',function(d) {return projection([d.long,d.lat])[0]})
.attr('y2',function(d,i){return i*30-3})
.attr('stroke-width','1')
.attr('stroke-dasharray','4,4')
.attr('stroke','rgb(100,100,100)')

t.enter().append('line')
.data(chargers)
.enter()
.append("line")
.attr('x1',function(d) {return projection([d.long,d.lat])[0]})
.attr('y1',function(d,i){return i*30-3})
.attr('x2',function(d) {return projection([d.long,d.lat])[0]})
.attr('y2',function(d) {return projection([d.long,d.lat])[1]})
.attr('stroke-width','1')
.attr('stroke-dasharray','4,4')
.attr('stroke','rgb(100,100,100)')


//draw site outline
g.selectAll("path5") //d3 geopath
.data(site.features) //get data to define path
.enter() //there are more data than elements, this selects them
.append("path") //appends path to data
.attr('class','outlines')
.attr("d", path5) //The d attribute defines a path to be drawn, only applies to appended elements
.style("fill", "none")
.style("fill-opacity", "1")
.style('stroke-opacity','1')
.style("stroke-width", '2')
.style("stroke", "rgb(0,0,0)")
.attr('stroke-dasharray','12 12')

//add labels to site line
svg
.append("text")
.attr('class','title')
.attr('x','400')
.attr('y','500')
.attr('font-family','helvetica')
.attr('font-size','1em')
.attr('font-weight','bold')
.style("fill", "rgb(0,0,0)")
.text('Times Square')

svg
.append("text")
.attr('class','title')
.attr('x','400')
.attr('y','517')
.attr('font-family','helvetica')
.attr('font-size','.7em')
.attr('font-weight','200')
.style("fill", "rgb(0,0,0)")
.text('The Future of Movement')

svg
.append("text")
.attr('class','title')
.attr('x','-75')
.attr('y','700')
.attr('font-family','helvetica')
.attr('font-size','.7em')
.attr('font-weight','200')
.style("fill", "rgb(0,0,0)")
.attr('text-anchor','middle')
.text('Citi Bike Stations')

svg
.append("line")
.attr('x1','400')
.attr('y1','495')
.attr('x2','378')
.attr('y2','495')
.attr('stroke-width','1.5')
.attr('stroke','rgb(0,0,0)')

svg
.append("line")
.attr('x1','-120')
.attr('y1','708')
.attr('x2','-10')
.attr('y2','708')
.attr('stroke-width','1.5')
.attr('stroke','rgb(0,0,0)')

function bikeText(event,d){

d3.select(this).attr('fill','black')
svg.append('text')//use this for second and all subsequent text add
.attr("x", '-75')
.attr("y", '720')
.attr('class','bikeStop')
.attr('font-family','helvetica')
.attr('font-size','.55em')
.attr('font-weight','bold')
.style("fill", "rgb(0,0,0)")
.attr('text-anchor','middle')
.text(d.name)

svg.append('text')//use this for second and all subsequent text add
.attr("x", '-75')
.attr("y", '730')
.attr('class','bikeStop')
.attr('font-family','helvetica')
.attr('font-size','.55em')
.attr('font-weight','400')
.style("fill", "rgb(50,50,50)")
.attr('text-anchor','middle')
.text(d.description)

svg.append("line")
.attr('x1','-10')
.attr('y1','720')
.attr('class','bikeLine')
.attr('x2',projection([d.long,d.lat])[0])
.attr('y2','720')
.attr('stroke-width','1')
.attr('stroke-dasharray','4,4')
.attr('stroke','rgb(100,100,100)')

svg.append("line")
.attr('x1',projection([d.long,d.lat])[0])
.attr('y1','720')
.attr('class','bikeLine')
.attr('x2',projection([d.long,d.lat])[0])
.attr('y2',projection([d.long,d.lat])[1])
.attr('stroke-width','1')
.attr('stroke-dasharray','4,4')
.attr('stroke','rgb(100,100,100)')
}

function removeText(event,d){//remove citi bike address information
d3.selectAll('text.bikeStop').remove()
d3.selectAll('line.bikeLine').remove()
d3.selectAll('circle.cb').attr('fill','white')
}

scaleBar
.projection(projection)
.size([width, height]);

svg.append("g")
.call(scaleBar);



return svg.node();
}
Insert cell
site = FileAttachment("site_boundary_02.geojson").json()
Insert cell
subSts = FileAttachment("sub_stops.geojson").json()
Insert cell
bldgs = FileAttachment("bldgs.geojson").json()
Insert cell
bbox = FileAttachment("site_boundary.geojson").json()
Insert cell
parks = FileAttachment("parks.geojson").json()
Insert cell
subLns = FileAttachment("subway_lines.geojson").json()
Insert cell
boundary = FileAttachment("nyc_boundary.geojson").json()
Insert cell
Insert cell
chargers = d3.csv(chargesLink,d3.autoType)
Insert cell
Insert cell
bikes = d3.csv(bikesLink,d3.autoType)
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more