Skip to content

Commit 91e1c6a

Browse files
Add export-options dialog for Copy button
Right-click Copy now shows a dialog to configure: - DPI (72-1200) for scaling the export image - Font size (6-36pt) for axis labels in export - Gate labels toggle to show/hide population names and statistics Settings are saved per-session and applied to the exported image before copying to clipboard. The label visibility is temporarily adjusted during export and restored afterward, allowing independent control in the live plot and export.
1 parent d0d7173 commit 91e1c6a

109 files changed

Lines changed: 19322 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/build-java.yml‎

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
name: build-java
2+
3+
# CI for the JavaFX desktop app (app/) + headless compute engine (engine/).
4+
#
5+
# The app is now Python-first: the Java bridge spawns the Python (FlowKit) engine
6+
# by default, with the legacy R engine kept as a fallback until it is retired. This
7+
# job therefore stands up BOTH engines so the test suite exercises the real data
8+
# path on each:
9+
# * Python — a venv at engine/py-env with FlowKit installed. PyEngineTest and the
10+
# command tests auto-detect this exact path and run against it.
11+
# * R — R 4.4 + jsonlite so the JavaFX<->R ProtocolConformanceTest runs for
12+
# real (it auto-skips if R is missing).
13+
#
14+
# Tests that need large FCS fixtures (test-assests/, gitignored) self-skip in CI via
15+
# JUnit assumptions — that is expected. The heavy Bioconductor/CytoExploreR stack is
16+
# NOT installed here; module integration tests get their own gated job later.
17+
#
18+
# This job does NOT produce the packaged StreamFLOW.exe — jpackage packaging
19+
# (app/package.ps1) is a separate release pipeline and is still being aligned with
20+
# the Python engine.
21+
22+
on:
23+
push:
24+
paths:
25+
- 'app/**'
26+
- 'engine/**'
27+
- '.github/workflows/build-java.yml'
28+
pull_request:
29+
paths:
30+
- 'app/**'
31+
- 'engine/**'
32+
workflow_dispatch:
33+
34+
jobs:
35+
build:
36+
runs-on: windows-latest
37+
timeout-minutes: 30
38+
steps:
39+
- uses: actions/checkout@v4
40+
41+
- name: Set up JDK 25 (Microsoft, LTS)
42+
uses: actions/setup-java@v4
43+
with:
44+
distribution: 'microsoft'
45+
java-version: '25'
46+
cache: maven
47+
48+
- name: Set up Python 3.11
49+
uses: actions/setup-python@v5
50+
with:
51+
python-version: '3.11'
52+
cache: pip
53+
54+
- name: Create engine venv + install FlowKit
55+
shell: pwsh
56+
run: |
57+
# PyEngineTest / command tests expect the venv at engine\py-env (the same
58+
# path run-dev.ps1 uses), so create it there exactly.
59+
python -m venv engine\py-env
60+
engine\py-env\Scripts\python.exe -m pip install --upgrade pip
61+
# flowkit pulls flowio, flowutils, numpy, scipy, pandas; matplotlib is used
62+
# by the render/stats commands; scikit-learn/umap-learn/openTSNE power the
63+
# Dim. Reduction, Clustering and Classifier modules.
64+
engine\py-env\Scripts\python.exe -m pip install flowkit matplotlib scikit-learn umap-learn openTSNE phenograph flowsom
65+
engine\py-env\Scripts\python.exe -c "import flowkit; print('FlowKit', flowkit.__version__)"
66+
67+
- name: Set up R 4.4 (legacy engine fallback)
68+
uses: r-lib/actions/setup-r@v2
69+
with:
70+
r-version: '4.4'
71+
72+
- name: Install jsonlite
73+
run: Rscript -e "install.packages('jsonlite', repos='https://cloud.r-project.org')"
74+
shell: cmd
75+
76+
- name: Build and test (Python + R engine bridges)
77+
working-directory: app
78+
shell: cmd
79+
run: >
80+
mvn -B clean test
81+
"-Dstreamflow.rscript=%RSCRIPT_PATH%"
82+
env:
83+
# r-lib/actions exposes R on PATH; resolve Rscript explicitly for the bridge.
84+
# The Python tests self-configure via the engine\py-env path created above.
85+
RSCRIPT_PATH: Rscript
86+
87+
- name: Upload test reports
88+
if: always()
89+
uses: actions/upload-artifact@v4
90+
with:
91+
name: surefire-reports
92+
path: app/target/surefire-reports/

