Public
Edited
Feb 16, 2023
1 fork
Insert cell
Insert cell
Insert cell
Insert cell
df_init = FileAttachment("asst3_google.csv").csv()
Insert cell
df = df_init.map(
function(d) {
d.geometry = JSON.parse(d.geometry.replace(/'/g, '"'));
return d
}
)
Insert cell
Insert cell
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<div id='map' style='width: 100%; height: 30rem; z-index: 0;'></div>
<div> Adjust Circle Radius: </div>

<div style="display: flex; align-items: center;">
<svg width="14" height="14">
<circle r="7" cx="7" cy="7" fill="#065535"/>
</svg>
<input type="range" id="radiusSlider1" min="0" max="20" value="2" oninput="this.nextElementSibling.value = this.value">
<output>2</output> Miles
</div>
<div style="display: flex; align-items: center;">
<svg width="14" height="14">
<circle r="7" cx="7" cy="7" fill='#6897bb'/>
</svg>
<input type="range" id="radiusSlider2" min="0" max="20" value="2" oninput="this.nextElementSibling.value = this.value">
<output>2</output> Miles
</div>
<div> Filter by Rating: </div>
<input type="range" id="ratingSlider" min="0" max="5" value="0" step="0.5" oninput="this.nextElementSibling.value = this.value">
<output>0</output> <span class="fa fa-star checked"></span> <span> and up</span>
Insert cell
maplibregl = {
//import maplibre
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
turf = {
//import turf which is used to find distances
const tf = await require("@turf/turf@5");
return tf;
}
Insert cell
d3Tip = {
// d3 tooltip import
const dt = await require("d3-tip");
return dt;
}
Insert cell
map = {
var map = new maplibregl.Map({
container: 'map',
style: 'https://api.maptiler.com/maps/streets/style.json?key=get_your_own_OpIi9ZULNHzrESv6T2vL',
center: [-122.169961, 37.429491],
zoom: 1,
pitch: 0,
maxBounds: [
[-122.8, 36.8],
[-121.8, 38.3]
] // restrict the view to a certain area (southwest and northeast coords)
});
map.addControl(new maplibregl.NavigationControl(), 'bottom-left'); // adds the controls
return map
}
Insert cell
svg = {
// make the 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
<style>
/* You can also set this in the initial declaration, just here for sake of explanation */
#map {
position: relative;
z-index: 0;
}
#radiusSlider1{
background: linear-gradient(to right, #82CFD0 0%, #82CFD0 50%, #fff 50%, #fff 100%);
border: solid 1px #82CFD0;
border-radius: 8px;
height: 7px;
width: 356px;
outline: none;
transition: background 450ms ease-in;
}
#radiusSlider2{
background: linear-gradient(to right, #82CFD0 0%, #82CFD0 50%, #fff 50%, #fff 100%);
border: solid 1px #82CFD0;
border-radius: 8px;
height: 7px;
width: 356px;
outline: none;
transition: background 450ms ease-in;
}
.d3-tip {
background: white;
padding: 10px;
border-radius: 10px;
}
.checked {
color: orange;
}
</style>
Insert cell
tip = d3Tip()
.attr('class', 'd3-tip')
.style('z-index', 5)
.html(function(event) {
// tool tip stuff
var d = d3.select(this).datum();
console.log(d);
var html = "<div'>";
html += "<strong>" + d.name + "</strong><br>";
html += "Rating: ";
console.log(d.name);
var rating = parseFloat(d.rating);
var rating_round = Math.round(rating)
console.log(rating);
//var starpct = Math.Round((rating / 5)*10)*10;
//html += '<div class="stars-outer"<div class="stars-inner" style="width:${starpct}"> </div></div>'
//add stars
for (var i = 0; i < 5; i++) {
if (rating_round > i) {
html += '<span class="fa fa-star checked"></span>';
} else {
html += '<span class="fa fa-star"></span>';
}
}
// better way to do all this??
html += ` ${rating}`
html += `<div>${d.formatted_address}</div>`
html += `<div>${d.formatted_phone_number}</div>`
if (d.dine_in == "True"){
html += `<div>&#x1F37D Dine In Available</div>`
}
if (d.serves_wine == "True"){
html += `<div>&#x1F377 serves Wine</div>`
}
if (d.serves_beer == "True"){
html += `<div>&#x1F37A Serves Beer</div>`
}
if (d.delivery == "True"){
html += `<div>&#x1F69A Delivery Available</div>`
}
if (d.serves_vegetarian_food == "True"){
html += `<div>&#x1F346 Vegetarian Options</div>`
}
if (d.serves_brunch == "True"){
html += `<div>&#x1F95E Serves Brunch</div>`
}
html += "</div>";
return html;
});
Insert cell
dots = {
// create our dos
let dots = svg
.selectAll("#cafe-circle")
.data(df)
.enter()
.append("circle")
.attr("class", "cafe-circle")
.attr("r", Math.max(map.getZoom()**2/25, 3))
.attr("fill", "#c0c0c0")
.attr("opacity", 0.65)
.style("z-index", 101)
.style("position", "absolute")
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
svg.call(tip);
return dots
}
Insert cell
{
function updateDots(val){
dots.attr("r", d => (parseFloat(d.rating) >= val) ? Math.max(map.getZoom()**2/25, 3):0)
}
d3.select("#ratingSlider").on("change", function(d) {
var rating_val = parseFloat(d3.select(this).property("value"));
updateDots(rating_val);
})
}
Insert cell
circles = {
function makeCircle(lng, lat, color){
return svg.append('circle')
.style("position", "absolute")
.attr("class", "inter-circle")
.style('z-index', 3)
.datum({lat: lat, lng: lng, size:2*1609.344})
.attr("cx", d => project(d.lng, d.lat).x)
.attr("cy", d => project(d.lng, d.lat).y)
.attr('r', d=> pixelValue(d.lat, d.size, map.getZoom()))
.attr('fill', color)
.attr('fill-opacity', 0.3)
.attr('stroke', color)
.attr('stroke-opacity', 0.8)
.attr('stroke-width', 2)
.on("mouseover", function() {
d3.event.stopPropagation();
})
.on("mouseout", function() {
d3.event.stopPropagation();
});
};
var circle_1 = makeCircle(map.getCenter().lng+.02, map.getCenter().lat+.02, "#065535")
var circle_2 = makeCircle(map.getCenter().lng-.02, map.getCenter().lat-.02, '#6897bb')
function make_drag(other_circle){
return d3.drag()
.on('drag', function(event, d) {
var new_coords = unproject(event.x, event.y);
d3.select(this)
.style('z-index', 3)
.attr('cx', d.x = event.x)
.attr('cy', d.y = event.y)
.datum(d=>({lat: new_coords.lat, lng: new_coords.lng, size:d.size}));
dots
.attr("opacity", d=>inCircle(d, d3.select(this)) && inCircle(d, other_circle) ? 0.8 : 0.65)
.attr("fill", d=>inCircle(d, d3.select(this)) && inCircle(d, other_circle)? "#cc0000" : "#c0c0c0");
})
.on('start', function() {
d3.select(this).attr("stroke", "black").style('z-index', 3);
//d3.event.sourceEvent.stopPropagation();
})
.on('end',function() {
d3.select(this).attr("stroke", "#6897bb").style('z-index', 3);
//d3.event.sourceEvent.stopPropagation();
});
};
circle_1.call(make_drag(circle_2));
circle_2.call(make_drag(circle_1));
var slider1 = d3.select("#radiusSlider1");
slider1.on("input", function() {
var value = this.value;
circle_1.datum().size = value*1609.344;
circle_1.attr('r', d=> pixelValue(d.lat, d.size, map.getZoom()));
dots
.attr("opacity", d=>inCircle(d, circle_2) && inCircle(d, circle_1) ? 0.8 : 0.65)
.attr("fill", d=>inCircle(d, circle_2) && inCircle(d, circle_1)? "#cc0000" : "#c0c0c0");;
});
var slider2 = d3.select("#radiusSlider2");
slider2.on("input", function() {
var value = this.value;
circle_2.datum().size = value*1609.344;
circle_2.attr('r', d=> pixelValue(d.lat, d.size, map.getZoom()));
dots
.attr("opacity", d=>inCircle(d, circle_2) && inCircle(d, circle_1) ? 0.8 : 0.65)
.attr("fill", d=>inCircle(d, circle_2) && inCircle(d, circle_1)? "#cc0000" : "#c0c0c0");;
});
circle_1.lower();
circle_2.lower();
return [circle_1, circle_2]
}
Insert cell
circle1 = circles[0]
Insert cell
circle2 = circles[1]
Insert cell
circle1.datum()
Insert cell
function project(lng,lat) {
return map.project(new maplibregl.LngLat(lng, lat));
}

Insert cell
function unproject(x,y) {
return map.unproject([x,y]);
}
Insert cell

function metersPerPixel(latitude, zoomLevel) {
var earthCircumference = 40075017;
var latitudeRadians = latitude * (Math.PI/180);
return earthCircumference * Math.cos(latitudeRadians) / Math.pow(2, zoomLevel+ 9);
};

Insert cell
function pixelValue(latitude, meters, zoomLevel) {
return meters / metersPerPixel(latitude, zoomLevel);
};
Insert cell
function inCircle(cafe, circle){
var dist = turf.distance(
turf.point(
[cafe.geometry.location.lng, cafe.geometry.location.lat]
),
turf.point(
[circle.datum().lng, circle.datum().lat]
),
{units: 'meters'}
)
return dist <= circle.datum().size
}
Insert cell
function render() {
var rating_val = parseFloat(d3.select("#ratingSlider").property("value"));
dots
.attr("cx", function (d) {
return project(d.geometry.location.lng, d.geometry.location.lat).x;
})
.attr("cy", function (d) {
return project(d.geometry.location.lng, d.geometry.location.lat).y;
})
.attr("r", d => (parseFloat(d.rating) >= rating_val) ? Math.max(map.getZoom()**2/25, 3):0)
.attr("opacity", d=>(inCircle(d, circle1) && inCircle(d, circle2))? 0.8 : 0.65)
.attr("fill", d=>(inCircle(d, circle1) && inCircle(d, circle2))? "#cc0000" : "#c0c0c0");
circle1
.attr("cx", d => project(d.lng, d.lat).x)
.attr("cy", d => project(d.lng, d.lat).y)
.attr("r", d=> pixelValue(d.lat, d.size, map.getZoom()))
circle2
.attr("cx", d => project(d.lng, d.lat).x)
.attr("cy", d => project(d.lng, d.lat).y)
.attr("r", d=> pixelValue(d.lat, d.size, map.getZoom()))

}
Insert cell
map.getCenter()
Insert cell
{
map.on("viewreset", render);
map.on("move", render);
map.on("moveend", render);
render();
}
Insert cell
map.getBounds()
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