Notebooks 2.0 is here.

Public
Edited
Oct 28, 2024
Insert cell
Insert cell
Insert cell
{
/* function to get distance from restaurant to circle */
function dist(x1, y1, x2, y2){
return Math.sqrt((x2 - x1) ** 2 + (y2 -y1) ** 2);
}

/* updates restaurant data points depending on intersection*/
function updateRestaurants(){
d3.selectAll('.restaurant').each(function(d){ //checks every restaurant
const restCirc = d3.select(this);
const projectedLocation = projection([d.longitude, d.latitude]);
//checks distance from center of circle A
const distA = dist(projectedLocation[0], projectedLocation[1], spotA.x, spotA.y);
//checks distance from center of circle B
const distB = dist(projectedLocation[0], projectedLocation[1], spotB.x, spotB.y);
//checks to see if restaurant is in the intersection of the circles
if(distA <= spotA.radius && distB <= spotB.radius){
restCirc.attr('fill', 'red') //highlights restaurants in radius
.attr('opacity', 1);
} else{
restCirc.attr('fill', 'gray')//grays out restaurants not in radius
.attr('opacity', 0.5);
}
});
}

/* function to make circle A draggable */
function dragA(event) { //
spotA.x = event.x;
spotA.y = event.y;
d3.select(this).attr("cx", spotA.x).attr("cy", spotA.y);
updateRestaurants() //update filtered restaurants after circle is moved
}

/* function to make circle B draggable */
function dragB(event) {
spotB.x = event.x;
spotB.y = event.y;
d3.select(this).attr("cx", spotB.x).attr("cy", spotB.y)
updateRestaurants()
}

/* Code below is taken from assignment 3 page for rendering the map */
// Add an SVG element to the DOM
var container = d3.create("div");
var svg = container
.append("svg")
.attr("width", mapWidth)
.attr("height", mapHeight)
.style("border", "1px solid black");

// Add SVG map at correct size, assuming map is saved in a subdirectory called `data`
svg
.append("image")
.attr("width", mapWidth)
.attr("height", mapHeight)
.attr("xlink:href", await FileAttachment("cs448b-map2.png").url());
/* End of code taken from assignemnt 3 page for rendering the map */

/* Starting positions for both draggable circles*/
var spotA = {x:300, y:300, radius: 100}//starting point for A
var spotB = {x:600, y:600, radius: 100}//starting point for B

/* create draggable circle A*/
var circA = svg.append("circle")
.attr("cx", spotA.x)
.attr("cy", spotA.y)
.attr("r", spotA.radius)
.attr('stroke', 'blue')
.attr('stroke-width', 2)
.attr("fill", "transparent")
.call(d3.drag().on("drag", dragA));

/*slider to control the Radius of circle A */
const circASlider = slider({
min: 10,
max: 300,
step: 1,
value: spotA.radius,
title: "Radius of Circle A"
});

container.node().appendChild(circASlider); // add circleA to DOM

/* takes input from slider A and changes radius */
circASlider.addEventListener("input", () => {
spotA.radius = circASlider.value;
circA.attr("r", spotA.radius);
updateRestaurants();
});

/* create draggable circle B*/
var circB = svg.append("circle")
.attr("cx", spotB.x)
.attr("cy", spotB.y)
.attr("r", spotB.radius)
.attr('stroke', 'blue')
.attr('stroke-width', 2)
.attr("fill", "transparent")
.call(d3.drag().on("drag", dragB));

/*slider to control the Radius of circle B */
const circBSlider = slider({
min: 10,
max: 300,
step: 1,
value: spotB.radius,
title: "Radius of Circle B"
});

container.node().appendChild(circBSlider); // add circleB to DOM

/* takes input from slider B and changes radius */
circBSlider.addEventListener("input", () => { //takes input and changes circle radius after slider is moved
spotB.radius = circBSlider.value;
circB.attr("r", spotB.radius);
updateRestaurants();
});

/* UPDATES datapoints when filters are interacted with */
function update() {
const cityVal = d3.select('#thephase').property('value');
var inputs = d3.selectAll("form.inputs-3a86ea.inputs-3a86ea-checkbox");
var priceList = inputs.property('value');
d3.selectAll(".restaurant")
.attr('visibility', d => {
var split = d.address.split(',').at(-2);
var noSpace = split[0] == " " ? split.substring(1) : split;
var noBackSpace = noSpace.at(-1) == " " ? noSpace.substring(0, noSpace.length - 1) : noSpace;
var answer = noBackSpace.toUpperCase();
if (priceList.includes(d.price) && (answer == cityVal || cityVal == "ALL CITIES")) {
return 'visible';
}
else {return 'hidden';}
});
}
/* PRICE FILTER */
var confirm = Inputs.checkbox(["$", "$$", "$$$", "$$$$"], {label: "Price"});
/* listens when checkboxes are checked and updates */
confirm.addEventListener("input", () => {
update();
});
container.node().appendChild(confirm); // append checkboxes to DOM

/* RESTAURANT CITY FILTER */
/* List of unique cities per restaurant
This was dynamically curated in the code box below this one.
*/
var options = [
"ALL CITIES", "SAN FRANCISCO","DALY CITY", "SOUTH SAN FRANCISCO", "PACIFICA"
,"BRISBANE","SAN BRUNO","BURLINGAME","COLMA"
,"MILLBRAE","SAN MATEO","FOSTER CITY","SAN CARLOS","REDWOOD CITY"
,"BELMONT","WOODSIDE","REDWOOD SHORES","PALO ALTO","MENLO PARK"
,"LOS ALTOS","MOUNTAIN VIEW","EAST PALO ALTO","CUPERTINO"
,"STANFORD","SUNNYVALE","SANTA CLARA","SAN JOSE"
,"FREMONT","MILPITAS","CAMPBELL","SARATOGA","LOS GATOS"
,"NEWARK"];
/* create dropdown menu and add it to the DOM */
const selector = container.append('select')
.attr("id", "thephase")
.attr('class', 'select');
const optionsphase = selector
.selectAll('option')
.data(options)
.enter()
.append('option')
.text(d => d);

/* listens to dropdown menu and updates */
selector.on('change', function() {
update();
});

/* adds "Hover to view Restaurant Info" Label */
function postHoverLabel() {
container.append('p')
.attr('class', 'ptLabel')
.style('position', 'absolute')
.style('top', '0px')
.style('right', '0px')
.style('background-color', 'white')
.style('width', 'auto')
.style('box-shadow', 'rgba(0, 0, 0, 0.24) 0px 3px 8px')
.style('padding', '1em')
.html("<b>Hover to View Restaurant Information</b>")
}

postHoverLabel(); // we call it once when the map first uploads

/* ADD DATA TO MAP */
const data = d3.csv(
"https://magrawala.github.io/cs448b-fa24/assets/a3/cs448b-fa24-a3.csv",
(row) => {
return {
// We parse the data into an array of csv objects. Field names are changed
id: row.id,
restaurant: row.name,
url: row.url,
image: row.image_url,
phone: row.phone,
rating: row.rating ? row.rating : "", //eliminate empty ratings
review_num: row.review_count,
address: row.address,
latitude: +row.latitude,
longitude: +row.longitude,
price: row.price != "N/A" ? row.price : "", // eliminate empty price ranges
transactions: row.transactions,
business_hours: row.business_hours,
categories: row.categories,
is_closed: row.is_closed
};
}).then((data) => {data.forEach((d) => {

/* adds Restaurant Information Label to the DOM */
function postInformation() {
container.append('p')
.attr('class', 'ptLabel')
.style('position', 'absolute')
.style('top', '0px')
.style('right', '0px')
.style('background-color', 'white')
.style('width', '350px')
.style('box-shadow', 'rgba(0, 0, 0, 0.24) 0px 3px 8px')
.style('padding', '1em')
.html(
"<b> Restaurant Information </b><br>" +
"Restaurant Name: " + d.restaurant + "<br>" +
"Type: " + d.categories + "<br>" +
"Price: " + d.price + "<br>" +
"Rating: " + d.rating + "<br>" +
"Address: " + d.address + "<br>" +
"Phone: " + d.phone + "<br>" +
"Number of Reviews: " + d.review_num + "<br>" +
"<a href=" + d.url + ">Website</a>"
);
}
// code to project circle from Assignment 3 page for rendering circle
var projectedLocation = projection([d.longitude, d.latitude]);
var circle = svg.append('circle')
.attr('cx', projectedLocation[0])
.attr('cy', projectedLocation[1])
.attr('r', 3)
.attr('class', 'restaurant')
.attr('visibility', 'visible')
.attr('fill', 'gray')
.attr('opacity', 0.5)
.datum(d) // this method works because of datum
.on('mouseover', function (event, d) { // HOVER
postInformation(); // adds Restaurant Information label
svg.append('text') // adds hover NAME text label next to data point
.attr('class', 'ptText') //Set class to 'ptLabel' so can remove easily later
.attr('x', projectedLocation[0] + 7) //Set label position near its circle
.attr('y', projectedLocation[1] - 7) //Set label position near its circle
.html(d.restaurant);
})
.on('mouseout', function (event, d) { //STOP HOVER
svg.selectAll('text').remove(); // remove NAME text label
container.selectAll('p').remove(); //remove Restaurant Information Label
postHoverLabel();
})
.on('click', function(event, d) {
postInformation();
});
updateRestaurants(); // function for draggable CIRCLES
});
});
return container.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
printTable(data)
Insert cell
Insert cell
Insert cell
Insert cell
import {printTable} from "@uwdata/data-utilities"
Insert cell
d3 = require('d3@7')
Insert cell
import { slider } from "@jashkenas/inputs"; //imported function to have the slider bars for the radius of each circle

Insert cell
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