‎.gitignore‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# ── Internal / dev-only docs (kept locally, not for the public repo) ──────────
22
AUDIT_REPORT.md
33
BUILDING.md
4+
MANUAL_TESTING.md
45

56
# ── Build outputs & dependencies ─────────────────────────────────────────────
67
node_modules/
@@ -15,10 +16,45 @@ yarn-error.log
1516
# ── Bundled R runtime (≈1 GB, produced by the build, never committed) ─────────
1617
R-Portable/
1718

19+
# ── Vendored dev tooling (large binaries kept locally, never committed) ───────
20+
# JDKs and Maven are set up by CI / the dev scripts, not stored in git.
21+
jdk-25.0.3+9/
22+
jdk-26.0.1/
23+
apache-maven-3.9.16/
24+
# Ikonli is consumed as a Maven dependency, not from its source tree.
25+
ikonli-master/
26+
# Vendored R package sources — cloned/extracted locally, installed into the R
27+
# library at build time, never committed (hundreds of MB of external source).
28+
engine/vendor/
29+
# Vendored Python package sources / wheels for the Python migration (large).
30+
python-migration/
31+
engine/py-vendor/
32+
# Python virtual environment for the engine (~500 MB, recreated by install scripts).
33+
engine/py-env/
34+
.venv/
35+
venv/
36+
env/
37+
38+
# ── Python bytecode / caches ─────────────────────────────────────────────────
39+
__pycache__/
40+
*.py[cod]
41+
*$py.class
42+
.pytest_cache/
43+
.mypy_cache/
44+
.ruff_cache/
45+
# claude-code-java toolkit (review skills/templates), not part of the app build.
46+
claude-code-java-main/
47+
48+
# ── Java / Maven build output ────────────────────────────────────────────────
49+
target/
50+
*.class
51+
1852
# ── Logs ─────────────────────────────────────────────────────────────────────
1953
*.log
2054
logs/
2155
install_log.txt
56+
# Captured stdout/stderr from install/build runs (engine_install.out, py_install.out, …)
57+
*.out
2258

2359
# ── R session artifacts (RStudio / Rscript leave these behind) ───────────────
2460
.Rhistory
@@ -40,9 +76,19 @@ install_log.txt
4076
# Office / OneDrive lock files (this repo lives under OneDrive\Documents)
4177
~$*
4278

79+
# ── Large / private test data (FCS files, fixtures — never for the public repo) ─
80+
test-assests/
81+
test-assets/
82+
test-data/
83+
*.fcs
84+
85+
# ── Local Claude Code config (personal permissions + machine-specific paths) ──
86+
.claude/
87+
4388
# ── OS / editor cruft ────────────────────────────────────────────────────────
4489
Thumbs.db
4590
desktop.ini
4691
.DS_Store
4792
.vscode/
4893
.idea/
94+
*.iml

‎app/.gitignore‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
target/
2+
*.class
3+
.idea/
4+
*.iml

