Public
Edited
Feb 3, 2023
1 fork
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function candlestickPlugin({ gap = 2, shadowColor = "#000000", bearishColor = "#e54245", bullishColor = "#4ab650", bodyMaxWidth = 20, shadowWidth = 2, bodyOutline = 1 } = {}) {

function drawCandles(u) {
u.ctx.save();

const offset = (shadowWidth % 2) / 2;

u.ctx.translate(offset, offset);

let [iMin, iMax] = u.series[0].idxs;

for (let i = iMin; i <= iMax; i++) {
let xVal = u.scales.x.distr == 2 ? i : u.data[0][i];
let open = u.data[1][i];
let high = u.data[2][i];
let low = u.data[3][i];
let close = u.data[4][i];
let longMap = u.data[5][i];
let shortMap = u.data[6][i];

let timeAsX = u.valToPos(xVal, "x", true);
let lowAsY = u.valToPos(low, "y", true);
let highAsY = u.valToPos(high, "y", true);
let openAsY = u.valToPos(open, "y", true);
let closeAsY = u.valToPos(close, "y", true);

Object.entries(longMap).forEach(([k,v]) => {
const priceAsY = u.valToPos(k, "y", true);
u.ctx.fillStyle = bullishColor;
u.ctx.textAlign = "left"
if(shortMap[parseFloat(k)-.25] && shortMap[parseFloat(k)-.25] * 3 < v){
u.ctx.fillStyle = "blue";
u.ctx.fillText(v, timeAsX, priceAsY);

}
});

Object.entries(shortMap).forEach(([k,v]) => {
const priceAsY = u.valToPos(k, "y", true);
u.ctx.fillStyle = bearishColor;
u.ctx.textAlign = "right"
if(longMap[parseFloat(k)+.25] && longMap[parseFloat(k)+.25] * 3 < v){
u.ctx.fillStyle = "orange";
u.ctx.fillText(v, timeAsX, priceAsY);

}
});

// shadow rect
let shadowHeight = Math.max(highAsY, lowAsY) - Math.min(highAsY, lowAsY);
let shadowX = timeAsX - (shadowWidth / 2);
let shadowY = Math.min(highAsY, lowAsY);

u.ctx.fillStyle = shadowColor;
u.ctx.fillRect(
Math.round(shadowX),
Math.round(shadowY),
Math.round(shadowWidth),
Math.round(shadowHeight),
);


// body rect
// let columnWidth = u.bbox.width / (iMax - iMin);
// let bodyWidth = Math.min(bodyMaxWidth, columnWidth - gap);
// let bodyHeight = Math.max(closeAsY, openAsY) - Math.min(closeAsY, openAsY);
// let bodyX = timeAsX - (bodyWidth / 2);
// let bodyY = Math.min(closeAsY, openAsY);
// let bodyColor = open > close ? bearishColor : bullishColor;
// u.ctx.fillStyle = shadowColor;
// u.ctx.fillRect(
// Math.round(bodyX),
// Math.round(bodyY),
// Math.round(bodyWidth),
// Math.round(bodyHeight),
// );
// u.ctx.fillStyle = bodyColor;
// u.ctx.fillRect(
// Math.round(bodyX + bodyOutline),
// Math.round(bodyY + bodyOutline),
// Math.round(bodyWidth - bodyOutline * 2),
// Math.round(bodyHeight - bodyOutline * 2),
// );
}

u.ctx.translate(-offset, -offset);

u.ctx.restore();
}

return {
opts: (u, opts) => {
uPlot.assign(opts, {
cursor: {
points: {
show: false,
}
}
});

opts.series.forEach(series => {
series.paths = () => null;
series.points = {show: false};
});
},
hooks: {
draw: drawCandles,
}
};
}
Insert cell
{ // uPlot chart setup
const div = document.createElement('div');
function fmtUSD(val, dec) {
return "$" + val.toFixed(dec).replace(/\d(?=(\d{3})+(?:\.|$))/g, "$&,");
}

function randInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
const fmtDate = uPlot.fmtDate("{YYYY}-{MM}-{DD}");
const tzDate = ts => uPlot.tzDate(new Date(ts * 1e3), "Etc/UTC");
let fmtVal = (u, raw, sidx, didx) => {
if (didx == null) {
let d = u.data[sidx];
return `${d[d.length - 1]} (last)`;
}

return raw == null ? '' : raw;
};

const opts = {
width: 1170,
height: 800,
title: "Area Fill",
scales: {
x: {
distr: 2,
}
},
plugins: [
candlestickPlugin()
],
axes: [
{},
{
values: (u, vals) => vals.map(v => fmtUSD(v, 0)),
}
],
series: [
{
label: "Date",
value: (u, ts, sidx, didx) => didx == null ? null : fmtDate(tzDate(ts)),
},
{
label: "Open",
value: (u, v, sidx, didx) => didx == null ? null : fmtUSD(v, 2),
},
{
label: "High",
value: (u, v, sidx, didx) => didx == null ? null : fmtUSD(v, 2),
},
{
label: "Low",
value: (u, v, sidx, didx) => didx == null ? null : fmtUSD(v, 2),
},
{
label: "Close",
value: (u, v, sidx, didx) => didx == null ? null : fmtUSD(v, 2),
}
],
};

const plot = new uPlot(opts, pivotedData, div);
return div;
}
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