Learn & Migrate

Your first ten minutes with CanvasXpress, plus translation guides from ggplot2 and Plotly — so you can reuse what you already know.

Your first ten minutes

The same chart in three languages, one engine. Pick your stack and paste.

JavaScript

<link rel="stylesheet" href="https://www.canvasxpress.org/dist/canvasXpress.css">
<script src="https://www.canvasxpress.org/dist/canvasXpress.min.js"></script>
<canvas id="chart" width="600" height="400"></canvas>
<script>
  new CanvasXpress("chart",
    { y: { vars: ["Sales"], smps: ["Q1","Q2","Q3","Q4"], data: [[10, 14, 9, 17]] } },
    { graphType: "Bar", title: "Quarterly Sales" });
</script>

R

install.packages("canvasXpress")
library(canvasXpress)

y <- matrix(c(10, 14, 9, 17), nrow = 1,
            dimnames = list("Sales", c("Q1","Q2","Q3","Q4")))
canvasXpress(data = y, graphType = "Bar", title = "Quarterly Sales")

Already have a ggplot? Wrap it: canvasXpress(ggplotObject) makes it interactive in one line — see the ggplot interface.

Python

pip install canvasxpress

from canvasxpress.canvas import CanvasXpress
from canvasxpress.data.keypair import CXDictData

chart = CanvasXpress(
    data=CXDictData({"y": {"vars": ["Sales"], "smps": ["Q1","Q2","Q3","Q4"],
                           "data": [[10, 14, 9, 17]]}}),
    config={"graphType": "Bar", "title": "Quarterly Sales"})

Next: open the examples gallery — every example is a live, editable spec you can copy.

Coming from ggplot2

CanvasXpress is built on the same grammar of graphics. Two paths: wrap an existing ggplot in R with canvasXpress(g), or author directly with cxplot, a ggplot2-style fluent builder in JavaScript. The concepts map almost one-to-one:

ggplot2CanvasXpress / cxplot
ggplot(df, aes(x, y))cx_plot(df, cx_aes(x, y)) — the data + aesthetic mapping
geom_point(), geom_line(), geom_bar()cx_geom_point(), cx_geom_line(), cx_geom_bar() — layers added with +
aes(color=, size=, shape=)same aesthetics — colour, size and shape scales resolve as in ggplot
facet_wrap(~g) / facet_grid(a~b)cx_facet_wrap(~g) / cx_facet_grid(a~b)
scale_*_manual/continuous()cx_scale_* equivalents (manual, continuous, brewer)
coord_flip(), coord_polar()cx_coord_flip(), polar coordinate support
theme_minimal(), theme_bw(), ggthemes19 built-in themes including the ggplot2 + ggthemes families
labs(title=, x=, y=)cx_labs(title=, x=, y=)
Static PNG/PDF outputInteractive by default — zoom, filter, tooltip, broadcast — and still exportable

See the cxplot interface for the full builder, or the ggplot interface for the one-line R wrapper.

Coming from Plotly

Both are declarative JSON figures with R/Python/JS APIs, so the mental model transfers. The main shift: Plotly builds a figure from traces; CanvasXpress maps a wide data matrix through a single grammar via graphType.

PlotlyCanvasXpress
A figure = list of traces + layoutOne data matrix (y/x/z) + one config
Trace type (scatter, bar, heatmap…)graphType (Scatter2D, Bar, Heatmap…)
mode: "markers"/"lines"graphType + scatterType / line options
layout.title, xaxis.titletitle, xAxisTitle
Group by splitting into multiple tracesGroup with one matrix + colorBy / annotations
fig.update_layout(...)keys in the config object
Faceting via subplotssegregateVariablesBy / segregateSamplesBy
Reproducibility handled in your codeBuilt in — the figure serializes to one portable spec (audit trail)

A fair, detailed feature comparison lives on the CanvasXpress vs. Plotly page — including where Plotly is the stronger choice.

Recipes: from your data to a figure

Nobody learns a library from a parameter index. Each recipe below starts from the object you actually have — a differential-expression table, a survival table, a MAF, an expression matrix with metadata — and shows the same figure in JavaScript, R and Python over the same data. Every snippet is run in all three languages before it is published (tools/recipes/verify.sh), and every JS spec validates against the published schema. Paste one, swap in your data.

1. Volcano plot from a differential-expression table

A DESeq2 / limma result has one row per gene with a log fold-change and a p-value. Put genes in vars, the two statistics in smps, and the significance call in a variable annotation to colour by.

JavaScript
<canvas id="chart" width="700" height="450"></canvas>
<script>
  var data = {"y":{"vars":["BRCA1","TP53","EGFR","MYC","KRAS","PTEN","CDK4","RB1","ATM","VEGFA","IL6","STAT3"],"smps":["log2FoldChange","-log10p"],"data":[[2.4,5.1],[-1.9,4.2],[1.1,1.3],[3.0,6.5],[0.2,0.4],[-2.6,5.8],[1.8,2.9],[-0.4,0.7],[0.1,0.2],[2.1,3.6],[-1.5,3.1],[0.6,1.0]]},"z":{"Significance":["Up","Down","NS","Up","NS","Down","Up","NS","NS","Up","Down","NS"]}};
  new CanvasXpress("chart", data, {
    "graphType": "Scatter2D",
    "xAxis": [
      "log2FoldChange"
    ],
    "yAxis": [
      "-log10p"
    ],
    "colorBy": "Significance",
    "colors": [
      "#1f77b4",
      "#7f7f7f",
      "#d62728"
    ],
    "showDecorations": true,
    "decorations": {
      "line": [
        {
          "x": 1,
          "color": "rgba(0,0,0,.4)",
          "width": 1
        },
        {
          "x": -1,
          "color": "rgba(0,0,0,.4)",
          "width": 1
        },
        {
          "y": 1.3,
          "color": "rgba(0,0,0,.4)",
          "width": 1
        }
      ]
    },
    "title": "Treated vs control",
    "xAxisTitle": "log2 fold change",
    "yAxisTitle": "-log10 p-value"
  });
