Published
Edited
Jun 19, 2022
1 fork
3 stars
Insert cell
Insert cell
chart = {
let div = d3.create("div")
.attr("id","background-div")

div.selectAll(".topic_btn")
.data(topics)
.join("button")
.attr("id",(d) => "btn_"+d)
.classed("topic_btn", true)
.style("left",(d,i) => (d === "mathematics")
? width/2 - 60 + "px"
: width/2 - 60 - 320 * (-1)**(i) + "px")
.style("top", (d,i) => (d === "mathematics")
? 25 + "px"
: (120 + 70*Math.floor(i/2)) + "px")
.style("color", (d,i) => (d === "mathematics")
? "black"
: d3.interpolatePlasma(0.5 + i/30))
.style("border", (d,i) => "2px solid "+d3.interpolatePlasma(0.5 + i/30))
.style("box-shadow", (d,i) => "0 0 4px 2px "+d3.interpolatePlasma(0.5 + i/30))
.text((d,i) => d.charAt(0).toUpperCase() + d.slice(1).replaceAll("-"," "))
.on("click", (e,d) => {
d3.selectAll(".topic_btn")
.style("background-color","rgba(40,40,40)")
.style("color", (d,i) => d3.interpolatePlasma(0.5 + i/30));
d3.select(e.target)
.style("background-color", "rgba(240, 249, 33,0.9)")
.style("color","black");
showTop30(d);
});

// On mobile, show topic buttons below chart
if(width < 800){
div.style("height", "800px");
div.selectAll(".topic_btn")
.style("left",(d,i) => (d === "mathematics")
? width/2 - 60 + "px"
: width/2 - 190 + 130 * (i % 3) + "px")
.style("top", (d,i) => (d === "mathematics")
? 25 + "px"
: (520 + 70*Math.floor(i/3)) + "px")
}
const images = await Promise.all(uploadedImages);
div.selectAll(".bubble")
.data(namesArray)
.join("img")
.classed("bubble",true)
.attr("src", (d,i) => images[i].src )
.style("z-index",(d,i) => i)
.style("transform", (d,i)=> "translate("+
(width/2 - 5 + 200* Math.cos((i/namesArray.length)*6.28)) + "px,"
+ (300 - 5 + 200* Math.sin((i/namesArray.length)*6.28)) + "px)"
);

let tooltip = div.append("div")
.attr("id","tooltip")
let timer = 0;
let transitioning = false;
let names;

let calcPos = (r) => "translate(" + (width/2 - 36 + 0.75 * r + spiralLengths[r-1] * Math.cos(spiralAngles[r-1])) + "px," + (300 - 36 + 0.75 * r + spiralLengths[r-1] * Math.sin(spiralAngles[r-1])) + "px)";
let showTop30 = (topic)=>{
names = rankings[topic];
let rank = (d) => names.indexOf(d) + 1;
transitioning = true;
setTimeout(()=>{transitioning = false},2000);
tooltip.transition().style("opacity",0);
div.selectAll(".bubble")
.on("mouseover", (e,d)=>{
if(transitioning === false && rank(d) > 0){
d3.select(e.target)
.transition()
.style("width", 100 - 0.5* rank(d) + "px")
.style("height", 100 - 0.5*rank(d) + "px")
.style("z-index", "1000");
tooltip.html("")
.append("p")
.text(d.replaceAll("-"," "))
.style("font-weight","700")
.style("text-align","center");
tooltip.append("p")
.text("(" + mathematicians[d].dates + ")")
.style("text-align","center");
tooltip.append("p")
.text(mathematicians[d].bio);

if(mathematicians[d].papers.length > 0){
tooltip.append("p")
.text("Most commonly cited papers:")
.style("font-weight","700")
mathematicians[d].papers.forEach(paper => {
tooltip.append("li")
.text(paper.split(" (")[0]);
});
}

tooltip.transition()
.duration(0)
.style("width",Math.min(500, width - 40) + "px")
.style("height", "auto");
// Transition the tooltip to its required height
setTimeout(()=>{
let tooltipHeight = tooltip.node().getBoundingClientRect().height;
let bbRect = e.target.getBoundingClientRect()
tooltip.transition()
.style("transform", `translate( ${Math.min(bbRect.x-50, width-140)}px, ${bbRect.y - 150}px)`)
.style("height", "24px")
.style("width","200px")
.style("opacity",1)
.transition()
.duration(1500)
.delay(1500)
.style("transform", `translate( ${Math.min(Math.max(bbRect.x-250,10), width - Math.min(500, width - 20))}px, 20px)`)
.style("width",Math.min(500, width - 40) + "px")
.transition()
.duration(tooltipHeight * 15)
.ease(d3.easeLinear)
.style("height", tooltipHeight + "px");
},20);
}
})
.on("mouseout",(e,d)=>{
if(transitioning === false && rank(d) > 0){
d3.select(e.target)
.transition()
.style("width", 72 - 1.5*rank(d) + "px")
.style("height", 72 -1.5*rank(d) + "px")
.style("z-index", d => 1000 - rank(d))
.style("transform", (d) => calcPos(rank(d)));
tooltip
.transition()
.duration(0)
.style("opacity",0);
}
})
.transition()
.duration(2000)
.ease(d3.easeCubicIn)
.style("width", (d,i)=> (rank(d) > 0)
? 72 - 1.5 * rank(d) + "px"
: "10px")
.style("height", (d,i)=> (rank(d) > 0)
? 72 - 1.5 * rank(d) + "px"
: "10px")
.style("transform", (d,i)=> (rank(d) > 0)
? calcPos(rank(d))
: "translate("+
(width/2 - 5 + 200* Math.cos((i/namesArray.length)*6.284)) + "px,"
+ (300 - 5 + 200* Math.sin((i/namesArray.length)*6.284)) + "px)")
.style("border-color", (d,i) => (rank(d) > 0)
? d3.interpolatePlasma(1 - 0.5 * rank(d)/names.length)
: "goldenrod")
.style("color", (d,i) => (rank(d) > 0)
? d3.interpolatePlasma(1 - 0.5 * rank(d)/names.length)
: "goldenrod")
.transition()
.delay((d,i) => 50 * rank(d))
.duration(0)
.style("z-index",(d,i) => (rank(d) > 0)
? 1000 - rank(d)
: i)
.style("box-shadow",(d,i) => rank(d)>0
? `0 0 ${20 - 0.3*rank(d)}px ${10 - 0.2*rank(d)}px`
: "0 0 4px 2px");
}
showTop30("mathematics");
return div.node();
}
Insert cell
spiralLengths = [...Array(30).keys()].map(i => Math.min(20+20*(i+1)**0.75 , 180 - 36 + 0.75 * (i+1)));
Insert cell
spiralAngles = {
let angles = [];
let diameters = [...Array(30).keys()].map(i => 72 - 1.5 * (i+1));
let lastCenterX = spiralLengths[0];
let lastCenterY = 0;
let rank = 1;
for(let a=0; a<100; a+=0.01){
let currentX = spiralLengths[rank - 1] * Math.cos(a);
let currentY = spiralLengths[rank - 1] * Math.sin(a);
if((currentX - lastCenterX)**2 + (currentY - lastCenterY)**2 > (0.98 * diameters[rank - 1])**2){
rank++;
angles.push(a);
lastCenterX = currentX;
lastCenterY = currentY;
if(rank > 30){break;}
}
}
return angles;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
styles = html`
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Averia+Libre:wght@700&family=PT+Sans+Narrow:wght@400;700&display=swap" rel="stylesheet">

<style>
#background-div{
height: 600px;
background: radial-gradient(circle, rgba(150,150,0,1) 0%, rgba(20,20,20) 50%, rgb(0,0,0) 100%),
linear-gradient(135deg, rgba(200,0,0,0.8) 0%, rgba(0,0,0,0) 40%,rgba(0,0,0,0) 60%, rgba(0,0,200,0.8) 100%);
background-blend-mode: overlay;
}

.topic_btn{
position: absolute;
background-color: rgba(40, 40, 40);
width: 120px;
height: 60px;
border-radius: 10px;
font-family: Averia Libre, cursive;
font-size: 16px;
font-weight: 700;
z-index: 1;
cursor: pointer;
}

#btn_mathematics{
color: black;
background-color: rgba(240, 249, 33,0.9);
}

.bubble{
opacity: 0.9;
position: absolute;
width: 10px;
height: 10px;
border-radius: 50%;
border: 2px solid goldenrod;
box-shadow: 0 0 4px 2px;
color: goldenrod;
object-fit: cover;
}

#tooltip{
position: absolute;
width: 200px;
font-family: 'PT Sans Narrow', sans-serif;
color: limegreen;
border-radius: 10px;
background-color: rgba(40, 40, 40, 0.7);
z-index: 1000;
padding: 5px;
overflow: hidden;
pointer-events: none;
}

#tooltip p{
margin-top: 0px;
margin-bottom: 8px;
}
</style>`
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