Public
Edited
Jul 30, 2022
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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));
}
}
Insert cell
Insert cell
height = width
Insert cell
iwidth = width - margin.left - margin.right
Insert cell
// Ideally iheight == iwidth so that the cells of the matrix are square
iheight = height - margin.top - margin.bottom
Insert cell
x = d3.scaleBand().domain(d3.range(0, graph.users.length)).range([0, iwidth])
Insert cell
y = d3.scaleBand().domain(d3.range(0, graph.comps.length)).range([0, iheight])
Insert cell
colorCount = d3
.scaleQuantile()
.domain(graph.edges.map((e) => e.count))
.range(d3.schemeBlues[5])
Insert cell
colorSuccess = d3.scaleQuantize().domain([0, 1]).range(d3.schemeOranges[5])
Insert cell
sliceOptions = new Map([
["Top 40", 40],
["Top 100", 100]
])
Insert cell
sortOptions = new Map([
["Name (A → Z)", "nameAsc"],
["Name (Z → A)", "nameDesc"],
["Logon Count (High → Low)", "countDesc"]
])
Insert cell
colorOptions = new Map([
["Logon Count", "logonCount"],
["Logon Failures", "logonSuccess"]
])
Insert cell
Insert cell
sortName = (a, b) => {
const firstA = a.id.substring(0, 1);
const firstB = b.id.substring(0, 1);
if (firstA == firstB) {
return parseInt(a.id.substring(1)) - parseInt(b.id.substring(1));
} else {
return firstA.localeCompare(firstB);
}
}
Insert cell
sortCount = (a, b) => {
return a.count - b.count;
}
Insert cell
{
// Test for sortName
const u = [{ id: "U14" }, { id: "SYSTEM" }, { id: "U2" }, { id: "U31" }];
u.sort(sortName);
return u;
}
Insert cell
sortFns = ({
nameAsc: (a, b) => sortName(a, b),
nameDesc: (a, b) => sortName(b, a),
countAsc: (a, b) => sortCount(a, b),
countDesc: (a, b) => sortCount(b, a)
})
Insert cell
import { data } from "@mehaase/w209-data-loading"
Insert cell
graph = {
const userMap = new Map();
const compMap = new Map();
const edgeMap = new Map();

for (let event of data) {
const user = event.src_user_name;
const succ = event.success === "Success";

if (userMap.has(user)) {
const u = userMap.get(user);
u.count += 1;
if (succ) {
u.success += 1;
} else {
u.fail += 1;
}
} else {
userMap.set(user, {
id: user,
count: 1,
success: succ ? 1 : 0,
fail: succ ? 0 : 1
});
}

const comp = event.dst_comp;
if (compMap.has(comp)) {
const c = compMap.get(comp);
c.count += 1;
if (succ) {
c.success += 1;
} else {
c.fail += 1;
}
} else {
compMap.set(comp, {
id: comp,
count: 1,
success: succ ? 1 : 0,
fail: succ ? 0 : 1
});
}

const key = `${user}-${comp}`;
if (edgeMap.has(key)) {
const e = edgeMap.get(key);
e.count += 1;
if (succ) {
e.success += 1;
} else {
e.fail += 1;
}
} else {
edgeMap.set(key, { count: 1, success: succ ? 1 : 0, fail: succ ? 0 : 1 });
}
}

let edges = [];

for (let [key, edge] of edgeMap) {
const [user, comp] = key.split("-");
edges.push({
source: userMap.get(user),
target: compMap.get(comp),
count: edge.count,
success: edge.success,
fail: edge.fail
});
}

let users = Array.from(userMap.values());
users.sort(sortFns[userSort]);
users = users.slice(0, sliceSize);

let comps = Array.from(compMap.values());
comps.sort(sortFns[compSort]);
comps = comps.slice(0, sliceSize);

// Remove edges that don't exist in our slice
const userSet = new Set(users.map((u) => u.id));
const compSet = new Set(comps.map((c) => c.id));
edges = edges.filter(
(e) => userSet.has(e.source.id) && compSet.has(e.target.id)
);

return {
users,
comps,
edges,
totalUsers: userMap.size,
totalComps: compMap.size,
totalEdges: edgeMap.size
};
}
Insert cell
Insert cell
import { Legend, Swatches } from "@d3/color-legend"
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more