ojsBookRefcount = transpose(ojs_bibl_book_refcount)
ojsKjvVerseRefcount = transpose(ojs_kjv_verse_refcount)
ojsKjvChapRefcount = transpose(ojs_kjv_chap_refcount)
ojsBiblRefsTable = transpose(ojs_biblical_references_tt_table)ATB biblical references by book/chapter/verse
Author
Sharon Howard
Published
1 November 2025
Modified
3 November 2025
Notes
Even in Thornton’s favourite Biblical books, references are often clustered; in most books they’re very sparse. These visualisations are intended to highlight both the clusters and the gaps.
Using KJV for reference.
Excludes:
- books with no references
- a handful of Apocrypha references
- BCP
Psalms are handled separately because they don’t fit well in the same frame.
book_chapter = ojsKjvChapRefcount.filter((d)=> d.book !="Psalms")
book_chapter_psalms = ojsKjvChapRefcount.filter((d)=> d.book =="Psalms")
bibl_book_refcount_no_psalms = ojsBookRefcount.filter((d)=> d.book != "Psalms")
kjv_refcount_no_psalms = ojsKjvVerseRefcount.filter((d)=> d.book !="Psalms")
kjv_refcount_psalms = ojsKjvVerseRefcount.filter((d)=> d.book =="Psalms")Books and chapters
- chapters with references are coloured dots; darker=more references (hover over the dots for details)
- chapters with no references are smaller pale grey dots
Plot.plot({
width,
//height,
//subtitle: "References in chapters",
//caption: "Psalms excluded",
marginLeft: 120,
inset: 5,
x: {
labelAnchor: "center",
},
y: {
grid: true,
// to make it keep the original sort order yhgtbfkm
domain: book_chapter.map(d=> d.book)
},
labelArrow: "none",
color: {
legend: true,
scheme: "Oranges"
},
marks: [
//Plot.ruleX([0]),
Plot.frame({stroke: "lightgray"}),
Plot.dot(
book_chapter,
{
x: "chapter",
y: "book",
r: 3,
fillOpacity: 0.5,
fill: "lightgray",
}) ,
Plot.dot(
book_chapter,
{
x: "chapter",
y: "book",
fill: "references",
stroke: "black",
strokeWidth: 0.9,
r: 4,
tip: true,
}) ,
] // marks
})Books with no references at all:
- Ruth
- Joel
- Obadiah
- Haggai
- Philemon
- 2 John
- 3 John
Chapter and verse
Far too much to show all at once, so this is just one book at a time.
- counts in the dropdown are the total for the book (per tagged reference)
- if a reference encompasses a range of verses, each verse in the range is given a dot (This can feel a bit misleading for a very large range but seemed the best approach in general)
- table below the chart shows the text for each reference and links to the page in the DSE
viewof selectMapBookVerse = //view(
Inputs.select(
// array in which book=[0], references=[1], book_title=[2]
bibl_book_refcount_no_psalms.map((d) => [d.book, d.book_refcount, d.book_title]),
{
label: "book",
sort: false,
unique: true,
format: ([name, value, title]) => `${title} (${value})`
}
)
//)kjv_refcountSelection =
kjv_refcount_no_psalms.filter((d) => d.book == selectMapBookVerse[0]);
plotTitle = selectMapBookVerse[2];
biblical_references_tt_table_selection = ojsBiblRefsTable.filter((d)=> d.book == selectMapBookVerse[0]);
book_chapter_notpsalms = book_chapter.filter((d) => d.book == selectMapBookVerse[0]);Plot.plot({
width,
//height,
//subtitle: "References per Psalm",
marginLeft: 80,
inset: 2,
x: {
labelAnchor: "center",
// https://github.com/observablehq/plot/issues/932
tickFormat: d => d > Math.floor(d) ? "" : `${d}` // hides .x numbers though not the ticks. that'll do for now.
},
y: {
//grid: true,
label: null,
},
labelArrow: "none",
color: {
legend: true,
scheme: "Oranges",
label: "references per chapter"
},
marks: [
//Plot.ruleX([0]),
Plot.frame({stroke: "lightgray"}),
Plot.dot(
book_chapter_notpsalms,
{
x: "chapter",
y: "book",
r: 2,
fillOpacity: 0.3,
fill: "lightgray",
}) ,
// ticks are more compact and look ok for this.
Plot.tickX(
book_chapter_notpsalms,
{
x: "chapter",
y: "book" ,
strokeWidth: 2,
stroke: "references",
tip: true,
// is this the only way to relabel book as psalm?
/*channels: {
"psalm":"chapter",
} , */
// tooltips
tip: {
format: {
y: false,
//"year of birth": (d) => `${d}`,
//x: false // now need to exclude this explicitly
},
//anchor:"bottom" // tips above the line
}
}), // /tick
] // marks
})Plot.plot({
width,
//height,
//subtitle: plotTitle,
y: {
//reverse: true, // seems this isn't needed with type=band
labelAnchor: "center",
type: "band",
//padding: 0.1, // (0 to 1) seems to make no difference?
},
// don't want it to handle numbers like frequencies
x: {labelAnchor: "center"},
labelArrow: "none",
//inset: 5, //not needed iwth type=band
color: {
legend: true,
scheme: "Reds",
label: "references per verse"
},
marks: [
Plot.frame({stroke: "lightgray"}),
// i think horizontal guideline looks better than grid.
Plot.ruleY(kjv_refcountSelection, {
x1:0,
x2: "verse",
y: "chapter",
stroke: "lightgray" ,
strokeWidth: 0.5,
strokeOpacity: 0.1,
}),
Plot.dot(
kjv_refcountSelection,
{
y: "chapter",
x: "verse",
r: 3,
fillOpacity: 0.5,
fill: "lightgray",
}) ,
Plot.dot(
kjv_refcountSelection,
{
y: "chapter",
x: "verse",
fill: "references",
stroke: "black",
strokeWidth: 0.9,
r: 3,
tip: true,
channels: {
"book":"book",
"chapter": "chapter"
}
}) ,
] // marks
})Inputs.table(biblical_references_tt_table_selection,
{
layout: "auto",
format: {
//page_url: page_url => htl.html`<a href=${page_url} target=_blank>link</a>`
// https://observablehq.com/d/1562bdd9c67de3e7
src: (d, i, data) => htl.html`<a href=${data[i].page_url} target=_blank>${d}</a>`
},
columns: [
"book_title",
"c_v",
"text_cut",
"type",
"vol",
"src",
],
header: {
"book_title": "book",
"c_v": "chapter",
"text_cut": "text",
"type": "type",
"vol": "bible",
"src": "ATB",
}
}
)(“NS” = bible version not specified)
Psalms
Plot.plot({
width,
//height,
//subtitle: "References per Psalm",
marginLeft: 50,
inset: 2,
x: {
labelAnchor: "center",
},
y: {
//grid: true,
label: null,
},
labelArrow: "none",
color: {
legend: true,
scheme: "Oranges",
label: "references per psalm"
},
marks: [
//Plot.ruleX([0]),
Plot.frame({stroke: "lightgray"}),
Plot.dot(
book_chapter_psalms,
{
x: "chapter",
y: "book",
r: 2,
fillOpacity: 0.3,
fill: "lightgray",
}) ,
// ticks are more compact and look ok for this.
Plot.tickX(
book_chapter_psalms,
{
x: "chapter",
y: "book" ,
strokeWidth: 2,
stroke: "references",
tip: true,
// is this the only way to relabel book as psalm?
channels: {
"psalm":"chapter",
} ,
// tooltips
tip: {
format: {
y: false,
//"year of birth": (d) => `${d}`,
x: false // now need to exclude this explicitly
},
//anchor:"bottom" // tips above the line
}
}), // /tick
] // marks
})This one is still tricky, because of one psalm with 170+ verses (!); if everything is visualised the rest are squished. I’ve added the slider so that initially only verses up to number 60 are displayed (in fact this shows everything significant except the outliers), and you can drag it to see the rest.
//use fixed min and mid point; don't really need to do this either
topVerse = d3.max(kjv_refcount_psalms.map(d => d.verse));// make slider view
// customise colour https://talk.observablehq.com/t/customize-inputs/8226/2
viewof rangeVerses = {
const form =
Inputs.range(
[1, topVerse], {
label: "limit",
step: 1,
value: 60 // default
}
);
form.range.style.cssText += "accent-color: #b20000";
return form;
}// make new filtered data array for the table
psalms_filtered =
kjv_refcount_psalms
.filter((d) => d.verse <= rangeVerses)
.map((d) => ({...d}) )Plot.plot({
width,
height: 1500,
//subtitle: "References per verse",
y: {//reverse: true, // seems it's not needed with type=band
label: "psalm",
labelAnchor: "center",
type: "band",
padding: 0.1, // (0 to 1) seems to make no difference
},
x: {labelAnchor: "center"},
labelArrow: "none",
color: {
legend: true,
scheme: "Reds",
label: "references per verse"
},
//inset: 5,
marks: [
Plot.frame({stroke: "lightgray"}),
// horizontal guideline.
Plot.ruleY(psalms_filtered, {
x1:0,
x2: "verse",
y: "chapter",
stroke: "lightgray" ,
strokeWidth: 0.5,
strokeOpacity: 0.1,
}),
Plot.dot(
psalms_filtered,
{
y: "chapter",
x: "verse",
r: 2,
fillOpacity: 0.5,
fill: "lightgray",
}) ,
// tried ticks, nah.
Plot.dot(
psalms_filtered,
{
y: "chapter",
x: "verse",
fill: "references",
stroke: "black",
strokeWidth: 0.9,
r: 2.5,
tip: true,
channels: {
//"psalm":"book",
"psalm": "chapter"
} ,
// tooltips
tip: {
format: {
y: false,
},
//anchor:"bottom" // tips above the line
}
}) ,
] // marks
})Inputs.table(
ojsBiblRefsTable.filter((d)=>d.book=="Psalms"),
{
layout: "auto",
format: {
//page_url: page_url => htl.html`<a href=${page_url} target=_blank>link</a>`
// https://observablehq.com/d/1562bdd9c67de3e7
src: (d, i, data) => htl.html`<a href=${data[i].page_url} target=_blank>${d}</a>`
},
columns: [
"book_title",
"c_v",
"text_cut",
"type",
"vol",
"src",
],
header: {
"book_title": "book",
"c_v": "psalm",
"text_cut": "text",
"type": "type",
"vol": "bible",
"src": "ATB",
}
}
)