function sparkbar(max) {
const colourScale = d3.scaleSequential(d3.interpolateCool)
.domain([0, max]);
return (x) => htl.html`<div style="
background: ${colourScale(x)};
color: black;
width: ${100 * x / max}%;
float: right;
padding-right: 3px;
box-sizing: border-box;
overflow: visible;
display: flex;
justify-content: end;">${x.toFixed(2).toLocaleString("en-US")}`
}
function tooltip(title, expl) {
const th = document.createElement("th");
th.title = expl
th.style.background = "#f0f8ff";
th.textContent = title;
th.addEventListener("mouseover", () => th.style.background = "#d0e8ff");
th.addEventListener("mouseout", () => th.style.background = "#f0f8ff");
return th;
}
function pkgfmt(pkg) {
const th = document.createElement("th");
th.title = "hover"
th.style.background = "#f0f8ff";
th.textContent = pkg;
th.addEventListener("mouseover", () => th.style.background = "#d0e8ff");
th.addEventListener("mouseout", () => th.style.background = "#f0f8ff");
th.addEventListener("click", () => {
localStorage.setItem("orgmetricsRepo", pkg);
th.style.background="#a0f8ff";
window.location.href="/repometrics-demo/repo.html";
});
return th;
}
Maintainers
repo_src = {
return transpose(repo_src_in).map(row => ({
...row,
}));
}
json_data = FileAttachment("results-json-data.json").json();
maintainer_pkgs = json_data['maintainer_pkgs'];
comaintainers = json_data['comaintainers'];
maintainers = Object.keys(maintainer_pkgs);
maintainerSet = localStorage.getItem("orgmetricsMaintainer") || maintainers [0]
viewof maintainer = Inputs.select(
maintainers,
{
multiple: false,
value: maintainerSet,
label: htl.html`<b>Maintainer:</b>`
}
)
s = localStorage.setItem("orgmetricsMaintainer", maintainer.toString());
these_pkgs = maintainer_pkgs[maintainer] || null;
these_cos = comaintainers[maintainer] || null
these_cos_list = these_cos ?
these_cos.map(i => htl.html`
<div onclick=${() => localStorage.setItem('orgmetricsMaintainer', i)}>
<li><a href="/repometrics-demo/maintainer.html">${i}</a></li>
</div>`) : htl.html`<li>No co-maintainers</li>`;
htl.html`<div><a href="https://github.com/${maintainer}" target="_blank">github.com/${maintainer}</a></div>`
Packages
This is a sub-set of the main table on the Organization page, showing only packages maintained by . Values are scaled between 0 and 1 based on the distribution of values across the entire organization, with higher values always better than lower values.
metricsGroupedTable = {
return transpose(metrics_table_in).map(row => ({
...row,
}));
}
metricsTable = metricsGroupedTable.filter(i => these_pkgs.includes(i.package));
Inputs.table(metricsTable, {
width: {
package: 100,
total: 200,
development: 200,
issues: 200,
popularity: 200,
meta: 200,
},
format: {
package: d => pkgfmt(d),
development: sparkbar(d3.max(metricsGroupedTable, d => d.development)),
issues: sparkbar(d3.max(metricsGroupedTable, d => d.issues)),
popularity: sparkbar(d3.max(metricsGroupedTable, d => d.popularity)),
meta: sparkbar(d3.max(metricsGroupedTable, d => d.meta)),
total: sparkbar(d3.max(metricsGroupedTable, d => d.total)),
},
header: {
development: tooltip("Development", "Code development and maintenance metrics"),
issues: tooltip("Issues", "GitHub issues and pull request activity"),
popularity: tooltip("Popularity", "Project popularity on CRAN (where applicable) and GitHub"),
meta: tooltip("Dependencies and releases", ""),
total: tooltip("Overall", "Average across all four categories of metrics."),
},
})
Co-Maintainers
Network
co_pkgs = these_cos ? these_cos.map(i => maintainer_pkgs[i]).flat() : [];
pkgs_expanded_full = [
...these_pkgs,
...co_pkgs
];
// Reduce to unique pkgs:
pkgs_expanded = [...new Set(pkgs_expanded_full)];
co_nodes = these_cos ?
these_cos.map(item => ({ id: item, group: "Co-maintainer", size: 6 })) : [];
nodes = [
{ id: maintainer, group: "Maintainer", size: 10 },
...pkgs_expanded.map(item => ({
id: item,
group: these_pkgs.includes(item) ? "packages" : "otherPackages",
size: these_pkgs.includes(item) ? 8 : 4,
})),
...co_nodes
];
// edges are mappings from co-maintainers to all packages. First collect list
// of all packages from co-maintainers:
these_co_pkgs = these_cos ? these_cos.reduce((acc, key) => {
if (maintainer_pkgs.hasOwnProperty(key)) {
if (!these_pkgs.includes(key)) {
acc[key] = maintainer_pkgs[key];
}
}
return acc;
}, {}) : [];
// Then flatten that to (source, target) pairs of (maintainer, package):
these_co_pkgs_flat = Object.entries(these_co_pkgs).flatMap(([source, targets]) =>
targets.map(target => ({ source, target }))
);
links = [
...these_pkgs.map(item => ({
source: maintainer, target: item, value: 4
})),
...these_co_pkgs_flat.map(item => ({
source: item.source, target: item.target, value: 2
}))
];
strength = -400;
chart = {
const width = 928;
const height = 600;
const types = Array.from(new Set(nodes.map(d => d.group)));
const color = d3.scaleOrdinal(types, d3.schemeCategory10);
const simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id))
.force("charge", d3.forceManyBody().strength(strength))
.force("x", d3.forceX())
.force("y", d3.forceY());
const svg = d3.create("svg")
.attr("viewBox", [-width / 2, -height / 2, width, height])
.attr("width", width)
.attr("height", height)
.attr("style", "max-width: 100%; height: auto; font: 14px sans-serif;");
const link = svg.append("g")
.attr("fill", "none")
.attr("stroke-width", 1.5)
.selectAll("path")
.data(links)
.join("path")
.attr("stroke", "gray")
.attr("stroke-width", d => d.value);
const node = svg.append("g")
.selectAll("g")
.data(nodes)
.join("g")
.call(drag(simulation));
node.append("circle")
.attr("stroke", "white")
.attr("stroke-width", 1.5)
.data(nodes)
.join("circle")
.attr("fill", d => color(d.group))
.attr("r", d => d.size);
node.append("text")
.attr("x", 8)
.attr("y", "0.31em")
.text(d => d.id)
.html(d => d.group === "Co-maintainer" ?
`<a href="/repometrics-demo/maintainer.html"
onclick="localStorage.setItem('orgmetricsMaintainer', '${d.id}')">${d.id}</a>` :
((d.group === "packages" || d.group === "otherPackages") ?
`<a href="/repometrics-demo/repo.html"
onclick="localStorage.setItem('orgmetricsRepo', '${d.id}')">${d.id}</a>` :
d.id))
.clone(true).lower()
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-width", 3);
simulation.on("tick", () => {
link.attr("d", linkArc);
node.attr("transform", d => `translate(${d.x},${d.y})`);
});
invalidation.then(() => simulation.stop());
return Object.assign(svg.node(), {scales: {color}});
}
function linkArc(d) {
const r = Math.hypot(d.target.x - d.source.x, d.target.y - d.source.y);
return `
M${d.source.x},${d.source.y}
A${r},${r} 0 0,1 ${d.target.x},${d.target.y}
`;
}
drag = simulation => {
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
return d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}