## Code

```
import { svg } from "/src/cscheid/cscheid.js";
{
svg.setupD3Prototype(d3);
let svgNode = d3.select("#main")
.append("svg")
.attr("viewBox", "0 0 920 400")
.attr("width", "100%")
.attr("height", "100%");
const scaleBounds = [40, 700];
let preTestScaleLogOdds = d3.scaleLinear()
.domain([3, -3])
.range(scaleBounds);
let postTestScaleLogOdds = d3.scaleLinear()
.domain([-3, 3])
.range(scaleBounds);
let logLikelihoodRatioScale = d3.scaleLinear()
.domain([-6, 6])
.range(scaleBounds);
let labels = ['pre-test probability',
'likelihood ratio',
'post-test probability'];
let yScale = d3.scaleLinear().domain([0,2]).range([100, 300]);
svgNode.append("g")
.selectAll("text")
.data(labels)
.enter()
.append("text")
.attr("x", 910)
.attr("y", function(d, i) { return yScale(i); })
.attr("dy", 3)
.text(function(d) { return d; })
.attr("class", "label");
let preTestLineG = svgNode.append("g");
let preTestLine = preTestLineG
.append("line")
.attr("x1", preTestScaleLogOdds(-3))
.attr("x2", preTestScaleLogOdds(3))
.attr("y1", 100)
.attr("y2", 100)
.attr("class", "test-line");
let lrLineG = svgNode.append("g");
let lrLine = lrLineG
.append("line")
.attr("x1", logLikelihoodRatioScale(-4))
.attr("x2", logLikelihoodRatioScale(4))
.attr("y1", 200)
.attr("y2", 200)
.attr("class", "test-line");
let postTestLineG = svgNode.append("g");
let postTestLine = postTestLineG
.append("line")
.attr("x1", postTestScaleLogOdds(-3))
.attr("x2", postTestScaleLogOdds(3))
.attr("y1", 300)
.attr("y2", 300)
.attr("class", "test-line");
//////////////////////////////////////////////////////////////////////////////
// this could be solved automatically..
function expFmt(v) {
let l = Math.pow(10, v);
let r = fmt(l / (1 + l));
return r
.replace(/0+$/, "")
.replace(/(\..*)09+$/, (match, p1) => `${p1}1`); // i'm so sorry, universe.
}
function lrFmt(v) {
let l = Math.pow(10, v);
if (l == ~~l)
return String(l);
else
return String(l)
.replace(/0+$/, "")
.replace(/(\..*)09+$/, (match, p1) => `${p1}1`); // i'm so sorry, universe.
}
let preProbTicks = {
list: [-3, -2, -0.954245, 0, 0.954245, 2, 3],
scale: preTestScaleLogOdds,
fmt: expFmt
};
let postProbTicks = {
list: [-3, -2, -0.954245, 0, 0.954245, 2, 3],
scale: postTestScaleLogOdds,
fmt: expFmt
};
let lrTicks = {
list: [-4, -3, -2, -1, 0, 1, 2, 3, 4],
scale: logLikelihoodRatioScale,
fmt: lrFmt
};
let fmt = d3.format(".3f");
function addTicks(ticks)
{
return function(sel) {
let gs = sel
.enterMany(ticks.list)
.append("g");
gs.append("line")
.attr("x1", function(d) { return ticks.scale(d); })
.attr("x2", function(d) { return ticks.scale(d); })
.attr("y1", -5).attr("y2", 5)
.attr("class", "test-line");
gs.append("text")
.attr("x", function(d) { return ticks.scale(d); })
.attr("y", -15)
.attr("class", "tick-label")
.text(function(d) { return ticks.fmt(d); });
};
}
preTestLineG
.append("g")
.attr("transform", svg.translate(0, 100))
.callReturn(addTicks(preProbTicks));
lrLineG
.append("g")
.attr("transform", svg.translate(0, 200))
.callReturn(addTicks(lrTicks));
postTestLineG
.append("g")
.attr("transform", svg.translate(0, 300))
.callReturn(addTicks(postProbTicks));
let nomogramLine = svgNode.append("line")
.attr("x1", preTestScaleLogOdds(-1))
.attr("y1", 100)
.attr("x2", postTestScaleLogOdds(0))
.attr("y2", 300)
.attr("class", "test-line");
function updateLine() {
nomogramLine.attr("x1", preTestHandle.attr("cx"));
nomogramLine.attr("x2", postTestHandle.attr("cx"));
}
function dragAttrs(sel) {
return sel.attr("r", 5)
.attr("fill", "white")
.attr("stroke", "black")
.attr("cursor", "pointer");
}
let preTestHandle = svgNode.append("circle")
.attr("cx", preTestScaleLogOdds(-1))
.attr("cy", 100)
.callReturn(dragAttrs)
.call(d3.drag().on("drag", function(event) {
debugger;
d3.select(this).attr("cx", event.x);
let lrX = Number(lrHandle.attr("cx"));
let dx = lrX - event.x;
postTestHandle.attr("cx", lrX + dx);
updateLine();
}));
let lrHandle = svgNode.append("circle")
.attr("cx", 0.5 * (postTestScaleLogOdds(0) + preTestScaleLogOdds(-1)))
.attr("cy", 200)
.callReturn(dragAttrs)
.call(d3.drag().on("drag", function(event) {
debugger;
d3.select(this).attr("cx", event.x);
let ptX = Number(preTestHandle.attr("cx"));
let dx = event.x - ptX;
postTestHandle.attr("cx", event.x + dx);
updateLine();
}));
let postTestHandle = svgNode.append("circle")
.attr("cx", postTestScaleLogOdds(0))
.attr("cy", 300)
.callReturn(dragAttrs)
.call(d3.drag().on("drag", function(event) {
debugger;
d3.select(this).attr("cx", event.x);
lrHandle.attr("cx", 0.5 * (event.x + Number(preTestHandle.attr("cx"))));
updateLine();
}));
}
```