Published
Edited
May 25, 2020
Importers
Insert cell
Insert cell
lgd = legend({
color,
title: "Words per chapter",
width: 320
})
Insert cell
chart = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, heightV])
.attr("font-family", "sans-serif")
.attr('transform', `translate(${(width < 600 ? width/16 : width/8)},0)`)

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

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

svg.append("g")
.selectAll("g")
.data(data.values)
.join("g")
.attr("transform", (d, i) => `translate(${xV(data.names[i])},0)`)
.selectAll("rect")
.data(d => d)
.join("rect")
.attr("y", (d, i) => yV(data.chapters[i]) + 1)
.attr("height", (d, i) => yV(data.chapters[i] + 1) - yV(data.chapters[i]) - 1)
.attr("width", xV.bandwidth() - 1)
.attr("fill", d => isNaN(d) ? "#eee" : d === 0 ? "#eee" : color(d))
.on('mouseover', function(d, i) {
const t = d3.transition('enlarge').duration(500)
tooltipMouseOver(d, i, t)
})
.on('mouseout', function(d, i) {
const t = d3.transition('dwindle').duration(500)
tooltipMouseOut(d, i, t)
})
svg.selectAll('text')
.style("font-size", fontSize);
return svg.node();
}
Insert cell
data = {
const data = d3.csvParse(await FileAttachment("teotw-chapter-pov.csv").text(), ({character, chapter_abbr, chapter_order, chapter, word_count}) => ({character, chapter, chapter_abbr, chapter_order: +chapter_order, word_count: +word_count}))
const names = [... new Set(data.map(({character}) => character))];
const values = []
const [firstChapter, lastChapter] = d3.extent(data, d => d.chapter_order)
const chapters = d3.range(firstChapter, lastChapter + 1);
const chapterNames = chapters.map(chapterOrder => data.find(d => d.chapter_order === chapterOrder).chapter);
for (const pov of data) {
const idx = names.findIndex(name => name === pov.character);
(values[idx] || (values[idx] = new Array(chapters.length).fill(0)))[pov.chapter_order - firstChapter] += pov.word_count;
}
return {
names,
values,
chapters,
chapterNames,
chapter_divisor: 30,
firstChapter
};
}
Insert cell
xV = d3.scaleBand()
.domain(data.names)
.rangeRound([margin.left, (width < 600 ? width*0.8 : width/3)])
Insert cell
yV = d3.scaleLinear()
.domain([d3.min(data.chapters), d3.max(data.chapters) + 1])
.rangeRound([margin.top, heightV - margin.bottom])
Insert cell
color = d3.scaleSequential([0, d3.max(data.values, d => d3.max(d))], d3.interpolatePuRd)
Insert cell
yvAxis = g => g
.call(g => g.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yV).ticks(null, "d").tickFormat(d => `Ch ${d - data.firstChapter - 1}`))
.call(g => g.select(".domain").remove()))
Insert cell
xvAxis = g => g
.attr("transform", `translate(0,${margin.top})`)
.call(d3.axisTop(xV).tickSize(0))
.call(g => g.select(".domain").remove())
.call(g =>
g
.selectAll('text')
.attr('transform', 'rotate(-80)')
.attr('text-anchor', 'start')
)
Insert cell
format = {
const f = d3.format(",d");
return d => isNaN(d) ? `0 words` : `${f(d)} words`;
}
Insert cell
fontSize = width / 50
Insert cell
heightV = (width < 600 ? width * 1.75 : width)
Insert cell
margin = ({top: width / 4, right: 1, bottom: 10, left: width / 15})
Insert cell
d3 = require("d3@5")
Insert cell
import {legend} from "@d3/color-legend"
Insert cell
function tooltipMouseOver(d, i, t) {
let el = d3.select("body");
let div = el
.append("g")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);

div
.html(`
${format(d)} in <b>${data.chapterNames[i]}</b>
`)
.style("left", (d3.event.pageX + 10) + "px")
.style("top", (d3.event.pageY - 10) + "px")
.transition(t)
.style("opacity", 0.9)
;
let el2 = d3.select(this);
el2
.transition(t)
.style("fill-opacity", 0.9)
.style("stroke", "black")
.style("stroke-width", "1px")
.style("stroke-opacity", 0.7);
}

Insert cell
function tooltipMouseOut(d, i, t) {
let el = d3.selectAll(".tooltip");
el.each(function() {
el.remove();
});
let el2 = d3.select(this);
el2
.transition(t)
.style("fill-opacity", 0.5)
.style("stroke-width", "0px");

}
Insert cell
tooltip = html`
<link href="https://fonts.googleapis.com/css?family=Montserrat" rel="stylesheet">

<style>
.text {
font-family: "Montserrat";
}
.grid {
font-family: "Montserrat";
}
div.tooltip {
font-family: "Montserrat";
position: absolute;
text-align: left;
padding: 0.5em;
font: 0.5em;
background: rgb(255, 255, 255);
border: 1px solid rgba(0, 0, 0, 0.4);
border-radius: 0.5em;
pointer-events: none;
}
.label {
font-weight: bold;
}
.bubble {
mix-blend-mode: multiply;
}
</style>`
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