Public
Edited
Jul 8
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart = {
const height = 500;
const margin = {top: 40, right: 30, bottom: 30, left: 50};

const xOccupancy = d3.scaleLinear()
.domain([(startingOccupancy / 100), 0])
.range([margin.left, width - margin.right]);
const xMax = xOccupancy(steps.at(-1).occupancyPct);
const xStep = d3.scaleLinear()
.domain(d3.extent(steps, (d) => d.step))
.range([margin.left, xMax]);

const y = d3.scaleLinear()
.domain([0, d3.max(steps, d => d.dose) + 5])
.range([height - margin.bottom, margin.top]);

const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width", width)
.attr("height", height)
.attr("style", "max-width: 100%; height: auto; height: intrinsic; font: 10px sans-serif;")
.style("-webkit-tap-highlight-color", "transparent")
.style("overflow", "visible");

svg.append("g")
.attr("transform", `translate(0,${margin.top})`)
.call(d3.axisTop(xOccupancy).tickFormat(formatPct))
.call(g =>
g.append("text")
.attr("x", width - margin.right)
.attr("y", 16)
.attr("text-anchor", "end")
.attr("fill", "currentColor")
.text("Target serotonin receptor occupancy")
);
svg.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(xStep).ticks(steps.length).tickFormat(d => d * form.stepInterval))
.call(g =>
g.select(".domain")
.attr("d", function() {
return d3.select(this).attr("d").replace(xMax, width - margin.right).replace(/V\d+$/, "");
})
)
.call(g =>
g.append("text")
.attr("x", width - margin.right)
.attr("y", -10)
.attr("text-anchor", "end")
.attr("fill", "currentColor")
.text("Day")
);

svg.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y).tickFormat(formatDose))
.call(g =>
g.append("text")
.attr("x", -margin.left)
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text("Dose (mg)")
);

const lineFactory = () => d3.line()
.x(d => xOccupancy(d.occupancyPct))
.y(d => y(d.dose));
const stepPath = svg.append("path")
.datum(steps)
.attr("fill", "none")
.attr("stroke", "#eee")
.attr("stroke-width", 1.5)
.attr("d", lineFactory().curve(d3.curveStepBefore))
.lower();

const path = svg.append("path")
.datum(steps)
.attr("fill", "none")
.attr("stroke", "MediumPurple")
.attr("stroke-width", 1.5)
.attr("d", lineFactory())

const circles = svg.append("g")
.attr("fill", "MediumPurple")
.selectAll("circle")
.data(steps)
.join("circle")
.attr("cx", d => xOccupancy(d.occupancyPct))
.attr("cy", d => y(d.dose))
.attr("r", 3);

const pathOverlay = svg.append("path")
.datum(steps)
.attr("fill", "none")
.attr("stroke", "transparent")
.attr("stroke-width", 20)
.attr("d", lineFactory())
.on("pointerenter pointermove", pointermoved)
.on("pointerleave", pointerleft)
.on("touchstart", event => event.preventDefault());

const tooltip = svg.append("g")
.style("pointer-events", "none");

// Add the event listeners that show or hide the tooltip.
const bisect = d3.bisector((d, x) => x - d.occupancyPct).center;
function pointermoved(event) {
const i = bisect(steps, xOccupancy.invert(d3.pointer(event)[0]));
tooltip.style("display", null);
const yPos = y(steps[i].dose);
tooltip.attr("transform", `translate(${xOccupancy(steps[i].occupancyPct)},${yPos})`);

const path = tooltip.selectAll("path")
.data([,])
.join("path")
.attr("fill", "white")
.attr("stroke", "black")
.attr("transform", "");

const text = tooltip.selectAll("text")
.data([,])
.join("text")
.call(text => text
.selectAll("tspan")
.data([
`Step ${steps[i].step}`,
formatDose(formatDoseNumber(steps[i].dose)),
formatPct(steps[i].occupancyPct) + ' occupancy',
])
.join("tspan")
.attr("x", 0)
.attr("y", (_, i) => `${i * 1.1}em`)
.attr("font-weight", (_, i) => i ? null : "bold")
.text(d => d)
);

size(text, path);
flip(text, path, yPos);
}

function pointerleft() {
tooltip.style("display", "none");
}

// Wraps the text with a callout path of the correct size, as measured in the page.
function size(text, path) {
const {x, y, width: w, height: h} = text.node().getBBox();
text.attr("transform", `translate(${-w / 2},${15 - y})`);
path.attr("d", `M${-w / 2 - 10},5H-5l5,-5l5,5H${w / 2 + 10}v${h + 20}h-${w + 20}z`);
}

function flip(text, path, yPos) {
const {width: w, height: h} = path.node().getBBox();
if (height - yPos - h <= 0) {
path.attr("transform", "rotate(180)");
const transform = text.attr("transform");
const {y: yText} = text.node().getBBox();
text.attr("transform", transform.replace(/translate\(([\d.-]+),\s?[\d.-]+\)/, `translate($1,${10 - h - yText})`));
}
}

return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
<table>
<thead>
<tr>
<td>step</td>
<td>day</td>
<td>dose</td>
<td>receptor occupancy</td>
</tr>
</thead>
<tbody>
${steps.map(({step, occupancy, occupancyPct, dose}) => (
htl.html`<tr>
<td>${step}</td>
<td>${step * form.stepInterval}</td>
<td>${formatDose(formatDoseNumber(dose))}</td>
<td>${formatPct(occupancyPct)}</td>
</tr>`))}
</tbody>
</table>
Insert cell
steps = {
let lowestDose = form.startingDose;
let lowestOccupancy = startingOccupancy;
const steps = [
{step: 0, occupancy: startingOccupancy, occupancyPct: startingOccupancy / 100, dose: form.startingDose},
];

while ((lowestOccupancy - form.taperSpeed.value) > 0) {
const prevStep = steps.at(-1);
const targetOccupancy = prevStep.occupancy - form.taperSpeed.value;
const dose = doseAtOccupancy(targetOccupancy, medication.K, medication.Vm);
steps.push({
step: prevStep.step + 1,
occupancy: targetOccupancy,
occupancyPct: targetOccupancy / 100,
dose: dose
});
lowestOccupancy = targetOccupancy;
lowestDose = dose;
}
return steps;
}
Insert cell
function doseAtOccupancy(occupancy, K, Vm) {
return (occupancy * K) / (Vm - occupancy);
}
Insert cell
function occupancyAtDose(dose, K, Vm) {
return (dose * Vm) / (K + dose);
}
Insert cell
Insert cell
dose_sert_systemic_review_supplemental_2.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
Insert cell
Insert cell
import {tanh} from "@fil/math"
Insert cell
Insert cell
Insert cell
inputOptions = ({width: 320})
Insert cell
(viewof form).querySelectorAll('label').forEach(label => label.style.setProperty('--label-width', '200px')),
(viewof medicationName).querySelectorAll('label').forEach(label => label.style.setProperty('--label-width', '200px'))
Insert cell
tex.block`${formulaTex}`
Insert cell
formulaTex = String.raw`f\left({x,K,V_m} \right) = \frac{{V_mx}}{{K + x}}`
Insert cell
sorensen_et_al_figures.classList.add("fix-line-height")
Insert cell
<style>
.fix-line-height {
line-height: 1.6;
}
.fix-line-height sub,
.fix-line-height sup {
line-height: 0;
}
</style>
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more