Published
Edited
Feb 27, 2020
10 forks
31 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
letters[0]
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
x = {
return d3.scaleLinear()
.domain(d3.extent(letters, d => d.frequency))
.range([2 * r, width - 2 * r]) // pad by circle radius
.nice(); // 😎
}
Insert cell
y = {
return d3.scaleLinear()
.domain(d3.extent(letters, d => d.points))
.range([height - 2 * r, 2 * r]) // pad by circle radius
.nice(); // 😎
}
Insert cell
Insert cell
Insert cell
types = d3.set(letters, d => d.type);
Insert cell
color = {
return d3.scaleOrdinal(d3.schemeSet2).domain(types.values());
}
Insert cell
Insert cell
vis = {
// wait until this cell is visible
await visibility();
const svg = d3.select(DOM.svg(width, height));
svg.style("border", "1px solid #bbbbbb");
// data layer to keep circles
const g = svg.append("g").attr("id", "circles");
g.selectAll("circle")
.data(letters)
.enter()
.append("circle")
.attr("cx", d => x(d.frequency))
.attr("cy", d => y(d.points))
.attr("r", r)
.style("fill", d => color(d.type));
// annotation layer to keep labels on top of data
svg.append("g").attr("id", "annotation");
return svg.node();
}
Insert cell
Insert cell
Insert cell
circles = d3.select(vis).select("g#circles").selectAll("circle");
Insert cell
annotations = d3.select(vis).select("g#annotation");
Insert cell
Insert cell
highlight = {
const svg = d3.select(vis);
// used to test out interactivity in this cell
const status = html`<code>highlight: none</code>`;
// event.namespace allows us to add multiple functions for one event
// only needed in examples like this where some events trigger multiple functions
circles.on("mouseover.highlight", function(d) {
d3.select(this)
.raise() // bring to front
.style("stroke", "red")
.style("stroke-width", 2);
// show what we interacted with
d3.select(status).text("highlight: " + d.letter);
});

circles.on("mouseout.highlight", function(d) {
d3.select(this).style("stroke", null);
d3.select(status).text("highlight: none");
});
return status;
}
Insert cell
Insert cell
hover1 = {
const svg = d3.select(vis);
// used to test out interactivity in this cell
const status = html`<code>hover: none</code>`;

circles.on("mouseover.hover1", function(d) {
let me = d3.select(this);
annotations.insert("text")
.attr("id", "label")
.attr("x", me.attr("cx"))
.attr("y", me.attr("cy"))
.attr("dy", r + 14)
.attr("text-anchor", "middle")
.text(d.codeword);

// show what we interacted with
d3.select(status).text("hover: " + d.letter);
});

circles.on("mouseout.hover1", function(d) {
annotations.select("text#label").remove();
d3.select(status).text("hover: none");
});
return status;
}
Insert cell
Insert cell
hover2 = {
const svg = d3.select(vis);
// used to test out interactivity in this cell
const status = html`<code>hover: none</code>`;

circles.on("mouseover.hover2", function(d) {
let me = d3.select(this);
let div = d3.select("body").append("div");
div.attr("id", "details");
div.attr("class", "tooltip");
let rows = div.append("table")
.selectAll("tr")
.data(Object.keys(d))
.enter()
.append("tr");
rows.append("th").text(key => key);
rows.append("td").text(key => d[key]);
// show what we interacted with
d3.select(status).text("hover: " + d.letter);
});

circles.on("mousemove.hover2", function(d) {
let div = d3.select("div#details");
// get height of tooltip
let bbox = div.node().getBoundingClientRect();
div.style("left", d3.event.clientX + "px")
div.style("top", (d3.event.clientY - bbox.height) + "px");
});
circles.on("mouseout.hover2", function(d) {
d3.selectAll("div#details").remove();
d3.select(status).text("hover: none");
});
return status;
}
Insert cell
Insert cell
brush1 = {
const svg = d3.select(vis);
// used to test out interactivity in this cell
const status = html`<code>brush: none</code>`;
circles.on("mouseover.brush1", function(d) {
circles.filter(e => (d.type !== e.type)).transition().style("fill", "#bbbbbb");
// show what we interacted with
d3.select(status).text("brush: " + d.type);
});
circles.on("mouseout.brush1", function(d) {
circles.transition().style("fill", d => color(d.type));
d3.select(status).text("brush: none");
});
return status;
}
Insert cell
Insert cell
brush2 = {
const svg = d3.select(vis);
// used to test out interactivity in this cell
const status = html`<code>brush: none</code>`;
let brush = d3.brush()
.on("start.brush2 brush.brush2 end.brush2", brushed);
function brushed() {
if (d3.event.selection) {
const [[x0, y0], [x1, y1]] = d3.event.selection;
// show what we interacted with
d3.select(status).text("brush: " + d3.event.selection);
circles.classed("dim", function(d) {
let cx = +d3.select(this).attr("cx");
let cy = +d3.select(this).attr("cy");
return !(x0 <= cx && cx < x1 &&
y0 <= cy && cy < y1);
});
}
else {
d3.select(status).text("brush: none");
circles.classed("dim", false);
}
}
// place brush BEHIND points so we still get pointer events
svg.insert("g", ":first-child").attr("class", "brush").call(brush);
return status;
}
Insert cell
Insert cell
drag = {
const svg = d3.select(vis);
// used to test out interactivity in this cell
const status = html`<code>drag: none</code>`;
let drag = d3.drag();
drag.on("start.drag", function(d) {
d3.select(this).raise(); // places circle above others
// show what we interacted with
d3.select(status).text("drag: " + d.letter);
});
drag.on("drag.drag", function(d) {
d3.select(this)
.attr("cx", d3.event.x)
.attr("cy", d3.event.y);
});
drag.on("end.drag", function(d) {
d3.select(status).text("drag: none");
});
circles.call(drag);
return status;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
html`
<style>
circle {
stroke: white;
stroke-width: 2px;
}

text, .tooltip {
font-family: sans-serif;
font-size: 10pt;
}

.dim {
fill-opacity: 0.3;
}

.tooltip {
position: absolute;
width: auto;
height: auto;
padding: 8px;
background: #ddd;
pointer-events: none;
border: 1px solid #eee;
border-radius: 10px;
}
</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