Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions assets/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -873,13 +873,13 @@ function bmRender() {
const total = bmFiltered.length;
if (total === 0) {
list.innerHTML = '<div class="bm-empty">No matching reference pairs.</div>';
meta.textContent = '0 reference pairs'; pager.style.display = 'none'; return;
meta.textContent = BROWSE_KIND_LABELS[browseKind] + ': 0 reference pairs'; pager.style.display = 'none'; return;
}
const totalPages = Math.max(1, Math.ceil(total / BM_PAGE_SIZE));
if (bmPage >= totalPages) bmPage = totalPages - 1;
const start = bmPage * BM_PAGE_SIZE; const end = Math.min(total, start + BM_PAGE_SIZE);
list.innerHTML = bmFiltered.slice(start, end).map(bmCardHtml).join('');
meta.innerHTML = `Showing <strong>${(start + 1).toLocaleString('en-GB')}–${end.toLocaleString('en-GB')}</strong> of <strong>${total.toLocaleString()}</strong> reference ${total === 1 ? "pair" : "pairs"}`;
meta.innerHTML = `<span class="visually-hidden">${BROWSE_KIND_LABELS[browseKind]}: </span>Showing <strong>${(start + 1).toLocaleString('en-GB')}–${end.toLocaleString('en-GB')}</strong> of <strong>${total.toLocaleString()}</strong> reference ${total === 1 ? "pair" : "pairs"}`;
info.textContent = `Page ${bmPage + 1} of ${totalPages}`;
prev.disabled = bmPage === 0; next.disabled = bmPage >= totalPages - 1;
pager.style.display = totalPages > 1 ? 'flex' : 'none';
Expand All @@ -892,8 +892,14 @@ function setupBrowseMobile(data) {
const next = document.getElementById('bm-next');
let debounceTimer;
input.addEventListener('input', () => { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => bmApplySearch(input.value), 200); });
prev.addEventListener('click', () => { if (bmPage > 0) { bmPage--; bmRender(); syncBrowseUrl(); document.querySelector('.app-main').scrollTo({ top: 0, behavior: 'smooth' }); } });
next.addEventListener('click', () => { bmPage++; bmRender(); syncBrowseUrl(); document.querySelector('.app-main').scrollTo({ top: 0, behavior: 'smooth' }); });
function showPage(delta) {
bmPage += delta; bmRender(); syncBrowseUrl();
const heading = document.getElementById('browse-results-heading');
heading.focus({preventScroll:true});
heading.scrollIntoView({block:'start', behavior:matchMedia('(prefers-reduced-motion: reduce)').matches ? 'instant' : 'smooth'});
}
prev.addEventListener('click', () => { if (bmPage > 0) showPage(-1); });
next.addEventListener('click', () => { if (!next.disabled) showPage(1); });
input.value = browseQuery; bmApplySearch(browseQuery);
}

Expand Down Expand Up @@ -961,7 +967,11 @@ function syncBrowseUrl() {
}

