Public
Edited
Dec 7, 2022
2 forks
Insert cell
# NYC Subway History w/ Cultural Foods
Insert cell
d3 = require("d3@6")
Insert cell
function getDistance (x1, y1, x2, y2){
let y = x2 - x1;
let x = y2 - y1;
return Math.sqrt(x * x + y * y);
}
Insert cell
viewof radius = Inputs.range([1, 4], {step: .25, label: "Culture Spot Size"})
Insert cell
chart = {
const width = 960,
height = 900;
const svg = d3.create("svg")
.attr("viewBox", [50, 50, width - 250, height - 150]);

// Use Mercator projection
var projection = d3
.geoMercator()
.fitSize([width - 140, height - 30], borough_outline);

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 path6 = d3.geoPath().projection(projection);
var path7 = d3.geoPath().projection(projection);
var path8 = d3.geoPath().projection(projection);
var path9 = d3.geoPath().projection(projection);
var path10 = d3.geoPath().projection(projection);

var g = svg.append("g").attr("id", "paths");

var clicks = []

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

g.selectAll("path1") //d3 geopath
.data(neighborhood.features) //Current
.enter() //there are more data than elements, this selects them
.append("path") //appends path to data
.attr('class','outlines')
.attr("d", path1) //The d attribute defines a path to be drawn, only applies to appended elements
.style('fill', 'none')
.style('stroke-opacity','.25')
.style("stroke-width", '.25')
.style("stroke", "rgb(0,0,0)")

function demColor(d,i) {
var color = 'none'

if(d.properties.ntaname == 'Flushing' || d.properties.ntaname == 'Sunset Park West' || d.properties.ntaname == 'Sunset Park East' || d.properties.ntaname == 'Chinatown' || d.properties.ntaname == 'Bensonhurst West' || d.properties.ntaname == 'Bensonhurst East'){color = 'cyan'}
if(d.properties.ntaname == 'North Riverdale-Fieldston-Riverdale' || d.properties.ntaname == 'Ocean Parkway South' || d.properties.ntaname == 'Kensington-Ocean Parkway'){color = 'red'}
if(d.properties.ntaname == 'Sheepshead Bay-Gerritsen Beach-Manhattan Beach'){color = 'brown'}
if(d.properties.ntaname == 'Jackson Heights' || d.properties.ntaname == 'Elmhurst'){color = 'magenta'}
if(d.properties.ntaname == 'Washington Heights North' || d.properties.ntaname == 'Wahington Heights South'|| d.properties.ntaname == 'Corona'){color = 'green'}
return color
}

g.selectAll("path10") //d3 geopath
.data(streetlines.features) //Current
.enter() //there are more data than elements, this selects them
.append("path") //appends path to data
.attr('class','outlines')
.attr("d", path10) //The d attribute defines a path to be drawn, only applies to appended elements
.style("fill", 'none')
.style('stroke-opacity','.25')
.style("stroke-width", '.15')
.style("stroke", "gray")
g.selectAll("path9") //d3 geopath
.data(current_lines.features) //CURRENT
.enter() //there are more data than elements, this selects them
.append("path") //appends path to data
.attr('class','outlines')
.attr("d", path8) //The d attribute defines a path to be drawn, only applies to appended elements
.style("fill", "none")
.style('stroke-opacity','.4')
.style("stroke-width", '.5')
.style("stroke", "rgb(0,0,0)")

var c = svg.selectAll("circle")//current
.data(current_stops.features)
.enter() //there are more data than elements, this selects them
.append("circle") //appends path to data
.attr('class','stops')
.attr("cx", function(d) {return path9.centroid(d)[0]})
.attr("cy", function(d) {return path9.centroid(d)[1]})
.attr('r', '1')
.attr('fill', 'gray')//add function to control color by 'type'
.style('fill-opacity','1')
.style('stroke','gray')
.style('stroke-width','1')
.on('mouseover' , stopText)
.on('mouseout', removestopText)
.on('click', curClick)

c.enter().append("circle")//current
.data(restaurant1.features)
.enter() //there are more data than elements, this selects them
.append("circle") //appends path to data
.attr('class','spots')
.attr("cx", function(d) {return path9.centroid(d)[0]})
.attr("cy", function(d) {return path9.centroid(d)[1]})
.attr('r', radius)
.attr('fill', cultColor)//add function to control color by 'type'
.style('fill-opacity','1')
.style('stroke','gray')
.style('stroke-width','1')
.on('mouseover' , CultureText)
.on('mouseout', removeCultureText)
.on('click', subClick)

var newSubways = [] //list to hold new subway lines
var clickCoords = [] //list to record the start point of our line
var startPtData = []
var latlong = []
var subwayBudget = 10
var subwayTime = 10
var firstIndex = 0
var secIndex = 0
var trainEnter = []

writesubwayTime()
writesubwayBudget()

function curClick(event,d){
if (clickCoords.length == 1){//this is the second click

secIndex = current_stops.features.indexOf(d)
//empty list for current stop(i) -- make a list with points to create a polyline
for (let i = firstIndex; i < secIndex + 1; i++) {
//assign variable and push location in to the list
trainEnter.push([path9.centroid(d)[0], path9.centroid(d)[1]])
}

//svg code to create polyline
//connect the point in clickCoord to current click
var startPoint = [latlong[0][0], latlong[0][1]]
var endPoint = [d.geometry.coordinates[0], d.geometry.coordinates[1]]
var subLength = turf.distance(startPoint, endPoint, {units: 'miles'})

//console.log('ending at ' + d.properties.name)
//console.log('going from ' + startPtData[0].properties.name)
newSubways.push({x1: clickCoords[0][0], y1: clickCoords[0][1], x2: path9.centroid(d)[0], y2: path9.centroid(d)[1]})
newSubLines()

subwayTime = subwayTime-(subLength*1.5)
subwayBudget = subwayBudget-(subLength*2)
writesubwayTime()
writesubwayBudget()
clickCoords = []
latlong = []
startPtData = []
//console.log(subLength)
//console.log(subwayBudget)
}else{//this is the first click
//add current click point to clickCoords
firstIndex = current_stops.features.indexOf(d)
console.log(firstIndex)
clickCoords.push([path9.centroid(d)[0], path9.centroid(d)[1]])
latlong.push([d.geometry.coordinates[0], d.geometry.coordinates[1]])
startPtData.push(d)
}
}

function subClick(event, d){
//console.log('click')

if (clickCoords.length == 1){//this is the second click
//connect the point in clickCoord to current click
var startPoint = [latlong[0][0], latlong[0][1]]
var endPoint = [d.geometry.coordinates[0], d.geometry.coordinates[1]]
var subLength = turf.distance(startPoint, endPoint, {units: 'miles'})

//console.log('ending at ' + d.properties.name)
//console.log('going from ' + startPtData[0].properties.name)
newSubways.push({x1: clickCoords[0][0], y1: clickCoords[0][1], x2: path9.centroid(d)[0], y2: path9.centroid(d)[1]})
newSubLines()

subwayTime = subwayTime-(subLength*1.5)
subwayBudget = subwayBudget-(subLength*2)
writesubwayTime()
writesubwayBudget()
clickCoords = []
latlong = []
startPtData = []
//console.log(subLength)
//console.log(subwayBudget)
}else{//this is the first click
//add current click point to clickCoords
clickCoords.push([path9.centroid(d)[0], path9.centroid(d)[1]])
latlong.push([d.geometry.coordinates[0], d.geometry.coordinates[1]])
startPtData.push(d)
}

//console.log(newSubways)
}
function newSubLines(){
//console.log('im in the function!')
var ln = svg.selectAll("line") //d3 geopath
.data(newSubways) //get data to define path
.enter() //there are more data than elements, this selects them
.append("line") //appends path to data
.attr('class','newSubwaysClass')
.attr('x1', function(d) {return d.x1})
.attr('y1', function(d) {return d.y1})
.attr('x2', function(d) {return d.x2})
.attr('y2', function(d) {return d.y2})
.style('stroke-opacity','1')
.style("stroke-width", '1')
.style('stroke', 'red')
}

function writesubwayBudget(){
d3.selectAll('text.budget').remove()
svg
.append('text')
.attr('class', 'budget')
.attr('x', '600')
.attr('y', '200')
.attr('font-size', '2em')
.attr('font-family', 'Helvetica')
.text(round(subwayBudget,3))
}


function writesubwayTime(){
d3.selectAll('text.Time').remove()
svg
.append('text')
.attr('class', 'Time')
.attr('x', '600')
.attr('y', '250')
.attr('font-size', '2em')
.attr('font-family', 'Helvetica')
.text(round(subwayTime,3))
}



function cultColor(d,i){
var color = 'black'

if(d.properties.culture=='Chinese'){color = 'rgb(244, 175, 180)'}
if(d.properties.culture=='Italian'){color = 'rgb(72, 58, 88)'}
if(d.properties.culture=='Irish'){color = 'rgb(86, 117, 104)'}
if(d.properties.culture=='Korean'){color = 'rgb(255, 209, 102)'}
if(d.properties.culture=='Mexican'){color = 'rgb(180, 149, 148)'}
if(d.properties.culture=='Jewish'){color = 'rgb(229, 75, 75)'}
if(d.properties.culture=='Russian'){color = 'rgb(148, 197, 204)'}
if(d.properties.culture=='Dominican'){color = 'rgb(52, 37, 47)'}
if(d.properties.culture=='Vietnamese'){color = 'rgb(101, 113, 83)'}
if(d.properties.culture=='Filipino'){color = 'rgb(215, 122, 97)'}
return color
}

function CultureText(event,d){

svg.append('text')
.attr('class','spotsText')
.attr('x','600')
.attr('y','100')
.attr('font-family','Helvetica')
.attr('font-size','.6em')
.attr('text-anchor','start')
.attr('font-weight','bold')
.attr('fill' , 'gray')
.text(d.properties.name)

d3.select(this).attr('r', '4')
}

function removeCultureText(event, d){
d3.select(this).attr('fill',cultColor)
d3.select(this).attr('r', radius)
d3.selectAll('text.spotsText').remove()
}

function stopText(event,d){

svg.append('text')
.attr('class','spotsText')
.attr('x','600')
.attr('y','100')
.attr('font-family','Helvetica')
.attr('font-size','.6em')
.attr('text-anchor','start')
.attr('font-weight','bold')
.attr('fill' , 'gray')
.text(d.properties.name)

d3.select(this).attr('r', '2')
}

function removestopText(event, d){
d3.select(this).attr('fill', 'gray')
d3.select(this).attr('r', '1')
d3.selectAll('text.spotsText').remove()
}
var t = svg.selectAll("text")
.data(Mtime)
.enter()
.append('text')
.attr('x', 116)
.attr('y', function(d,i) {return 115 + 20*i})
.attr('font-family','Helvetica')
.attr('font-size', '.55em')
.attr('text.anchor','start')
.attr('font-weight', 'normal')
.attr('fill', 'gray')
.text(function(d) {return d})


c.enter().append('circle')
.data(Mtime)
.enter()
.append('circle')
.attr('cx', 110)
.attr('cy', function(d,i) {return 112 + 20*i}) //function is for looping data
.attr('r',3)
.attr('fill','gray')
.on('click', MtimeClick)

function MtimeClick(event,d){
if(d == 'Chinese'){
d3.selectAll('circle.spots').remove()
c.enter().append("circle") //circle
.data(cultureSpots)
.enter() //there are more data than elements, this selects them
.append("circle") //appends path to data
.attr('class', 'spots')
.attr("cx", function(d) {return projection([d.Longitude,d.Latitude])[0]})
.attr("cy", function(d) {return projection([d.Longitude,d.Latitude])[1]})
.attr('r', radius)
.attr('fill', chineseColor)
.style('fill-opacity','1')
.style('stroke', chineseStroke)
.style('stroke-width','1')
.on('mouseover' , CultureText)
.on('mouseout', removeCultureText)

function chineseColor(d,i){
var color = 'none'

if(d.Culture=='Chinese'){color = 'rgb(244, 175, 180)'}
return color
}
function chineseStroke(d,i){
var color = 'none'

if(d.Culture=='Chinese'){color = 'gray'}
return color
}
}

if(d == 'Italian'){
d3.selectAll('circle.spots').remove()
c.enter().append("circle") //circle
.data(cultureSpots)
.enter() //there are more data than elements, this selects them
.append("circle") //appends path to data
.attr('class', 'spots')
.attr("cx", function(d) {return projection([d.Longitude,d.Latitude])[0]})
.attr("cy", function(d) {return projection([d.Longitude,d.Latitude])[1]})
.attr('r', radius)
.attr('fill', italianColor)
.style('fill-opacity','1')
.style('stroke', italianStroke)
.style('stroke-width','1')
.on('mouseover' , CultureText)
.on('mouseout', removeCultureText)

function italianColor(d,i){
var color = 'none'

if(d.Culture=='Italian'){color = 'rgb(72, 58, 88)'}
return color
}
function italianStroke(d,i){
var color = 'none'

if(d.Culture=='Italian'){color = 'gray'}
return color
}
}

if(d == 'Irish'){
d3.selectAll('circle.spots').remove()
c.enter().append("circle") //circle
.data(cultureSpots)
.enter() //there are more data than elements, this selects them
.append("circle") //appends path to data
.attr('class', 'spots')
.attr("cx", function(d) {return projection([d.Longitude,d.Latitude])[0]})
.attr("cy", function(d) {return projection([d.Longitude,d.Latitude])[1]})
.attr('r', radius)
.attr('fill', irishColor)
.style('fill-opacity','1')
.style('stroke', irishStroke)
.style('stroke-width','1')
.on('mouseover' , CultureText)
.on('mouseout', removeCultureText)

function irishColor(d,i){
var color = 'none'

if(d.Culture=='Irish'){color = 'rgb(86, 117, 104)'}
return color
}
function irishStroke(d,i){
var color = 'none'

if(d.Culture=='Irish'){color = 'gray'}
return color
}
}

if(d == 'Korean'){
d3.selectAll('circle.spots').remove()
c.enter().append("circle") //circle
.data(cultureSpots)
.enter() //there are more data than elements, this selects them
.append("circle") //appends path to data
.attr('class', 'spots')
.attr("cx", function(d) {return projection([d.Longitude,d.Latitude])[0]})
.attr("cy", function(d) {return projection([d.Longitude,d.Latitude])[1]})
.attr('r', radius)
.attr('fill', koreanColor)
.style('fill-opacity','1')
.style('stroke', koreanStroke)
.style('stroke-width','1')
.on('mouseover' , CultureText)
.on('mouseout', removeCultureText)

function koreanColor(d,i){
var color = 'none'

if(d.Culture=='Korean'){color = 'rgb(255, 209, 102)'}
return color
}
function koreanStroke(d,i){
var color = 'none'

if(d.Culture=='Korean'){color = 'gray'}
return color
}
}

if(d == 'Mexican'){
d3.selectAll('circle.spots').remove()
c.enter().append("circle") //circle
.data(cultureSpots)
.enter() //there are more data than elements, this selects them
.append("circle") //appends path to data
.attr('class', 'spots')
.attr("cx", function(d) {return projection([d.Longitude,d.Latitude])[0]})
.attr("cy", function(d) {return projection([d.Longitude,d.Latitude])[1]})
.attr('r', radius)
.attr('fill', mexicanColor)
.style('fill-opacity','1')
.style('stroke', mexicanStroke)
.style('stroke-width','1')
.on('mouseover' , CultureText)
.on('mouseout', removeCultureText)

function mexicanColor(d,i){
var color = 'none'

if(d.Culture=='Mexican'){color = 'rgb(180, 149, 148)'}
return color
}
function mexicanStroke(d,i){
var color = 'none'

if(d.Culture=='Mexican'){color = 'gray'}
return color
}
}

if(d == 'Jewish'){
d3.selectAll('circle.spots').remove()
c.enter().append("circle") //circle
.data(cultureSpots)
.enter() //there are more data than elements, this selects them
.append("circle") //appends path to data
.attr('class', 'spots')
.attr("cx", function(d) {return projection([d.Longitude,d.Latitude])[0]})
.attr("cy", function(d) {return projection([d.Longitude,d.Latitude])[1]})
.attr('r', radius)
.attr('fill', jewishColor)
.style('fill-opacity','1')
.style('stroke', jewishStroke)
.style('stroke-width','1')
.on('mouseover' , CultureText)
.on('mouseout', removeCultureText)

function jewishColor(d,i){
var color = 'none'

if(d.Culture=='Jewish'){color = 'rgb(229, 75, 75)'}
return color
}
function jewishStroke(d,i){
var color = 'none'

if(d.Culture=='Jewish'){color = 'gray'}
return color
}
}

if(d == 'Russian'){
d3.selectAll('circle.spots').remove()
c.enter().append("circle") //circle
.data(cultureSpots)
.enter() //there are more data than elements, this selects them
.append("circle") //appends path to data
.attr('class', 'spots')
.attr("cx", function(d) {return projection([d.Longitude,d.Latitude])[0]})
.attr("cy", function(d) {return projection([d.Longitude,d.Latitude])[1]})
.attr('r', radius)
.attr('fill', russianColor)
.style('fill-opacity','1')
.style('stroke', russianStroke)
.style('stroke-width','1')
.on('mouseover' , CultureText)
.on('mouseout', removeCultureText)

function russianColor(d,i){
var color = 'none'

if(d.Culture=='Russian'){color = 'rgb(148, 197, 204)'}
return color
}
function russianStroke(d,i){
var color = 'none'

if(d.Culture=='Russian'){color = 'gray'}
return color
}
}

if(d == 'Dominican'){
d3.selectAll('circle.spots').remove()
c.enter().append("circle") //circle
.data(cultureSpots)
.enter() //there are more data than elements, this selects them
.append("circle") //appends path to data
.attr('class', 'spots')
.attr("cx", function(d) {return projection([d.Longitude,d.Latitude])[0]})
.attr("cy", function(d) {return projection([d.Longitude,d.Latitude])[1]})
.attr('r', radius)
.attr('fill', dominicanColor)
.style('fill-opacity','1')
.style('stroke', dominicanStroke)
.style('stroke-width','1')
.on('mouseover' , CultureText)
.on('mouseout', removeCultureText)

function dominicanColor(d,i){
var color = 'none'

if(d.Culture=='Dominican'){color = 'rgb(52, 37, 47)'}
return color
}
function dominicanStroke(d,i){
var color = 'none'

if(d.Culture=='Dominican'){color = 'gray'}
return color
}
}

if(d == 'Vietnamese'){
d3.selectAll('circle.spots').remove()
c.enter().append("circle") //circle
.data(cultureSpots)
.enter() //there are more data than elements, this selects them
.append("circle") //appends path to data
.attr('class', 'spots')
.attr("cx", function(d) {return projection([d.Longitude,d.Latitude])[0]})
.attr("cy", function(d) {return projection([d.Longitude,d.Latitude])[1]})
.attr('r', radius)
.attr('fill', vietColor)
.style('fill-opacity','1')
.style('stroke', vietStroke)
.style('stroke-width','1')
.on('mouseover' , CultureText)
.on('mouseout', removeCultureText)

function vietColor(d,i){
var color = 'none'

if(d.Culture=='Vietnamese'){color = 'rgb(101, 113, 83)'}
return color
}
function vietStroke(d,i){
var color = 'none'

if(d.Culture=='Vietnamese'){color = 'gray'}
return color
}
}

if(d == 'Filipino'){
d3.selectAll('circle.spots').remove()
c.enter().append("circle") //circle
.data(cultureSpots)
.enter() //there are more data than elements, this selects them
.append("circle") //appends path to data
.attr('class', 'spots')
.attr("cx", function(d) {return projection([d.Longitude,d.Latitude])[0]})
.attr("cy", function(d) {return projection([d.Longitude,d.Latitude])[1]})
.attr('r', radius)
.attr('fill', filipinoColor)
.style('fill-opacity','1')
.style('stroke', filipinoStroke)
.style('stroke-width','1')
.on('mouseover' , CultureText)
.on('mouseout', removeCultureText)

function filipinoColor(d,i){
var color = 'none'

if(d.Culture=='Filipino'){color = 'rgb(215, 122, 97)'}
return color
}
function filipinoStroke(d,i){
var color = 'none'

if(d.Culture=='Filipino'){color = 'gray'}
return color
}
}

}
var wrap = svg.selectAll("text.history")
.each(function(d, i) { wrap_text(d3.select(this), 250) });//value controls how wide text gets





return svg.node();
}
Insert cell
restaurant1 = FileAttachment("restaurant1.geojson").json()
Insert cell
restaurants = FileAttachment("Restaurants.geojson").json()
Insert cell
streetlines = FileAttachment("Streetlines.geojson").json()
Insert cell
borough_outline = FileAttachment("Borough_Outline.geojson").json()
Insert cell
boroughs = FileAttachment("Boroughs.geojson").json()
Insert cell
current_lines = FileAttachment("Current_Lines.geojson").json()
Insert cell
current_stops = FileAttachment("Current_Stops.geojson").json()
Insert cell
Mtime = ['Chinese', 'Italian', 'Irish', 'Korean', 'Mexican', 'Jewish', 'Russian', 'Dominican', 'Vietnamese', 'Filipino' ]
Insert cell
import { wrap_text, wrap_text_nchar } from "@ben-tanen/svg-text-and-tspan-word-wrapping"
Insert cell
Insert cell
cultureSpots = d3.csv(cultureLink, d3.autoType)
Insert cell
neighborhood = FileAttachment("Neighborhood.geojson").json()
Insert cell
turf = require("@turf/turf@5")
Insert cell
round = (n, places) => {
if (!places) return Math.round(n);
const d = 10 ** places;
return Math.round(n * d) / d;
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more