Kaplan–Meier survival curves in JavaScript with CanvasXpress
Kaplan–Meier curves summarize time-to-event data: the step-down survival probability over time, usually split by a group like treatment arm. CanvasXpress computes the survival steps for you — you give it a time column and a censoring column — so in the browser it’s just a data object and a config. The result is interactive and reproducible.
Load the library
<link href="https://cdn.jsdelivr.net/gh/neuhausi/canvasXpress@69.6.1/inst/htmlwidgets/lib/canvasXpress/canvasXpress.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/gh/neuhausi/canvasXpress@69.6.1/inst/htmlwidgets/lib/canvasXpress/canvasXpress.min.js"></script>The markup and the spec
Subjects are the variables (rows); each row holds [Time, Time-Censor] (1 = censored, 0 = event). The arm rides along as a variable annotation. The key is graphType: "KaplanMeier" and passing both columns to xAxis.
<canvas id="km" width="650" height="500"></canvas>
<script>
var subj = [], rows = [], arm = [];
for (var i = 0; i < 200; i++) {
var a = Math.random() < 0.5 ? "Treatment" : "Control";
var base = a === "Treatment" ? 60 : 40; // treatment survives longer
var time = Math.round(-base * Math.log(Math.random()) * 10) / 10; // exponential
var cens = Math.random() < 0.3 ? 1 : 0; // 1 = censored, 0 = event
subj.push("Subj" + (i + 1));
rows.push([time, cens]);
arm.push(a);
}
var data = {
y: { vars: subj, smps: ["Time", "Time-Censor"], data: rows },
z: { Arm: arm }
};
var config = {
graphType: "KaplanMeier",
xAxis: ["Time", "Time-Censor"],
colorBy: "Arm",
title: "Overall survival by treatment arm",
xAxisTitle: "Time (weeks)", yAxisTitle: "Probability of survival",
invertCensored: true, // draw censoring ticks
showKMConfidenceIntervals: false
};
new CanvasXpress("km", data, config);
</script>Live result
Hover any step for the time and survival probability; the tick marks are censored subjects. CanvasXpress derives the survival curve from the raw table, so you never precompute the steps — and the same spec re-renders the identical curve from R or Python.
A common gotcha
The survival transform only fires with graphType: "KaplanMeier". A bare "Scatter2D" gives a raw scatter of the censor values instead — survival probability is computed by the engine, not a column in your data.