Public
Edited
Aug 20
Paused
Fork of Star map
Insert cell
Insert cell
Insert cell
map = {
const cx = width / 2;
const cy = height / 2;
const radius = d3.scaleLinear([6, -1], [0, 8]);

const path = d3.geoPath(projection);

const star_opacity_max = 1;
const star_opacity_min = 0.2;

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "display: block; margin: 0 -14px; width: 100%; height: auto; font: 10px sans-serif; color: white; background: black;")
.attr("text-anchor", "middle")
.attr("fill", "white");

// scaffolding
const dark_blue_string = "#000066"
// rings
svg.append("g")
.attr("stroke", dark_blue_string)
.attr("stroke-width", 5)
.attr("fill", "none")
.selectAll()
.data(data_api.axes.experienceScores.slice(1))
.join("circle")
.attr("cx", width/2)
.attr("cy", height/2)
.attr("r", (d) => width/2 - projection([0, experience_scale(d) - 10])[1])
// radial axes
const radial_axes = svg.append("g")
.style("opacity", 0)
radial_axes.append("g")
.attr("stroke", dark_blue_string)
.attr("stroke-width", 10)
.attr("stroke-dasharray", "1,20")
.attr("stroke-linecap", "round")
.selectAll()
.data(d3.range(0, 360, 60)) // every 60 degree
.join("line")
.datum(d => [projection([d, 0]), projection([d, 90])])
.attr("x1", ([[x1]]) => x1)
.attr("x2", ([, [x2]]) => x2)
.attr("y1", ([[, y1]]) => y1)
.attr("y2", ([, [, y2]]) => y2);
radial_axes.append("g")
.selectAll()
.data(d3.range(30, 360, 60).map((d, i) => ({
text: data_api.axes.sectors[i],
angle: d
}))) // every 60 degree
.join("text")
.attr("font-size", 50)
.attr("dy", "0.35em")
.text((d) => d.text)
.attr("transform", (d) => {
const [x, y] = projection([d.angle, -2])
return `translate(${x},${y}) rotate(${d.angle > 90 && d.angle < 270 ? 180+d.angle : d.angle})`
})

// star links
const star_links = svg.append("g")
.attr("stroke", "#999")
.attr("stroke-width", 5)
.selectAll("line")
.data(data_links)
.join("line")
.attr("x1", d => projection([data[d.source].lon, data[d.source].lat])[0])
.attr("y1", d => projection([data[d.source].lon, data[d.source].lat])[1])
.attr("x2", d => projection([data[d.target].lon, data[d.target].lat])[0])
.attr("y2", d => projection([data[d.target].lon, data[d.target].lat])[1])
.attr("stroke-opacity", 0)
// central star
const grad = svg.append("defs").append("radialGradient")
.attr("id", "central-star")
grad.append("stop")
.attr("offset", "0%")
.style("stop-color", "white")
grad.append("stop")
.attr("offset", "100%")
.style("stop-color", "#00000000")
const central_star = svg.append("g")
central_star.append("circle")
.attr("cx", width/2)
.attr("cy", height/2)
.attr("r", 120)
.attr("fill", "url(#central-star)")
central_star.append("circle")
.attr("cx", width/2)
.attr("cy", height/2)
.attr("r", 80)
.attr("fill", "white")
central_star.append("text")
.attr("font-size", 50)
.attr("dy", "0.35em")
.text("Data")
.attr("fill", "black")
.attr("text-anchor", "middle")
.attr("x", width/2)
.attr("y", height/2)

// stars
const stars = svg.append("g")
.selectAll()
.data(data)
.join("g")
.attr("id", (d) => "star" + d.id)
.classed("star", true)
stars.append("g")
.classed("minimal", true)
.attr("transform", "translate(-40, -40) scale(0.25)")
.html((d) => d.svgs[0])
stars.append("g")
.classed("full", true)
.attr("transform", "translate(-160, -160) scale(1)")
.attr("display", "none")
.html((d) => d.svgs[1])
stars.selectAll("svg").select("path")
.attr("fill", undefined)

// Twinkle
const quarter_period = star_twinkle_speed*1000/4
const twinkle = stars.transition()
.delay(() => random_delay())
.ease(d3.easeCubicInOut)
.on("start", function repeat() {
d3.active(this)
.transition()
.delay(quarter_period) //off time
.duration(quarter_period)
.attr("opacity", star_opacity_min)
.transition()
.delay(quarter_period) //on time
.duration(quarter_period)
.attr("opacity", star_opacity_max)
.on("end", repeat);
});

// Highlight
const highlight = (id, scale) => {
d3.select("#star" + id).select(".full")
.attr("display", "unset")
.attr("opacity", 0)
.attr("transform", `translate(-${160*0.1}, -${160*0.1}) scale(0.1)`)
.transition()
.duration(quarter_period)
.attr("opacity", 1)
.attr("transform", `translate(-${160*scale}, -${160*scale}) scale(${scale})`)
d3.select("#star" + id)
.transition()
.duration(quarter_period)
.attr("opacity", 1)
}

// Orbit
let elapsed_old = 0;
const timer = d3.timer((elapsed) => {
if (snap % 2 === 0) {
// normal
for (const i in data) {
const star_orbit_multiplier = orbit_scales[1](data[i][1])*0.1
data[i][0] = (data[i][0] + (data[i].speed + (elapsed - elapsed_old)/10)*star_orbit_multiplier) % 360;
}
elapsed_old = elapsed;
radial_axes.transition()
.duration(2000)
.style("opacity", 0)
central_star.transition()
.duration(2000)
.style("opacity", 1)
stars.attr("transform", (d) => `translate(${projection(d)})`)
} else {
// snapped
let selected_star_id = d3.randomInt(data.length)()
timer.stop();
elapsed_old = 0;

radial_axes.transition()
.duration(2000)
.style("opacity", 1)
central_star.transition()
.duration(2000)
.style("opacity", 0)

stars.selectAll(".minimal") // need to be a different object from transform/translate
.attr("opacity", star_opacity_max)
.transition()
.duration(2000)
.attr("opacity", star_opacity_min)
stars.transition()
.duration(2000)
.attrTween("transform", (d) => (t) => {
const lon = d.lon + (d.lon < d[0] ? 360 : 0);
return `translate(${projection([d3.interpolate(d[0], lon)(t), d.lat])})`;
})
.on("end", () => {
for (const i in data) {
data[i][0] = data[i].lon;
data[i][1] = data[i].lat;
}

star_links
.attr("stroke-opacity", 0)
.filter(d => d.source === selected_star_id) // one-way
.transition()
.duration(quarter_period)
.attr("stroke-opacity", 1)
highlight(selected_star_id, 2)

// peers
d3.select("#star" + selected_star_id)
.each((d) => {
d.peers.forEach((p) => {
highlight(p, 1)
})
})
});
}
});
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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