Published
Edited
Nov 19, 2020
Importers
5 stars
Insert cell
Insert cell
render_data_table(data.slice(0, 10))
Insert cell
state_data = data
.filter(d => d.state == "Arizona")
.map(d => {
return {
x: +d.enroll,
y: +d.mmr,
name: d.name,
city: d.city
};
})
Insert cell
render_data_table(state_data.slice(0, 10))
Insert cell
Insert cell
chart = {
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);

const circles = svg
.selectAll("circle")
.data(state_data)
.join("circle")
.attr("r", 5)
.attr("cx", d => x(d.x))
.attr("cy", d => y(d.y))
.attr("opacity", .3);
svg.append("g").call(xAxis);

svg.append("g").call(yAxis);

return svg.node();
}
Insert cell
md`### Simple text hover`
Insert cell
simple_hover_chart = {
const svg = d3
.create("svg")
.attr("viewBox", [0, 0, width, height])
.style("overflow", "display");

const circles = svg
.selectAll("circle")
.data(state_data)
.join("circle")
.attr("r", 5)
.attr("cx", d => x(d.x))
.attr("cy", d => y(d.y))
.attr("opacity", .3);

const tip = svg
.append("text")
.style("pointer-events", "none")
.style("text-anchor", "middle");
// Add a hover
circles.on("mouseenter", event => {
const value = event.target.__data__;
const pointer = d3.pointer(event);
tip
.attr("x", pointer[0])
.attr("y", pointer[1])
.text(value.name);
});

circles.on("mouseout", event => {
tip.text("");
});
svg.append("g").call(xAxis);

svg.append("g").call(yAxis);

return svg.node();
}
Insert cell
md`### Bounded hover tips`
Insert cell
bounded_hover_chart = {
const svg = d3
.create("svg")
.attr("viewBox", [0, 0, width, height])
.style("overflow", "display");

const tip = svg
.append("g") // append a group of stuff
.style("pointer-events", "none")
.style("text-anchor", "middle");

const circles = svg
.selectAll("circle")
.data(state_data)
.join("circle")
.attr("r", 5)
.attr("cx", d => x(d.x))
.attr("cy", d => y(d.y))
.attr("opacity", .3);

// Add a hover
circles.on("mouseenter", event => {
const value = event.target.__data__;
const pointer = d3.pointer(event, this);

// Append the text to the G
tip
.style("text-anchor", "middle")
.attr("transform", `translate(${pointer[0]}, ${pointer[1]})`)
.append("text")
.text(value.name);
const bbox = tip.node().getBBox();

// Make sure it doesn't go beyond the left of the chart
if (pointer[0] + bbox.x < margin.left) {
tip.attr(
"transform",
`translate(${margin.left + bbox.width / 2}, ${pointer[1]})`
);
}

// Make sure it doesn't go to the right of the chart
if (pointer[0] - bbox.x > width - margin.right) {
tip.attr(
"transform",
`translate(${width - margin.right - bbox.width / 2}, ${pointer[1]})`
);
}
});

circles.on("mouseout", event => {
tip.selectAll("*").remove();
});
svg.append("g").call(xAxis);

svg.append("g").call(yAxis);

return svg.node();
}
Insert cell
md`### Tip with background`
Insert cell
hover_with_rect = {
const svg = d3
.create("svg")
.attr("viewBox", [0, 0, width, height])
.style("overflow", "display");

const circles = svg
.selectAll("circle")
.data(state_data)
.join("circle")
.attr("r", 5)
.attr("cx", d => x(d.x))
.attr("cy", d => y(d.y))
.attr("opacity", .3);

const tip = svg
.append("g") // append a group of stuff
.style("pointer-events", "none")
.style("text-anchor", "middle");

// Add a hover
circles.on("mouseenter", event => {
const value = event.target.__data__;
const pointer = d3.pointer(event, this);

const text = [value.name, "City: " + value.city];
// Append the text to the G
const side_padding = 3;

tip
.style("text-anchor", "middle")
.attr("transform", `translate(${pointer[0]}, ${pointer[1]})`)
.selectAll("text")
.data(text)
.join('text')
.style("dominant-baseline", "ideographic")
.text(d => d)
.attr("y", (d, i) => (i - (text.length - 1)) * 15)
.style("font-weight", (d, i) => (i === 0 ? "bold" : "normal"));

const bbox = tip.node().getBBox();

// Add a rectangle (as background)
tip
.append("rect")
.attr("y", bbox.y)
.attr("x", bbox.x - side_padding)
.attr("width", bbox.width + side_padding * 2)
.attr("height", bbox.height)
.style("fill", "white")
.style("stroke", "#d3d3d3")
.lower();

// Make sure it doesn't go beyond the left of the chart
if (pointer[0] + bbox.x < margin.left) {
tip.attr(
"transform",
`translate(${margin.left + bbox.width / 2}, ${pointer[1]})`
);
}

// Make sure it doesn't go to the right of the chart
if (pointer[0] - bbox.x > width - margin.right) {
tip.attr(
"transform",
`translate(${width - margin.right - bbox.width / 2}, ${pointer[1]})`
);
}
});

circles.on("mouseout", event => {
tip.selectAll("*").remove();
});
svg.append("g").call(xAxis);

svg.append("g").call(yAxis);

return svg.node();
}
Insert cell
md`## Multiple line hover`
Insert cell
Insert cell
md`## Move hover to a separate function`
Insert cell
external_hover_function = {
const svg = d3
.create("svg")
.attr("viewBox", [0, 0, width, height])
.style("overflow", "display");

const circles = svg
.selectAll("circle")
.data(state_data)
.join("circle")
.attr("r", 5)
.attr("cx", d => x(d.x))
.attr("cy", d => y(d.y))
.attr("opacity", .3);

const tip = svg
.append("g") // append a group of stuff
.style("pointer-events", "none")
.style("text-anchor", "middle");

// Add a hover
circles.on("mouseenter", event => {
const value = event.target.__data__;
const pointer = d3.pointer(event, this);

const text = [value.name, "City: " + value.city];
tip.call(hover, pointer, text);
});

circles.on("mouseout", event => {
tip.selectAll("*").remove();
});
svg.append("g").call(xAxis);

svg.append("g").call(yAxis);

return svg.node();
}
Insert cell
hover = (tip, pos, text) => {
const side_padding = 3;

tip
.style("text-anchor", "middle")
.style("pointer-events", "none")
.attr("transform", `translate(${pos[0]}, ${pos[1]})`)
.selectAll("text")
.data(text)
.join('text')
.style("dominant-baseline", "ideographic")
.text(d => d)
.attr("y", (d, i) => (i - (text.length - 1)) * 15)
.style("font-weight", (d, i) => (i === 0 ? "bold" : "normal"));

const bbox = tip.node().getBBox();

// Add a rectangle (as background)
tip
.append("rect")
.attr("y", bbox.y)
.attr("x", bbox.x - side_padding)
.attr("width", bbox.width + side_padding * 2)
.attr("height", bbox.height)
.style("fill", "white")
.style("stroke", "#d3d3d3")
.lower();

// Reposition the full g element to make sure it doesn't go offscreen
let x = pos[0];
let y = pos[1];

// Make sure it doesn't go beyond the left of the chart
if (x + bbox.x < margin.left) {
x = margin.left + bbox.width / 2 + side_padding;
}
// Make sure it doesn't go beyond the right of the chart
else if (x - bbox.x > width - margin.right) {
x = width - margin.right - bbox.width / 2;
}

// Make sure it doesn't go over the top of the chart
if (y + bbox.y < 0) {
y = margin.top + bbox.height + 10;
}
tip.attr("transform", `translate(${x}, ${y})`);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// Working version
voronoi_chart_and_points = {
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);

const circles = svg
.selectAll("circle")
.data(state_data)
.join("circle")
.attr("cx", d => x(d.x))
.attr("cy", d => y(d.y))
.attr("r", 5);

// Voronoi diagram for hovers (but don't show it!)
const cells = svg
.append("g")
.attr("fill", "none")
.attr("pointer-events", "all")
.selectAll("path")
.data(state_data)
.join("path")
.attr("d", (d, i) => voronoi.renderCell(i));

svg.append("g").call(xAxis);

svg.append("g").call(yAxis);
const tip = svg.append("g").style("pointer-events", "none");
cells
.on("mouseenter", event => {
const value = event.target.__data__;
const pointer = [x(value.x), y(value.y)];
const text = [value.name, "City: " + value.city];
tip.call(hover, pointer, text);
})
.on("mouseout", event => {
tip.selectAll("*").remove();
});
return svg.node();
}
Insert cell
x = d3
.scaleLinear()
.domain(d3.extent(state_data, d => +d.x))
.range([margin.left, width - margin.right])
Insert cell
y = d3
.scaleLinear()
.domain(d3.extent(state_data, d => +d.y))
.range([height - margin.bottom, margin.top])
Insert cell
xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x))
Insert cell
yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y))
Insert cell
margin = ({top: 20, right: 30, bottom: 30, left: 40})
Insert cell
height = 500
Insert cell
d3 = require("d3@6")
Insert cell
md`## Appendix`
Insert cell
data = await d3.csv(
"https://raw.githubusercontent.com/WSJ/measles-data/master/all-measles-rates.csv",
d3.auto
)
Insert cell
import { render_data_table, table_styles } from "@uw-info474/utilities"
Insert cell
table_styles
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