Public
Edited
Feb 16, 2023
Insert cell
Insert cell
d3 = require("d3")
Insert cell
bayAreaMap= await d3.json("https://raw.githubusercontent.com/geoiq/gc_data/master/datasets/9018.geojson")
Insert cell
map_pointers = [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
122.2111576795578,
-17.928395068168417
]
}
}
]
Insert cell
scale = 180
Insert cell
center_val = map_pointers[0].geometry.coordinates
Insert cell
projection = d3.geoMercator()
.scale([scale])
.center(center_val)
.translate([75, 100]);
Insert cell
pathRenderer = d3.geoPath().projection(projection);

Insert cell
yelpCoords= FileAttachment("asst3_yelp (1).csv").csv()
Insert cell
dataset = yelpCoords.map(shop => {
let coordinates = shop.coordinates.split(',')
shop.longitude = parseFloat(coordinates[0])
shop.latitude = parseFloat(coordinates[1])
return shop
})
Insert cell
viewof circleOneRadius = Scrubber(
d3.range(50, 401, 1),
{ autoplay: false, delay: 1500, loop: false }
)
Insert cell
viewof circleTwoRadius = Scrubber(
d3.range(50, 401, 1),
{ autoplay: false, delay: 1500, loop: false }
)
Insert cell
prices = ['$', "$$", "$$$"]
Insert cell
viewof price_select = Inputs.select(prices, {value: "$", label: "Price selection"})
Insert cell
ratings = ["1", "2", "2.5", "3", "3.5", "4", "4.5", "5"]
Insert cell
viewof rating_select = Inputs.select(ratings, {value: "1", label: "Rating selection"})
Insert cell
otherData = oldData.filter(d => d.price != price_select)
Insert cell
oldData = dataset.filter(d => d.rating === rating_select)
Insert cell
newData = oldData.filter(d => d.price === price_select)
Insert cell
chart = {
const width=1200, height=1850;
const svg = d3.select(DOM.svg(width, height))
// Use Mercator projection
var projection = d3.geoMercator()
.fitExtent([[20, 20], [width, height]], bayAreaMap);

var path = d3.geoPath()
.projection(projection);
// Draw each province as a path
svg.append('g').selectAll('path')
.data(bayAreaMap.features)
.enter().append('path')
.attr('d', path)
// Styling
.style('fill', 'white')
.style('stroke', '#ccc')

//Add text on top of the map
svg.append("text")
.attr("x", width / 2)
.attr("y", 60)
.attr("text-anchor", "middle")
.attr("font-size", "50px")
.text("Bay Area Restaurants");

svg.append("text")
.attr("x", width / 2)
.attr("y", 100)
.attr("text-anchor", "middle")
.attr("font-size", "25px")
.text("Select your desired price level and rating from the dropdown menu");

svg.append("text")
.attr("x", width / 2)
.attr("y", 130)
.attr("text-anchor", "middle")
.attr("font-size", "20px")
.text("Red dots represent the price you selected and all of the dots on the map have the rating that you selected");

//Plot circles that don't have the price selected as purple
let dots = svg.selectAll('circle')
.data(otherData)
.join('circle')
.attr('cx', d => projection([d.latitude, d.longitude])[0])
.attr('cy', d => projection([d.latitude, d.longitude])[1])
.attr('fill', "purple")
.attr('r', 3)

//Hover functionality
.on("mouseover", function (event, d) {
const tooltipGroup = svg.append("g")
.attr("id", "tooltip")
.style("display", "block")

svg.select("#store-name")
.text(d.name);
svg.select("#store-stars")
.text("This is a " + d.rating + " Star Establishment");
svg.select("#store-price")
.text("Average meal cost: " + d.price);
let positionOffest = 8;
svg.select("#tooltip")
.style("left", event.pageX + 10 + "px")
.style("top", event.pageY + 10 + "px")
.style("display", "block")
.attr("fill", "black");
svg.select("#tooltip")
.style("transform", `translate(${event.x}px,${event.y - 1900 + positionOffest}px)`)
.style("display", "block");
});

const labels = svg.append("g").attr("id", "labels")
labels.selectAll("text")
.data(bayAreaMap)
.join('text')
.attr('text-anchor', 'middle')
.attr('fill', 'white')
.text(d => d.properties.CITY)
.attr('x', d => pathRenderer.centroid(d)[0])
.attr('y', d => pathRenderer.centroid(d)[1])

/* intersection circle 1 */
svg.append("circle")
.attr("id", "circleOne")
.attr("cx", d => projection([-122.3642615, 37.5863038])[0])
.attr("cy", d => projection([-122.3642615, 37.5863038])[1])
.attr("r", 50)
.attr("fill-opacity", 0.25)
.attr("fill", "steelblue")
.attr("stroke", "steelblue")
.call(drag);

/* intersection circle 2 */
svg.append("circle")
.attr("id", "circleTwo")
.attr("cx", d => projection([-122.3642615, 37.5863038])[0] + 100)
.attr("cy", d => projection([-122.3642615, 37.5863038])[1] + 100)
.attr("r", 50)
.attr("fill-opacity", 0.25)
.attr("fill", "steelblue")
.attr("stroke", "steelblue")
.call(drag);

let dots2 = svg.selectAll('circle')
.data(newData)
.attr('cx', d => projection([d.latitude, d.longitude])[0])
.attr('cy', d => projection([d.latitude, d.longitude])[1])
.attr('fill', "red")
.attr('r', 3)

/* hide tooltip on mouseout ******/
.on("mouseout", function (event, d) {
svg.select("#tooltip").style("display", "none");
d3.select(this).attr("stroke", "none");
});
const tooltipGroup = svg.append("g") // the tooltip needs to be added last so that it stays on top of all circles
.attr("id", "tooltip")
.style("display", "none") // hidden by default
.append("rect").attr("width", 240).attr("height", 80).attr("fill", "steelblue").attr("rx", 10).attr("opacity", 0.25);

dots.filter(d => {
let circle1_x = d3.select("#circleOne").cx
let circle1_y = d3.select("#circleOne").cy

let circle2_x = d3.select("#circleTwo").cx
let circle2_y = d3.select("#circleTwo").cy
let x = projection([d.latitude, d.longitude])[0]
let y = projection([d.latitude, d.longitude])[1]
console.log(x)
console.log(y)
let circleOneEnclose = encloses(circle1_x, circle1_y, circleOneRadius, x, y)
let circleTwoEnclose = encloses(circle2_x, circle2_y, circleTwoRadius, x, y)
let bool = circleOneEnclose && circleTwoEnclose
return bool;
}).attr('fill', 'blue')

svg.select("#tooltip")
.append("text")
.attr("id", "store-name")
.attr("x", 10)
.attr("y", 20)
.attr("font-size", "16px")
.attr("font-weight", "bold")
.attr("text-decoration", "underline")
.attr("fill", "black");

svg.select("#tooltip")
.append("text")
.attr("id", "store-price")
.attr("x", 10)
.attr("y", 40)
.attr("font-size", "14px")
.attr("font-weight", "bold")
.attr("fill", "black");

svg.select("#tooltip")
.append("text")
.attr("id", "store-stars")
.attr("x", 10)
.attr("y", 60)
.attr("font-size", "14px")
.attr("font-weight", "bold")
.attr("fill", "black");

return svg.node()
}
Insert cell
drag = {

function dragstarted(event, d) {
d3.select(this).raise().attr("stroke", "black");
}

function dragged(event, d) {
d3.select(this).attr("cx", event.x).attr("cy", event.y);
}

function dragended(event, d) {
d3.select(this).attr("stroke", null);
}

return d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
Insert cell
updateCircleOneRadius = function (newRadius) {
d3.select("#circleOne")
.attr('r', newRadius)
}
Insert cell
updateCircleTwoRadius = function (newRadius) {
d3.select("#circleTwo")
.attr('r', newRadius)
}
Insert cell
function encloses(ax, ay, r1, bx, by) {
const a = ax - bx;
const b = ay - by;
const distance = Math.sqrt(a * a + b * b);
if (distance <= r1){
return true;
}
else{
return false;
}
}
Insert cell
updateCircleOneRadius(circleOneRadius)
Insert cell
updateCircleTwoRadius(circleTwoRadius)
Insert cell
import {Scrubber} from "@mbostock/scrubber"
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