Public
Edited
Feb 16, 2023
3 forks
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof radii = Inputs.range([50, 250], {value: 50, step: 25, label: "Radii: "})
Insert cell
Insert cell
Insert cell
// sets up maplibregl object
maplibregl = {
const gl = await require("maplibre-gl@2.4.0");
const href = await require.resolve("maplibre-gl@2.4.0/dist/maplibre-gl.css");
document.head.appendChild(html`<link href=${href} rel=stylesheet>`);
return gl;
}
Insert cell
Insert cell
// function to filter based on number of stars
filtered = yelp.filter(function(d) {
return d.rating >= stars
//return d.review_count >= numReviews
})
Insert cell
Inputs.table(filtered, {
columns: [
"name",
// "image_url",
"rating",
"review_count",
"price",
"address"
],
header: {
name: "Name",
//image_url: "Image",
rating: "Stars",
price: "Price range",
address: "Address",
review_count: "Number of Reviews"
},
})
Insert cell
centerPoint = [-122.3, 37.6]
Insert cell
// creates map

map = {
var map = new maplibregl.Map({
container: 'map',
style: 'https://api.maptiler.com/maps/positron/style.json?key=get_your_own_OpIi9ZULNHzrESv6T2vL',
center: centerPoint,
zoom: 4,
maxBounds: [
[-122.8, 36.8],
[-121.8, 38.3]
], // restrict the view to a certain area (southwest and northeast coords)
scrollZoom: true
});
map.addControl(new maplibregl.NavigationControl(), 'bottom-left'); // adds the controls
return map
}
Insert cell
// object for svg of entire map
svg = {
var container = map.getCanvasContainer();

var svg = d3
.select(container)
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.style("position", "absolute")
.style("z-index", 2); // make this appear above the mapbox backing layer
return svg
}
Insert cell
Insert cell
// setting the color scheme of the map
colorScale = d3.scaleOrdinal()
.domain(yelp.map(d => d.rating))
.range(d3.schemeDark2);
Insert cell
// object for all mapped boba shops

dots = {
let data = [
[37.4292654112971,-122.173590660095],
[37.6, -122.3]
]
let dots = svg.append("g")
// .attr("id", "shop")
.selectAll("circle")
.data(filtered)
.enter()
.append("circle")
.attr("class", "points")
.attr("r", 20)
// .attr("cx", d => d.cy)
// .attr("cy", d => d.cx)
.attr("fill", d => colorScale(d.rating))
.attr("opacity", 1)
.on("mouseover", function (event, d) { // <-- need to use the regular function definition to have access to "this"
console.log(d.name + " " + d.cx + " "+ d.cy + " " + d.image_url);
console.log(project(d.cy, d.cx).x + " " + project(d.cy, d.cx).y);
console.log("Intersect? " + intersection(project(d.cy, d.cx).x + " " + project(d.cy, d.cx).y));
svg.select("#tooltip-text")
.text(d.name + " " + d.rating)
svg.select("#tooltip")
// move the tooltip to where the cursor is
.attr("transform", `translate(${project(d.cy, d.cx).x + 10}, ${project(d.cy, d.cx).y + 10})`)
.attr("fill", "white")
.style("display", "block"); // make tooltip visible
d3.select(this)
.attr("fill", "green");
// .attr("r", 10);
})
.on("mouseout", function (event, d) {
svg.select("#tooltip").style("display", "none"); // hide tooltip
d3.select(this).attr("stroke", "none")
// .attr("opacity", "1"); // undo the stroke
});

return dots
}
Insert cell
//object for tooltip that appears on hover

tooltip = {
let tools = svg.append("g") // the tooltip needs to be added last so that it stays on top of all circles
.attr("id", "tooltip")
.attr("background-color", "white")
.attr("padding", 5)
.style("display", "none") // hidden by default
.append("text")
.attr("id", "tooltip-text")
.attr("x", 5)
.attr("y", 15)
.attr("font-size", "14px")
.attr("font-weight", "bold")
.attr("fill", "black")
.append("image")
.attr("id", "image");
return tools
}
Insert cell
Insert cell
function project(lng,lat) {
return map.project(new maplibregl.LngLat(lng, lat));
}
Insert cell
//renders the dots initially? honestly im not sure why we need this still but it breaks without it lol

function render() {
dots
.attr("cx", function (d) {
return project(d.cy, d.cx).x;
})
.attr("cy", function (d) {
return project(d.cy, d.cx).y;
})
.attr("r", Math.max(map.getZoom()**2/25, 3));
}
Insert cell
//from https://observablehq.com/@d3/circle-dragging-i

