FlowJo SweetSpot Web is a browser-based panel optimization tool for functional flow cytometry in CHO cell cultures. Upload a FlowJo single-stain titration export and it identifies the optimal concentration for each dye — the one that maximizes the fraction of cells detected in the target channel while minimizing spectral contamination of the other channels.
It runs entirely in the browser through vanilla JavaScript. No installation, no server, no data leaves your machine.
The Stain Index (SI) is the standard metric for flow cytometry panel optimization:
It measures how well two populations are resolved: the bright (positive) cells and the dim (negative) cells. The denominator anchors the measurement to the spread of the negative cluster, so a high SI means the two populations are cleanly separated and the instrument can reliably assign each cell to one group.
This works because antibody-conjugated fluorophores create genuine bimodality. A CD3-PE antibody stains T cells and leaves all other cells unstained. Biology itself creates the two populations. The job of panel optimization is then to choose reagent concentrations that maximize the gap between those clusters — and SI captures that gap directly.
Physiological dyes — TMRM (mitochondrial potential), Bodipy (lipid content), CellRox Deep Red (oxidative stress), and GFP as a recombinant reporter — do not produce two populations. They load into every cell based on that cell's current metabolic or physiological state. The result is a single continuous distribution that shifts along the fluorescence axis as a unit. There is no biologically negative subpopulation.
This means the classic SI optimization question — how do I maximize the gap between positive and negative? — does not apply. With dyes, all cells are positive. What varies is how positive each cell is, and that variation is the biological information you are trying to preserve.
Because dye uptake is concentration-dependent, the experimenter controls the brightness of the distribution by choosing the staining concentration. Higher concentration → brighter signal → apparently higher SI. But this creates a false incentive:
| Problem | What happens at high concentration | Consequence |
|---|---|---|
| Detector saturation | The upper tail of the distribution hits the instrument ceiling | Cells that differ biologically are compressed to the same voltage; heterogeneity is lost |
| Spreading error | Brighter signals produce more photon shot noise in adjacent channels | Other dyes in the panel are harder to resolve |
| Hidden heterogeneity | The distribution compresses near the detector limit | Cell-to-cell differences in metabolic state — the biological signal of interest — become invisible |
| Multivariate corruption | Spreading error from one dye invades other channels | Mitochondrial potential, lipid content, and ROS can no longer be analyzed jointly as independent measurements |
Since there is no biological negative population, this tool uses the unstained control as a surrogate dim reference — the cells with zero dye present, whose fluorescence reflects only autofluorescence and instrument noise. This lets SI remain a meaningful indicator of whether the signal clears the noise floor.
But SI alone still does not answer the real question. The primary decision criterion is therefore population behavior: at a given concentration, what fraction of cells is being captured in the target channel, and what fraction is spilling into channels that belong to other dyes? The concentration that maximizes the first while minimizing the second — above a minimum signal quality threshold — is the sweet spot.
This is what the Score metric computes directly, without assuming that brighter is always better.
In FlowJo, set up the following gate structure under your parent gate (e.g. CHO/Singlets):
CHO/Singlets
├── GFP-A+ GFP-A-
├── PE-A+ PE-A-
├── SNARF-A+ SNARF-A-
└── APC-A+ APC-A-
In the Table Editor, export for each sample the following statistics per gate:
Freq. of ParentMedian ({channel})Robust SD ({channel})
Add three metadata columns: Sample, Colorante (dye name), and Concentracion (concentration). Include one row per stained sample and one row for the unstained control with Colorante = ST.
Export as .csv or .xlsx. One sheet, one row per sample.
The app recognizes English aliases automatically:
Dye,Stain,Markerfor the dye column;Concentration,Dosefor the concentration column.
| Parameter | Description | Default |
|---|---|---|
| Population base | Parent gate path as it appears in column names | CHO/Singlets |
| Detector max | Upper instrument limit for saturation estimation | 1 000 000 |
| Minimum acceptable SI | SI floor below which a concentration is penalized | 7 |
| Target channel | Detector channel for each dye | editable per dye |
GFP is marked informational_only: its expression level is set by the biology of the cell, not by a titrable dose. It participates in invasion calculations — its spill can reach other channels, and other dyes can spill into its channel — but no concentration selection is performed for it.
Click Run analysis. For each stained sample (dye × concentration × clone), the engine computes:
- Stain Index against the unstained control
- Fraction of cells in the target channel's positive gate
- Fraction of cells falling in each neighbor channel's positive gate (invasion)
- Score, saturation envelope, and selection outcome
Cards at the top show the recommended concentration for each dye with a one-line interpretation.
Metric trends plot (3 panels, top to bottom):
- Stain Index — reference signal quality against the unstained control
- Invasion total — fraction of cells appearing in non-target positive gates; lower is better
- Score — the combined metric; the peak of this curve is the recommended concentration
Selection criteria plot — one panel per dye showing the Score curve across concentrations, with the selected condition marked.
Best conditions table — final recommendation with Score, % positivas, invasion total, SI, and saturation flags per dye.
Ws breakdown table — per-neighbor invasion detail: frequency of cells invading each neighbor channel and the apparent SI in that channel (diagnostic).
Where
SI is used as a correction factor, not as the primary decision driver. See SI factor below.
A concentration is flagged as Clip_Risk when Near_Clip when
The primary signal metric: what fraction of the population is being captured in the target channel at this concentration? More is better — a higher fraction means more of the biological heterogeneity is represented in the measurement.
For each neighbor channel
This measures how much of the population is being falsely detected in the wrong channels due to spectral spillover at this concentration. Lower is better.
Apparent SI (diagnostic only): for each neighbor
$j$ , the app also computes how strongly the invading signal appears in that channel:$$ApparentSI_j = \frac{MFI_{j,,\text{stained+}} - MFI_{j,,\text{ST-}}}{2 \times rSD_{j,,\text{ST-}}}$$ This is stored in the Ws breakdown table and useful for identifying which neighbors are most affected, but it does not enter the Score calculation.
A soft gate on signal quality. It ramps linearly from 0 to 1 as SI goes from 0 to
- A concentration with insufficient signal is penalized proportionally — it cannot win if better options exist
- A concentration that exceeds the threshold is not further rewarded — extra brightness beyond the minimum does not improve the score
The score captures the three-way balance the experiment actually requires:
| Component | Behavior |
|---|---|
| Freq_Signal ↑ | More cells detected → Score rises |
| Invasion_Total ↑ | More cells in wrong channels → Score falls |
| SI_factor < 1 | Insufficient signal → Score reduced proportionally |
| SI_factor = 1 | Signal is adequate → frequencies alone drive the decision |
The optimal concentration is the one that places the most cells in the right channel, while spilling the fewest into others, given that the signal clears the minimum quality threshold.
Given all concentrations for a dye:
-
Safety filter: remove concentrations with
Clip_Risk = Trueif any safe alternatives exist; then prefer non-Near_Clipconcentrations -
SI floor: if any candidate achieves
$SI \geq SI_{\min}$ , restrict the candidate pool to those; otherwise retain all (statusBELOW_SI_THRESHOLD) - Data quality: prefer candidates with fewer neighbor channels with missing invasion data
-
Select:
$\arg\max(Score)$ within the candidate pool
| Zero installation | Runs fully client-side — no Python, no pip, no server |
| Frequency-first scoring | Decision is based on population behavior, not signal intensity alone |
| SI as correction factor | Stain Index gates insufficient signal without over-rewarding brightness |
| Saturation detection | Upper envelope flags concentrations near or at the detector ceiling |
| GFP-aware | Recombinant reporter participates in invasion estimates but is not titrated |
| Clone support | Multiple CHO clones in the same export are tracked separately |
| Interactive plots | Plotly-powered; zoom, hover, and compare conditions |
| CSV / XLSX input | Accepts both formats; auto-detects percentage vs. fraction frequency scale |
| Downloadable results | Best conditions and full results exported as CSV in one click |
| No data upload | Everything runs locally in the browser — no data leaves your machine |
The app expects one row per stained sample. Column names are constructed from the Population base and channel settings.
Metadata columns (required)
| Column | Aliases | Description |
|---|---|---|
Sample |
— | Sample or file name |
Colorante |
Dye, Stain, Marker |
Dye name (see canonical names below) |
Concentracion |
Concentration, Dose |
Concentration or identifier |
Gate statistics per channel (required for each dye in the panel)
Using CHO/Singlets as population base and PE-A as an example channel:
| Column | Description |
|---|---|
CHO/Singlets/PE-A+ | Freq. of Parent |
Fraction of cells in the positive gate |
CHO/Singlets/PE-A+ | Median (PE-A) |
Median fluorescence of the positive gate |
CHO/Singlets/PE-A+ | Robust SD (PE-A) |
Robust SD of the positive gate |
CHO/Singlets/PE-A- | Median (PE-A) |
Median fluorescence of the negative gate |
CHO/Singlets/PE-A- | Robust SD (PE-A) |
Robust SD of the negative gate |
The following names are recognized (case-insensitive):
| Input name | Canonical name | Default channel |
|---|---|---|
ST, unstained, Control ST unstained |
Unstained control | — |
GFP |
GFP (proteina recombinante) | GFP-A |
TMRM |
TMRM | PE-A |
Bodipy |
Bodipy | SNARF-A |
CellRox, CellRox Deep Red |
CellRox Deep Red | APC-A |
Channels can be reassigned in the Dye configuration table without editing the file.
Analysis and visualization
Frontend
flowjo-sweetspot-web/
├── index.html ← markup and help panels
├── styles.css ← all custom styles
└── app.js ← analysis engine, UI logic, and plots
Emiliano Balderas Ramírez Bioengineer · PhD Candidate in Biochemical Sciences Instituto de Biotecnología (IBt), UNAM
Clonalyzer 2 — Fed-batch kinetics analysis for CHO cultures: specific rates, yields, and integral cell counts from a single CSV drop.
CellSplit — Neubauer cell counting and passage planning for CHO cultures.
CellBlock — Shared biosafety cabinet scheduling for cell culture research groups.