update = {
const userPositions = {};
graph.users.forEach((v, i) => (userPositions[v.id] = i));
const compPositions = {};
graph.comps.forEach((v, i) => (compPositions[v.id] = i));
const t = d3.transition().duration(1500);
const g = d3.select(svg).select("g.content");
const gTooltip = d3.select(svg).select("g.tooltip");
const xHighlight = d3.select(svg).select("g.highlighters rect.x-highlight");
const yHighlight = d3.select(svg).select("g.highlighters rect.y-highlight");
const positionDelayMultiple = 800 / sliceSize;
let highlightXLabel = null;
let highlightYLabel = null;
const shrinkLabels = iwidth / graph.users.length <= 8;
const colorScale = colorBy === "logonCount" ? colorCount : colorSuccess;
d3.select(svg).on("mousedown", hideHighlight);
d3.select(svg)
.select(".x-axis-label")
.text(
`← Showing ${graph.users.length.toLocaleString()} out of ${
graph.totalUsers
} Users →`
);
d3.select(svg)
.select(".y-axis-label")
.text(
`← Showing ${graph.comps.length.toLocaleString()} out of ${
graph.totalComps
} Computers →`
);
function setTooltip(event, edge) {
if (colorBy === "logonCount") {
const val = edge.count.toLocaleString();
const s = edge.count == 1 ? "" : "s";
gTooltip.select("text").text(`${val} Logon${s}`);
} else {
const val1 = edge.success.toLocaleString();
const s1 = edge.success == 1 ? "" : "es";
const val2 = edge.fail.toLocaleString();
const s2 = edge.fail == 1 ? "" : "s";
gTooltip.select("text").text(`${val1} Success/${val2} Fail`);
}
const tipx = Math.min(x(userPositions[edge.source.id]), x.range()[1] - 160);
const tipy = y(compPositions[edge.target.id]) + 20;
gTooltip
.style("visibility", "visible")
.attr("transform", `translate(${tipx}, ${tipy})`);
}
function setHighlight(event, edge) {
const labelHighlight = 40;
xHighlight
.style("visibility", "visible")
.attr("x", x.range()[0] - labelHighlight)
.attr("y", y(compPositions[edge.target.id]))
.attr("width", x.range()[1] - x.range()[0] + labelHighlight)
.attr("height", y.bandwidth());
yHighlight
.style("visibility", "visible")
.attr("x", x(userPositions[edge.source.id]))
.attr("y", y.range()[0] - labelHighlight)
.attr("width", x.bandwidth())
.attr("height", y.range()[1] - y.range()[0] + labelHighlight);
event.stopPropagation();
}
function hideTooltip() {
gTooltip.style("visibility", "hidden");
}
function hideHighlight() {
xHighlight.style("visibility", "hidden");
yHighlight.style("visibility", "hidden");
}
g.select("rect.background")
.attr("x", x.range()[0])
.attr("y", y.range()[0])
.attr("width", x.range()[1])
.attr("height", y.range()[1]);
const xLabels = g
.selectAll("text.x-axis")
.data(graph.users, (d) => d.id)
.join(
(enter) =>
enter
.append("text")
.attr("class", "x-axis")
.text((n) => n.id)
.style("text-anchor", "start")
.style("opacity", 0)
.style("user-select", "none")
.attr(
"transform",
(n, i) =>
`translate(${x(i) + x.bandwidth() / 2 + 4}, -10) rotate(-90)`
)
.call((enter) => enter.transition(t).style("opacity", 1)),
(update) => update,
(exit) =>
exit.call((exit) => exit.transition(t).style("opacity", 0).remove())
)
.style("font-size", shrinkLabels ? "6pt" : "8pt")
.transition(t)
.delay((n) => userPositions[n.id] * positionDelayMultiple)
.attr(
"transform",
(n, i) => `translate(${x(i) + x.bandwidth() / 2 + 4}, -10) rotate(-90)`
);
const yLabels = g
.selectAll("text.y-axis")
.data(graph.comps, (d) => d.id)
.join(
(enter) =>
enter
.append("text")
.attr("class", "y-axis")
.text((n) => n.id)
.style("text-anchor", "end")
.style("opacity", 0)
.style("user-select", "none")
.attr("x", (n) => -5)
.attr("y", (n, i) => y(i) + y.bandwidth() / 2 + 4)
.call((enter) => enter.transition(t).style("opacity", 1)),
(update) => update,
(exit) =>
exit.call((exit) => exit.transition(t).style("opacity", 0).remove())
)
.style("font-size", shrinkLabels ? "6pt" : "8pt")
.transition(t)
.delay((n) => compPositions[n.id] * positionDelayMultiple)
.attr("y", (n, i) => y(i) + y.bandwidth() / 2 + 4);
const cells = g
.selectAll("rect.cell")
.data(graph.edges, (e) => `${e.source.id}-${e.target.id}`)
.join(
(enter) =>
enter
.append("rect")
.attr("class", "cell")
.style("fill", (e) => colorScale(e.count))
.style("stroke", "#888")
.style("opacity", 0)
.attr("cursor", "crosshair")
.attr("width", x.bandwidth())
.attr("height", y.bandwidth())
.attr("x", (n) => x(userPositions[n.source.id]))
.attr("y", (n) => y(compPositions[n.target.id]))
.call(
(enter) => hideHighlight && enter.transition(t).style("opacity", 1)
),
(update) => update,
(exit) =>
exit.call((exit) => exit.transition(t).style("opacity", 0).remove())
)
.on("mousedown", setHighlight)
.on("mouseover mousemove", setTooltip)
.on("mouseout", hideTooltip)
.transition(t)
.call((t) => hideHighlight())
.delay(
(n) =>
(compPositions[n.target.id] + userPositions[n.source.id]) *
positionDelayMultiple
)
.attr("width", x.bandwidth())
.attr("height", y.bandwidth())
.attr("x", (n) => x(userPositions[n.source.id]))
.attr("y", (n) => y(compPositions[n.target.id]));
if (colorBy === "logonCount") {
cells.style("fill", (e) => colorCount(e.count));
} else {
cells.style("fill", (e) => colorSuccess(e.fail / e.count));
}
}