Mediators
Relationships between nodes
viewof strength_xz = Inputs.range([0, 1], {
value: 0.5,
step: 0.05,
label: html`<span class="node node-x">X</span> → <span class="node node-z">Z</span> strength`
})
viewof strength_zy = Inputs.range([0, 1], {
value: 0.5,
step: 0.05,
label: html`<span class="node node-z">Z</span> → <span class="node node-y">Y</span> strength`
})
viewof strength_xy = Inputs.range([0, 1], {
value: 0.5,
step: 0.05,
label: html`<span class="node node-x">X</span> → <span class="node node-y">Y</span> strength`
})Adjustments
// ----------------
// Status readout
// ----------------
{
const pctDirect = Math.round(y_direct_x / yMax * 100);
const pctMediated = Math.round(y_mediated / yMax * 100);
const pctZ = Math.round(y_from_z_own / yMax * 100);
const pctOwn = Math.max(0, 100 - pctDirect - pctMediated - pctZ);
return html`<div class="alert alert-secondary status-readout">
<h5 class="alert-heading">What Y contains</h5>
<table>
<tr>
<td><svg width="12" height="12"><rect width="12" height="12" fill="${dag.colorX}"/></svg></td>
<td><span class="node node-x">X</span>'s direct influence on <span class="node node-y">Y</span></td>
<td>${pctDirect}%</td>
</tr>
<tr>
<td><svg width="12" height="12">
<defs>
<pattern id="legend-hatch-med" patternUnits="userSpaceOnUse"
width="6" height="6" patternTransform="rotate(-45)">
<rect width="6" height="6" fill="${dag.colorX}"/>
<line x1="0" y1="0" x2="0" y2="6"
stroke="${dag.colorZ}" stroke-width="2.5"/>
</pattern>
</defs>
<rect width="12" height="12" fill="url(#legend-hatch-med)"/>
</svg></td>
<td><span class="node node-x">X</span>'s influence on <span class="node node-y">Y</span> via <span class="node node-z">Z</span></td>
<td>${pctMediated}%</td>
</tr>
<tr>
<td><svg width="12" height="12"><rect width="12" height="12" fill="${dag.colorZ}"/></svg></td>
<td><span class="node node-z">Z</span>'s own variation flowing to <span class="node node-y">Y</span></td>
<td>${pctZ}%</td>
</tr>
<tr>
<td><svg width="12" height="12"><rect width="12" height="12" fill="${dag.colorY}"/></svg></td>
<td><span class="node node-y">Y</span>'s own variation</td>
<td>${pctOwn}%</td>
</tr>
<tr class="summary ${adjust_z ? 'dimmed' : ''}">
<td>
<svg width="12" height="12"><rect width="12" height="12" fill="${dag.colorX}"/></svg>
+
<svg width="12" height="12">
<rect width="12" height="12" fill="url(#legend-hatch-med)"/>
</svg>
</td>
<td>Total <span class="node node-x">X</span> → <span class="node node-y">Y</span> effect</td>
<td>${pctDirect + pctMediated}%</td>
</tr>
<tr class="summary ${adjust_z ? '' : 'dimmed'}">
<td>
<svg width="12" height="12"><rect width="12" height="12" fill="${dag.colorX}"/></svg>
</td>
<td>Direct-only <span class="node node-x">X</span> → <span class="node node-y">Y</span> effect</td>
<td>${pctDirect}%</td>
</tr>
</table>
</div>`;
}// -----------------
// Interactive DAG
// -----------------
{
const width = 600;
const height = 250;
const nodeRadius = 36;
// Mediator: Z is between X and Y, positioned at top
const nodes = {
X: { x: 130, y: 200, label: "X" },
Z: { x: width / 2, y: 60, label: "Z" },
Y: { x: 470, y: 200, label: "Y" }
};
const svg = d3.create("svg")
.attr("viewBox", `0 0 ${width} ${height}`)
.attr("width", width)
.attr("height", height)
.style("max-width", "100%");
const defs = svg.append("defs");
dag.addArrowMarkers(defs);
// Mediation hatch: red stripes on gold, slope downward
// "X flowing through Z's territory"
dag.addHatchPattern(
defs, "hatch-mediated", dag.colorX, dag.colorZ, -45
);
dag.addCircleClip(
defs, "z-clip", nodes.Z.x, nodes.Z.y, nodeRadius
);
dag.addCircleClip(
defs, "y-clip", nodes.Y.x, nodes.Y.y, nodeRadius
);
// Arrows
const edges = [
{
id: "xz", from: nodes.X, to: nodes.Z,
strength: strength_xz, blocked: false
},
{
id: "zy", from: nodes.Z, to: nodes.Y,
strength: strength_zy, blocked: adjust_z
},
{
id: "xy", from: nodes.X, to: nodes.Y,
strength: strength_xy, blocked: false
}
];
for (const edge of edges) {
dag.drawEdge(svg, edge, nodeRadius);
}
// Nodes
// X is solid
dag.drawSolidNode(
svg, nodes.X.x, nodes.X.y, nodeRadius, dag.colorX
);
// Z: strength directly controls fill proportion
dag.drawNode(svg, nodes.Z.x, nodes.Z.y, nodeRadius, "z-clip", {
bottomUp: [
{ prop: strength_xz, fill: dag.colorX }
],
topDown: []
}, "horizontal", dag.colorZ);
// Y: blue base, incoming effects overlay
dag.drawNode(svg, nodes.Y.x, nodes.Y.y, nodeRadius, "y-clip", {
bottomUp: [
{ prop: Math.min(y_direct_x / yMax, 1), fill: dag.colorX },
{
prop: Math.min(y_mediated / yMax, 1),
fill: "url(#hatch-mediated)"
}
],
topDown: [
{
prop: Math.min(y_from_z_own / yMax, 1),
fill: dag.colorZ
}
]
}, undefined, dag.colorY);
// Labels
for (const n of Object.values(nodes)) {
dag.drawLabel(svg, n.x, n.y, n.label);
}
return svg.node();
}