‎app/README.md‎

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# StreamFLOW — Java desktop UI (Phase 0)
2+
3+
Native JavaFX desktop front-end for the StreamFLOW flow-cytometry suite. The
4+
heavy compute stays in R (Bioconductor / CytoExploreR) running as a background
5+
process; Java owns the window, file dialogs, and async orchestration. See
6+
`../engine/streamflow_engine.R` for the R side and the project plan for the full
7+
architecture.
8+
9+
This is **Phase 0**: the JavaFX ⇄ R bridge, the async job model, the app shell,
10+
and a developer console that proves the architecture end-to-end. The real
11+
analysis modules land in later phases.
12+
13+
## Prerequisites
14+
15+
The repo is **self-contained for building** — no system JDK or Maven needed:
16+
17+
- **JDK**: bundled **Microsoft Build of OpenJDK 25 (LTS)** at `../jdk-25.0.3+9`.
18+
- **Maven**: bundled **Apache Maven 3.9.16** at `../apache-maven-3.9.16`.
19+
- The `build.ps1` / `run-dev.ps1` scripts pin `JAVA_HOME`, use the bundled Maven,
20+
and set `MAVEN_OPTS` to trust the **Windows certificate store** (so Maven Central
21+
works behind a TLS-intercepting proxy/AV — otherwise you'd get a PKIX
22+
"unable to find valid certification path" error).
23+
- **R** is the only external requirement: `jsonlite` for Phase 0, and from Phase 1
24+
on the full Bioconductor/CytoExploreR stack. In a packaged build this is the
25+
bundled `R-Portable`; in dev, point the scripts at any Rscript with `-Rscript`.
26+
27+
> The repo lives in a OneDrive-synced folder, which can transiently lock
28+
> `target/`; if `mvn clean` fails to delete it, just `rm -rf target` and retry.
29+
30+
## Run the app (dev)
31+
32+
```powershell
33+
# from app\
34+
./run-dev.ps1 # Rscript from PATH / bundled R-Portable
35+
./run-dev.ps1 -Rscript "C:\Program Files\R\R-4.4.2\bin\Rscript.exe"
36+
```
37+
38+
Then in the **Developer / Engine Console**:
39+
- **Ping / R Version** — basic round-trip over the stdio JSON protocol.
40+
- **Sleep (5s, cancellable)** — start it, then click the nav items and drag the
41+
window: the UI stays fully responsive while R works (the freeze fix). Hit
42+
**Cancel** to prove cooperative cancellation.
43+
- **Trigger Error** — shows the real R error message surfaced to the UI.
44+
- **Noisy stdout** — R does `cat()`/`print()`; the JSON stream stays intact
45+
(proves the `sink(stderr())` guard).
46+
47+
## Verify (Phase 0 gate)
48+
49+
```powershell
50+
# from app\ — runs the protocol conformance test against a real R engine
51+
./build.ps1 -Rscript "C:\Program Files\R\R-4.4.2\bin\Rscript.exe"
52+
```
53+
54+
`ProtocolConformanceTest` asserts: ready handshake, echo/version round-trip,
55+
progress frames, cooperative cancel, error+traceback replies, malformed-request
56+
resilience, and that stray R stdout never corrupts the stream. (The test is
57+
skipped automatically if no Rscript is available.)
58+
59+
Orphan-safety check (manual): launch the app, find the child `Rscript.exe` in
60+
Task Manager, kill the `java`/JavaFX process — the engine should exit on its own
61+
within seconds (stdin EOF), leaving no lingering R process.
62+
63+
## Build a Windows app (jpackage)
64+
65+
Produces a self-contained app-image (StreamFLOW.exe + bundled JRE + R), so end
66+
users need neither Java nor R:
67+
68+
```powershell
69+
# 1) one-time / when R deps change — stage a minimal portable R (slow, ~1 GB):
70+
powershell -ExecutionPolicy Bypass -File engine\stage-r-portable.ps1
71+
# 2) build the app image (or add -Installer for an .exe, needs WiX):
72+
powershell -ExecutionPolicy Bypass -File app\package.ps1
73+
# output: app\target\dist\StreamFLOW\
74+
```
75+
76+
The app resolves the bundled engine + R-Portable via `-Dstreamflow.appDir`
77+
(set by `package.ps1`). *Status: scripts are ready; the first packaged build still
78+
needs a validation run (jpackage classpath + runtime path).*
79+
80+
## Layout
81+
82+
```
83+
app/
84+
pom.xml Maven build (OpenJFX, controlsfx, Jackson, JUnit)
85+
run-dev.ps1 / build.ps1 JDK-pinned launchers
86+
src/main/java/org/streamflow/
87+
StreamFlowApp.java JavaFX entry point
88+
bridge/ R process + stdio JSON protocol + async jobs
89+
ui/MainController.java shell controller (Phase 0 dev console)
90+
src/main/resources/org/streamflow/ui/
91+
main.fxml, streamflow-dark.css
92+
src/test/java/.../ProtocolConformanceTest.java
93+
```

