Published
Edited
Dec 5, 2021
Insert cell
Insert cell
map = {
var w = 200;
var h = 50;
var margin_left = 80;
var margin_histo = 20;
let container = d3.select(DOM.element("div"));
let context = this ? this.getContext("2d") : DOM.context2d(width, width);
container.append(() => context.canvas);
const canvas = context.canvas;
canvas.style.backgroundColor = 'black'
color.opacity = 0.4;
let toolTip = container.append("div").attr("id","tooltip");
//let circle = container.append("div").attr("id","circle");
let height = context.canvas.height;
let originalScale = height / 2.1;
let scale = originalScale;
let previousScaleFactor = 1;
//let rotation;
let sphere = {type: 'Sphere'};
let path = d3.geoPath(projection, context);
let tooltipQuadtree = getQuadtree();
let tooltipPositions = [];
let sentences_fin = [];
let tooltipPositionsIndex = {};
let tooltipPositionsSentences = {};
let tooltipPositionsGeo = {};
let tooltipPositionsYear = {};

var year_colors = {
y_1869: "#35055a",
y_1870: "#3f0556",
y_1871: "#580556",
y_1872: "#54057e",
y_1873: "#5305b8",
y_1874: "#740556",
y_1875: "#a82bef",
y_1876: "#862bef",
y_1877: "#bc96ef",
y_1878: "#c581ee",
y_1879: "#c581ee"
};
//let twitterUserFeatures = twitterUsers.features;
//let {x,y} = globe_rotation
/**
* After each zoom/pan projection recalculation, force the svg(really canvas) paths to update
*/
function drawWorld() {
//console.log(year_colors["y_1873"])
context.clearRect(0, 0, width, height);
context.beginPath(), path(graticule), context.lineWidth = .4; context.strokeStyle = "#ccc", context.stroke();
context.lineWidth = lineWidth;
context.strokeStyle = "white"
context.shadowBlur = 5;
context.shadowColor = "violet"
context.beginPath(), path(land), context.fillStyle = 'transparent', context.fill(); context.strokeStyle = "#fff";
context.lineWidth = 0.5;
context.stroke();

context.lineWidth = 3.5;
context.fillStyle = "transparent";
context.shadowBlur = 5;
context.shadowColor = "violet"
context.shadowOffsetX = 2
context.shadowOffsetY = 2
context.beginPath(), path(sphere), context.strokeStyle = "white", context.stroke();
//set up the points
path.pointRadius(magnitudeRadius);
twitterUsers.forEach(d => {
var year_key = "y_" + d.properties.Year.toString()
context.beginPath(), context.shadowBlur = 10; context.shadowColor = year_colors[year_key]; context.fillStyle = year_colors[year_key]; path(d); context.fill()
});
path.pointRadius(null);

//reset the tooltip caches
tooltipPositions = []; tooltipPositionsIndex = {}; tooltipPositionsSentences = {}; tooltipPositionsGeo = {}; tooltipQuadtree = getQuadtree();
//update tooltip caches
for(let i=0, len=twitterUsers.length; i<len; i++){
let mid_arr = twitterUsers[i].properties.city_context;
mid_arr = mid_arr.sort((a, b) => b.length - a.length);
let pixelCoords = projection(twitterUsers[i].geometry.coordinates);
tooltipPositions.push(pixelCoords);
tooltipPositionsSentences[pixelCoords.join(",")] = mid_arr;
tooltipPositionsGeo[pixelCoords.join(",")] = twitterUsers[i].geometry.coordinates;
tooltipPositionsIndex[pixelCoords.join(",")] = twitterUsers[i].properties.City;
tooltipPositionsYear[pixelCoords.join(",")] = twitterUsers[i].properties.Year;
}
//update the quadtree
tooltipQuadtree.addAll(tooltipPositions);
//hide the tooltip if the map is being zoomed/panned
d3.select('#tooltip').style('opacity', 0);
//d3.select('#circle').style('opacity', 0);
}
/**
* Every time the globe is zoomed or panned, recalculate the correct projection parameters
* and then request that the map data be redrawn/updated
*/
d3.geoZoom()
.projection(projection)
.northUp(true)
.onMove(drawWorld)
(d3.select(context.canvas).node());

//initiate the first globe draw
drawWorld();

//bind the tooltips
d3.select(context.canvas).on("mousemove click",function(){
d3.select('h2').style('color', 'pink');
let mouse = d3.mouse(this);
let closest = tooltipQuadtree.find(mouse[0], mouse[1], 10);
var toolTip = d3.select('#tooltip')
//var circle = d3.select('#circle')

//tooltipPositionsSentences = tooltipPositionsSentences.sort((a, b) => b.length - a.length);
if(closest){
var list_length = tooltipPositionsSentences[closest.join(",")].length
if(list_length == 7)
{
sentences_fin = tooltipPositionsSentences[closest.join(",")].slice(2, list_length)
}
else if(list_length == 6){
sentences_fin = tooltipPositionsSentences[closest.join(",")].slice(1, list_length)
}
else if(list_length == 0){
sentences_fin = tooltipPositionsSentences[closest.join(",")]
}
else{
sentences_fin = tooltipPositionsSentences[closest.join(",")].slice(0, list_length)
}
var color_pick = year_colors["y_" + tooltipPositionsYear[closest.join(",")].toString()]

//"@"+ list_length + ', ' + sentences_fin
toolTip.style('opacity', 0.8)
.style('background', 'transparent')
.style('position', 'absolute')
.style('color', 'white')
.style('pointer-events', 'none')
.style('border-color', 'white')
.style('top', 15 + 'px')
.style('left', 15 + 'px')
.html(()=>{
let innerTableContent = '<h2 style="color:white;display:inlinet">' + tooltipPositionsIndex[closest.join(",")] + '</h2>'
innerTableContent += '<h3 style="color:' + color_pick + '";display:inline;font-family: "IBM Plex Mono", monospace;>' + tooltipPositionsYear[closest.join(",")] + '</h3>'
innerTableContent += '<ul style="list-style-type:none;">'
sentences_fin.forEach(element => {
innerTableContent += '<li style="border-bottom:1px solid white">' + element + '</li>'
});
innerTableContent += "</ul>"
return innerTableContent
});
} else {
d3.select('#tooltip')
.style('opacity', 0)
.style('cursor', 'default')
.style('pointer-events', 'none')
.html('');
}
});
return container.node();
}
Insert cell
mini_chart = {
var w = 200;
var h = 50;
var margin_left = 80;
var margin_histo = 20;
const svg = d3.create("svg").attr("viewBox", [0, 0, 2*w+margin_left+margin_histo, h])
const defs = svg.append("defs");
var nodes = twitterUsers;

svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "black");

