Published
Edited
Aug 17, 2020
Insert cell
Insert cell
Insert cell
chart = {
const width = 800,
height = 800;
const fontSize = 15;
const margin = { top: 10, right: 10, bottom: 10, left: 10 };
let datas = galleryMap.map(g => ({
x: g.pos.x,
y: g.pos.y,
id: g.id,
name: g.name,
r: g.radius*0.7,
cluster: g.cluster,
clusterProbability: g.clusterProbability,
px: g.pos.x,
py: g.pos.y,}));
const transition = d3.transition()
.duration(300)
.ease(d3.easeLinear);
const xscale = d3.scaleLinear()
.domain([0, 1])
.range([0, width]);
const yscale = d3.scaleLinear()
.domain([0, 1])
.range([0, height]);
const categoryCount = Math.max(...datas.map(d => d.cluster)) + 1;
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0 - width*0.1, 0 - height*0.1, width * 1.2, height * 1.2])
const g = svg.append("g");
const bubbles = g.selectAll(".bubble")
.data(datas)
.enter().append("g")
.attr('transform', d => `translate(${xscale(d.x)}, ${yscale(d.y)})`)
.attr('class', d => `bubble cluster-${d.cluster}`)
.attr('id', d => `bubble-${d.id}`)
.on("mouseover", function(d) {
d3.select(d3.event.target).attr('stroke', 'black');
g.selectAll(`.circle.cluster-${d.cluster}`)
.transition()
.attr('fill-opacity', 0.4)
tooltip.style("visibility", "visible");
tooltip.text(d.name);
})
.on("mouseout", function(d) {
d3.select(d3.event.target).attr('stroke', 'transparent');
g.selectAll(`.circle.cluster-${d.cluster}`)
.transition()
.attr('fill-opacity', 1.0)
tooltip.style("visibility", "hidden");
})
.on("mousemove", function(d){
tooltip.style("top", (d3.event.pageY-10)+"px").style("left",(d3.event.pageX+10)+"px");
})
const circles = bubbles.append('circle')
.attr('r', d => xscale(d.r))
.attr('class', d => `circle cluster-${d.cluster}`)
.attr('id', d => `circle-${d.id}`)
.attr("fill", d => d3.interpolateRainbow(d.cluster/categoryCount))
.attr('fill-opacity', 1.0)
.attr('stroke', 'transparent')
const texts = bubbles.append('text')
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'middle')
.attr('fill', 'white')
.classed('bubble-text', true)
.style('font-size', d => fontSize + 'px')
.style('pointer-events', 'none')
.text(d => {
return d.name.slice(0, Math.floor(xscale(d.r)/fontSize))
})
const tooltip = d3.select("body").append("div")
.attr("class", "svg-tooltip")
.style("position", "absolute")
.style("visibility", "hidden")
.text("I'm a circle!");
const simulation = d3.forceSimulation(datas)
.force('collide', d3.forceCollide().radius(d => d.r + 0.001))
.force("x", d3.forceX(d => d.px).strength(0.02))
.force("y", d3.forceY(d => d.py).strength(0.02))
.on('tick', () => {
bubbles
.attr('transform', d => `translate(${xscale(d.x)}, ${yscale(d.y)})`)
});
const zoom = d3.zoom()
.scaleExtent([0.5, 32])
.on("zoom", () => {
const transform = d3.event.transform;
texts
.style('font-size', d => fontSize / transform.k + 'px')
.text(d => {
return d.name.slice(0, Math.floor(xscale(d.r*1.2)/fontSize * transform.k))
})
g.attr("transform", transform);
});
function zoomToBubble(bubble) {
let [x, y] = getTranslation(bubble.attr("transform"));
let scale = 25;
svg.transition()
.duration(3000)
.call(zoom.transform,
d3.zoomIdentity
.translate(width / 2, height / 2)
.scale(scale)
.translate(-x, -y))
}
svg.call(zoom);
galleryInput.addEventListener('input', () => {
zoomToBubble(d3.select(`#bubble-${galleryMap[parseInt(galleryInput.value.index)].id}`));
})
return svg.node()
}
Insert cell
d3 = require("d3@5", "d3-array@2")
Insert cell
galleryMap = await FileAttachment("gallerymap.txt").json();
Insert cell
styles = html`
<style>

.svg-tooltip {
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
background: rgba(69,77,93,.9);
border-radius: .1rem;
color: #fff;
display: block;
font-size: 11px;
max-width: 320px;
padding: .2rem .4rem;
position: absolute;
text-overflow: ellipsis;
white-space: pre;
z-index: 300;
visibility: hidden;
}
</style>`
Insert cell
import {form} from "@mbostock/form-input"

Insert cell
function getTranslation(transform) {
// Create a dummy g for calculation purposes only. This will never
// be appended to the DOM and will be discarded once this function
// returns.
var g = document.createElementNS("http://www.w3.org/2000/svg", "g");
// Set the transform attribute to the provided string value.
g.setAttributeNS(null, "transform", transform);
// consolidate the SVGTransformList containing all transformations
// to a single SVGTransform of type SVG_TRANSFORM_MATRIX and get
// its SVGMatrix.
var matrix = g.transform.baseVal.consolidate().matrix;
// As per definition values e and f are the ones for the translation.
return [matrix.e, matrix.f];
}
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