Published
Edited
Feb 11, 2020
1 fork
Insert cell
Insert cell
createHeatmap('teaser')
Insert cell
md`We'll use ethers.js to fetch block data:`
Insert cell
provider = ethers.getDefaultProvider()
Insert cell
md`
First, let's define a function to process a block. For this, we fetch all the transactions in a block, then go through them in order, recording the maximum amount of gas available at each gas price.
`
Insert cell
get_gas_by_price = async function(block_number) {
const block = await provider.getBlock(block_number, true);
const transactions = block.transactions;
if(transactions.length == 0) {
return [];
}
transactions.sort((a, b) => b.gasPrice - a.gasPrice);
const receipts = await Promise.all(transactions.map(tx => provider.getTransactionReceipt(tx.hash)));
let gasPrice = transactions[0].gasPrice;
let gasLeft = block.gasLimit;
let gasAtPrice = 0;
let items = [];
for(let i = 0; i < transactions.length; i++) {
let tx = transactions[i];
let receipt = receipts[i];
if(tx.gasPrice < gasPrice) {
items.push({gasPrice: gasPrice, gasLeft: gasLeft, gas: gasAtPrice});
gasLeft -= gasAtPrice;
gasAtPrice = 0;
gasPrice = tx.gasPrice;
}
gasAtPrice += receipt.gasUsed.toNumber();
}
items.push({gasPrice: gasPrice, gasLeft: gasLeft, gas: gasLeft});
return items;
}
Insert cell
md`
Let's compute this function for a bunch of recent blocks:
`
Insert cell
blockCount = 50
Insert cell
gasMaps = {
let items = [];
let latest = await provider.getBlockNumber('latest');
let seen = latest - blockCount;
while(true) {
for(let i = seen + 1; i < latest; i++) {
items.push({block: i, map: await get_gas_by_price(i)});
items = items.slice(-blockCount);
yield items;
}
seen = latest;
latest = await provider.getBlockNumber('latest');
await Promises.delay(5000);
}
};
Insert cell
md`
Now let's compute the domains - minimum and maximum gas price and limit:
`
Insert cell
latest = Math.max(...gasMaps.map(item => item.block));
Insert cell
oldest = Math.min(...gasMaps.map(item => item.block));
Insert cell
minGasPrice = Math.min(...gasMaps.flatMap(items => items.map.flatMap(item => item.gasPrice)));
Insert cell
maxGasPrice = Math.max(...gasMaps.flatMap(items => items.map.flatMap(item => item.gasPrice)));
Insert cell
maxGasLimit = Math.max(...gasMaps.flatMap(items => items.map.flatMap(item => item.gasLeft)));
Insert cell
md`
Next, let's set some parameters for our visualisation:
`
Insert cell
mutable params = {
let out = {};
out["svg"] = {
"width": width, // use width variable to calculate our svg size
"height": width / 1.618 // golden ratio
};
out["margin"] = {
"top": 10,
"right": 10,
"bottom": 20,
"left": 50
};
out["plot"] = {
"x": out["margin"]["left"],
"y": out["margin"]["top"],
"width": out["svg"]["width"] - out["margin"]["left"] - out["margin"]["right"],
"height": out["svg"]["height"] - out["margin"]["top"] - out["margin"]["bottom"]
};
return out;
}
Insert cell
md`And create scales and axes using d3:`
Insert cell
scale = {
return {
x: d3.scaleLinear().domain([0, maxGasLimit]).range([0, params.plot.width]),
y: d3.scaleLinear().domain([oldest - 1, latest]).range([0, params.plot.height]),
color: d3.scaleSequentialLog(d3.interpolatePlasma).domain([minGasPrice, maxGasPrice]),
};
}
Insert cell
axis = {
let x = d3.axisBottom(scale.x);
x.tickPadding(0);
let y = d3.axisLeft(scale.y);
y.tickPadding(0);

return {x: x, y: y};
}
Insert cell
md`Now some functions, borrowed from [this notebook](https://observablehq.com/@sjengle/zillow-affordability-heatmap) for generating an SVG and a Plot using d3:`
Insert cell
createSVG = function(id) {
let svg = d3.select(DOM.svg(params.svg.width, params.svg.height));
svg.attr("id", id);
let plot = svg.append("g");
plot.attr("id", "plot");
plot.attr("transform", translate(params.plot.x, params.plot.y));
let rect = plot.append("rect");
rect.attr("id", "background");
rect.attr("x", 0);
rect.attr("y", 0);
rect.attr("width", params.plot.width);
rect.attr("height", params.plot.height);
// this returns an SVG for the notebook cell
return svg.node();
}
Insert cell
createPlot = function(id) {
let node = createSVG(id);
let svg = d3.select(node);

let gx = svg.append("g");
gx.attr("id", "x-axis");
gx.attr("class", "axis");
gx.attr("transform", translate(params.plot.x, params.plot.y + params.plot.height));
gx.call(axis.x);

let gy = svg.append("g");
gy.attr("id", "y-axis");
gy.attr("class", "axis");
gy.attr("transform", translate(params.plot.x, params.plot.y));
gy.call(axis.y);
return node;
}
Insert cell
md`Finally, a function to generate our heatmap:`
Insert cell
createHeatmap = function(id) {
let node = createPlot(id);
let svg = d3.select(node);
let plot = svg.select("g#plot");
// create one group per row
let rows = plot.selectAll("g.cell")
.data(gasMaps)
.enter()
.append("g");
rows.attr("class", "cell");
rows.attr("id", function(d) { return "block-" + d.block; });
// shift the entire group to the appropriate y-location
rows.attr("transform", function(d) {
return translate(0, scale.y(d.block - 1));
});
// create one rect per cell within row group
let cells = rows.selectAll("rect")
.data(function(d) { return d.map; })
.enter()
.append("rect");

cells.attr("x", function(d) { return scale.x(d.gasLeft - d.gas); });
cells.attr("y", 0); // handled by group transform
cells.attr("width", d => scale.x(d.gas));
cells.attr("height", params.plot.height / (latest - oldest));
cells.append("title").text(function(d) { return (d.gasPrice / 1e9) + " gwei"; });
// here is the color magic!
cells.style("fill", function(d) { return scale.color(d.gasPrice); });
cells.style("stroke", function(d) { return scale.color(d.gasPrice); });
return node;
}
Insert cell
createHeatmap('heatmap')
Insert cell
md`# Imports`
Insert cell
d3 = require('d3@5')
Insert cell
ethers = require('ethers')
Insert cell
md`# Helper Functions`
Insert cell
// helper method to make translating easier
translate = function(x, y) {
return "translate(" + x + "," + y + ")";
};
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