</script>
R
library(canvasXpress)
# res: a DESeq2 results() data.frame (log2FoldChange, pvalue), rownames = genes
res <- data.frame(row.names = c("BRCA1", "TP53", "EGFR", "MYC", "KRAS", "PTEN", "CDK4", "RB1", "ATM", "VEGFA", "IL6", "STAT3"),
                  log2FoldChange = c(2.4, -1.9, 1.1, 3.0, 0.2, -2.6, 1.8, -0.4, 0.1, 2.1, -1.5, 0.6),
                  pvalue = 10^-c(5.1, 4.2, 1.3, 6.5, 0.4, 5.8, 2.9, 0.7, 0.2, 3.6, 3.1, 1.0))
y <- cbind(log2FoldChange = res$log2FoldChange, `-log10p` = -log10(res$pvalue))
rownames(y) <- rownames(res)
z <- data.frame(Significance = ifelse(res$log2FoldChange > 1 & res$pvalue < 0.05, "Up",
                ifelse(res$log2FoldChange < -1 & res$pvalue < 0.05, "Down", "NS")),
                row.names = rownames(res))
canvasXpress(data = y, varAnnot = z, graphType = "Scatter2D",
             xAxis = "log2FoldChange", yAxis = "-log10p", colorBy = "Significance",
             colors = c("#1f77b4", "#7f7f7f", "#d62728"), showDecorations = TRUE,
             decorations = list(line = list(list(x = 1), list(x = -1), list(y = 1.3))),
             title = "Treated vs control", xAxisTitle = "log2 fold change", yAxisTitle = "-log10 p-value")
Python
import pandas as pd
from canvasxpress.canvas import CanvasXpress
from canvasxpress.data.keypair import CXDictData
# res: a DESeq2-style result table, index = genes
res = pd.DataFrame({"log2FoldChange": [2.4, -1.9, 1.1, 3.0, 0.2, -2.6, 1.8, -0.4, 0.1, 2.1, -1.5, 0.6],
                    "pvalue": [10**-x for x in [5.1, 4.2, 1.3, 6.5, 0.4, 5.8, 2.9, 0.7, 0.2, 3.6, 3.1, 1.0]]},
                   index=["BRCA1", "TP53", "EGFR", "MYC", "KRAS", "PTEN", "CDK4", "RB1", "ATM", "VEGFA", "IL6", "STAT3"])
res["-log10p"] = -res["pvalue"].apply(lambda p: __import__("math").log10(p))
sig = ["Up" if f > 1 and p < 0.05 else "Down" if f < -1 and p < 0.05 else "NS"
       for f, p in zip(res["log2FoldChange"], res["pvalue"])]
chart = CanvasXpress(
    data=CXDictData({"y": {"vars": list(res.index), "smps": ["log2FoldChange", "-log10p"],
                           "data": res[["log2FoldChange", "-log10p"]].values.tolist()},
                     "z": {"Significance": sig}}),
    config={"graphType": "Scatter2D", "xAxis": ["log2FoldChange"], "yAxis": ["-log10p"],
            "colorBy": "Significance", "colors": ["#1f77b4", "#7f7f7f", "#d62728"],
            "showDecorations": True,
            "decorations": {"line": [{"x": 1}, {"x": -1}, {"y": 1.3}]},
            "title": "Treated vs control", "xAxisTitle": "log2 fold change", "yAxisTitle": "-log10 p-value"})

2. Kaplan–Meier curves from a survival table

No model object needed: give the engine one row per subject with time and status (1 = event) and a grouping annotation. graphType: "KaplanMeier" computes the estimator, confidence bands, median survival and the risk table itself.

JavaScript
<canvas id="chart" width="700" height="450"></canvas>
<script>
  var data = {"y":{"vars":["P01","P02","P03","P04","P05","P06","P07","P08","P09","P10","P11","P12","P13","P14","P15","P16","P17","P18","P19","P20","P21","P22","P23","P24"],"smps":["time","status"],"data":[[306,1],[455,1],[1010,0],[210,1],[883,1],[1022,0],[310,1],[361,1],[218,1],[166,1],[170,1],[654,1],[728,1],[71,1],[567,1],[144,1],[613,1],[707,1],[61,1],[88,1],[301,1],[81,1],[624,1],[371,1]]},"z":{"Arm":["A","B","A","B","A","B","A","B","A","B","A","B","A","B","A","B","A","B","A","B","A","B","A","B"]}};
  new CanvasXpress("chart", data, {
    "graphType": "KaplanMeier",
    "xAxis": [
      "time"
    ],
    "yAxis": [
      "status"
    ],
    "colorBy": "Arm",
    "showKMConfidenceIntervals": true,
    "showKMMedianSurvivalTime": true,
    "kmRiskTable": true,
    "xAxisTitle": "Days",
    "yAxisTitle": "Survival probability",
    "title": "Overall survival by arm"
  });
