Skip to content

Instantly share code, notes, and snippets.

@romainGuiet
Created January 28, 2026 08:31
Show Gist options
  • Select an option

  • Save romainGuiet/81bf5e601e3ddf414246ccd2765a4f0d to your computer and use it in GitHub Desktop.

Select an option

Save romainGuiet/81bf5e601e3ddf414246ccd2765a4f0d to your computer and use it in GitHub Desktop.
a QuPath GUI to load points from csv file
import javafx.application.Platform
import javafx.scene.Scene
import javafx.scene.control.*
import javafx.scene.layout.*
import javafx.stage.Stage
import javafx.stage.DirectoryChooser
import javafx.geometry.Insets
import javafx.geometry.Pos
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import javafx.scene.control.cell.PropertyValueFactory
import javafx.scene.paint.Color
import java.io.File
// QuPath imports for creating detections
import qupath.lib.objects.PathObjects
import qupath.lib.roi.ROIs
import qupath.lib.regions.ImagePlane
Platform.runLater {
// Create the main stage
def stage = new Stage()
stage.setTitle("QuPath Custom GUI - CSV Loader")
// Create TabPane
def tabPane = new TabPane()
// Variables shared between tabs
def topGenesFromTab1 = []
def allGenesFromTab1 = [] // Store all unique genes
def currentCsvData = []
def currentHeaders = []
def selectedFolder = [null] as File[]
def geneColumnIndex = -1
def xColumnIndex = -1
def yColumnIndex = -1
def zColumnIndex = -1
def dimensionUnit = "micron"
// Variables for Tab 2 (declare them here so they can be accessed from Tab 1)
def geneDropdowns = []
def colorPickers = []
def progressBar = null
// ===== TAB 1: CSV File Loader =====
def tab1 = new Tab("CSV Loader")
tab1.setClosable(false)
// Create content for Tab 1
def vbox1 = new VBox(10)
vbox1.setPadding(new Insets(15))
// Title
def label1 = new Label("CSV File Loader")
label1.setStyle("-fx-font-size: 14px; -fx-font-weight: bold;")
// === FILE BROWSER SECTION WITH EXTENSION AND DELIMITER ===
def folderHBox = new HBox(10)
folderHBox.setAlignment(Pos.CENTER_LEFT)
def folderLabel = new Label("Folder:")
def folderField = new TextField()
folderField.setPromptText("No folder selected")
folderField.setPrefWidth(200)
folderField.setEditable(false)
def browseButton = new Button("Browse...")
def extensionLabel = new Label("Ext:")
def extensionField = new TextField(".part")
extensionField.setPrefWidth(60)
def delimiterLabel = new Label("Delim:")
def delimiterField = new TextField(",")
delimiterField.setPrefWidth(40)
browseButton.setOnAction({ e ->
def directoryChooser = new DirectoryChooser()
directoryChooser.setTitle("Select Folder Containing CSV Files")
def folder = directoryChooser.showDialog(stage)
if (folder != null) {
selectedFolder[0] = folder
folderField.setText(folder.getAbsolutePath())
}
})
folderHBox.getChildren().addAll(folderLabel, folderField, browseButton,
extensionLabel, extensionField, delimiterLabel, delimiterField)
// === LOAD BUTTON ===
def loadButton = new Button("Load CSV Files")
loadButton.setPrefWidth(120)
// === TABLE FOR DISPLAYING CSV DATA ===
def csvTable = new TableView<ObservableList<String>>()
csvTable.setPrefHeight(150)
csvTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY)
// === ALL COORDINATE AND GENE DROPDOWNS IN ONE LINE ===
def coordHBox = new HBox(10)
coordHBox.setAlignment(Pos.CENTER_LEFT)
def xLabel = new Label("X:")
def xCombo = new ComboBox<String>()
xCombo.setPrefWidth(100)
def yLabel = new Label("Y:")
def yCombo = new ComboBox<String>()
yCombo.setPrefWidth(100)
def zLabel = new Label("Z:")
def zCombo = new ComboBox<String>()
zCombo.setPrefWidth(100)
def geneLabel = new Label("Gene:")
def geneCombo = new ComboBox<String>()
geneCombo.setPrefWidth(100)
def dimLabel = new Label("Dimension:")
def dimensionCombo = new ComboBox<String>()
dimensionCombo.getItems().addAll("micron", "pixel")
dimensionCombo.setValue("micron")
dimensionCombo.setPrefWidth(80)
coordHBox.getChildren().addAll(xLabel, xCombo, yLabel, yCombo, zLabel, zCombo,
geneLabel, geneCombo, dimLabel, dimensionCombo)
// === ANALYZE ABUNDANCE BUTTON (moved up) ===
def analyzeButton = new Button("Analyse Abundancy")
analyzeButton.setPrefWidth(150)
// Variables to hold the abundance table and label (will be created dynamically)
def abundanceLabel = null
def abundanceTable = null
// === LOAD BUTTON ACTION ===
loadButton.setOnAction({ e ->
if (selectedFolder[0] == null) {
showAlert("Error", "Please select a folder first.")
return
}
def extension = extensionField.getText()
def delimiter = delimiterField.getText()
if (!extension || !delimiter) {
showAlert("Error", "Please specify both extension and delimiter.")
return
}
try {
// Find CSV files with matching extension
def csvFiles = selectedFolder[0].listFiles().findAll {
it.isFile() && it.getName().endsWith(extension)
}
if (csvFiles.isEmpty()) {
showAlert("Warning", "No files found with extension: $extension")
return
}
// Read the first CSV file found
def csvFile = csvFiles[0]
def lines = csvFile.readLines()
if (lines.isEmpty()) {
showAlert("Warning", "Selected CSV file is empty.")
return
}
// Parse CSV headers (first line)
def headers = lines[0].split(delimiter).collect { it.trim().replace('"', '') }
currentHeaders = headers
// Store all CSV data for analysis (skip header line)
currentCsvData = lines.drop(1).collect { line ->
line.split(delimiter).collect { it.trim().replace('"', '') }
}
// Clear and setup table columns using headers
csvTable.getColumns().clear()
headers.eachWithIndex { header, index ->
def column = new TableColumn<ObservableList<String>, String>(header)
column.setCellValueFactory({ param ->
return new javafx.beans.property.SimpleStringProperty(
param.getValue().size() > index ? param.getValue().get(index) : ""
)
})
csvTable.getColumns().add(column)
}
// Add first 5 DATA rows to table (skip header line)
def tableData = FXCollections.observableArrayList()
def dataLines = lines.drop(1) // Skip header
def maxRows = Math.min(5, dataLines.size())
for (int i = 0; i < maxRows; i++) {
def csvRowData = FXCollections.observableArrayList(
dataLines[i].split(delimiter).collect { it.trim().replace('"', '') }
)
tableData.add(csvRowData)
}
csvTable.setItems(tableData)
// Update coordinate dropdowns
xCombo.getItems().clear()
yCombo.getItems().clear()
zCombo.getItems().clear()
geneCombo.getItems().clear()
xCombo.getItems().addAll(headers)
yCombo.getItems().addAll(headers)
zCombo.getItems().addAll(headers)
zCombo.getItems().add("Current annotation Z")
geneCombo.getItems().addAll(headers)
// Set default values if available
if (headers.contains("x_location")) xCombo.setValue("x_location")
if (headers.contains("y_location")) yCombo.setValue("y_location")
if (headers.contains("z_location")) {
zCombo.setValue("z_location")
} else {
zCombo.setValue("Current annotation Z")
}
if (headers.contains("feature_name")) geneCombo.setValue("feature_name")
println("Loaded CSV file: ${csvFile.getName()}")
println("Found ${csvFiles.size()} CSV files in folder")
println("Headers: $headers")
println("Data rows: ${currentCsvData.size()}")
} catch (Exception ex) {
showAlert("Error", "Failed to load CSV file: ${ex.getMessage()}")
ex.printStackTrace()
}
})
// === ANALYZE ABUNDANCE ACTION ===
analyzeButton.setOnAction({ e ->
if (currentCsvData.isEmpty() || currentHeaders.isEmpty()) {
showAlert("Error", "Please load a CSV file first.")
return
}
def geneColumn = geneCombo.getValue()
if (!geneColumn) {
showAlert("Error", "Please select a gene column.")
return
}
def geneIndex = currentHeaders.indexOf(geneColumn)
if (geneIndex == -1) {
showAlert("Error", "Selected gene column not found.")
return
}
// Store column indices for Tab 2
geneColumnIndex = geneIndex
xColumnIndex = currentHeaders.indexOf(xCombo.getValue())
yColumnIndex = currentHeaders.indexOf(yCombo.getValue())
zColumnIndex = zCombo.getValue() == "Current annotation Z" ? -1 : currentHeaders.indexOf(zCombo.getValue())
dimensionUnit = dimensionCombo.getValue()
try {
// Count gene frequencies
def geneCounts = [:]
def allUniqueGenes = [] as Set
currentCsvData.each { row ->
if (row.size() > geneIndex) {
def gene = row[geneIndex]
if (gene && gene.trim()) {
geneCounts[gene] = (geneCounts[gene] ?: 0) + 1
allUniqueGenes.add(gene)
}
}
}
// Sort by count and get top 10 for display
def topGenes = geneCounts.entrySet()
.sort { -it.value }
.take(10)
// Store top genes for reference and all genes for Tab 2 dropdowns
topGenesFromTab1 = topGenes.collect { [gene: it.key, count: it.value] }
allGenesFromTab1 = allUniqueGenes.sort() // Sort alphabetically
// Update Tab 2 dropdowns with all genes
geneDropdowns.each { dropdown ->
dropdown.getItems().clear()
dropdown.getItems().add("none")
dropdown.getItems().addAll(allGenesFromTab1)
}
// Set default values for first 10 dropdowns based on top genes
topGenesFromTab1.eachWithIndex { geneData, index ->
if (index < geneDropdowns.size()) {
geneDropdowns[index].setValue(geneData.gene)
// Set different colors for each gene
def colors = [
Color.RED, Color.BLUE, Color.GREEN, Color.ORANGE, Color.PURPLE,
Color.CYAN, Color.MAGENTA, Color.YELLOW, Color.PINK, Color.BROWN
]
colorPickers[index].setValue(colors[index % colors.size()])
}
}
// Remove existing abundance table and label if they exist
if (abundanceLabel != null) {
vbox1.getChildren().remove(abundanceLabel)
}
if (abundanceTable != null) {
vbox1.getChildren().remove(abundanceTable)
}
// Create new abundance label and table
abundanceLabel = new Label("Top 10 Gene Abundance Results:")
abundanceLabel.setStyle("-fx-font-weight: bold;")
abundanceTable = new TableView()
abundanceTable.setPrefHeight(50) // Reduced height for single row
abundanceTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY)
// Disable row header and make table more compact
abundanceTable.setRowFactory({ tv ->
def row = new TableRow()
row.setPrefHeight(25) // Set row height
return row
})
// Create a simple data structure - array of strings for counts only
def abundanceRowData = new String[10]
// Fill the array with count data only
def numGenes = Math.min(topGenes.size(), 10)
for (int i = 0; i < numGenes; i++) {
def gene = topGenes[i]
abundanceRowData[i] = "${gene.value}" // Only the count
}
// Fill remaining slots with empty strings
for (int i = numGenes; i < 10; i++) {
abundanceRowData[i] = ""
}
// Create columns with gene names as headers
for (int i = 0; i < 10; i++) {
def column = new TableColumn()
// Set column header to gene name or empty if no gene
if (i < numGenes) {
def gene = topGenes[i]
column.setText(gene.key.toString()) // Gene name as header
} else {
column.setText("") // Empty header for unused columns
}
column.setPrefWidth(100)
column.setResizable(true)
def columnIndex = i // Capture the index for the closure
column.setCellValueFactory({ cellData ->
def rowData = cellData.getValue()
return new javafx.beans.property.SimpleStringProperty(
rowData[columnIndex] != null ? rowData[columnIndex] : ""
)
})
abundanceTable.getColumns().add(column)
}
// Create table data with single row
def abundanceData = FXCollections.observableArrayList()
abundanceData.add(abundanceRowData)
abundanceTable.setItems(abundanceData)
// Make table more compact - disable various interactive features
abundanceTable.setTableMenuButtonVisible(false)
abundanceTable.setEditable(false)
// Optional: Style to make it even more compact
abundanceTable.setStyle("-fx-table-cell-border-color: transparent;")
// Add label and table to the layout
vbox1.getChildren().addAll(abundanceLabel, abundanceTable)
println("Gene abundance analysis completed:")
println("Total unique genes found: ${allUniqueGenes.size()}")
topGenes.eachWithIndex { entry, index ->
println("${index + 1}. ${entry.key}: ${entry.value} occurrences")
}
println("Tab 2 dropdowns updated with ${allUniqueGenes.size()} genes")
} catch (Exception ex) {
showAlert("Error", "Failed to analyze gene abundance: ${ex.getMessage()}")
ex.printStackTrace()
}
})
// Helper method to show alerts
def showAlert = { title, message ->
Platform.runLater {
def alert = new Alert(Alert.AlertType.INFORMATION)
alert.setTitle(title)
alert.setHeaderText(null)
alert.setContentText(message)
alert.showAndWait()
}
}
// Helper method to get current annotation Z plane
def getCurrentAnnotationZPlane = {
def imageData = getCurrentImageData()
if (imageData == null) return ImagePlane.getDefaultPlane()
def selectedObject = getSelectedObject()
if (selectedObject != null && selectedObject.getROI() != null) {
return selectedObject.getROI().getImagePlane()
}
return ImagePlane.getDefaultPlane()
}
// Add all widgets to Tab 1
vbox1.getChildren().addAll(
label1,
new Separator(),
folderHBox,
loadButton,
new Label("CSV Preview (first 5 data rows):"),
csvTable,
new Label("Column Mapping:"),
coordHBox,
analyzeButton
)
tab1.setContent(new ScrollPane(vbox1))
// ===== TAB 2: Point Loading & Gene Selection =====
def tab2 = new Tab("Point Loading")
tab2.setClosable(false)
def vbox2 = new VBox(10)
vbox2.setPadding(new Insets(15))
def label2 = new Label("Point Loading & Gene Selection")
label2.setStyle("-fx-font-size: 14px; -fx-font-weight: bold;")
// === FIRST LINE: Load Points Button and Points Number Field ===
def firstLineHBox = new HBox(10)
firstLineHBox.setAlignment(Pos.CENTER_LEFT)
def loadPointsButton = new Button("Load Points")
loadPointsButton.setPrefWidth(120)
def pointsLabel = new Label("Points number:")
def pointsField = new TextField("10000")
pointsField.setPrefWidth(80)
firstLineHBox.getChildren().addAll(loadPointsButton, pointsLabel, pointsField)
// === 10 LINES OF GENE DROPDOWNS AND COLOR PICKERS ===
def geneSelectionVBox = new VBox(5)
geneSelectionVBox.setPadding(new Insets(10, 0, 10, 0))
for (int i = 0; i < 10; i++) {
def lineHBox = new HBox(10)
lineHBox.setAlignment(Pos.CENTER_LEFT)
def geneDropdown = new ComboBox<String>()
geneDropdown.setPrefWidth(150)
geneDropdown.getItems().add("none")
geneDropdowns.add(geneDropdown)
def colorPicker = new ColorPicker(Color.rgb(255, 0, 0))
colorPickers.add(colorPicker)
def lineLabel = new Label("Gene ${i + 1}:")
lineLabel.setPrefWidth(60)
lineHBox.getChildren().addAll(lineLabel, geneDropdown, colorPicker)
geneSelectionVBox.getChildren().add(lineHBox)
}
// === FINAL BUTTON TO LOAD SELECTED GENES ===
def loadSelectedGenesButton = new Button("Load Selected Genes")
loadSelectedGenesButton.setPrefWidth(180)
// === PROGRESS BAR ===
progressBar = new ProgressBar(0)
progressBar.setPrefWidth(300)
progressBar.setVisible(false)
def progressLabel = new Label("")
progressLabel.setVisible(false)
// === LOAD POINTS BUTTON ACTION (loads first N points grouped by gene) ===
loadPointsButton.setOnAction({ e ->
if (currentCsvData.isEmpty() || currentHeaders.isEmpty()) {
showAlert("Error", "Please load CSV data in Tab 1 first.")
return
}
if (geneColumnIndex == -1 || xColumnIndex == -1 || yColumnIndex == -1) {
showAlert("Error", "Please configure column mappings in Tab 1 first.")
return
}
try {
def pointsNumber = Integer.parseInt(pointsField.getText())
// Get pixel size from current image
def imageData = getCurrentImageData()
if (imageData == null) {
showAlert("Error", "No image is currently open in QuPath.")
return
}
def pixelSizeMicrons = imageData.getServer().getPixelCalibration().getAveragedPixelSizeMicrons()
def pixelSizeYMicrons = imageData.getServer().getPixelCalibration().getPixelHeight()
// Get the appropriate image plane
def plane = ImagePlane.getPlane( getSelectedROI() )
// Show progress
progressBar.setVisible(true)
progressLabel.setVisible(true)
progressLabel.setText("Loading first ${pointsNumber} points...")
progressBar.setProgress(0.1)
// Collect first N points grouped by gene
def pointsByGene = [:]
def pointCount = 0
currentCsvData.each { row ->
if (pointCount >= pointsNumber) return // Stop if we've reached the limit
if (row.size() > Math.max(geneColumnIndex, Math.max(xColumnIndex, yColumnIndex))) {
try {
def geneName = row[geneColumnIndex].trim()
def xRaw = Double.parseDouble(row[xColumnIndex].trim())
def yRaw = Double.parseDouble(row[yColumnIndex].trim())
// Convert coordinates based on dimension unit
def x, y
if (dimensionUnit == "micron") {
x = xRaw / pixelSizeMicrons
y = yRaw / pixelSizeYMicrons
} else {
x = xRaw
y = yRaw
}
if (!pointsByGene.containsKey(geneName)) {
pointsByGene[geneName] = []
}
pointsByGene[geneName].add([x: x, y: y])
pointCount++
} catch (NumberFormatException nfe) {
// Skip invalid coordinate entries
}
}
}
progressBar.setProgress(0.5)
if (!pointsByGene.isEmpty()) {
def totalGenes = pointsByGene.size()
def geneIndex = 0
def totalPointsCreated = 0
// Create separate detection objects for each gene
pointsByGene.each { geneName, points ->
if (!points.isEmpty()) {
// Create arrays for coordinates
def xCoords = points.collect { it.x } as double[]
def yCoords = points.collect { it.y } as double[]
// Create PointsROI using QuPath's ROIs class
def pointsROI = ROIs.createPointsROI(xCoords, yCoords, plane)
// Create detection object with gene-specific class
def pathClass = getPathClass(geneName)
if (pathClass == null) {
pathClass = createPathClass(geneName)
}
def detection = PathObjects.createDetectionObject(pointsROI, pathClass)
detection.setName("${geneName} (${points.size()} points)")
// Add to current image hierarchy
addObject(detection)
totalPointsCreated += points.size()
geneIndex++
}
progressBar.setProgress(0.5 + (geneIndex as double) / totalGenes * 0.5)
}
progressBar.setProgress(1.0)
progressLabel.setText("Completed: ${totalPointsCreated} points loaded across ${pointsByGene.size()} genes")
// Update the hierarchy
fireHierarchyUpdate()
println("Created ${pointsByGene.size()} detection objects with total ${totalPointsCreated} points")
showAlert("Success", "Successfully loaded ${totalPointsCreated} points across ${pointsByGene.size()} genes!")
} else {
progressLabel.setText("No valid points found")
showAlert("Warning", "No valid coordinate data found in CSV.")
}
// Hide progress bar after 2 seconds
Platform.runLater({
Thread.sleep(2000)
progressBar.setVisible(false)
progressLabel.setVisible(false)
})
} catch (NumberFormatException nfe) {
showAlert("Error", "Invalid points number. Please enter a valid integer.")
progressBar.setVisible(false)
progressLabel.setVisible(false)
} catch (Exception ex) {
showAlert("Error", "Failed to load points: ${ex.getMessage()}")
ex.printStackTrace()
progressBar.setVisible(false)
progressLabel.setVisible(false)
}
})
// === LOAD SELECTED GENES ACTION (loads ALL points for each selected gene) ===
loadSelectedGenesButton.setOnAction({ e ->
if (currentCsvData.isEmpty() || currentHeaders.isEmpty()) {
showAlert("Error", "Please load CSV data in Tab 1 first.")
return
}
if (geneColumnIndex == -1 || xColumnIndex == -1 || yColumnIndex == -1) {
showAlert("Error", "Please configure column mappings in Tab 1 first.")
return
}
// Count selected genes
def selectedGenes = []
geneDropdowns.eachWithIndex { dropdown, index ->
def selectedGene = dropdown.getValue()
if (selectedGene != null && selectedGene != "none") {
selectedGenes.add([gene: selectedGene, color: colorPickers[index].getValue(), index: index])
}
}
if (selectedGenes.isEmpty()) {
showAlert("Warning", "No genes selected. Please select at least one gene.")
return
}
try {
// Get pixel size from current image
def imageData = getCurrentImageData()
if (imageData == null) {
showAlert("Error", "No image is currently open in QuPath.")
return
}
def pixelSizeMicrons = imageData.getServer().getPixelCalibration().getAveragedPixelSizeMicrons()
def pixelSizeYMicrons = imageData.getServer().getPixelCalibration().getPixelHeight()
// Get the appropriate image plane
//def plane = (zColumnIndex == -1) ? getCurrentAnnotationZPlane() : ImagePlane.getDefaultPlane()
def plane = ImagePlane.getPlane( getSelectedROI() )
// Show progress
progressBar.setVisible(true)
progressLabel.setVisible(true)
progressBar.setProgress(0)
def totalGenes = selectedGenes.size()
// Process each selected gene
selectedGenes.eachWithIndex { geneData, geneIndex ->
def selectedGene = geneData.gene
def selectedColor = geneData.color
progressBar.setProgress((geneIndex as double) / totalGenes)
progressLabel.setText("Processing ${selectedGene}... (${geneIndex + 1}/${totalGenes})")
// Filter data for this gene and collect ALL coordinates
def genePointsData = []
currentCsvData.each { row ->
if (row.size() > Math.max(geneColumnIndex, Math.max(xColumnIndex, yColumnIndex))) {
def geneName = row[geneColumnIndex].trim()
if (geneName == selectedGene) {
try {
def xRaw = Double.parseDouble(row[xColumnIndex].trim())
def yRaw = Double.parseDouble(row[yColumnIndex].trim())
// Convert coordinates based on dimension unit
def x, y
if (dimensionUnit == "micron") {
x = xRaw / pixelSizeMicrons
y = yRaw / pixelSizeYMicrons
} else {
x = xRaw
y = yRaw
}
genePointsData.add([x: x, y: y])
} catch (NumberFormatException nfe) {
// Skip invalid coordinate entries
}
}
}
}
if (!genePointsData.isEmpty()) {
// Create arrays for coordinates
def xCoords = genePointsData.collect { it.x } as double[]
def yCoords = genePointsData.collect { it.y } as double[]
// Create PointsROI using QuPath's ROIs class
def pointsROI = ROIs.createPointsROI(xCoords, yCoords, plane)
// Create or get path class with the selected color
def pathClass = getPathClass(selectedGene)
if (pathClass == null) {
pathClass = createPathClass(selectedGene)
}
// Convert JavaFX Color to integers
def red = (selectedColor.getRed() * 255) as int
def green = (selectedColor.getGreen() * 255) as int
def blue = (selectedColor.getBlue() * 255) as int
// Set the color for this path class
pathClass.setColor(red, green, blue)
def detection = PathObjects.createDetectionObject(pointsROI, pathClass)
detection.setName("${selectedGene} (${genePointsData.size()} points)")
// Add to current image hierarchy
addObject(detection)
println("Created detection for ${selectedGene}: ${genePointsData.size()} points with color RGB(${red}, ${green}, ${blue})")
} else {
println("No data found for gene: ${selectedGene}")
}
}
// Complete progress
progressBar.setProgress(1.0)
progressLabel.setText("Completed: ${totalGenes} genes processed")
// Update the hierarchy to show the new detections
fireHierarchyUpdate()
//showAlert("Success", "Successfully loaded selected gene detections!")
// Hide progress bar after 2 seconds
Platform.runLater({
Thread.sleep(2000)
progressBar.setVisible(false)
progressLabel.setVisible(false)
})
} catch (NumberFormatException nfe) {
showAlert("Error", "Invalid points number. Please enter a valid integer.")
progressBar.setVisible(false)
progressLabel.setVisible(false)
} catch (Exception ex) {
showAlert("Error", "Failed to load gene detections: ${ex.getMessage()}")
ex.printStackTrace()
progressBar.setVisible(false)
progressLabel.setVisible(false)
}
})
// Add all widgets to Tab 2
vbox2.getChildren().addAll(
label2,
new Separator(),
firstLineHBox,
new Label("Gene Selection & Colors:"),
geneSelectionVBox,
loadSelectedGenesButton,
new Separator(),
progressLabel,
progressBar
)
tab2.setContent(new ScrollPane(vbox2))
// Add tabs to TabPane
tabPane.getTabs().addAll(tab1, tab2)
// Create scene and show stage
def scene = new Scene(tabPane, 700, 750)
stage.setScene(scene)
stage.show()
stage.centerOnScreen()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment