Published
Edited
Jun 9, 2021
1 fork
Insert cell
md`# Probes by tag`
Insert cell
Insert cell
map = {
// let lat =
// sum_array(min_rtts_with_geo.map(m => m.latlon[0])) /
// min_rtts_with_geo.length;
// let lon =
// sum_array(min_rtts_with_geo.map(m => m.latlon[1])) /
// min_rtts_with_geo.length;
// projection.rotate([-lat, -lon]);

const circle = d3.geoCircle(),
height = width * (9 / 16), // 16:9 aspect ratio
svg = d3
.create("svg")
.attr("width", width)
.attr("height", height);

const path = d3.geoPath().projection(projection);

// used to capture pan and zoom
const topg = svg.append("g").attr("class", "topg");

// svg
// .append("path")
// .datum(water)
// .attr("class", "outer")
// .attr("fill", "none")
// .attr("stroke", "#999")
// .attr("stroke-width", "0.5px")
// .attr("d", path)
// .attr("id", "usPath");

topg
.selectAll("path")
.data(land.features)
.enter()
.append("path")
.attr("d", path)
.attr("fill", "none")
.attr("stroke", "#999")
.attr("stroke-width", "0.5px");

// .attr("class", "outer")
// .attr("fill", "none")
// .attr("stroke", "#999")
// .attr("stroke-width", "0.5px")
// .enter()
// // .append("path")
// .attr("d", path);

// all probes, keep for reference
// svg
// .append("g")
// .attr("fill", "blue")
// .attr("stroke-width", "0.5px")
// .selectAll("circle")
// .data(all_probes.objects.filter(p => p.latitude && p.longitude))
// .join("circle")
// .attr("cx", p => {
// console.log(p);
// return projection([p.longitude, p.latitude])[0];
// })
// .attr("cy", p => projection([p.longitude, p.latitude])[1])
// .attr("r", 2);

topg
.append("g")
.selectAll("circle")
.data(plottable_probes)
.enter()
.append("a")
.attr("xlink:href", d => `https://atlas.ripe.net/probes/${d.id}`)
.attr("target", d => "_blank")
.append("circle")

.attr("stroke", "none")
///.attr("fill", d => {
/// return colorScale(scale(d.min_rtt));
///})
.attr("stroke-width", "0.5px")
.attr("cx", d => projection(
[ d.geometry.coordinates[0], d.geometry.coordinates[1] ]
)[0] )
.attr("cy", d => projection(
[ d.geometry.coordinates[0], d.geometry.coordinates[1] ]
)[1] )
.attr("r", d => 2)
.on("mouseover", d => {
mutable hover = d;
})
.on("mouseout", () => {
mutable hover = null;
});

const zoom = d3
.zoom()
.scaleExtent([1, 32])
.on('zoom', () => {
const { transform } = d3.event;
topg.attr('transform', transform);
topg.selectAll("circle").attr('r', 2 / transform.k);
topg.selectAll("d").attr('stroke-width', .5 / transform.k);

// map lines also get thinner as we zoom in
topg.selectAll("path").attr("stroke-width", .5 / transform.k);
});

svg.call(zoom);

// return svg.node();
return Object.assign(svg.node(), {
zoomIn: () => svg.transition().call(zoom.scaleBy, 2),
zoomOut: () => svg.transition().call(zoom.scaleBy, 0.5)
// zoomRandom: random,
// zoomReset: reset
});
}
Insert cell
world = (await fetch("https://unpkg.com/world-atlas@1/world/110m.json")).json()
Insert cell
plottable_probes = probe_objects.filter( d => d.geometry && d.geometry.coordinates )
Insert cell
probe_objects = {
let probes, url
url = `https://atlas.ripe.net:443/api/v2/probes/?tags=${tag_input}&status_name=Connected`
probes = await paginated_fetch(url);
return probes
}
Insert cell
tags = {
let t, url
url = "https://atlas.ripe.net/api/v2/probes/tags/"
t = await paginated_fetch( url );
return t.map( x => x.slug ).sort()
}
// d3.json("https://atlas.ripe.net/api/v2/probes/tags/").then( r => r.results.map( x => x.slug ).sort() )

Insert cell
function paginated_fetch(url = "", previousResponse = []) {
return fetch(url)
.then(response => response.json())
.then(newResponse => {
const response = [...previousResponse, ...newResponse['results']]; // Combine the two arrays
if (newResponse['next']) {
url = newResponse['next'];
mutable fetch_status = `Fetching page ${new URLSearchParams(
url.split('?')[1]
).get('page')}`;
return paginated_fetch(url, response);
}
return response;
});
}
Insert cell
mutable fetch_status = ''
Insert cell
mutable hover = 0
Insert cell
width = 954
Insert cell
height = width * (9 / 16)
Insert cell
projection = {
return (
d3
// .geoOrthographic()
// .geoMercator()
.geoEqualEarth()
.scale(200)
.translate([width / 2, height / 2]) // w, h
.precision(.1)
);
}
Insert cell
land = topojson.feature(world, world.objects.land)
Insert cell
topojson = require("topojson@3")
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