</script>
R
library(canvasXpress)
# surv: one row per subject, as you would pass to survival::Surv(time, status)
surv <- data.frame(row.names = c("P01", "P02", "P03", "P04", "P05", "P06", "P07", "P08", "P09", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"),
                   time = c(306, 455, 1010, 210, 883, 1022, 310, 361, 218, 166, 170, 654, 728, 71, 567, 144, 613, 707, 61, 88, 301, 81, 624, 371), status = c(1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), Arm = c("A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", "B"))
canvasXpress(data = as.matrix(surv[, c("time", "status")]), varAnnot = surv["Arm"],
             graphType = "KaplanMeier", xAxis = "time", yAxis = "status", colorBy = "Arm",
             showKMConfidenceIntervals = TRUE, showKMMedianSurvivalTime = TRUE, kmRiskTable = TRUE,
             xAxisTitle = "Days", yAxisTitle = "Survival probability", title = "Overall survival by arm")
Python
import pandas as pd
from canvasxpress.canvas import CanvasXpress
from canvasxpress.data.keypair import CXDictData
surv = pd.DataFrame({"time": [306, 455, 1010, 210, 883, 1022, 310, 361, 218, 166, 170, 654, 728, 71, 567, 144, 613, 707, 61, 88, 301, 81, 624, 371], "status": [1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], "Arm": ["A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", "B", "A", "B"]}, index=["P01", "P02", "P03", "P04", "P05", "P06", "P07", "P08", "P09", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"])
chart = CanvasXpress(
    data=CXDictData({"y": {"vars": list(surv.index), "smps": ["time", "status"],
                           "data": surv[["time", "status"]].values.tolist()},
                     "z": {"Arm": list(surv["Arm"])}}),
    config={"graphType": "KaplanMeier", "xAxis": ["time"], "yAxis": ["status"], "colorBy": "Arm",
            "showKMConfidenceIntervals": True, "showKMMedianSurvivalTime": True, "kmRiskTable": True,
            "xAxisTitle": "Days", "yAxisTitle": "Survival probability", "title": "Overall survival by arm"})

3. Oncoprint from a MAF-style mutation table

From a MAF (one row per sample × gene × variant) build two sample × gene string matrices — copy-number and mutation class — and pass them as extra y layers next to a presence flag. Samples are the vars, genes the smps; the oncoprint draws genes down the side with per-gene alteration frequencies.

JavaScript
<canvas id="chart" width="700" height="450"></canvas>
<script>
  var data = {"y":{"vars":["S1","S2","S3","S4","S5","S6","S7","S8"],"smps":["TP53","KRAS","EGFR","PIK3CA","BRAF","PTEN"],"data":[[1,0,0,1,0,0],[0,1,0,0,0,1],[1,0,0,1,0,0],[0,0,1,0,0,0],[1,1,0,0,0,0],[0,1,0,0,1,0],[0,0,0,1,0,1],[1,0,0,0,0,1]],"data2":[["","","","","",""],["","","","","","Deletion"],["","","","","",""],["","","Amplification","","",""],["","","","","",""],["","","","","",""],["","","","","","Deletion"],["","","","","",""]],"data3":[["Missense","","","Missense","",""],["","Missense","","","",""],["Nonsense","","","Missense","",""],["","","","","",""],["Missense","Missense","","","",""],["","Missense","","","Missense",""],["","","","Missense","",""],["Frameshift","","","","","Frameshift"]]},"z":{"Subtype":["LUAD","LUSC","LUAD","LUAD","LUSC","LUAD","LUSC","LUAD"]}};
  new CanvasXpress("chart", data, {
    "graphType": "Oncoprint",
    "oncoprintCNA": "data2",
    "oncoprintMUT": "data3",
    "varOverlays": [
      "Subtype"
    ],
    "title": "Alterations in 8 tumours"
  });
</script>
R
library(canvasXpress)
# From a MAF: one row per sample x gene x variant -> two sample x gene string matrices
genes <- c("TP53", "KRAS", "EGFR", "PIK3CA", "BRAF", "PTEN"); smps <- c("S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8")
cna <- matrix(c("", "", "", "", "", "", "", "", "", "", "", "Deletion", "", "", "", "", "", "", "", "", "Amplification", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "Deletion", "", "", "", "", "", ""), nrow = 8, byrow = TRUE, dimnames = list(smps, genes))
mut <- matrix(c("Missense", "", "", "Missense", "", "", "", "Missense", "", "", "", "", "Nonsense", "", "", "Missense", "", "", "", "", "", "", "", "", "Missense", "Missense", "", "", "", "", "", "Missense", "", "", "Missense", "", "", "", "", "Missense", "", "", "Frameshift", "", "", "", "", "Frameshift"), nrow = 8, byrow = TRUE, dimnames = list(smps, genes))
present <- (cna != "" | mut != "") * 1
z <- data.frame(Subtype = c("LUAD", "LUSC", "LUAD", "LUAD", "LUSC", "LUAD", "LUSC", "LUAD"), row.names = smps)
# samples are the rows (vars), genes the columns (smps): the oncoprint draws genes down the side.
# Extra layers travel as named matrices in the data list; "Oncoprint" is the Heatmap engine.
canvasXpress(data = list(y = present, data2 = cna, data3 = mut), varAnnot = z,
             graphType = "Heatmap", oncoprintCNA = "data2", oncoprintMUT = "data3",
             varOverlays = "Subtype", title = "Alterations in 8 tumours")
Python
from canvasxpress.canvas import CanvasXpress
from canvasxpress.data.keypair import CXDictData
# From a MAF: one row per sample x gene x variant -> two sample x gene string matrices
genes = ["TP53", "KRAS", "EGFR", "PIK3CA", "BRAF", "PTEN"]; smps = ["S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8"]
cna = [["", "", "", "", "", ""], ["", "", "", "", "", "Deletion"], ["", "", "", "", "", ""], ["", "", "Amplification", "", "", ""], ["", "", "", "", "", ""], ["", "", "", "", "", ""], ["", "", "", "", "", "Deletion"], ["", "", "", "", "", ""]]
mut = [["Missense", "", "", "Missense", "", ""], ["", "Missense", "", "", "", ""], ["Nonsense", "", "", "Missense", "", ""], ["", "", "", "", "", ""], ["Missense", "Missense", "", "", "", ""], ["", "Missense", "", "", "Missense", ""], ["", "", "", "Missense", "", ""], ["Frameshift", "", "", "", "", "Frameshift"]]
present = [[1 if (c or m) else 0 for c, m in zip(cr, mr)] for cr, mr in zip(cna, mut)]
# samples are the rows (vars), genes the columns (smps): the oncoprint draws genes down the side
chart = CanvasXpress(
    data=CXDictData({"y": {"vars": smps, "smps": genes, "data": present, "data2": cna, "data3": mut},
                     "z": {"Subtype": ["LUAD", "LUSC", "LUAD", "LUAD", "LUSC", "LUAD", "LUSC", "LUAD"]}}),
    config={"graphType": "Oncoprint", "oncoprintCNA": "data2", "oncoprintMUT": "data3",
            "varOverlays": ["Subtype"], "title": "Alterations in 8 tumours"})

4. Clustered heatmap from an expression matrix with sample metadata

A SummarizedExperiment is an assay matrix plus colData and rowData. Those map one-to-one onto y, x (sample annotations) and z (variable annotations). Clustering and dendrograms run inside the engine.

JavaScript
<canvas id="chart" width="700" height="450"></canvas>
<script>
  var data = {"y":{"vars":["G1","G2","G3","G4","G5","G6","G7","G8","G9","G10","G11","G12"],"smps":["Ctl1","Ctl2","Ctl3","Trt1","Trt2","Trt3"],"data":[[5.0,5.75,5.25,8.0,7.5,7.0],[5.5,5.0,5.75,3.75,4.5,4.0],[6.0,5.5,5.0,5.75,5.25,6.0],[5.25,6.0,5.5,7.0,7.75,7.25],[5.75,5.25,6.0,4.0,3.5,4.25],[5.0,5.75,5.25,6.0,5.5,5.0],[5.5,5.0,5.75,7.25,8.0,7.5],[6.0,5.5,5.0,4.25,3.75,4.5],[5.25,6.0,5.5,5.0,5.75,5.25],[5.75,5.25,6.0,7.5,7.0,7.75],[5.0,5.75,5.25,4.5,4.0,3.5],[5.5,5.0,5.75,5.25,6.0,5.5]]},"x":{"Condition":["Control","Control","Control","Treated","Treated","Treated"]},"z":{"Pathway":["Apoptosis","Cell cycle","Signalling","Apoptosis","Cell cycle","Signalling","Apoptosis","Cell cycle","Signalling","Apoptosis","Cell cycle","Signalling"]}};
  new CanvasXpress("chart", data, {
    "graphType": "Heatmap",
    "samplesClustered": true,
    "variablesClustered": true,
    "smpOverlays": [
      "Condition"
    ],
    "varOverlays": [
      "Pathway"
    ],
    "colorSpectrum": [
      "#2166ac",
      "#f7f7f7",
      "#b2182b"
    ],
    "heatmapIndicatorPosition": "topRight",
    "title": "Expression (log2 TPM)"
  });
</script>
R
library(canvasXpress)
# se: a SummarizedExperiment -> assay(se), colData(se), rowData(se)
y <- matrix(c(5.0, 5.75, 5.25, 8.0, 7.5, 7.0, 5.5, 5.0, 5.75, 3.75, 4.5, 4.0, 6.0, 5.5, 5.0, 5.75, 5.25, 6.0, 5.25, 6.0, 5.5, 7.0, 7.75, 7.25, 5.75, 5.25, 6.0, 4.0, 3.5, 4.25, 5.0, 5.75, 5.25, 6.0, 5.5, 5.0, 5.5, 5.0, 5.75, 7.25, 8.0, 7.5, 6.0, 5.5, 5.0, 4.25, 3.75, 4.5, 5.25, 6.0, 5.5, 5.0, 5.75, 5.25, 5.75, 5.25, 6.0, 7.5, 7.0, 7.75, 5.0, 5.75, 5.25, 4.5, 4.0, 3.5, 5.5, 5.0, 5.75, 5.25, 6.0, 5.5), nrow = 12, byrow = TRUE,
            dimnames = list(c("G1", "G2", "G3", "G4", "G5", "G6", "G7", "G8", "G9", "G10", "G11", "G12"), c("Ctl1", "Ctl2", "Ctl3", "Trt1", "Trt2", "Trt3")))
x <- data.frame(Condition = rep(c("Control", "Treated"), each = 3), row.names = colnames(y))   # colData
z <- data.frame(Pathway = rep(c("Apoptosis", "Cell cycle", "Signalling"), 4), row.names = rownames(y))  # rowData
canvasXpress(data = y, smpAnnot = x, varAnnot = z, graphType = "Heatmap",
             samplesClustered = TRUE, variablesClustered = TRUE,
             smpOverlays = "Condition", varOverlays = "Pathway",
             colorSpectrum = c("#2166ac", "#f7f7f7", "#b2182b"),
             heatmapIndicatorPosition = "topRight", title = "Expression (log2 TPM)")
Python
import pandas as pd
from canvasxpress.canvas import CanvasXpress
from canvasxpress.data.keypair import CXDictData
expr = pd.DataFrame([[5.0, 5.75, 5.25, 8.0, 7.5, 7.0], [5.5, 5.0, 5.75, 3.75, 4.5, 4.0], [6.0, 5.5, 5.0, 5.75, 5.25, 6.0], [5.25, 6.0, 5.5, 7.0, 7.75, 7.25], [5.75, 5.25, 6.0, 4.0, 3.5, 4.25], [5.0, 5.75, 5.25, 6.0, 5.5, 5.0], [5.5, 5.0, 5.75, 7.25, 8.0, 7.5], [6.0, 5.5, 5.0, 4.25, 3.75, 4.5], [5.25, 6.0, 5.5, 5.0, 5.75, 5.25], [5.75, 5.25, 6.0, 7.5, 7.0, 7.75], [5.0, 5.75, 5.25, 4.5, 4.0, 3.5], [5.5, 5.0, 5.75, 5.25, 6.0, 5.5]], index=["G1", "G2", "G3", "G4", "G5", "G6", "G7", "G8", "G9", "G10", "G11", "G12"], columns=["Ctl1", "Ctl2", "Ctl3", "Trt1", "Trt2", "Trt3"])       # assay
col_data = pd.DataFrame({"Condition": ["Control"] * 3 + ["Treated"] * 3}, index=expr.columns)
row_data = pd.DataFrame({"Pathway": ["Apoptosis", "Cell cycle", "Signalling"] * 4}, index=expr.index)
chart = CanvasXpress(
    data=CXDictData({"y": {"vars": list(expr.index), "smps": list(expr.columns), "data": expr.values.tolist()},
                     "x": col_data.to_dict("list"), "z": row_data.to_dict("list")}),
    config={"graphType": "Heatmap", "samplesClustered": True, "variablesClustered": True,
            "smpOverlays": ["Condition"], "varOverlays": ["Pathway"],
            "colorSpectrum": ["#2166ac", "#f7f7f7", "#b2182b"],
            "heatmapIndicatorPosition": "topRight", "title": "Expression (log2 TPM)"})

5. Boxplot by treatment group with the points and a test

Tidy data (one measurement per sample, group in a column) becomes a one-row matrix with the group as a sample annotation. groupingFactors does the split; the engine overlays the raw points and can annotate a test. One-dimensional charts default to horizontal; graphOrientation flips them, smpTitle names the group axis and xAxisTitle the value axis.

JavaScript
<canvas id="chart" width="700" height="450"></canvas>
<script>
  var data = {"y":{"vars":["Biomarker"],"smps":["s1","s2","s3","s4","s5","s6","s7","s8","s9","s10","s11","s12","s13","s14","s15","s16","s17","s18"],"data":[[5.1,4.8,5.5,5.0,4.6,5.3,6.2,6.8,5.9,6.5,7.0,6.1,8.1,7.6,8.4,7.9,8.8,8.0]]},"x":{"Dose":["Placebo","Placebo","Placebo","Placebo","Placebo","Placebo","Low","Low","Low","Low","Low","Low","High","High","High","High","High","High"]}};
  new CanvasXpress("chart", data, {
    "graphType": "Boxplot",
    "groupingFactors": [
      "Dose"
    ],
    "colorBy": "Dose",
    "showBoxplotOriginalData": true,
    "jitter": true,
    "title": "Biomarker by dose",
    "graphOrientation": "vertical",
    "smpTitle": "Dose group",
    "xAxisTitle": "Biomarker (ng/mL)",
    "groupingFactorLevelsOrder": {
      "Dose": [
        "Placebo",
        "Low",
        "High"
      ]
    }
  });
</script>
R
library(canvasXpress)
# tidy: one row per sample
tidy <- data.frame(sample = c("s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "s12", "s13", "s14", "s15", "s16", "s17", "s18"), Dose = c("Placebo", "Placebo", "Placebo", "Placebo", "Placebo", "Placebo", "Low", "Low", "Low", "Low", "Low", "Low", "High", "High", "High", "High", "High", "High"), Biomarker = c(5.1, 4.8, 5.5, 5.0, 4.6, 5.3, 6.2, 6.8, 5.9, 6.5, 7.0, 6.1, 8.1, 7.6, 8.4, 7.9, 8.8, 8.0))
y <- matrix(tidy$Biomarker, nrow = 1, dimnames = list("Biomarker", tidy$sample))
x <- data.frame(Dose = tidy$Dose, row.names = tidy$sample)
canvasXpress(data = y, smpAnnot = x, graphType = "Boxplot", groupingFactors = "Dose", colorBy = "Dose",
             showBoxplotOriginalData = TRUE, jitter = TRUE,
             groupingFactorLevelsOrder = list(Dose = c("Placebo", "Low", "High")),
             graphOrientation = "vertical", smpTitle = "Dose group", xAxisTitle = "Biomarker (ng/mL)", title = "Biomarker by dose")
Python
import pandas as pd
from canvasxpress.canvas import CanvasXpress
from canvasxpress.data.keypair import CXDictData
tidy = pd.DataFrame({"sample": ["s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "s12", "s13", "s14", "s15", "s16", "s17", "s18"], "Dose": ["Placebo", "Placebo", "Placebo", "Placebo", "Placebo", "Placebo", "Low", "Low", "Low", "Low", "Low", "Low", "High", "High", "High", "High", "High", "High"], "Biomarker": [5.1, 4.8, 5.5, 5.0, 4.6, 5.3, 6.2, 6.8, 5.9, 6.5, 7.0, 6.1, 8.1, 7.6, 8.4, 7.9, 8.8, 8.0]})
chart = CanvasXpress(
    data=CXDictData({"y": {"vars": ["Biomarker"], "smps": list(tidy["sample"]), "data": [list(tidy["Biomarker"])]},
                     "x": {"Dose": list(tidy["Dose"])}}),
    config={"graphType": "Boxplot", "groupingFactors": ["Dose"], "colorBy": "Dose",
            "showBoxplotOriginalData": True, "jitter": True,
            "groupingFactorLevelsOrder": {"Dose": ["Placebo", "Low", "High"]},
            "graphOrientation": "vertical", "smpTitle": "Dose group", "xAxisTitle": "Biomarker (ng/mL)", "title": "Biomarker by dose"})

6. PCA of samples, coloured by condition

Run the PCA in your language (R's prcomp, numpy's SVD), then plot the scores: samples become vars, the components smps, the design a variable annotation to colour by (add ellipseBy for group ellipses once you have more than a handful of samples per group). The JavaScript version carries the scores the R snippet computed.

JavaScript
<canvas id="chart" width="700" height="450"></canvas>
<script>
  var data = {"y":{"vars":["Ctl1","Ctl2","Ctl3","Ctl4","Trt1","Trt2","Trt3","Trt4"],"smps":["PC1","PC2"],"data":[[-3.165,1.931],[-2.649,0.046],[-3.296,-2.65],[-3.315,1.666],[3.166,-0.953],[3.467,1.213],[1.872,-3.296],[3.921,2.042]]},"z":{"Condition":["Control","Control","Control","Control","Treated","Treated","Treated","Treated"]}};
  new CanvasXpress("chart", data, {
    "graphType": "Scatter2D",
    "xAxis": [
      "PC1"
    ],
    "yAxis": [
      "PC2"
    ],
    "colorBy": "Condition",
    "xAxisTitle": "PC1 (46%)",
    "yAxisTitle": "PC2 (18%)",
    "title": "PCA"
  });
</script>
R
library(canvasXpress)
set.seed(1)
expr <- matrix(rnorm(200), 25, 8, dimnames = list(paste0("G", 1:25), c("Ctl1", "Ctl2", "Ctl3", "Ctl4", "Trt1", "Trt2", "Trt3", "Trt4")))
expr[1:10, 5:8] <- expr[1:10, 5:8] + 3                       # a treatment effect
pca <- prcomp(t(expr), scale. = TRUE)
scores <- pca$x[, 1:2]
pct <- round(100 * pca$sdev^2 / sum(pca$sdev^2))[1:2]
z <- data.frame(Condition = rep(c("Control", "Treated"), each = 4), row.names = rownames(scores))
canvasXpress(data = scores, varAnnot = z, graphType = "Scatter2D", xAxis = "PC1", yAxis = "PC2",
             colorBy = "Condition",
             xAxisTitle = paste0("PC1 (", pct[1], "%)"), yAxisTitle = paste0("PC2 (", pct[2], "%)"), title = "PCA")
Python
import numpy as np
from canvasxpress.canvas import CanvasXpress
from canvasxpress.data.keypair import CXDictData
rng = np.random.default_rng(1)
samples = ["Ctl1", "Ctl2", "Ctl3", "Ctl4", "Trt1", "Trt2", "Trt3", "Trt4"]
expr = rng.normal(size=(25, 8)); expr[:10, 4:] += 3           # genes x samples, a treatment effect
X = (expr.T - expr.T.mean(0)) / expr.T.std(0)                    # samples x genes, scaled
U, S, Vt = np.linalg.svd(X, full_matrices=False)
scores = (U * S)[:, :2]; pct = np.round(100 * S**2 / (S**2).sum())[:2]
chart = CanvasXpress(
    data=CXDictData({"y": {"vars": samples, "smps": ["PC1", "PC2"], "data": scores.tolist()},
                     "z": {"Condition": ["Control"] * 4 + ["Treated"] * 4}}),
    config={"graphType": "Scatter2D", "xAxis": ["PC1"], "yAxis": ["PC2"], "colorBy": "Condition",
                        "xAxisTitle": f"PC1 ({int(pct[0])}%)", "yAxisTitle": f"PC2 ({int(pct[1])}%)", "title": "PCA"})

7. Correlation matrix of clinical variables

Pass the raw variables × subjects matrix; the engine computes and draws the correlation matrix. Nothing is precomputed, so hover still resolves to the underlying values.

JavaScript
<canvas id="chart" width="700" height="450"></canvas>
<script>
  var data = {"y":{"vars":["Height","Weight","BMI","Age","Glucose"],"smps":["p1","p2","p3","p4","p5","p6","p7","p8","p9","p10","p11","p12"],"data":[[170,180,165,190,175,160,185,172,168,178,182,163],[65,85,60,95,78,55,90,70,66,80,88,58],[22.5,26.2,22.0,26.3,25.5,21.5,26.3,23.7,23.4,25.2,26.6,21.8],[34,45,29,52,41,27,48,38,33,44,50,30],[88,102,84,110,96,80,105,90,86,99,108,83]]}};
  new CanvasXpress("chart", data, {
    "graphType": "Correlation",
    "correlationAxis": "variables",
    "title": "Pearson correlation"
  });
</script>
R
library(canvasXpress)
y <- matrix(c(170, 180, 165, 190, 175, 160, 185, 172, 168, 178, 182, 163, 65, 85, 60, 95, 78, 55, 90, 70, 66, 80, 88, 58, 22.5, 26.2, 22.0, 26.3, 25.5, 21.5, 26.3, 23.7, 23.4, 25.2, 26.6, 21.8, 34, 45, 29, 52, 41, 27, 48, 38, 33, 44, 50, 30, 88, 102, 84, 110, 96, 80, 105, 90, 86, 99, 108, 83), nrow = 5, byrow = TRUE, dimnames = list(c("Height", "Weight", "BMI", "Age", "Glucose"), c("p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9", "p10", "p11", "p12")))
canvasXpress(data = y, graphType = "Correlation", correlationAxis = "variables", title = "Pearson correlation")
Python
from canvasxpress.canvas import CanvasXpress
from canvasxpress.data.keypair import CXDictData
chart = CanvasXpress(
    data=CXDictData({"y": {"vars": ["Height", "Weight", "BMI", "Age", "Glucose"], "smps": ["p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9", "p10", "p11", "p12"], "data": [[170, 180, 165, 190, 175, 160, 185, 172, 168, 178, 182, 163], [65, 85, 60, 95, 78, 55, 90, 70, 66, 80, 88, 58], [22.5, 26.2, 22.0, 26.3, 25.5, 21.5, 26.3, 23.7, 23.4, 25.2, 26.6, 21.8], [34, 45, 29, 52, 41, 27, 48, 38, 33, 44, 50, 30], [88, 102, 84, 110, 96, 80, 105, 90, 86, 99, 108, 83]]}}),
    config={"graphType": "Correlation", "correlationAxis": "variables", "title": "Pearson correlation"})

8. Interaction network from an edge list

Two tables — nodes and edges — are the whole input. Node attributes drive colour and size. A circular layout is the readable choice for a small pathway; switch networkLayoutType to forceDirected for large graphs (the layout is seeded, so it reproduces).

JavaScript
<canvas id="chart" width="700" height="450"></canvas>
<script>
  var data = {"nodes":[{"id":"TP53","name":"TP53","type":"TSG"},{"id":"MDM2","name":"MDM2","type":"Oncogene"},{"id":"CDKN1A","name":"CDKN1A","type":"TSG"},{"id":"BAX","name":"BAX","type":"Effector"},{"id":"ATM","name":"ATM","type":"Kinase"},{"id":"CHEK2","name":"CHEK2","type":"Kinase"}],"edges":[{"id1":"ATM","id2":"CHEK2","type":"activates"},{"id1":"CHEK2","id2":"TP53","type":"activates"},{"id1":"ATM","id2":"TP53","type":"activates"},{"id1":"TP53","id2":"MDM2","type":"induces"},{"id1":"MDM2","id2":"TP53","type":"inhibits"},{"id1":"TP53","id2":"CDKN1A","type":"induces"},{"id1":"TP53","id2":"BAX","type":"induces"}]};
  new CanvasXpress("chart", data, {
    "graphType": "Network",
    "colorNodeBy": "type",
    "networkLayoutType": "circular",
    "showAnimation": false,
    "title": "p53 signalling",
    "nodeFontSize": 14,
    "nodeSize": 24
  });
</script>
R
library(canvasXpress)
nodes <- data.frame(id = c("TP53", "MDM2", "CDKN1A", "BAX", "ATM", "CHEK2"), name = c("TP53", "MDM2", "CDKN1A", "BAX", "ATM", "CHEK2"), type = c("TSG", "Oncogene", "TSG", "Effector", "Kinase", "Kinase"))
edges <- data.frame(id1 = c("ATM", "CHEK2", "ATM", "TP53", "MDM2", "TP53", "TP53"), id2 = c("CHEK2", "TP53", "TP53", "MDM2", "TP53", "CDKN1A", "BAX"), type = c("activates", "activates", "activates", "induces", "inhibits", "induces", "induces"))
canvasXpress(nodeData = nodes, edgeData = edges, graphType = "Network", colorNodeBy = "type",
             networkLayoutType = "circular", nodeSize = 24, nodeFontSize = 14, showAnimation = FALSE, title = "p53 signalling")
Python
from canvasxpress.canvas import CanvasXpress
from canvasxpress.data.keypair import CXDictData
nodes = [{"id": "TP53", "name": "TP53", "type": "TSG"}, {"id": "MDM2", "name": "MDM2", "type": "Oncogene"}, {"id": "CDKN1A", "name": "CDKN1A", "type": "TSG"}, {"id": "BAX", "name": "BAX", "type": "Effector"}, {"id": "ATM", "name": "ATM", "type": "Kinase"}, {"id": "CHEK2", "name": "CHEK2", "type": "Kinase"}]
edges = [{"id1": "ATM", "id2": "CHEK2", "type": "activates"}, {"id1": "CHEK2", "id2": "TP53", "type": "activates"}, {"id1": "ATM", "id2": "TP53", "type": "activates"}, {"id1": "TP53", "id2": "MDM2", "type": "induces"}, {"id1": "MDM2", "id2": "TP53", "type": "inhibits"}, {"id1": "TP53", "id2": "CDKN1A", "type": "induces"}, {"id1": "TP53", "id2": "BAX", "type": "induces"}]
chart = CanvasXpress(
    data=CXDictData({"nodes": nodes, "edges": edges}),
    config={"graphType": "Network", "colorNodeBy": "type", "networkLayoutType": "circular", "nodeSize": 24, "nodeFontSize": 14,
            "showAnimation": False, "title": "p53 signalling"})

9. Time series with real dates

Dates go in smps as ISO strings; isGraphTime + timeFormat make the engine parse and space them as time, so gaps are honest. In one-dimensional charts the sample axis is titled with smpTitle and the value axis with xAxisTitle.

JavaScript
<canvas id="chart" width="700" height="450"></canvas>
<script>
  var data = {"y":{"vars":["Enrolled","Completed"],"smps":["2026-01-01","2026-01-02","2026-01-03","2026-01-04","2026-01-05","2026-01-06","2026-01-07","2026-01-08","2026-01-09","2026-01-10","2026-01-11","2026-01-12","2026-01-13","2026-01-14"],"data":[[12,14,13,17,19,18,22,25,24,27,29,28,31,30],[8,9,9,11,12,12,14,15,15,17,18,18,20,21]]}};
  new CanvasXpress("chart", data, {
    "graphType": "Line",
    "isGraphTime": true,
    "timeFormat": "isoDate",
    "lineDecoration": "symbol",
    "title": "Cumulative enrolment",
    "smpTitle": "Date",
    "xAxisTitle": "Subjects"
  });
</script>
R
library(canvasXpress)
dates <- as.character(seq(as.Date("2026-01-01"), by = "day", length.out = 14))
y <- matrix(c(12, 14, 13, 17, 19, 18, 22, 25, 24, 27, 29, 28, 31, 30, 8, 9, 9, 11, 12, 12, 14, 15, 15, 17, 18, 18, 20, 21), nrow = 2, byrow = TRUE, dimnames = list(c("Enrolled", "Completed"), dates))
canvasXpress(data = y, graphType = "Line", isGraphTime = TRUE, timeFormat = "isoDate", lineDecoration = "symbol",
             smpTitle = "Date", xAxisTitle = "Subjects", title = "Cumulative enrolment")
Python
import pandas as pd
from canvasxpress.canvas import CanvasXpress
from canvasxpress.data.keypair import CXDictData
dates = [d.strftime("%Y-%m-%d") for d in pd.date_range("2026-01-01", periods=14)]
chart = CanvasXpress(
    data=CXDictData({"y": {"vars": ["Enrolled", "Completed"], "smps": dates, "data": [[12, 14, 13, 17, 19, 18, 22, 25, 24, 27, 29, 28, 31, 30], [8, 9, 9, 11, 12, 12, 14, 15, 15, 17, 18, 18, 20, 21]]}}),
    config={"graphType": "Line", "isGraphTime": True, "timeFormat": "isoDate", "lineDecoration": "symbol",
            "smpTitle": "Date", "xAxisTitle": "Subjects", "title": "Cumulative enrolment"})

10. Stacked bar of category counts (a contingency table)

A table() / crosstab is already a matrix: categories in vars, groups in smps. Switch graphType to StackedPercent for proportions, or Bar for dodged.

JavaScript
<canvas id="chart" width="700" height="450"></canvas>
<script>
  var data = {"y":{"vars":["Responder","Stable","Progressive"],"smps":["Arm A","Arm B","Arm C"],"data":[[12,9,15],[6,8,5],[4,7,3]]}};
  new CanvasXpress("chart", data, {
    "graphType": "Stacked",
    "colorScheme": "Tableau",
    "xAxisTitle": "Patients",
    "title": "Best response by arm",
    "legendPosition": "right"
  });
</script>
R
library(canvasXpress)
tab <- matrix(c(12, 9, 15, 6, 8, 5, 4, 7, 3), nrow = 3, byrow = TRUE, dimnames = list(c("Responder", "Stable", "Progressive"), c("Arm A", "Arm B", "Arm C")))   # e.g. table(response, arm)
canvasXpress(data = tab, graphType = "Stacked", colorScheme = "Tableau",
             xAxisTitle = "Patients", title = "Best response by arm", legendPosition = "right")
Python
import pandas as pd
from canvasxpress.canvas import CanvasXpress
from canvasxpress.data.keypair import CXDictData
tab = pd.DataFrame([[12, 9, 15], [6, 8, 5], [4, 7, 3]], index=["Responder", "Stable", "Progressive"], columns=["Arm A", "Arm B", "Arm C"])   # e.g. pd.crosstab(response, arm)
chart = CanvasXpress(
    data=CXDictData({"y": {"vars": list(tab.index), "smps": list(tab.columns), "data": tab.values.tolist()}}),
    config={"graphType": "Stacked", "colorScheme": "Tableau", "xAxisTitle": "Patients",
            "title": "Best response by arm", "legendPosition": "right"})

11. Dose–response on a log axis with a smoothed fit

Log-scale the x axis and ask the engine for a loess smoother; the curve is computed in the chart from the raw points, so the figure carries the data, not a traced line.

JavaScript
<canvas id="chart" width="700" height="450"></canvas>
<script>
  var data = {"y":{"vars":["d1","d2","d3","d4","d5","d6","d7","d8","d9"],"smps":["Dose","Compound A"],"data":[[0.01,2],[0.03,4],[0.1,9],[0.3,21],[1,48],[3,74],[10,89],[30,96],[100,99]]}};
  new CanvasXpress("chart", data, {
    "graphType": "Scatter2D",
    "xAxis": [
      "Dose"
    ],
    "yAxis": [
      "Compound A"
    ],
    "xAxisTransform": "log10",
    "xAxisTitle": "Dose (\u00b5M)",
    "yAxisTitle": "% inhibition",
    "title": "IC50",
    "showLoessFit": true,
    "xAxisTransformTicks": true
  });
</script>
R
library(canvasXpress)
dose <- c(0.01, 0.03, 0.1, 0.3, 1, 3, 10, 30, 100); resp <- c(2, 4, 9, 21, 48, 74, 89, 96, 99)
y <- cbind(Dose = dose, `Compound A` = resp); rownames(y) <- paste0("d", seq_along(dose))   # one row per observation
canvasXpress(data = y, graphType = "Scatter2D", xAxis = "Dose", yAxis = "Compound A",
             xAxisTransform = "log10", xAxisTransformTicks = TRUE, showLoessFit = TRUE,
             xAxisTitle = "Dose (µM)", yAxisTitle = "% inhibition", title = "IC50")
Python
from canvasxpress.canvas import CanvasXpress
from canvasxpress.data.keypair import CXDictData
dose = [0.01, 0.03, 0.1, 0.3, 1, 3, 10, 30, 100]; resp = [2, 4, 9, 21, 48, 74, 89, 96, 99]
chart = CanvasXpress(
    data=CXDictData({"y": {"vars": [f"d{i+1}" for i in range(len(dose))], "smps": ["Dose", "Compound A"],
                           "data": [[d, r] for d, r in zip(dose, resp)]}}),   # one row per observation
    config={"graphType": "Scatter2D", "xAxis": ["Dose"], "yAxis": ["Compound A"], "xAxisTransform": "log10", "xAxisTransformTicks": True,
            "showLoessFit": True,
            "xAxisTitle": "Dose (µM)", "yAxisTitle": "% inhibition", "title": "IC50"})

Every gallery example is live-editable too: on any example page, Edit & run in place opens the chart’s own JSON in an editor and re-renders it when you save — no external service — alongside the JSFiddle and CodePen exports.

Where to go next