Published
Edited
Apr 4, 2022
3 forks
13 stars
Insert cell
Insert cell
Insert cell
Insert cell
Plot.plot({
color: {
domain: [1, -1],
range: ["white", "black"]
},
x: {
label: "time →",
ticks: 10,
tickFormat: (rank) =>
data[rank] ? d3.timeFormat("%d %b")(data[rank].x1) : ""
},
y: {
grid: true
},
marks: [
Plot.ruleY([d3.min(data, (d) => d.v) - 2 * brick_size]),
Plot.ruleX(months, { strokeWidth: 0.06, x: position }),
Plot.ruleX(years, { strokeWidth: 0.5, x: position }),
Plot.textX(years, {
strokeWidth: 0.5,
x: position,
text: (d) => `${d.getFullYear()}`,
textAnchor: "start",
dx: 3,
dy: -155
}),
Plot.rect(data, {
x1: (d) => d.rank,
x2: (d) => d.rank + 1,
y1: "floor",
y2: "ceil",
fill: "direction",
stroke: "black"
}),
showDots
? Plot.dot(aapl, {
x: (d) => position(d.Date),
y: "Close",
fill: "red",
r: 1
})
: []
],
width,
height: 350
})
Insert cell
Insert cell
Plot.plot({
color: {
domain: [1, -1],
range: ["white", "black"]
},
x: {
label: "time →",
ticks: []
},
y: {
grid: true
},
marks: [
Plot.ruleY([d3.min(data, (d) => d.v) - 2 * brick_size]),
Plot.ruleX(months, { strokeWidth: 0.06, x: (d) => d }),
Plot.ruleX(years, { strokeWidth: 0.5, x: (d) => d }),
Plot.textX(years, {
strokeWidth: 0.5,
x: (d) => d,
text: (d) => `${d.getFullYear()}`,
textAnchor: "start",
dx: 3,
dy: -155
}),
Plot.rect(data, {
x1: "x1",
x2: "x2",
y1: "floor",
y2: "ceil",
fill: "direction",
stroke: "black"
}),
showDots
? Plot.dot(aapl, {
x: (d) => d.Date,
y: "Close",
fill: "red",
r: 1
})
: []
],
width,
height: 350
})
Insert cell
data = renko(aapl, { x: "Date", y: "Close", brick_size })
Insert cell
function renko(data, { x = "date", y = "value", brick_size = 10.0 } = {}) {
const dates = Plot.valueof(data, x);
const values = Plot.valueof(data, y);
const B = brick_size;
const bricks = [];
let i = 0;
let rank = 0;
const v = values[i];
let brick = {
rank,
start: i,
x1: dates[i],
v1: v,
v: (0.5 + Math.floor(v / B)) * B,
floor: Math.floor(v / B) * B,
ceil: (1 + Math.floor(v / B)) * B
};
let j = i;
for (; i < values.length; ++i) {
const v = values[i];
if (Math.abs(v - brick.v) < 0.5 * B) {
j = i;
}
while (Math.abs(v - brick.v) >= 1.5 * B) {
brick.v2 = values[j];
brick.x2 = dates[j];
brick.end = j;
const direction = Math.sign(v - brick.v);
if (rank === 0) brick.direction = direction;
bricks.push(brick);

brick = {
rank: ++rank,
direction,
start: j,
x1: dates[j],
v1: values[j],
v: brick.v + direction * B,
floor: brick.floor + direction * B,
ceil: brick.ceil + direction * B
};
j = i;
}
}
brick.end = i - 1;
brick.x2 = dates[i - 1];
brick.v2 = values[i - 1];
bricks.push(brick);

return bricks;
}
Insert cell
Insert cell
summary = {
const dformat = d3.utcFormat("%B, %Y");
const mformat = d3.format("$.3");
const summary = [];
let period;
for (const d of data) {
if (!period) {
period = [d];
} else {
if (d.direction !== period[0].direction) {
summary.push(period);
period = [d];
} else {
period.push(d);
}
}
}
summary.push(period);

// we could also compute statistics such as median, quantiles, deviation…
return md`${summary.map(
(period) => `
* Between ${dformat(period[0].x1)}
and ${dformat(period[period.length - 1].x2)}
the index ${period[0].direction > 0 ? "went up" : "went down"} ${
period.length
} pips,
from ${mformat(period[0].v1)} to ${mformat(period[period.length - 1].v2)}.`
)}`;
}
Insert cell
Insert cell
position = d3.scaleLinear(
data.map((d) => +d.x1).concat([+data[data.length - 1].x2]),
d3.range(data.length + 1)
)
Insert cell
data.map((d) => +d.x1).concat([+data[data.length - 1].x2])
Insert cell
d3.range(data.length + 1)
Insert cell
months = d3.utcMonths(...d3.extent(data, (d) => d.x1))
Insert cell
years = d3.utcYears(...d3.extent(data, (d) => d.x1))
Insert cell
aapl = FileAttachment("aapl.csv").csv({ typed: true })
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