//// Circle Legende
svg.append("g").attr('transform','translate(400,10) scale(0.5)').attr("color", "white").call(legend_circle);


var data = [{"color":"#35055a","value":1869},{"color":"#3f0556","value":1870},{"color":"#580556","value":1871},{"color":"#54057e","value":1872},{"color":"#5305b8","value":1873},{"color":"#740556","value":1874},{"color":"#a82bef","value":1875},{"color":"#862bef","value":1876},{"color":"#bc96ef","value":1877},{"color":"#c581ee","value":1878},{"color":"#c581ee","value":1879}];
var extent = d3.extent(data, d => d.value);
var padding = 15;
var width = 320;
var innerWidth = width - (padding * 2);
var barHeight = 10;
var height = 28;

var xScale = d3.scaleLinear()
.range([0, innerWidth])
.domain(extent);

var xTicks = data.map(d => d.value);

var xAxis = d3.axisBottom(xScale)
.tickSize(barHeight)
.tickFormat(x => x)
.tickValues(xTicks);

var g = svg.append("g").attr("transform", "translate(" + padding + ", 20)");
var linearGradient = defs.append("linearGradient").attr("id", "myGradient");
linearGradient.selectAll("stop")
.data(data)
.enter().append("stop")
.attr("offset", d => ((d.value - extent[0]) / (extent[1] - extent[0]) * 100) + "%")
.attr("stop-color", d => d.color);

g.append("rect")
.attr("width", innerWidth)
.attr("height", barHeight)
.style("fill", "url(#myGradient)");