// drag function referenced by draggable circle
drag = {

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

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

function dragended(event, d) {
// d3.select(this).attr("stroke", "purple");
updateStars(stars);
}

return d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
Insert cell
{
let data1 = [circleCenters[0]];
let data2 = [circleCenters[1]];
let circle1 = svg.append("g")
.selectAll("circle")
.data(data1)
.join("circle")
.attr("id", "drag1")
.attr("fill", "white")
.attr("stroke", "green")
.attr("opacity", .5)
// .attr("r", 100)
.attr("cx", d => project(d[0], d[1]).x)
.attr("cy", d => project(d[0], d[1]).y)
.call(drag);

let circle2 = svg.append("g")
.selectAll("circle")
.data(data2)
.join("circle")
.attr("id", "drag2")
.attr("fill", "white")
.attr("stroke", "green")
.attr("opacity", .5)
// .attr("r", 100)
.attr("cx", d => project(d[0], d[1]).x)
.attr("cy", d => project(d[0], d[1]).y)
.call(drag);

console.log("circles rendered");
}


Insert cell
//QUESTION: how to do bounding box?

intersection = function(cx, cy) {
let approx = radii;
// let get1 = svg.select("#drag1");
// let drag1 = get1.node().getBBox();
let cx1 = svg.select("#drag1").attr("cx");
let cy1 = svg.select("#drag1").attr("cy");
console.log("CIRCLE 1 " + cx1 + " " + cy1);

// let get2 = svg.select("#drag2");
// let drag2 = get2.node().getBBox();
let cx2 = svg.select("#drag2").attr("cx");
let cy2 = svg.select("#drag2").attr("cy");
console.log("CIRCLE 2 " + cx2 + " " + cy2);

let check1 = Math.sqrt(((cx - cx1) ** 2) + ((cy - cy1) ** 2));
let check2 = Math.sqrt(((cx - cx2) ** 2) + ((cy - cy2) ** 2));
console.log("CHECK " + check1 + " " + check2);

// return check2;
if(check1 <= approx && check2 <= approx) {
return true;
} else {
return false;
}
}
Insert cell
intersection(project(-122.3, 37.6).x, project(-122.3, 37.6).y)
Insert cell
radii
Insert cell
// updates points on map according to filtering by stars

updateStars = function (stars) {
var filtered;
if(test === 1) {
filtered = yelp.filter(d => intersection(project(d.cy, d.cx).x, project(d.cy, d.cx).y));
} else {
filtered = yelp.filter(d => d.rating >= stars);
}
// i think this is where collision checking should happen, like maybe we filter by x and y values?
console.log(stars);
// let filtered = yelp.filter(d => d.rating >= stars
const shops = d3.selectAll(".points")
// .selectAll("#shop")
// .selectAll(".countryCircles")
// .data(yelp.filter(d => d.rating >= stars).filter(d => intersection(project(d.cy, d.cx).x, project(d.cy, d.cx).y)))
.data(filtered)
// .data(filtered.filter(function intersect() {
// node = this.node();
// nodeBox = node.getBBox();
// nodeLeft = nodeBox.x;
// nodeRight = nodeBox.x + nodeBox.width;
// nodeTop = nodeBox.y;
// nodeBottom = nodeBox.y + nodeBox.height;
// }))
.join(
// Add code to customize how countries enter the scene.
// Idea: fade in from transparent and grow from zero size
// Make sure new elements have their properties properly initialized!
enter => enter.append("circle")
// .transition()
// .duration(1000)
.attr('cx', d => project(d.cy, d.cx).x)
.attr('cy', d => project(d.cy, d.cx).y)
.attr('r', d => 0)
.attr("opacity", 0)
// .attr("fill", "black")
,
update => update,
// Add code to customize how countries exit the scene.
// Idea: use transition to fade out to transparent and shrink to zero size before removal
exit => exit.transition()
.duration(1000)
.attr("r", d => 0)
.attr("opacity", 0)
.remove()
);
// Animate enter + update countries to current position and size
// Hint: If you modify opacity above, you probably want to update it here!
shops.transition()
.duration(1000)
// .ease(d3.easeCubic)
// .attr('cx', d => project(d.cy, d.cx).x)
// .attr('cy', d => project(d.cy, d.cx).y)
// .attr('r', d => size(d.pop))
// .attr("opacity", .75);
// d3.select("#scatter-dynamic")
// .select("#yearLabel")
// .text(newYear);

const drag = d3.selectAll(".drag")
// .selectAll("#shop")
// .selectAll(".countryCircles")
.data(stars)
// .data(filtered.filter(function intersect() {
// node = this.node();
// nodeBox = node.getBBox();
// nodeLeft = nodeBox.x;
// nodeRight = nodeBox.x + nodeBox.width;
// nodeTop = nodeBox.y;
// nodeBottom = nodeBox.y + nodeBox.height;
// }))
.join(
// Add code to customize how countries enter the scene.
// Idea: fade in from transparent and grow from zero size
// Make sure new elements have their properties properly initialized!
enter => enter
// .transition()
// .duration(1000)
// .attr('cx', d => project(d.cy, d.cx).x)
// .attr('cy', d => project(d.cy, d.cx).y)
// .attr('r', d => 0)
// .attr("opacity", 0)
// .attr("fill", "black")
,
update => update,
// Add code to customize how countries exit the scene.
// Idea: use transition to fade out to transparent and shrink to zero size before removal
exit => exit.transition()
// .duration(1000)
// .attr("opacity", 0)
// .remove()
);

drag.transition()
.duration(1000)
}
Insert cell
updateRadii = function(radii) {
d3.select("#drag1").attr("r", radii);
d3.select("#drag2").attr("r", radii);
}
Insert cell
{
map.on("viewreset", render());
map.on("move", render());
map.on("moveend", render());
render();
}
Insert cell
updateStars(stars);

Insert cell
updateRadii(radii);
Insert cell
// circles = {
// let circles = svg.append("g")
// .attr("id", "drag")
// .selectAll("circle")
// .data(circleCenters)
// .join("circle")
// .attr("fill", "white")
// .attr("stroke", "green")
// .attr("opacity", .5)
// .attr("r", 100)
// .attr("cx", d => project(d[0], d[1]).x)
// .attr("cy", d => project(d[0], d[1]).y)
// .call(drag);
// return circles
// }
Insert cell
string = yelp[0].cx

Insert cell
string2 = yelp[0].cy

Insert cell
circleCenters[0]
Insert cell
Insert cell
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