// ===== Browse kind filter =====
let browseKind = new URLSearchParams(location.search).get('kind') || 'all';
const BROWSE_KIND_LABELS = {all: 'All studies', replication: 'Replications', reproduction: 'Reproductions', 'reproduction-numerical': 'Numerical Reproductions', 'reproduction-robustness': 'Robustness Reproductions'};
function normalizeBrowseKind(kind) {
return Object.hasOwn(BROWSE_KIND_LABELS, kind) ? kind : 'all';
}
let browseKind = normalizeBrowseKind(new URLSearchParams(location.search).get('kind'));
let browseQuery = new URLSearchParams(location.search).get('q') || '';
let browseOutcomeChart = null;
let browseComputationalChart = null;
Expand Down Expand Up @@ -1002,7 +1012,7 @@ function renderBrowseOutcomeCharts() {

// Zero matches get a visible explanation and recovery actions instead of an empty table and
// chart. FLoRA is incomplete, so absence here is not evidence that no repetition exists.
const BROWSE_KIND_NOUNS = {all: 'reference pairs', replication: 'replications', 'reproduction-numerical': 'numerical reproductions', 'reproduction-robustness': 'robustness reproductions'};
const BROWSE_KIND_NOUNS = {all: 'reference pairs', replication: 'replications', reproduction: 'reproductions', 'reproduction-numerical': 'numerical reproductions', 'reproduction-robustness': 'robustness reproductions'};
function renderBrowseEmpty(n) {
const el = document.getElementById('browse-empty'); if (!el) return;
el.hidden = n > 0;
Expand Down Expand Up @@ -1056,7 +1066,8 @@ function trendsFilteredData() { return filterByKind(fullRowData, trendsKind); }
function updateTrendsCount() {
const el = document.getElementById('trend-filter-count'); if (!el) return;
const n = trendsFilteredData().length; const total = fullRowData.length;
el.textContent = trendsKind === 'all' ? `${n.toLocaleString()} reference ${n === 1 ? "pair" : "pairs"}` : `${n.toLocaleString()} of ${total.toLocaleString()} reference ${total === 1 ? "pair" : "pairs"}`;
const count = trendsKind === 'all' ? `${n.toLocaleString()} reference ${n === 1 ? "pair" : "pairs"}` : `${n.toLocaleString()} of ${total.toLocaleString()} reference ${total === 1 ? "pair" : "pairs"}`;
el.innerHTML = `<span class="visually-hidden">${BROWSE_KIND_LABELS[trendsKind]}: </span>${count}`;
}

// Plain per-category counts (year/journal/field) - no outcome breakdown. A prior version
Expand Down Expand Up @@ -1346,7 +1357,7 @@ function renderMcCharts() {
const statCards = buckets.filter(b => ov[b.overviewKey] > 0).map(b => {
const n = ov[b.overviewKey] || 0;
const pct = FloraCharts.percent(n, ov.n_total);
return `<div class="mc-stat"><span class="mc-stat-value" style="color:${b.color}">${n.toLocaleString()}</span><span class="mc-stat-label">${b.label} (${escapeHtml(pct)}%)</span></div>`;
return `<div class="mc-stat"><span class="mc-stat-value">${n.toLocaleString()}</span><span class="mc-stat-label">${b.label} (${escapeHtml(pct)}%)</span></div>`;
}).join('');
document.getElementById('mc-overview').innerHTML = `
<div class="mc-stat"><span class="mc-stat-value">${ov.n_total.toLocaleString()}</span><span class="mc-stat-label">Reference pairs with OMC</span></div>
Expand Down
9 changes: 6 additions & 3 deletions assets/chart-accessibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ window.FloraCharts = (() => {
|| el.closest('.trend-block')?.querySelector('.trend-desc h3')
|| el.closest('.mc-chart-card, .plot-card')?.querySelector('.mc-chart-title, h3')
|| el.closest('.modal-body')?.querySelector('h3');
const prefix = el.id.startsWith('browse-') ? 'Browse results: ' : '';
const prefix = el.id.startsWith('browse-') ? 'Browse results: ' : /^plot-(cit|cocit)/.test(el.id) ? (el.id.endsWith('-model') ? 'Adjusted association: ' : 'Raw trajectory: ') : '';
return heading ? prefix + plain(heading.textContent) : el.id.replace(/-/g, ' ');
}
// Removes a chart together with everything that describes or exports it, so an
Expand All @@ -84,7 +84,7 @@ window.FloraCharts = (() => {
anchor.insertAdjacentElement('afterend', details);
}
const wasOpen = details.open;
details.innerHTML = `<summary>View chart data</summary><p class="chart-summary">${escape(summary)}</p><p class="chart-context">${escape(context(el))}</p><div class="table-scroll" tabindex="0" role="region" aria-label="Chart values"><table><caption>${escape(visibleTitle(el))}</caption><thead><tr>${headers.map(h=>`<th scope="col">${escape(h)}</th>`).join('')}</tr></thead><tbody>${rows.map(row=>`<tr>${row.map((v,i)=>i ? `<td>${escape(fmt(v, headers[i]))}</td>` : `<th scope="row">${escape(fmt(v, headers[i]))}</th>`).join('')}</tr>`).join('')}</tbody></table></div>`;
details.innerHTML = `<summary aria-label="View chart data for ${escape(visibleTitle(el))}">View chart data</summary><p class="chart-summary">${escape(summary)}</p><p class="chart-context">${escape(context(el))}</p><div class="table-scroll" tabindex="0" role="region" aria-label="${escape(visibleTitle(el))}: chart values"><table><caption>${escape(visibleTitle(el))}</caption><thead><tr>${headers.map(h=>`<th scope="col">${escape(h)}</th>`).join('')}</tr></thead><tbody>${rows.map(row=>`<tr>${row.map((v,i)=>i ? `<td>${escape(fmt(v, headers[i]))}</td>` : `<th scope="row">${escape(fmt(v, headers[i]))}</th>`).join('')}</tr>`).join('')}</tbody></table></div>`;
details.open = wasOpen;
const label = el.closest('.outcome-chart-row')?.querySelector('.outcome-chart-label') || el.closest('.trend-block')?.querySelector('.trend-title');
let action = document.getElementById(id + '-copy');
Expand All @@ -108,7 +108,7 @@ window.FloraCharts = (() => {
try {
await navigator.clipboard.writeText(csvText(headers, rows, summary + '\n' + context(el)));
action.title = 'Chart CSV copied';
action.setAttribute('aria-label', 'Chart CSV copied');
action.setAttribute('aria-label', 'Chart CSV copied for ' + visibleTitle(el));
status.textContent = 'Chart CSV copied';
setTimeout(() => { action.title = 'Copy chart CSV'; action.setAttribute('aria-label', 'Copy chart CSV for ' + visibleTitle(el)); }, 2000);
} catch {
Expand All @@ -130,6 +130,8 @@ window.FloraCharts = (() => {
table(el,rows,['Category',...datasets.map(d=>d.label)],sample);
if (config.options.scales) for (const axis of Object.values(config.options.scales)) if (axis.ticks) axis.ticks.autoSkip = !config.options.indexAxis || config.options.indexAxis !== 'y';
if (config.options.plugins?.legend) config.options.plugins.legend.reverse = false;
Chart.defaults.font.family = theme().fontFamily;
if (matchMedia('(prefers-reduced-motion: reduce)').matches) config.options.animation = false;
return new Chart(ctx, config);
}
// options.table = {headers, rows, summary} replaces the generic per-trace table, for
Expand Down Expand Up @@ -179,6 +181,7 @@ window.FloraCharts = (() => {
toggle.onclick=()=>{modes.set(id,modes.get(id)==='count'?'percent':'count');const args=originals.get(id);plot(el,...args);};
}
toggle.textContent=counts?'Show percentages':'Show counts';
toggle.setAttribute('aria-label', toggle.textContent + ' for ' + visibleTitle(el));
}
if (window.innerWidth < 600) {
layout.margin={...layout.margin,l:60,r:layout.yaxis2 ? 65 : 20,b:100};
Expand Down
28 changes: 18 additions & 10 deletions assets/citation-impact.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@
</tr>`;
}).join('');
return `<div class="cocit-breakdown-table" tabindex="0" role="region" aria-label="${escapeHtml(title)}">
<h4>${title}</h4>
<h3>${title}</h3>
<table>
<thead><tr><th>Group</th><th>N originals</th><th>Per-paper mean</th><th>Median</th><th>Weighted mean</th></tr></thead>
<tbody>${rows}</tbody>
Expand Down Expand Up @@ -398,6 +398,11 @@
}

function renderTable() {
const focused = document.activeElement;
const pageAction = focused.closest('#pagination button')?.dataset.action;
const pageNumber = focused.closest('#pagination button')?.dataset.p;
const timelineRow = focused.closest('#originals-table tbody tr');
const rowIndex = timelineRow ? [...timelineRow.parentElement.children].indexOf(timelineRow) : -1;
const q = document.getElementById('search-input').value || '';
const tokens = searchTokens(q);
const outFilter = document.getElementById('filter-outcome').value;
Expand All @@ -420,7 +425,13 @@
th.classList.remove('sort-asc', 'sort-desc');
if (th.dataset.sort === col) th.classList.add(dir === 'asc' ? 'sort-asc' : 'sort-desc');
});
document.querySelectorAll('#originals-table th').forEach(th => {
const active = th.querySelector(`[data-sort="${col}"]`);
if (active) th.setAttribute('aria-sort', dir === 'asc' ? 'ascending' : 'descending');
else th.removeAttribute('aria-sort');
});
document.querySelectorAll('.cocit-sort-btn').forEach(btn => {
btn.setAttribute('aria-pressed', String(btn.dataset.sort === col));
btn.classList.remove('sort-asc', 'sort-desc');
if (btn.dataset.sort === col) btn.classList.add(dir === 'asc' ? 'sort-asc' : 'sort-desc');
});
Expand Down Expand Up @@ -457,10 +468,12 @@
const winStart = Math.max(1, CI.page - Math.floor(winSize / 2));
const winEnd = Math.min(pages, winStart + winSize - 1);
let pHtml = '';
if (CI.page > 1) pHtml += `<button data-p="${CI.page - 1}">‹</button>`;
for (let i = winStart; i <= winEnd; i++) pHtml += `<button data-p="${i}" class="${i === CI.page ? 'active' : ''}">${i}</button>`;
if (CI.page < pages) pHtml += `<button data-p="${CI.page + 1}">›</button>`;
if (CI.page > 1) pHtml += `<button type="button" data-action="previous" data-p="${CI.page - 1}" aria-label="Previous page">‹</button>`;
Comment thread
LukasWallrich marked this conversation as resolved.
for (let i = winStart; i <= winEnd; i++) pHtml += `<button type="button" data-p="${i}" ${i === CI.page ? 'aria-current="page"' : ''} class="${i === CI.page ? 'active' : ''}" aria-label="Page ${i}">${i}</button>`;
if (CI.page < pages) pHtml += `<button type="button" data-action="next" data-p="${CI.page + 1}" aria-label="Next page">›</button>`;
pag.innerHTML = pHtml;
if (pageNumber) (pag.querySelector(pageAction ? `[data-action="${pageAction}"]` : `[data-p="${pageNumber}"]`) || pag.querySelector('[aria-current]'))?.focus({preventScroll:true});
else if (rowIndex >= 0) (tbody.rows[Math.min(rowIndex, tbody.rows.length - 1)]?.querySelector('.timeline-open') || document.getElementById('originals-table')).focus();
}

// Fetch one original's full record (citation timeline and replication list),
Expand Down Expand Up @@ -656,12 +669,7 @@
CI.page = 1;
renderTable();
}
document.querySelectorAll('#originals-table thead th[data-sort]').forEach(th => {
th.tabIndex = 0; th.setAttribute('role','button');
th.addEventListener('click', () => sortBy(th.dataset.sort));
th.addEventListener('keydown', e => { if(e.key === 'Enter' || e.key === ' ') {e.preventDefault();sortBy(th.dataset.sort);} });
});
document.querySelectorAll('.cocit-sort-btn').forEach(btn => {
document.querySelectorAll('#originals-table button[data-sort]').forEach(btn => {
btn.addEventListener('click', () => sortBy(btn.dataset.sort));
});
document.querySelector('#originals-table tbody').addEventListener('click', e => {
Expand Down
13 changes: 9 additions & 4 deletions assets/ds-tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,14 @@
--color-plot-band-original: rgba(37, 99, 235, .15); /* study-original band */
--color-plot-point: rgba(80, 80, 80, .15); /* jittered observations */
--color-plot-rule: rgba(100, 100, 100, .4); /* reference lines */
--color-plot-rule-label: rgba(100, 100, 100, .6);
--color-plot-rule-label: #6b7280;

/* Chart series colours (FLoRA explorer). Light values keep the
established outcome palette; dark values below keep series marks
at >= 3:1 against --color-surface-raised, except --color-chart-not-coded,
which stays deliberately faint (2.6:1) for unassessed rows. */
--color-badge-successful: #246f3d;
--color-badge-on-outcome: #fff;
--color-chart-successful: #2f8f4f;
--color-chart-failed: #b3331e;
--color-chart-mixed: #d49b1d;
Expand Down Expand Up @@ -291,10 +293,13 @@
--color-plot-band-original: rgba(147, 180, 253, .18);
--color-plot-point: rgba(210, 210, 220, .2);
--color-plot-rule: rgba(200, 200, 210, .45);
--color-plot-rule-label: rgba(200, 200, 210, .6);
--color-plot-rule-label: #c3bac0;

--color-chart-successful: #4ade80;
--color-chart-failed: #f08a7c;
/* Lightness separates success and failure under red/green colour-vision loss. */
--color-badge-successful: var(--color-chart-successful);
--color-badge-on-outcome: #17131a;
--color-chart-successful: #86efac;
--color-chart-failed: #ed7465;
--color-chart-mixed: #e0b040;
--color-chart-inconclusive: #a3a9b6;
--color-chart-qualified: #c0a8fa;
Expand Down
Loading
Loading