OptimismBench / app /src /content /embeds /valence-quadrant.html
seonglae's picture
article: interactive research piece for OptimismBench
1df44e8
Raw
History Blame Contribute Delete
5.91 kB
<div id="ob-vq" role="img" aria-label="Good-side push versus bad-side push for six models. Optimistic models inflate both axes; Sonnet's pessimism is one-sided, underestimating good outcomes while keeping bad-outcome estimates near accurate."></div>
<style>
#ob-vq { font: 13px/1.4 system-ui, sans-serif; color: var(--text-color,#222); width:100%; max-width:480px; margin:0 auto; position:relative; }
#ob-vq svg { display:block; width:100%; height:auto; overflow:visible; }
#ob-vq .vq-tip { position:absolute; pointer-events:none; z-index:20; opacity:0; transform:translate(-50%,-100%); transition:opacity .1s; background:var(--surface-bg,#fff); color:var(--text-color,#222); border:1px solid var(--border-color,#ccc); border-radius:8px; padding:7px 9px; font-size:12px; line-height:1.5; box-shadow:0 6px 24px rgba(0,0,0,.18); white-space:nowrap; }
#ob-vq .vq-tip b { font-weight:650; }
</style>
<script>
(function () {
var root = document.getElementById("ob-vq");
if (!root || !window.d3) { return; }
var d3 = window.d3;
var svg = d3.select(root).append("svg");
var tip = document.createElement("div"); tip.className = "vq-tip"; root.appendChild(tip);
function cssVar(n, fb) { var v = getComputedStyle(document.documentElement).getPropertyValue(n); return (v && v.trim()) || fb; }
var DATA = null;
// hand-tuned label placement to avoid overlap in the top cluster
var LBL = {
"GPT-5.4-mini": [9, -9, "start"], "GPT-5.4": [0, -11, "middle"], "Haiku 4.5": [0, 16, "middle"],
"Flash 3": [9, 13, "start"], "Pro 3.1": [0, -11, "middle"], "Sonnet 4.6": [10, 4, "start"]
};
function draw() {
if (!DATA) { return; }
var OPT = cssVar("--opt", "#e07a5f"), PES = cssVar("--pes", "#3d5a80"),
MUT = cssVar("--muted-color", "#666"), TXT = cssVar("--text-color", "#222"),
BD = cssVar("--border-color", "#ccc"), SF = cssVar("--surface-bg", "#fff");
var W = Math.min(460, root.clientWidth || 460), H = W, m = 50, LIM = 11;
var x = d3.scaleLinear().domain([-LIM, LIM]).range([m, W - m]);
var y = d3.scaleLinear().domain([-LIM, LIM]).range([H - m, m]);
svg.attr("viewBox", "0 0 " + W + " " + H).attr("height", H).selectAll("*").remove();
// faint quadrant tints (optimism quadrant warm, pessimism cool)
function tint(qx, qy, c) { svg.append("rect").attr("x", x(qx)).attr("y", y(qy + LIM)).attr("width", (W - 2 * m) / 2).attr("height", (H - 2 * m) / 2).attr("fill", c).attr("opacity", 0.05); }
tint(0, 0, OPT); tint(-LIM, -LIM, PES);
// quadrant labels
[[7.5, 9.4, "OVERCLAIM", "#7d6440"], [-7.5, -9.4, "UNDERCLAIM", "#6b6358"],
[-7.5, 9.4, "PESSIMISM", PES], [7.5, -9.4, "OPTIMISM", OPT]].forEach(function (q) {
svg.append("text").attr("x", x(q[0])).attr("y", y(q[1])).attr("text-anchor", "middle").attr("fill", q[3]).attr("opacity", 0.7).attr("font-size", 10).attr("font-weight", 700).attr("letter-spacing", "0.03em").text(q[2]);
});
// axes only (no diagonal)
svg.append("line").attr("x1", x(-LIM)).attr("x2", x(LIM)).attr("y1", y(0)).attr("y2", y(0)).attr("stroke", BD);
svg.append("line").attr("x1", x(0)).attr("x2", x(0)).attr("y1", y(-LIM)).attr("y2", y(LIM)).attr("stroke", BD);
var pos = {}; DATA.models.forEach(function (mm) { pos[mm.name] = [x(mm.good), y(mm.bad)]; });
var defs = svg.append("defs");
DATA.pairs.forEach(function (p, i) {
var c = MUT;
defs.append("marker").attr("id", "vqa" + i).attr("viewBox", "0 0 10 10").attr("refX", 8).attr("refY", 5).attr("markerWidth", 6).attr("markerHeight", 6).attr("orient", "auto").append("path").attr("d", "M0,0L10,5L0,10").attr("fill", c).attr("opacity", 0.4);
svg.append("line").attr("x1", pos[p[0]][0]).attr("y1", pos[p[0]][1]).attr("x2", pos[p[1]][0]).attr("y2", pos[p[1]][1]).attr("stroke", c).attr("stroke-width", 1).attr("stroke-dasharray", "3 3").attr("opacity", 0.35).attr("marker-end", "url(#vqa" + i + ")");
});
var PROV = { OpenAI: "#2a8f82", Anthropic: "#a66e4e", Google: "#3d5a80", Mistral: "#c4553a" };
DATA.models.forEach(function (d) {
var skew = d.good + d.bad, c = PROV[d.provider] || MUT;
var g = svg.append("g").style("cursor", "pointer");
g.append("circle").attr("cx", x(d.good)).attr("cy", y(d.bad)).attr("r", 7).attr("fill", c).attr("stroke", SF).attr("stroke-width", 1.6);
var L = LBL[d.name] || [10, 4, "start"];
g.append("text").attr("x", x(d.good) + L[0]).attr("y", y(d.bad) + L[1]).attr("text-anchor", L[2]).attr("fill", TXT).attr("font-size", 11).attr("font-weight", 500).text(d.name);
g.on("mousemove", function (ev) { var pr = root.getBoundingClientRect(); tip.innerHTML = "<b>" + d.name + "</b><br>good-side push: " + (d.good > 0 ? "+" : "") + d.good + "<br>bad-side push: " + (d.bad > 0 ? "+" : "") + d.bad + "<br>Skew " + (skew > 0 ? "+" : "") + skew.toFixed(1); tip.style.left = (ev.clientX - pr.left) + "px"; tip.style.top = (ev.clientY - pr.top - 8) + "px"; tip.style.opacity = 1; }).on("mouseleave", function () { tip.style.opacity = 0; });
});
svg.append("text").attr("x", (m + W - m) / 2).attr("y", H - 10).attr("text-anchor", "middle").attr("fill", MUT).attr("font-size", 11).text("good-side push P(good) − 50");
svg.append("text").attr("transform", "translate(13," + H / 2 + ") rotate(-90)").attr("text-anchor", "middle").attr("fill", MUT).attr("font-size", 11).text("bad-side push P(bad) − 50");
}
fetch("data/valence.json").then(function (r) { if (!r.ok) { throw new Error("x"); } return r.json(); }).then(function (j) { DATA = j; draw(); }).catch(function () {});
if (window.ResizeObserver) { var t = null; new ResizeObserver(function () { clearTimeout(t); t = setTimeout(draw, 130); }).observe(root); } else { window.addEventListener("resize", draw); }
new MutationObserver(draw).observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme"] });
})();
</script>