chart2 = {
const svg = d3
.create("svg")
.attr("viewBox", [0, 0, width, data.length * inputs.rowHeight])
.style("font", "13px sans-serif"),
height = data.length * inputs.rowHeight,
margin = { top: 35, bottom: 35, left: 350 },
domainRegex = /(?:-?\d+),\s?(?:-?\d+)/,
xDomain = domainRegex.test(inputs.xdomain)
? inputs.xdomain.split(",").map((d) => parseInt(d))
: [d3.min(data, (d) => d.Lower), d3.max(data, (d) => d.Upper)],
x = d3
.scaleLinear()
.domain(xDomain)
.range([margin.left, margin.left + inputs.plotWidth])
.clamp(true),
y = d3
.scaleLinear()
.domain([-1, data.length + 2])
.range([margin.top, height - margin.bottom]),
yAxis = (g) =>
g.attr("transform", `translate(${margin.left},0)`).call(d3.axisLeft(y)),
xAxis = (g) =>
g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).ticks(inputs.numTicks))
.style("font", "13px sans-serif"),
radiusScale = d3
.scaleSqrt()
.domain([1, d3.max(data, (d) => d.N)])
.range([3, 8]);
svg.append("g").call(xAxis);
const labels_g = svg
.append("g")
.attr("text-anchor", "middle")
.attr("class", "labels");
labels_g
.append("text")
.attr("font-weight", "bold")
.attr("x", d3.mean(x.range()))
.attr("y", y(-1) - 5)
.text(inputs.title);
labels_g
.append("text")
.attr("x", d3.mean(x.range()))
.attr("y", y(data.length) + inputs.rowHeight * 2 + 23)
.text(inputs.xlabel);
svg
.append("g")
.append("line")
.attr("stroke", "#A1A1A1")
.attr("stroke-width", 2)
.attr("stroke-dasharray", "4 4")
.attr("x1", (d) => x(0))
.attr("x2", (d) => x(0))
.attr("y1", y.range()[0])
.attr("y2", y.range()[1]);
const regy = y(data.length + 1);
if (regressionInputs.showBaseline) {
svg
.append("g")
.append("line")
.attr("stroke", regressionInputs.color)
.attr("stroke-width", 2)
.attr("x1", (d) => x(regressionInputs.estimate))
.attr("x2", (d) => x(regressionInputs.estimate))
.attr("y1", y.range()[0])
.attr("y2", y.range()[1]);
}
var lines = svg.append("g");
lines
.selectAll("line.a")
.data(data)
.join("line")
.attr("stroke", "black")
.attr("stroke-width", 2)
.attr("x1", (d) => x(d.Lower))
.attr("x2", (d) => x(d.Upper))
.attr("y1", (d, i) => y(i))
.attr("y2", (d, i) => y(i));
lines
.selectAll("path.lower")
.data(data)
.join("path")
.attr("stroke", "black")
.attr("stroke-width", 2)
.attr("d", (d, i) => {
const dx = d.Lower < xDomain[0] ? inputs.rowHeight * 0.3 : 0;
return d3.line()([
[x(d.Lower) + dx, y(i) - inputs.rowHeight * 0.2],
[x(d.Lower), y(i)],
[x(d.Lower) + dx, y(i) + inputs.rowHeight * 0.2]
]);
});
lines
.selectAll("path.upper")
.data(data)
.join("path")
.attr("stroke", "black")
.attr("stroke-width", 2)
.attr("d", (d, i) => {
const dx = d.Upper > xDomain[1] ? inputs.rowHeight * 0.3 : 0;
return d3.line()([
[x(d.Upper) - dx, y(i) - inputs.rowHeight * 0.2],
[x(d.Upper), y(i)],
[x(d.Upper) - dx, y(i) + inputs.rowHeight * 0.2]
]);
});
const addText = (colName, xPosition, headerName, textAnchor) => {
let g = svg.append("g");
g.selectAll("text")
.data(data)
.join("text")
.attr("x", xPosition)
.attr("y", (d, i) => y(i))
.attr("text-anchor", textAnchor)
.attr("alignment-baseline", "central")
.text((d) => d[colName]);
g.append("text")
.attr("x", xPosition)
.attr("y", y(-1) - 5)
.attr("text-anchor", textAnchor)
.attr("font-weight", "bold")
.text((d) => headerName);
};
addText("Author(s) and Year", 20, "Author(s) and Year", "start");
addText("Confidence", 190, "Confidence", "middle");
addText("Timing", 265, "Timing", "middle");
addText("N", 330, "N", "end");
addText("Result", margin.left + inputs.plotWidth + 150, "Result", "end");
// regression text
svg
.append("text")
.attr("font-weight", "bold")
.attr("x", margin.left + inputs.plotWidth + 150)
.attr("y", regy)
.attr("text-anchor", "end")
.attr("alignment-baseline", "central")
.text(
`${regressionInputs.estimate} [${regressionInputs.lower_ci}, ${regressionInputs.upper_ci}]`
);
svg
.append("text")
.attr("font-weight", "bold")
.attr("x", 20)
.attr("y", regy)
.attr("text-anchor", "start")
.attr("alignment-baseline", "central")
.text(regressionInputs.description);
lines
.selectAll("line.upper")
.data(data)
.join("line")
.attr("stroke", "black")
.attr("stroke-width", 2)
.attr("x1", 0)
.attr("x2", width)
.attr("y1", y(data.length + 2))
.attr("y2", y(data.length + 2));
lines
.selectAll("line.lower")
.data(data)
.join("line")
.attr("stroke", "black")
.attr("stroke-width", 2)
.attr("x1", 0)
.attr("x2", width)
.attr("y1", y(-1))
.attr("y2", y(-1));
svg
.append("g")
.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", (d) => x(d.Estimate))
.attr("cy", (d, i) => y(i))
.attr("r", (d) => radiusScale(d.N));
// add regression
svg
.append("path")
.attr("stroke", "black")
.attr("fill", regressionInputs.color)
.attr("stroke-width", 2)
.attr("d", (d, i) => {
return d3.line()([
[x(regressionInputs.lower_ci), regy],
[x(regressionInputs.estimate), regy + inputs.rowHeight * 0.4],
[x(regressionInputs.upper_ci), regy],
[x(regressionInputs.estimate), regy - inputs.rowHeight * 0.4],
[x(regressionInputs.lower_ci), regy]
]);
});
return svg.node();
}