Learn & Migrate

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

Start where you are

How CanvasXpress thinks differently

Being honest about the learning curve: if your first CanvasXpress chart doesn't look the way you expected, it is almost never a styling problem — it is one of two conventions that come from the library's scientific roots, where data arrives as a matrix of variables × samples (think a gene-expression table: variables in rows, samples in columns). Once you know them, they stop surprising you. Coming from Plotly, Tableau or Datawrapper, these are the two to internalize.

1. Data is one wide matrix, not a list of traces

There are no "series" or "traces" to append. You give the engine one block — vars (the measured variables), smps (the samples/observations) and a data matrix — and the graphType plus a few axis mappings decide the rest. This is what makes a figure a single portable, reproducible specification, but it is the opposite of building a chart up trace-by-trace.

See Data formats.

2. Scatter plots map variables to axes

In a Scatter2D, each plotted point is a variable (a row in vars) and the two measures you put on the axes are samples (columns in smps). To plot weight against mileage across cars, the cars go in vars, "wt" and "mpg" go in smps, and you set "xAxis": ["wt"] and "yAxis": ["mpg"]. The intuitive "measures are columns" layout will plot the wrong thing.

See Quick Start.

One convention we changed rather than asked you to learn: bar, line, area and dot plots now default to a vertical orientation (value on the y-axis), matching ggplot2 and everyday expectations. Set graphOrientation: "horizontal" when the horizontal layout reads better — for example with long category labels.

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.

Coming from ECharts

ECharts builds a chart from an option object of series; CanvasXpress maps a wide data matrix through one grammar via graphType. If you reach for ECharts for scientific and bioinformatics work, the shift is mostly about where your data lives — in a typed matrix, not inlined per series.

EChartsCanvasXpress
An option object with series[]One data matrix (y/x/z) + one config
Series type (scatter, bar, heatmap…)graphType (Scatter2D, Bar, Heatmap…)
xAxis/yAxis component objectsxAxis/yAxis keys naming matrix rows/columns
Group by adding one series per groupGroup with one matrix + colorBy / annotations
visualMap for colour scalescolorBy + colorSpectrum on the matrix
Faceting via grid + multiple coordinate systemssegregateVariablesBy / segregateSamplesBy
Clustering, stats and dendrograms are your codeBuilt into the render (hierarchical clustering, tests, transforms)
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. Apache ECharts page — including where ECharts is the stronger choice (general-purpose dashboards, geographic maps, a larger ecosystem).

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

⇧