Here's a plot for catalog views that shows the relative age of threads, growing to the right, the relative reply counts, growing to the top, and the relative bump freshness, active in red, dormant in blue. The sample images are for /tech/, /leftypol/ and /b/.
(tools => ((install, sitespec) => {
const site = sitespec.find (spec => spec.pagetest ())
if (!site) { return; }
const config = site.getconfig ()
if (!config.ok) { return; }
install (config)
if (!config.ok) { return; }
site.getdata (config)
if (!config.ok) {
config.holder.remove ()
return
}
site.draw (config)
}) (
config => {
// > width height insertmode inserttarget
// < holder canvas context
const modes = {
after: (holder, target) => { target.parentNode.insertBefore (holder, target.nextSibling); },
before: (holder, target) => { target.parentNode.insertBefore (holder, target); },
first: (holder, target) => { target.insertBefore (holder, target.firstChild); },
last: (holder, target) => { target.appendChild (holder); },
}
const holder = document.createElement ("div")
holder.innerHTML = '<div style="text-align: center;"><canvas id="canvasid" width="' + config.width + '" height="' + config.height + '" style="border: 1px solid; z-index: 100; position: relative;">canvas</canvas></div>'
modes [config.insertmode] (holder, config.inserttarget)
const canvas = document.getElementById ('canvasid')
if (canvas.getContext) {
config.holder = holder
config.canvas = canvas
config.context = canvas.getContext ('2d')
} else {
holder.remove ()
config.ok = false
}
}, [{
pagetest: () => /^https?:\/\/(leftypol\.org|leftychan\.net)\//.test (document.location.href) && (document.getElementById ('uniqueip') != null) && (document.querySelectorAll ('div.post.op').length == 1),
getconfig: () => {
const target = document.getElementById ('thread-interactions')
if (target == null) { return { ok: false }; }
const count = 60, step = 10
return {
ok: true,
width: count * step,
height: 100,
count: count,
step: step,
back: 'rgba( 0, 0, 60, 1)',
bars: 'rgba( 0, 0, 255, 1)',
grid: 'rgba(128, 128, 128, 1)',
insertmode: 'before',
inserttarget: target,
}
},
getdata: config => {
const dates = Array.from (document.querySelectorAll ('p.intro time')).map (e => e.innerText.match (/^\d{4}-\d{2}-\d{2}/)).filter (m => m != null).map (m => m [0])
tools.getdatadatecount (config, dates)
},
draw: tools.drawrelative
}, {
pagetest: () => /^https?:\/\/(leftypol\.org|leftychan\.net)\//.test (document.location.href) && (document.getElementById ('Grid') != null),
getconfig: () => {
const target = document.getElementById ('Grid')
if (target == null) { return { ok: false }; }
return {
ok: true,
width: 400,
height: 400,
radius: 8,
insertmode: 'after',
inserttarget: target,
}
},
getdata: config => {
const now = Date.now () / 1000
const data = Array.from (document.querySelectorAll ('div.mix[data-bump][data-reply][data-time]')).map (e => ["data-bump", "data-reply", "data-time"].map (s => parseInt (e.getAttribute (s), 10))).map (([b, r, t]) => [now - b, r, now - t])
config.data = data
},
draw: config => {
const ctx = config.context
const w = config.width
const h = config.height
const radius = config.radius
const data = config.data
const [maxb, maxr, maxt] = Array.from ({length: 3}, (x, k) => data.reduce ((a, b) => Math.max (a, b [k]), 0) || 1)
data.reverse ()
for (const [b, r, t] of data) {
const x = t * w / maxt
const y = h - r * h / maxr
const c = Math.floor (b * 255 / maxb)
const f = 'rgba(' + (255 - c) + ', 0, ' + c + ', 1)'
ctx.fillStyle = f
ctx.beginPath ()
ctx.arc (x, y, radius, 0, 2 * Math.PI, true)
ctx.fill ()
}
}
}]
)) ({
drawrelative: config => {
const ctx = config.context
const data = config.data
const max = data.reduce ((a, b) => Math.max (a, b), 0) || 1
const w = config.width
const h = config.height
const count = config.count
const step = config.step
ctx.fillStyle = config.back
ctx.fillRect (0, 0, w, h)
ctx.strokeStyle = config.grid
for (let k = 0; k < count; k++) {
ctx.beginPath ()
ctx.moveTo (step * k, 0)
ctx.lineTo (step * k, h)
ctx.stroke ()
}
ctx.fillStyle = config.bars
for (let k = 0; k < count; k++) {
const y = data [k] * h / max
ctx.fillRect (w - step - k * step, h - y, step, y)
}
},
getdatadatecount: (config, datestrings) => {
// YYYY-MM-DD
const all = datestrings.reduce ((acc, key) => {
if (key in acc) {
acc [key] += 1
} else {
acc [key] = 1
}
return acc
}, {})
const now = new Date ()
const key = k => {
const when = new Date (now)
when.setUTCDate (now.getUTCDate () - k)
return when.getUTCFullYear ().toString ().padStart (4, '0') + '-' + (when.getUTCMonth () + 1).toString ().padStart (2, '0') + '-' + when.getUTCDate ().toString ().padStart (2, '0')
}
const ret = Array.from ({length: config.count}, (x, k) => all [key (k)] ?? 0)
config.data = ret
}
})
Just a demo.