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

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