Note
If you are looking for a k-mer counter, check https://github.com/gmarcais/Jellyfish
Jellyfish automates the creation of Jellyfish plots based on the output from ClonEvol or similar tools that infer tumor phylogeny and subclonal composition. These plots integrate a hierarchical sample structure and tumor phylogeny into a single visualization, allowing for the display of both spatial and temporal evolution of the tumor. The design of the Jellyfish plot was first introduced in the following paper:
Lahtinen, A., Lavikka, K., Virtanen, A., et al. "Evolutionary states and trajectories characterized by distinct pathways stratify patients with ovarian high-grade serous carcinoma." Cancer Cell 41, 1103–1117.e12 (2023). DOI: 10.1016/j.ccell.2023.04.017.
The Jellyfish plots in the paper were drawn manually—a time-consuming and error-prone process. This tool draws them automatically based on the input data.
You can explore the auto-generated Jellyfish at
https://hautaniemilab.github.io/jellyfish/,
based on the data from the Lahtinen, et al. (2023) paper, available as example
data in the data/ directory. If you wish to have Jellyfish plots for
your own data, continue reading!
A Jellyfish plot displays subclonal compositions of tumor samples along a sample tree, while embedding a phylogeny inside that structure. This allows subclone lineages to be shown at the samples where they first appear.
The phylogeny is a tree that represents the evolutionary relationships between subclones. Each subclone is a population of cells defined by a characteristic set of genetic mutations.
A sample is a tumor specimen that may contain multiple subclones with specified clonal prevalences (proportions). The sample tree defines the relationships between samples, such as metastatic progression or sampling order.
Each sample has a rank, which determines its horizontal position in the plot. Ranks can be user-defined (for example, to organize samples by time point) or automatically assigned from the depth of the sample in the sample tree.
For each subclone, Jellyfish considers its clade, meaning the subclone and all of its descendant subclones. It collects the set of samples that contain any member of this clade and finds their Lowest Common Ancestor (LCA) in the sample tree. The LCA is the most recent sample that is an ancestor of all clade-containing samples and has no ancestor that also contains any member of the clade.
Jellyfish draws the subclone at its LCA sample as an emerging bell. This indicates where the clade first appears in the sample tree and helps push newly emerging subclones toward the leaves, improving readability.
Samples without an explicit parent are attached to the inferred root, a virtual sample used to host LCAs for subclones that would otherwise appear to emerge in multiple unrelated branches of the sample tree.
The inferred root is assigned rank 0. LCAs placed at the root may appear earlier than intended in time, resulting in implausible evolutionary scenarios. To avoid this, users can insert additional hypothetical samples into the sample tree to host those LCAs.
Any sample without subclones is treated as an inferred sample. Its subclonal composition is filled automatically with the relevant subclones, each assigned equal proportions.
- Visualizes tumor phylogeny and subclonal compositions as a Jellyfish plot.
- Allows visualizing both temporal and spatial relationships between samples.
- Sorts samples based on the subclonal composition and divergence, effectively grouping similar samples together.
- Provides basic interactivity for exploring the plot, such as highlighting subclones and clades upon hover or click, and displaying details in tooltips.
- Generates phylogeny-aware color schemes for subclones, inspired by Visualizing Clonal Evolution in Cancer by Krzywinski.
- Exports the plot as publication-ready SVG or PNG files.
- Adjustable layout parameters for fine-tuning the plot appearance.
If you are an R user, you may want to use the Jellyfisher R package to generate Jellyfish plots in RStudio, R Markdown, Shiny apps, or plain R. Otherwise, continue reading.
Jellyfish is a web application written in TypeScript. You need to have Node.js installed to run the tool.
git clone https://github.com/HautaniemiLab/jellyfish.git(or download the repository as a ZIP archive)cd jellyfishnpm installnpm run dev(starts a development server)
Once the development server is running, open your browser and navigate to http://localhost:5173/. You should see the user interface, which allows you to render Jellyfish plots based on your data.
If you want to share the interactive Jellyfish plots with others, you can build the project as an application and deploy it as a static web site on any web server. An example of such a web site is available at https://hautaniemilab.github.io/jellyfish/.
Steps:
- Perform steps 1-3 from the previous section.
npm run build:app(builds the project)cp -R data dist/app/(copies the example data to the build directory)cd dist/apppython3 -m http.server(starts a local web server for testing)- Open your browser and navigate to http://localhost:8000/. You should see the user interface.
- To deploy the site to a web server, copy the contents of the
dist/appdirectory to the server.
Jellyfish can be used as a library in other JavaScript applications, such as the Jellyfisher R package. For an example of how to use the library, see Jellyfisher's source code.
Steps:
- Perform steps 1-3 from the first section.
npm run build:lib(builds the library)- The compiled library is available in the
dist/libdirectory.
Jellyfish reads data as tab-separated files from the data/ directory. Below is
a description of the data structure, with example files provided in the
directory.
To use your own data, it is recommended to place it in a separate directory,
such as private-data/, which is excluded from the Git repository. Then, create
a .env.local file (see the Vite
docs for details) at the
project root with the following content to use the new data directory:
VITE_DATA_DIR=private-dataThe structure of the required data files is described below. For datasets
containing a single patient, the patient (string) columns can be omitted.
sample(string): specifies the unique identifier for each sample.displayName(string, optional): allows for specifying a custom name for each sample. If the column is omitted, thesamplecolumn is used as the display name.rank(integer): specifies the position of each sample in the Jellyfish plot. For example, different stages of a disease can be ranked in chronological order: diagnosis (1), interval (2), and relapse (3). The zeroth rank is reserved for the root of the sample tree. Ranks can be any integer, and unused ranks are automatically excluded from the plot. If therankcolumn is absent, ranks are assigned based on each sample’s depth in the sample tree.parent(string): identifies the parent sample for each entry. Samples without a specified parent are treated as children of an imaginary root sample.
| sample | displayName | rank | parent | patient |
|---|---|---|---|---|
| P1_iOme_DNA1 | iOme | 5 | P1 | |
| P1_iPer1_DNA1 | iPer1 | 5 | P1_pPer1_DNA1 | P1 |
| P1_pAsc_DNA1 | pAsc | 1 | P1 | |
| P1_pPer1_DNA1 | pPer1 | 1 | P1 | |
| P2_iOme2_DNA1 | iOme2 | 5 | P2_pOme2_DNA1 | P2 |
| P2_iOvaR1_DNA1 | iOvaR1 | 5 | P2 | |
| P2_pOme2_DNA1 | pOme2 | 1 | P2 |
subclone(string): specifies subclone IDs, which can be any string.parent(string): designates the parent subclone. The subclone without a parent is considered the root of the phylogeny.color(string, optional): specifies the color for the subclone. If the column is omitted, colors will be generated automatically.branchLength(number): specifies the length of the branch leading to the subclone. The length may be based on, for example, the number of unique mutations in the subclone. The branch length is shown in the Jellyfish plot's legend as a bar chart. It is also used when generating a phylogeny-aware color scheme.
| subclone | parent | color | branchLength | patient |
|---|---|---|---|---|
| 1 | #cccccc | 2745 | P1 | |
| 2 | 1 | #a6cee3 | 54 | P1 |
| 3 | 1 | #b2df8a | 270 | P1 |
| 5 | 1 | #ff99ff | 216 | P1 |
| 1 | #cccccc | 1914 | P2 | |
| 4 | 5 | #cab2d6 | 2581 | P2 |
| 5 | 1 | #ff99ff | 1314 | P2 |
| 6 | 1 | #fdbf6f | 1651 | P2 |
| 7 | 6 | #fb9a99 | 137 | P2 |
| 8 | 4 | #bbbb77 | 462 | P2 |
Subclonal compositions are specified in a tidy format, where each row represents a subclone in a sample.
sample(string): specifies the sample ID.subclone(string): specifies the subclone ID.clonalPrevalence(number): specifies the clonal prevalence of the subclone in the sample. The clonal prevalence is the proportion of the subclone in the sample. The clonal prevalences in a sample must sum to 1.
The sample and subclone columns together form a unique key for each row. The
subclones with no prevalence in a sample are not required to be included in the
table.
| sample | subclone | clonalPrevalence | patient |
|---|---|---|---|
| P1_iOme_DNA1 | 1 | 0.842 | P1 |
| P1_iPer1_DNA1 | 1 | 0.78 | P1 |
| P1_pAsc_DNA1 | 1 | 0.174 | P1 |
| P1_pPer1_DNA1 | 2 | 0.874 | P1 |
| P1_iOme_DNA1 | 3 | 0.158 | P1 |
| P1_iPer1_DNA1 | 3 | 0.22 | P1 |
| P1_pAsc_DNA1 | 3 | 0.1655 | P1 |
| P1_pPer1_DNA1 | 3 | 0.125 | P1 |
| P1_pAsc_DNA1 | 5 | 0.6605 | P1 |
| P2_iOme2_DNA1 | 1 | 0.1 | P2 |
| P2_iOvaR1_DNA1 | 1 | 0.024 | P2 |
| P2_pOme2_DNA1 | 1 | 0.1715 | P2 |
| P2_iOme2_DNA1 | 4 | 0.4995 | P2 |
| P2_iOme2_DNA1 | 5 | 0.401 | P2 |
| P2_pOme2_DNA1 | 5 | 0.309 | P2 |
| P2_iOvaR1_DNA1 | 6 | 0.3105 | P2 |
| P2_iOvaR1_DNA1 | 7 | 0.665 | P2 |
| P2_pOme2_DNA1 | 8 | 0.5195 | P2 |
Ranks may have optional titles that are displayed above the sample column.
rank(integer): specifies the rank number. The zeroth rank is reserved for the inferred root of the sample tree. However, you are free to define a title for it.title(string): specifies the title for the rank.
| rank | title |
|---|---|
| 0 | Before diag. |
| 1 | Diagnosis |
| 2 | Diagnosis 2 |
| 3 | Interval |
| 4 | Relapse |
If you use Jellyfish in your research, please cite the following paper:
Kari Lavikka, Altti Ilari Maarala, Jaana Oikkonen, Sampsa Hautaniemi, Jellyfish: integrative visualization of spatio-temporal tumor evolution and clonal dynamics, Bioinformatics, 2025;, btaf091, https://doi.org/10.1093/bioinformatics/btaf091
Copyright (c) 2025 Kari Lavikka. MIT licensed, see LICENSE for details.
Jellyfish is developed in The Systems Biology of Drug Resistance in Cancer group at the University of Helsinki.
This project has received funding from the European Union's Horizon 2020 research and innovation programme under grant agreement No. 965193 (DECIDER) and No. 847912 (RESCUER).