novice_expert_schemas = {
const w = schemas_width;
const h = schemas_height;
const fs = schemas_font_size;
const colNeg = "#D55E00";
const colPos = "#0072B2";
const colNeutral = "#64748b";
const colCross = "#B8821A";
const labels = [
"Fakt A", "Fakt B", "Fakt C",
"Regel 1", "Regel 2", "Regel 3",
"Def. X", "Def. Y", "Def. Z"
];
const clusters = [0, 0, 0, 1, 1, 1, 2, 2, 2];
const novicePos = [
[0.12, 0.22], [0.38, 0.12], [0.65, 0.20],
[0.18, 0.50], [0.45, 0.48], [0.72, 0.55],
[0.08, 0.80], [0.42, 0.82], [0.70, 0.78]
];
const expertPos = [
[0.15, 0.22], [0.30, 0.22], [0.225, 0.40],
[0.60, 0.22], [0.75, 0.22], [0.675, 0.40],
[0.33, 0.72], [0.48, 0.72], [0.405, 0.88]
];
const intraEdges = [
[0, 1], [1, 2], [0, 2],
[3, 4], [4, 5], [3, 5],
[6, 7], [7, 8], [6, 8]
];
const crossEdges = [[1, 3], [2, 6], [5, 7]];
const clusterBoxes = [
{ x: 0.08, y: 0.12, w: 0.30, h: 0.38, label: "Schema 1" },
{ x: 0.53, y: 0.12, w: 0.30, h: 0.38, label: "Schema 2" },
{ x: 0.26, y: 0.62, w: 0.30, h: 0.36, label: "Schema 3" }
];
const mx = 40, my = 30;
const cw = w - 2 * mx;
const ch = h - 2 * my;
const toX = (nx) => mx + nx * cw;
const toY = (ny) => my + ny * ch;
const svg = d3.create("svg")
.attr("width", w).attr("height", h)
.attr("viewBox", `0 0 ${w} ${h}`)
.style("background", "transparent")
.style("cursor", "pointer")
.style("user-select", "none");
const clusterGs = svg.selectAll("g.cluster-bg")
.data(clusterBoxes).join("g").attr("class", "cluster-bg");
clusterGs.append("rect")
.attr("x", d => toX(d.x)).attr("y", d => toY(d.y))
.attr("width", d => d.w * cw).attr("height", d => d.h * ch)
.attr("rx", 4).attr("fill", colPos).attr("fill-opacity", 0)
.attr("stroke", colPos).attr("stroke-opacity", 0)
.attr("stroke-width", 1.5).attr("stroke-dasharray", "6,4");
clusterGs.append("text")
.attr("x", d => toX(d.x + d.w / 2)).attr("y", d => toY(d.y) - 6)
.attr("text-anchor", "middle").style("font-size", "12px")
.style("fill", colPos).style("font-style", "italic").style("opacity", 0)
.text(d => d.label);
const intraLines = svg.selectAll("line.intra")
.data(intraEdges).join("line").attr("class", "intra")
.attr("x1", d => toX(novicePos[d[0]][0])).attr("y1", d => toY(novicePos[d[0]][1]))
.attr("x2", d => toX(novicePos[d[1]][0])).attr("y2", d => toY(novicePos[d[1]][1]))
.attr("stroke", colPos).attr("stroke-width", 2).attr("stroke-opacity", 0);
const crossLines = svg.selectAll("line.cross")
.data(crossEdges).join("line").attr("class", "cross")
.attr("x1", d => toX(novicePos[d[0]][0])).attr("y1", d => toY(novicePos[d[0]][1]))
.attr("x2", d => toX(novicePos[d[1]][0])).attr("y2", d => toY(novicePos[d[1]][1]))
.attr("stroke", colCross).attr("stroke-width", 1.5).attr("stroke-opacity", 0)
.attr("stroke-dasharray", "4,3");
const nodeR = 28;
const nodeGs = svg.selectAll("g.node")
.data(labels.map((l, i) => ({ label: l, i })))
.join("g").attr("class", "node")
.attr("transform", d => `translate(${toX(novicePos[d.i][0])},${toY(novicePos[d.i][1])})`);
nodeGs.append("rect")
.attr("x", -nodeR).attr("y", -14).attr("width", nodeR * 2).attr("height", 28)
.attr("rx", 4).attr("fill", colNeg).attr("fill-opacity", 0.8);
nodeGs.append("text")
.attr("text-anchor", "middle").attr("dy", "0.35em")
.style("font-size", "12px").style("font-weight", "bold").style("fill", "white")
.text(d => d.label);
const subtitle = svg.append("text")
.attr("x", w / 2).attr("y", h - 8)
.attr("text-anchor", "middle").style("font-size", fs).style("fill", colNeutral)
.text("Isolierte Fakten \u2014 klicken zum Vernetzen");
let expert = false;
svg.on("click", () => {
expert = !expert;
const dur = 1000;
const ease = d3.easeCubicInOut;
nodeGs.transition().duration(dur).ease(ease)
.attr("transform", d => {
const pos = expert ? expertPos[d.i] : novicePos[d.i];
return `translate(${toX(pos[0])},${toY(pos[1])})`;
});
nodeGs.select("rect").transition().duration(dur).ease(ease)
.attr("fill", expert ? colPos : colNeg);
intraLines.transition().duration(dur).ease(ease)
.attr("x1", d => toX((expert ? expertPos : novicePos)[d[0]][0]))
.attr("y1", d => toY((expert ? expertPos : novicePos)[d[0]][1]))
.attr("x2", d => toX((expert ? expertPos : novicePos)[d[1]][0]))
.attr("y2", d => toY((expert ? expertPos : novicePos)[d[1]][1]))
.attr("stroke-opacity", expert ? 0.6 : 0);
crossLines.transition().duration(dur).ease(ease)
.attr("x1", d => toX((expert ? expertPos : novicePos)[d[0]][0]))
.attr("y1", d => toY((expert ? expertPos : novicePos)[d[0]][1]))
.attr("x2", d => toX((expert ? expertPos : novicePos)[d[1]][0]))
.attr("y2", d => toY((expert ? expertPos : novicePos)[d[1]][1]))
.attr("stroke-opacity", expert ? 0.4 : 0);
clusterGs.select("rect").transition().duration(dur).ease(ease)
.attr("fill-opacity", expert ? 0.06 : 0)
.attr("stroke-opacity", expert ? 0.5 : 0);
clusterGs.select("text").transition().duration(dur).ease(ease)
.style("opacity", expert ? 1 : 0);
subtitle.transition().duration(300).style("opacity", 0)
.transition().delay(expert ? 600 : 0).duration(300).style("opacity", 1)
.text(expert
? "Vernetzte Schemata \u2014 klicken zum Aufl\u00f6sen"
: "Isolierte Fakten \u2014 klicken zum Vernetzen");
});
return svg.node();
}