g.append("g")
.call(xAxis)
.selectAll("text")
.style("fill", "white")
.style("font-family", "IBM Plex Mono");
return svg.node();
}
//.thresholds([...Array(+d3.format(".0f")(d3.extent(nodes_n_occ)[1]+1) ).keys()]);
Insert cell
axisScale = d3.scaleLinear()
.domain(colorScale.domain())
.range([margin.left, width - margin.right])
Insert cell
style = html`
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&display=swap">
<style>
@font-face {
font-family: Messapia-Bold;
src: url("Messapia-Bold.otf") format("opentype");
}

@font-face {
font-family: Messapia-Regular;
src: url("Messapia-Regular.otf") format("opentype");
}

li {
font-family: "IBM Plex Mono", monospace;
/* font-size: 48px; */
}

h2 {
font-family: "Messapia-Regular";
}
</style>
`
Insert cell
colorScale = d3.scaleSequential(d3.interpolatePiYG).domain([0, 42])
Insert cell
margin = ({top: 20, right: 40, bottom: 30, left: 40})
Insert cell
viewof spinSpeed = slider({min: 0, max: 3, value: 0.4})
Insert cell
legend_circle = legendCircle()
.tickValues([5, 30, 100])
.tickFormat((d, i) => i >= 1 ? d3.format("~s")(d) + " mentions" : d + " mentions")
.scale(
d3.scaleSqrt()
.domain([0, d3.max(twitterUsers, d => d3.max([10^6, + parseInt(d.properties.Counts)]))])
.range([5, 30])
)
.tickSize(10)
Insert cell
viewof lineWidth = slider({min: 0.01, max: 5, value: 0.35})
Insert cell
viewof quakeSize = slider({min: 1, max: 20, value: 10})
Insert cell
mutable globe_rotation = ({x: 180, y:0})
Insert cell
Insert cell
Insert cell
d3 = require("d3-fetch@1", "d3-geo@1", "d3-quadtree@1", "d3@5", 'd3-geo-zoom')
Insert cell
topojson = require("topojson-client@3")
Insert cell
world = d3.json("https://unpkg.com/world-atlas@1/world/110m.json")
Insert cell
land = topojson.feature(world, world.objects.countries)
Insert cell
graticule = d3.geoGraticule10()
Insert cell
//width = width //default to built in width instead - https://observablehq.com/@tmcw/responsive-notebook-design-protips
Insert cell
longitude = 50
Insert cell
//Use longitude and -20 latitude to set the initial rotation and tilt of the globe
projection = d3.geoOrthographic()
.rotate([longitude, -20])
.translate([width / 2, width / 2])
.fitExtent([[300, 300], [width-300, width-300]], {type: "Sphere"})
.precision(0.1)
Insert cell
quakeCircles = {
const circle = d3.geoCircle();
return twitterUsers.map(quake => {
//console.log(quake)
return circle.center(quake.geometry.coordinates).radius(magnitudeRadius(quake) / 5).precision(25)();
});
}
Insert cell
magnitudeRadius = {
const scale = d3.scaleSqrt().domain([0, 100]).range([2.5, quakeSize]);
return function(quake) {
//console.log(quake)
console.log(scale(quake.properties.Counts * 5));
return scale(quake.properties.Counts * 5);
}
}
Insert cell
mutable isRotate = false
Insert cell
mutable mouse_movement = false
Insert cell
function onMoveStoped() {
mutable isRotate = false
mutable mouse_movement = false
}
Insert cell
function onMoveStarted(e){
mutable isRotate = true
mutable mouse_movement = true
mutable mouse = {
x: e.offsetX,
y: e.offsetY
}
}
Insert cell
function getQuadtree(){ return d3.quadtree().extent([[0, 0],[width, width]]); }
Insert cell
twitterUsers = (await fetch("https://raw.githubusercontent.com/Ro0oot/spiritualist/main/all_data_with_year.geojson")).json()
Insert cell
Insert cell
Insert cell
legendCircle = function(context){
let scale,
tickValues,
tickFormat = d => d,
tickSize = 5;
function legend(context){
let g = context.select("g");
if (!g._groups[0][0]){
g = context.append("g");
}
g.attr("transform", `translate(${[1, 1]})`);
const ticks = tickValues || scale.ticks();
const max = ticks[ticks.length - 1];
g.selectAll("circle")
.data(ticks.slice().reverse())
.enter().append("circle")
.attr("fill", "none")
.attr("stroke", "currentColor")
.attr("cx", scale(max))
.attr("cy", scale)
.attr("r", scale);
g.selectAll("line")
.data(ticks)
.enter().append("line")
.attr("stroke", "currentColor")
.attr("stroke-dasharray", "4, 2")
.attr("x1", scale(max))
.attr("x2", tickSize + scale(max) * 2)
.attr("y1", d => scale(d) * 2)
.attr("y2", d => scale(d) * 2);
g.selectAll("text")
.data(ticks)
.enter().append("text")
.attr("font-family", "'IBM Plex Mono', sans-serif")
.attr("font-size", 11)
.attr("fill", "white")
.attr("dx", 3)
.attr("dy", 4)
.attr("x", tickSize + scale(max) * 2)
.attr("y", d => scale(d) * 2)
.text(tickFormat);
}
legend.tickSize = function(_){
return arguments.length ? (tickSize = +_, legend) : tickSize;
}
legend.scale = function(_){
return arguments.length ? (scale = _, legend) : scale;
}

legend.tickFormat = function(_){
return arguments.length ? (tickFormat = _, legend) : tickFormat;
}
legend.tickValues = function(_){
return arguments.length ? (tickValues = _, legend) : tickValues;
}
return legend;
}
Insert cell
import {slider, color} from "@jashkenas/inputs"
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