Published
Edited
Dec 26, 2019
Importers
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
rollData = rollsToData(exampleRolls, 4, 16) // for the sake of debugging
Insert cell
Insert cell
Insert cell
// duplicate roll simulation loop from article so we don't have import and risk circular annoyance
function simulateRolls(quantity, sides, simulations) {
var rolls = []
for (var i = 0; i < simulations; i++) {
var die = []
for(var j = 0; j < quantity; j++){
let roll = Math.floor(Math.random() * sides) + 1
die.push(roll)
}
rolls.push(die)
}
return rolls;
}
Insert cell
// press play to rerun cell
drawChart(
rollsToData(
simulateRolls(3, 6, 10000), // rolls
3, // min
3*6), // max
1 // exponent
);
Insert cell
Insert cell
allPossible3d6Results = (function() {
var rolls = []
for (var i = 1; i <= 6; i++) {
for (var j = 1; j <= 6; j++) {
for (var k = 1; k <= 6; k++) {
rolls.push([i, j, k])
}
}
}
return rolls
}())
Insert cell
drawChart(
rollsToData(
allPossible3d6Results, // rolls
3, // min
3*6), // max
1 // exponent
);
Insert cell
Insert cell
// this formats our array of arrays into a format that is easy to chart in d3
function rollsToData(arrayOfRolls, minPossibleTotal, maxPossibleTotal) {
var totalRolls = arrayOfRolls.length
var counts = [] // tally of each roll total
arrayOfRolls.forEach(roll => {
var rollTotal = roll.reduce((total, dieFace) => total + dieFace) // sum each die face
if (!Number.isInteger(counts[rollTotal])) { // this is our first time seeing this total
counts[rollTotal] = 1
} else {
counts[rollTotal] += 1
}
})
var data = []
// indices with no defined element are not iterated over
counts.forEach((count, rollTotal) => {
if(!data[rollTotal]) {
data[rollTotal] = {}
}
data[rollTotal].name = rollTotal
data[rollTotal].count = count
data[rollTotal].pct = (count / totalRolls) * 100
})
// pad out rolls we could have seen but didn't to make charting simpler
for (var i = minPossibleTotal; i <= maxPossibleTotal; i++) {
if(!data[i]){
data[i] = { name: i, count: 0, pct: 0}
}
}

return data.filter(e => e); // gets rid of undefined elements, may be a few at the beginning of our data
}
Insert cell
// Draw a chart from the data. Width is the Observable built-in for the entire article
// optional params:
// height - sets the height (obv)
// margin - sets the margins inside the chart, if overriding need to supply all of: top, right, bottom, left
function drawChart(data, exponent = 1, height = 600, margin = {top: 30, right: 30, bottom: 30, left: 30}) {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);
var x = xScale(data, margin)
var y = yScale(data, exponent, height, margin)
const tooltip = d3.select("body").append("div")
.attr("class", "svg-tooltip")
.style("position", "absolute")
.style("visibility", "hidden")
.text("I'm a circle!");
svg.append("g")
.attr("fill", "steelblue")
.selectAll("rect")
.data(data)
.join("rect")
.attr("x", d => x(d.name))
.attr("y", d => y(d.pct))
.attr("height", d => y(0) - y(d.pct))
.attr("width", x.bandwidth())
// highlight, so we know which one the tooltip is for
.on('mouseover', function(){
d3.select(this)
.transition()
.attr('fill', '#0C9CDF');
})
// undo highlighting on mouse out
.on('mouseout', function(){
d3.select(this)
.transition()
.attr('fill', 'steelblue');
})
// create a simple tooltip
.append("svg:title")
.text(function(d) { return `total: ${d.name}\ncount: ${d.count}\npct: ${d.pct.toFixed(4)}%`; });
svg.append("g")
.call(xAxis(x, data, height, margin));

svg.append("g")
.call(yAxis(y, margin));
return svg.node();
}
Insert cell
function xScale(data, margin) {
return d3.scaleBand()
.domain(data.map(d => d.name))
.range([margin.left, width - margin.right])
.padding(0.1)
}
Insert cell
function yScale(data, exponent, height, margin) {
return d3.scalePow()
.exponent(exponent)
.domain([0, d3.max(data, d => d.pct)]).nice()
.range([height - margin.bottom, margin.top])
}
Insert cell
function xAxis(x, data, height, margin) {
var everyNthTick = Math.ceil(data.length / 20); // don't want the x labels to be too crowded
return g => {
g.attr("transform", `translate(0,${height - margin.bottom})`)
.call(
d3.axisBottom(x)
.tickSizeOuter(0)
.tickValues(
x.domain()
.filter(
function(d,i){ return !(i % everyNthTick)})))
}
}
Insert cell
function yAxis(y, margin) {
return g => {
g.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y))
.call(g => g.select(".domain").remove())
}
}
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