viewof chart = {
const margin = { top: 40, right: 170, bottom: 80, left: 60 };
const width = 800 - margin.left - margin.right;
const height = 500 - margin.top - margin.bottom;
const svg = d3
.create("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
const g = svg
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
const x = d3
.scaleBand()
.domain(data2.map((d) => d.model))
.range([0, width])
.padding(0.3);
const xSubgroup = d3
.scaleBand()
.domain(["agreement", "cost"])
.range([0, x.bandwidth()])
.padding(0.1);
const yLeft = d3.scaleLinear().domain([50, 100]).range([height, 0]);
const yRight = d3.scaleLinear().domain([0, 0.5]).range([height, 0]);
g.append("g")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "rotate(-45)")
.style("text-anchor", "end")
.style("font-size", "12px");
// Add Y axis left
g.append("g")
.call(d3.axisLeft(yLeft))
.style("font-size", "12px")
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", -40)
.attr("x", -height / 2)
.attr("text-anchor", "middle")
.style("font-size", "14px")
.text("Agreement with gold standard (%)");
// Add Y axis right
g.append("g")
.attr("transform", `translate(${width},0)`)
.call(d3.axisRight(yRight))
.style("font-size", "12px")
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 60)
.attr("x", -height / 2)
.attr("text-anchor", "middle")
.style("font-size", "14px")
.text("Average cost per report (USD)");
// Add the bars for agreement
g.selectAll(".bar-agreement")
.data(data2)
.join("rect")
.attr("class", "bar-agreement")
.attr("x", (d) => x(d.model) + xSubgroup("agreement"))
.attr("y", (d) => yLeft(d.agreementWithGoldStandard))
.attr("width", xSubgroup.bandwidth())
.attr("height", (d) => height - yLeft(d.agreementWithGoldStandard))
.attr("fill", "#006400");
// Add the bars for cost
g.selectAll(".bar-cost")
.data(data2)
.join("rect")
.attr("class", "bar-cost")
.attr("x", (d) => x(d.model) + xSubgroup("cost"))
.attr("y", (d) => yRight(d.costPerReport))
.attr("width", xSubgroup.bandwidth())
.attr("height", (d) => height - yRight(d.costPerReport))
.attr("fill", "#EEBC1D");
// Add grid lines
g.append("g")
.attr("class", "grid")
.call(d3.axisLeft(yLeft).tickSize(-width).tickFormat(""))
.style("stroke-dasharray", "3,3")
.style("opacity", 0.3);
// Add title
svg
.append("text")
.attr("x", (width + margin.left) / 2 + 30)
.attr("y", margin.top / 2)
.attr("text-anchor", "middle")
.style("font-size", "20px")
.style("font-weight", "bold")
.text("Agreement with gold standard vs average cost per report");
// Add legend
const legendData = [
{
name: "Agreement with gold standard",
lines: ["Agreement with", "gold standard"],
color: "#006400"
},
{ name: "Cost", lines: ["Cost"], color: "#EEBC1D" }
];
const legend = svg
.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 12)
.attr("text-anchor", "start");
let legendOffsetY = 10;
legendData.forEach((d, i) => {
const legendItem = legend
.append("g")
.attr(
"transform",
`translate(${margin.left + width + 50}, ${margin.top + legendOffsetY})`
);
legendItem
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 19)
.attr("height", 19)
.attr("fill", d.color);
const text = legendItem
.append("text")
.attr("x", 24)
.attr("y", 9.5)
.attr("dy", "0");
d.lines.forEach((line, index) => {
text
.append("tspan")
.attr("x", 24)
.attr("dy", index === 0 ? 0 : "1.2em")
.text(line);
});
legendOffsetY += d.lines.length * 18;
});
return svg.node();
}