The demo by @glv is good and also highlights the problem of resolution that I raised earlier and was dismissed as unimportant. In your data you have 12 generations and if all ancestors were present that is 4096 ancestors at generation 12. So using 1 pixel for the black dot and 1 white pixel to visually separate them then the image must be at least 8192 pixels wide.
This code displays the data you provided one dot per ancestor but fails at higher generations as the dots merge…
let ancestors = [1, 2, 3, 4, 5, 8, 9, 16, 17, 34, 35, 70, 71, 140, 141, 280, 281, 560, 561, 1120, 1121, 2240, 2241];
function setup() {
console.clear(); console.log('GLOBAL mode');
let p5canvas = createCanvas(1360, 300);
chart = createAncestorImage(1280, 200, 20, 10, ancestors);
}
function draw() {
background(128);
image(chart, 0, 0);
}
function createAncestorImage(chartWidth, chartHeight, border, ygap, ans) {
function level(an) { return 1 + Math.floor(Math.log2(an)); }
function levelSize(lvl) { return 2 ** (lvl - 1); }
function levelMidRange(lvl) { return (2 ** (lvl - 1) + 2 ** lvl - 1) / 2 };
let pg = createGraphics(chartWidth + 2 * border, chartHeight + 2 * border);
pg.background(255);
pg.fill(0);
ans.forEach(a => {
let lvl = level(a), lvlSize = levelSize(lvl);
let lvlMidRange = levelMidRange(lvl);
let xgap = chartWidth / (lvlSize), dy = border + lvl * ygap;
let px = border + (chartWidth / 2) + (a - lvlMidRange) * xgap;
let py = border + lvl * ygap;
pg.ellipse(px, py, 1, 1);
});
return pg;
}