‎app/build.ps1‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# StreamFLOW (Java) — build & test
2+
# Pins JAVA_HOME to the bundled Microsoft JDK 25 (LTS), then compiles and runs
3+
# the protocol conformance test (Phase 0 verification gate).
4+
#
5+
# Usage:
6+
# ./build.ps1 # compile + test (test skips if R absent)
7+
# ./build.ps1 -Rscript "C:\R\bin\Rscript.exe" # run the conformance test against R
8+
# ./build.ps1 -SkipTests
9+
#
10+
param(
11+
[string]$Rscript = "",
12+
[switch]$SkipTests
13+
)
14+
15+
$ErrorActionPreference = "Stop"
16+
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
17+
$repo = Split-Path -Parent $here
18+
19+
$jdk = Join-Path $repo "jdk-25.0.3+9"
20+
if (-not (Test-Path (Join-Path $jdk "bin\java.exe"))) {
21+
throw "Microsoft JDK 25 not found at $jdk"
22+
}
23+
$env:JAVA_HOME = $jdk
24+
$env:PATH = "$jdk\bin;$env:PATH"
25+
Write-Host "JAVA_HOME = $env:JAVA_HOME"
26+
27+
# Trust the Windows certificate store so Maven Central works behind a
28+
# TLS-intercepting proxy/AV (otherwise: PKIX 'unable to find valid cert path').
29+
$env:MAVEN_OPTS = "-Djavax.net.ssl.trustStoreType=Windows-ROOT -Djavax.net.ssl.trustStore=NUL"
30+
31+
# Prefer the bundled Maven; fall back to one on PATH.
32+
$mvn = Join-Path $repo "apache-maven-3.9.16\bin\mvn.cmd"
33+
if (-not (Test-Path $mvn)) {
34+
if (Get-Command mvn -ErrorAction SilentlyContinue) { $mvn = "mvn" }
35+
else { throw "Maven not found (looked for $mvn and PATH)." }
36+
}
37+
38+
$goal = if ($SkipTests) { "compile" } else { "test" }
39+
$mvnArgs = @("-B", "-f", (Join-Path $here "pom.xml"), "clean", $goal)
40+
if ($Rscript -ne "") {
41+
$mvnArgs += "-Dstreamflow.rscript=$Rscript"
42+
}
43+
Write-Host "Running: $mvn $($mvnArgs -join ' ')"
44+
& $mvn @mvnArgs

