Public
Edited
Dec 18, 2023
Insert cell
# London Base Map
Insert cell
d3 = require("d3@6")
Insert cell
chart = {
const width = 900,
height = 700;
const svg = d3.create("svg")
.attr("viewBox", [-35, -50, width-80, height-20]);

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


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 g = svg.append("g").attr("id", "paths");
var c = svg.selectAll("circle")
svg
.append("text")
.attr('x','550')
.attr('y','550')
.attr('fill','DarkOrange')
.style('font-family','Montserrat')
.style('font-size','26px')
.style('font-weight','bold')//use 'fill' to change font color
.text('LONDON MAP')

svg
.append("text")
.attr('x','550')
.attr('y','570')
.style('font-family','Montserrat')
.style('font-size','12px')
.style('font-weight','light')//use 'fill' to change font color
.text('FOOD WASTE IN LONDON')

svg
.append("circle")//use this line everytime after we create a circle
.attr('cx','1')
.attr('cy','10')
.attr('r',6)
.attr('fill','Coral')
.style('fill-opacity','1')
.style('stroke','black')
.style('stroke-width','.5')
.on('mouseover', addfill)
.on('mouseleave', removefill)

function addfill(event,d) {
d3.select(this).style("fill", 'DarkSeaGreen')
d3.select(this).style('fill-opacity','.9')
d3.select(this).style("stroke", 'DarkOrange')

svg
.append('text')
.attr("class","super_label")
.attr('x','610')
.attr('y','10')
.style('font-family','Montserrat')
.attr('font-size','18px')
.text('Drop-off Food Waste at Supermarkets')

var wrap = svg.selectAll('text.super_label')
.each(function(d,i) {wrap_text(d3.select(this),75)})
var c = svg.selectAll("circle") //use this line the first time we create a circle
.data(supermarketLocations.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',4)
.attr("class","supermarket")
.attr('fill','DarkSeaGreen')
.style('fill-opacity','.8')
.style('stroke','DarkOrange')
.style('stroke-width','0.25')
}
//take away park labels/park fill
function removefill(event,d) {
d3.selectAll("circle.supermarket").remove()
d3.selectAll("text.super_label").remove()
d3.select(this).style("fill", "Coral")
d3.select(this).style("stroke", "black")
}
svg
.append("text")
.attr('x','14')
.attr('y','13')
.style('font-family','Montserrat')
.style('font-size','14px')
.style('font-weight','light')//use 'fill' to change font color
.text('STEP 1')



svg
.append("circle")//use this line everytime after we create a circle
.attr('cx','1')
.attr('cy','30')
.attr('r',6)
.attr('fill','Coral')
.style('fill-opacity','1')
.style('stroke','black')
.style('stroke-width','0.5')
.on('mouseover', Addfill)
.on('mouseleave', Removefill)

function Addfill(event,d) {
d3.select(this).style("fill", 'Crimson')
d3.select(this).style('fill-opacity','.9')


svg
.append('text')
.attr("class","teams_label")
.attr('x','610')
.attr('y','10')
.style('font-family','Montserrat')
.attr('font-size','18px')
.text('The most food waste donated to a team declares a winner')

var wrap = svg.selectAll('text.teams_label')
.each(function(d,i) {wrap_text(d3.select(this),100)})

/* //text created from a dataset should go above text written directly
var t = svg.selectAll("text")
.data(football_loyalties.features)
.enter() //there are more data than elements, this selects them
.append("text") //appends path to data
.attr("class","loyal_labels")
.attr("x", function(d) {return path1.centroid(d)[0]-5})
.attr("y", function(d) {return path1.centroid(d)[1]-10})
.style('font-family','georgia')
.style('font-size','5px')
.style('text-anchor','middle')
.style('fill','MidnightBlue')//use 'fill' to change font color
.style('font-weight','light')//use 'fill' to change font color
.text(function(d){return d.properties.name})

var wrap = svg.selectAll('text.loyal_labels')
.each(function(d,i) {wrap_text(d3.select(this),70)})
*/
g.selectAll("path2") //d3 geopath
.data(football_loyalties.features) //get data to define path
.enter() //there are more data than elements, this selects them
.append("path") //appends path to data
.attr('class','outlines1')
.attr("d", path2) //The d attribute defines a path to be drawn, only applies to appended elements
.style("fill", function(d){return d.properties.color})
.style('fill-opacity','.8')
}
//take away park labels/park fill
function Removefill(event,d) {
d3.selectAll("path.outlines1").remove()
d3.selectAll("text.teams_label").remove()
d3.selectAll("text.loyal_labels").remove()
d3.select(this).style("fill", "Coral")
}
svg
.append("text")
.attr('x','14')
.attr('y','34')
.style('font-family','Montserrat')
.style('font-size','14px')
.style('font-weight','light')//use 'fill' to change font color
.text('STEP 2')



svg
.append("circle")//use this line everytime after we create a circle
.attr('cx','1')
.attr('cy','50')
.attr('r',6)
.attr('fill','Coral')
.style('fill-opacity','1')
.style('stroke','black')
.style('stroke-width','0.25')
.on('mouseover', AddFill)
.on('mouseleave', RemoveFill)

function AddFill(event,d) {
d3.select(this).style("fill", 'LightPink')
d3.select(this).style('fill-opacity','.9')
d3.select(this).style("stroke", 'DarkOrange')


svg
.append('text')
.attr("class","pub_label")
.attr('x','610')
.attr('y','10')
.style('font-family','Montserrat')
.attr('font-size','18px')
.text('Pubs within each boundary celebrate the winning team')

var wrap = svg.selectAll('text.pub_label')
.each(function(d,i) {wrap_text(d3.select(this),75)})
c.enter().append("circle")//use this line everytime after we create a circle
.data(pubs_loyalties.features)
.enter() //there are more data than elements, this selects them
.append("circle") //appends path to data
.attr("class","pubs")
.attr("cx", function(d) {return path1.centroid(d)[0]})
.attr("cy", function(d) {return path1.centroid(d)[1]})
.attr('r',3)
.attr('fill','LightPink')
.style('fill-opacity','1')
.style('stroke','DarkOrange')
.style('stroke-width','0.25')
}
//take away park labels/park fill
function RemoveFill(event,d) {
d3.selectAll("circle.pubs").remove()
d3.selectAll("text.pub_label").remove()
d3.select(this).style("fill", "Coral")
d3.select(this).style("stroke", "black")
}

svg
.append("text")
.attr('x','14')
.attr('y','55')
.style('font-family','Montserrat')
.style('font-size','14px')
.style('font-weight','light')//use 'fill' to change font color
.text('STEP 3')



g.selectAll("path2") //d3 geopath
.data(borders.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("stroke", "DarkOrange")
.style('stroke-width','2')
.style("fill", "none")
.style('stroke-opacity','1')



g.selectAll("path2") //d3 geopath
.data(football_loyalties.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", "Cornsilk")
.style('fill-opacity','.9')
.on('mouseover', addFill)
.on('mouseleave', removeFill)

function addFill(event,d) {

console.log(d.properties.Layer)
var curTeam = d.properties.Layer
d3.select(this).style("fill", function(d){return d.properties.color})
d3.select(this).style('fill-opacity','.9')
d3.select(this).style("stroke", function(d){return d.properties.color})
d3.select(this).style('stroke-opacity','.8')
svg
.append('text')
.attr("class","labels")
.attr('x','590')
.attr('y','40')
.style('font-family','Montserrat')
.attr('font-size','.85em')
.text(d.properties.name)

var wrap = svg.selectAll('text.labels')
.each(function(d,i) {wrap_text(d3.select(this),100)})

c.enter().append("circle")//use this line everytime after we create a circle
.data(pubs_loyalties.features)
.enter() //there are more data than elements, this selects them
.append("circle") //appends path to data
.attr("class","pubs")
.attr("cx", function(d) {return path1.centroid(d)[0]})
.attr("cy", function(d) {return path1.centroid(d)[1]})
.attr('r',3)
.attr('fill','LightPink')
//.style('fill-opacity','1')
.style('fill-opacity',hovered)
.style('stroke','DarkOrange')
.style('stroke-width',hovered)

function hovered(d,i){
var color = 0

if(d.properties.layer==curTeam){
color = 1
console.log('match')}

return color
}

c.enter().append("circle")//use this line everytime after we create a circle
.data(supermarketLocations.features)
.enter() //there are more data than elements, this selects them
.append("circle") //appends path to data
.attr("class","superms")
.attr("cx", function(d) {return path1.centroid(d)[0]})
.attr("cy", function(d) {return path1.centroid(d)[1]})
.attr('r',4)
.attr('fill','DarkSeaGreen')
//.style('fill-opacity','1')
.style('fill-opacity',hovered)
.style('stroke','DarkOrange')
.style('stroke-width',hovered)

function hovered(d,i){
var color = 0

if(d.properties.layer==curTeam){
color = 1
console.log('match')}

return color
}

}
//take away park labels/park fill
function removeFill(event,d) {
d3.selectAll("text.labels").remove()
d3.selectAll("circle.pubs").remove()
d3.selectAll("circle.superms").remove()
d3.select(this).style("fill", "Cornsilk")
d3.select(this).style('fill-opacity','.8')
d3.select(this).style("stroke", "none")
}



//heyy can you see this? yes great - we're matching by team name? can you confirm for the borough outlines, that's the name property, and for supermakets, it's the layer property, correct? yes for the supermarkets, let me check for the outlines it should be "Layer" for the loyalty outlines and "layer" for the supermarkets got it, up above i created a varialble called curTeam that is the Layer of the outline you're hovering over. Then when you draw the supermarkets, in the fill opacity style there's a function called hovered that checks if the current supermarket layer property matches the Layer property of the currently hovered fill. The only issue is, it's not working... lol I'm going to have a quick look at things ok

// you have multiple addfill and removefill functions, we should clear those out or is there a reason to have multiple? Well I have ther three circles on the top left that you can mouseover to show certain things plus the mouseover for changing the colors of the oulines in the map and I wasn't sure how to make that all work under one mouseover so i separated them - yes got it, and they have names that are differentiated enough to work but not enough for me to realize it until now :) so i guess it's good, it's fine to have as many mouseover functions are you want, I thought they were copies of one another. - yea i tried to keep the name the same for all of them for no specific reason lol it's cool as long as it makes sense to you --- it's more or less working now, the interesting thing is that when you rollover one chelsea region, the supermakets show up from multiple regions bc there are multiple chelseas - same w/ other teams. maybe that's ok?Kind of your call - i had a feeling that would be the case i was thinking of having like a list of all the teams to the side that you could rollover to show the supermarkets that fall under each team but i wasn't sure if that would be overkill - I don't think you need it. I would focus on explaining how it works, the steps are nice, now you have the supermarkets, see if you can add the pubs - you can put them in the same rollover function and use the same logic as I'm using in the supermakets. I don't think you need to go further in spelling out specifics about the pubs or groceries. -- that works for me. thanks for your help! no prob, let me know if something else comes up! will do!👍



/*


var c = svg.selectAll("circle") //use this line the first time we create a circle
.data(artgalleries.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',2)
.attr('fill','grey')
.style('stroke-opacity','.2')
.style('stroke','black')
.style('stroke-width','0.25')
.style('fill-opacity','.2')
.on('mouseover',galleryText)
.on('mouseout',removeGalText)
function galleryText(event,d){
console.log(d)
svg
.append("text")
.attr('x','750')
.attr('y','500')
.attr('class','gallery_position')
.style('font-family','courier new')
.style('font-size','16px')
.style('font-weight','bold')//use 'fill' to change font color
.style('fill-opacity','.5')
.text(d.properties.name)

p.enter().append("polyline")
.data(GalIcon)
.enter() //there are more data than elements, this selects them
.append("polyline") //appends path to data
.attr("points", function(d) {return d})
.attr('class','Artline')
.attr('fill','none')
.style('stroke-opacity','.5')
.style('stroke','black')
.style('stroke-width','.5')

p.enter().append("polyline")
.data(frameRollover)
.enter() //there are more data than elements, this selects them
.append("polyline") //appends path to data
.attr("points", function(d) {return d})
.attr('class','Frameline')
.attr('fill','none')
.style('stroke-opacity','.5')
.style('stroke','black')
.style('stroke-width','2')

p.enter().append("polyline")
.data(peopleFill)
.enter() //there are more data than elements, this selects them
.append("polyline") //appends path to data
.attr("points", function(d) {return d})
.attr('class','PFill')
.attr('fill','grey')
.style('fill-opacity','.5')
.style('stroke','none')
.style('stroke-width','2')

p.enter().append("polyline")
.data(artFill)
.enter() //there are more data than elements, this selects them
.append("polyline") //appends path to data
.attr("points", function(d) {return d})
.attr('class','ArtFill')
.attr('fill','grey')
.style('fill-opacity','.5')
.style('stroke','none')
.style('stroke-width','2')

var wrap = svg.selectAll('text.gallery_position')
.each(function(d,i) {wrap_text(d3.select(this),75)})
}

function removeGalText(){
d3.selectAll('text.gallery_position').remove()
d3.selectAll('polyline.Artline').remove()
d3.selectAll('polyline.Frameline').remove()
d3.selectAll('polyline.PFill').remove()
d3.selectAll('polyline.ArtFill').remove()
}
*/

/*c.enter().append("circle")//use this line everytime after we create a circle
.data(pubs_loyalties.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','LightPink')
.style('fill-opacity','.4')
.style('stroke','black')
.style('stroke-width','0.25')

*/

/*
c.enter().append("circle")//use this line everytime after we create a circle
.data(personal_spots)
.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',4)
.attr('fill','purple')
.style('stroke','black')
.style('stroke-width','0.25')
.style('fill-opacity','1')
.on('mouseover',SpotsText)
.on('mouseout',removeSpotsText)


function SpotsText(event,d){
console.log(d)

p.enter().append("polyline")
.data(spotsHighlight)
.enter() //there are more data than elements, this selects them
.append("polyline") //appends path to data
.attr("points", function(d) {return d})
.attr('class','SHiLite')
.attr('fill','purple')
.style('fill-opacity','0.1')
.style('stroke-opacity','0.5')
.style('stroke','purple')
.style('stroke-width','4')

svg
.append("text")
.attr('x', projection([d.long,d.lat])[0]+12)
.attr('y', projection([d.long,d.lat])[1]-7)
.attr('class','Spots_position')
.style('font-family','courier new')
.style('font-size','16px')
.style('font-weight','bold')//use 'fill' to change font color
.text(d.name)//don't need 'properties' when you're trying to grab data from a spreadsheet


svg
.append("text")
.attr('x','75')
.attr('y','70')
.attr('class','T_Txt')
.style('font-family','courier new')
.style('font-size','32px')
.style('font-weight','bold')//use 'fill' to change font color
.text('PERSONAL MAP')

svg
.append("text")
.attr('x','75')
.attr('y','90')
.attr('class','ST_Txt')
.style('font-family','courier new')
.style('font-size','12px')
.style('font-weight','light')//use 'fill' to change font color
.text('NYC in a weekend')

svg
.append("text")
.attr('x', 760)
.attr('y', 70)
.attr('class','Des_position')
.style('font-family','courier new')
.style('font-size','12px')
.style('font-weight','light')//use 'fill' to change font color
.text(d.description)//don't need 'properties' when you're trying to grab data from a spreadsheet

var wrap = svg.selectAll('text.Spots_position')
.each(function(d,i) {wrap_text(d3.select(this),75)})

var wrap = svg.selectAll('text.Des_position')
.each(function(d,i) {wrap_text(d3.select(this),75)})

*/
/* svg
.append("line")
.attr('x1','180')
.attr('y1','150')
.attr('x2', projection([d.long,d.lat])[0])
.attr('y2', projection([d.long,d.lat])[1])
.attr('class','Sline')
.attr('stroke-dasharray',"4 2")
.style("stroke-width", '0.5')
.style("stroke", "rgb(0,0,0)")
}

function removeSpotsText(){
d3.selectAll('text.Spots_position').remove()
d3.selectAll('line.Sline').remove()
d3.selectAll('polyline.SHiLite').remove()
d3.selectAll('text.T_Txt').remove()
d3.selectAll('text.ST_Txt').remove()
d3.selectAll('text.Des_position').remove()
}
*/







/* svg
.append("line")
.attr('x1','100')
.attr('y1','106')
.attr('x2','200')
.attr('y2','106')
.style("stroke-width", '1')
.style("stroke", "rgb(0,0,0)")
*/

var p = svg.selectAll("polyline") //use this line the first time we create a polyline
// .data(HHiLite)
// .enter() //there are more data than elements, this selects them
// .append("polyline") //appends path to data
// .attr("points", function(d) {return d})
// .attr('fill','white')
// .style('fill-opacity','.5')
// .style('stroke','none')
// .style('stroke-width','0.5')

/*p.enter().append("polyline")
.data(test2)
.enter() //there are more data than elements, this selects them
.append("polyline") //appends path to data
.attr("points", function(d) {return d})
.attr('fill','white')
.style('fill-opacity','0.2')
.style('stroke','white')
.style('stroke-width','0.5')

*/

return svg.node();
}
Insert cell
borders = FileAttachment("borders2@1.geojson").json()
Insert cell
football_loyalties = FileAttachment("football_loyalties_v2@1.geojson").json()
Insert cell
boroughsBg = FileAttachment("boroughs bg.geojson").json()
Insert cell
boundingBox = FileAttachment("london bounding box2.geojson").json()
Insert cell
backgroundFC = FileAttachment("background@1.geojson").json()
Insert cell
supermarketLocations = FileAttachment("supermarkets_loyalties.geojson").json()
Insert cell
buildings = FileAttachment("London buildings@1.geojson").json()
Insert cell
RailLines = FileAttachment("London Railways.geojson").json()
Insert cell
Insert cell
london_pubs = d3.csv(london_pubs_link,d3.autoType)
Insert cell
import { wrap_text, wrap_text_nchar } from "@ben-tanen/svg-text-and-tspan-word-wrapping"
Insert cell
BoroughsBg = FileAttachment("boroughs background2.txt").tsv({array:true})
Insert cell
londonBoroughs = FileAttachment("London boroughs actual.geojson").json()
Insert cell
londonRoads = FileAttachment("London Roads.geojson").json()
Insert cell
pubs_loyalties = FileAttachment("pubs_loyalties2.geojson").json()
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