‎app/package.ps1‎

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# StreamFLOW — build a self-contained Windows app via jpackage.
2+
#
3+
# Produces an app-image (folder with StreamFLOW.exe + bundled JRE) that includes
4+
# the JavaFX app, BOTH engines' scripts, and two staged runtimes — a portable
5+
# Python (FlowKit, the default engine) and a portable R (for R-based plugins) — so
6+
# the end user needs no Java, Python, or R installed ("fat binary", ~1.5 GB).
7+
#
8+
# 1) when Python deps change: powershell -File engine\stage-python.ps1
9+
# 2) when R deps change: powershell -File engine\stage-r-portable.ps1
10+
# 3) build the app: powershell -File app\package.ps1
11+
# [-Installer] also build an .exe/.msi installer (needs WiX on PATH)
12+
#
13+
# NOTE: first run needs validation (jpackage classpath + runtime path resolution).
14+
param([switch]$Installer)
15+
16+
$ErrorActionPreference = "Stop"
17+
$here = Split-Path -Parent $MyInvocation.MyCommand.Path # app\
18+
$repo = Split-Path -Parent $here
19+
$jdk = Join-Path $repo "jdk-25.0.3+9"
20+
$mvn = Join-Path $repo "apache-maven-3.9.16\bin\mvn.cmd"
21+
$env:JAVA_HOME = $jdk
22+
$env:PATH = "$jdk\bin;$env:PATH"
23+
$env:MAVEN_OPTS = "-Djavax.net.ssl.trustStoreType=Windows-ROOT -Djavax.net.ssl.trustStore=NUL"
24+
25+
$rportable = Join-Path $here "target\R-Portable"
26+
if (-not (Test-Path (Join-Path $rportable "bin\Rscript.exe"))) {
27+
throw "R-Portable not staged. Run: powershell -File engine\stage-r-portable.ps1"
28+
}
29+
30+
$pyportable = Join-Path $here "target\python"
31+
if (-not (Test-Path (Join-Path $pyportable "python.exe"))) {
32+
throw "Portable Python not staged. Run: powershell -File engine\stage-python.ps1"
33+
}
34+
35+
# 1) Build the jar + collect runtime dependencies into target\libs.
36+
Write-Host "Building jar + collecting dependencies…"
37+
& $mvn -B -f (Join-Path $here "pom.xml") clean package `
38+
dependency:copy-dependencies "-DoutputDirectory=target/libs" "-DincludeScope=runtime" "-DskipTests=true"
39+
Copy-Item (Join-Path $here "target\streamflow-2.0.0.jar") (Join-Path $here "target\libs\") -Force
40+
41+
# 2) Stage app content (engine scripts + portable Python + R-Portable) next to the launcher.
42+
# Excludes engine/vendor (R package SOURCE — only needed at R-Portable stage time)
43+
# and engine/py-env (the dev venv — the relocatable target\python is bundled instead).
44+
$content = Join-Path $here "target\content"
45+
if (Test-Path $content) { Remove-Item -Recurse -Force $content }
46+
New-Item -ItemType Directory -Force -Path $content | Out-Null
47+
robocopy (Join-Path $repo "engine") (Join-Path $content "engine") /E /XD "vendor" "py-env" "__pycache__" /NFL /NDL /NJH /NJS | Out-Null
48+
robocopy $pyportable (Join-Path $content "python") /E /NFL /NDL /NJH /NJS | Out-Null
49+
robocopy $rportable (Join-Path $content "R-Portable") /E /NFL /NDL /NJH /NJS | Out-Null
50+
51+
# 3) jpackage app-image (JavaFX runs from the classpath; the openjfx win jars carry natives).
52+
$dist = Join-Path $here "target\dist"
53+
if (Test-Path $dist) { Remove-Item -Recurse -Force $dist }
54+
$type = if ($Installer) { "exe" } else { "app-image" }
55+
Write-Host "Running jpackage ($type)…"
56+
& (Join-Path $jdk "bin\jpackage.exe") `
57+
--type $type `
58+
--name "StreamFLOW" `
59+
--app-version "2.0.0" `
60+
--input (Join-Path $here "target\libs") `
61+
--main-jar "streamflow-2.0.0.jar" `
62+
--main-class "org.streamflow.StreamFlowApp" `
63+
--app-content $content `
64+
--icon (Join-Path $repo "build\icon.ico") `
65+
--dest $dist `
66+
--java-options "-Dstreamflow.appDir=`$APPDIR\..\content" `
67+
--java-options "-Dstreamflow.engine.kind=python" `
68+
--vendor "StreamFLOW"
69+
70+
Write-Host "Built -> $dist"

0 commit comments

Comments
 (0)