Ij2 (interactive) results table

results-table
ij2

#1

@ASHISRAVINDRAN, @imagejan, @haesleinhuepf
I was wondering what the status of an (interactive) ij2 results table is…?
Is there something useable?


#2

The net.imagej.table.* classes defining ImageJ2 results tables recently moved to scijava-table into the org.scijava.table package.

There are still a few pending pull requests on some other components:


If by interactive you mean that you have a menu bar with menu commands to save the table, this is planned but not finished yet:

If you’re looking for more advanced interactivity, such as editing table cells: I don’t think this will be available any time soon, unless you start implementing it according to your requirements, and contribute it to SciJava :slight_smile:


#3

Thanks a lot!

In fact, my requirements would be:

  • Load & Save from tab-delimited file
  • Ability to sort by column (basic Swing feature)
  • Option to attach an ActionListener, which gets notified when the user selects a certain row.

In terms of missing contributions, I guess these features are not yet there, right?


#4

@ctrueden @haesleinhuepf

I started looking into adding MouseListener and KeyListener to the table window but I don’t know where to start. My code looks like this:

final GenericTable table = Utils.createTable( someData );
uiService.show( table );

My issue is that the actual table window panel seems to be generated within the uiService.show call and thus I do not know to get a handle on it or add Listeners to it.


What I did for now is convert the GenericTable to a JTable: https://github.com/tischi/fiji-plugin-morphometry/blob/master/src/main/java/de/embl/cba/morphometry/table/TableUtils.java#L10

And then have an own JPanel showing the JTable: https://github.com/tischi/fiji-plugin-morphometry/blob/master/src/main/java/de/embl/cba/morphometry/table/InteractiveTablePanel.java#L19

Let me know how I could help integrating into scijava.

Here is a movie showing some interactivity of the table:
https://drive.google.com/open?id=1dexd5Vscv4Tr1Y_MogfK_r8oTpv3o4pT


#5

I have the suspicion that you need to derive an own class from the given Table implementation.

If you share a minimal example code repository on git, I’m happy to contribute on that. :slight_smile:


#6

My suspicion was different, because I felt that the ActionListeners should not be part of the Table but of the JPanel that shows the table…but I will work on creating a sandbox repo…

[EDIT] I am wrong: also in the normal Swing JTable the Listeners are in fact part of the Table.


#7

Done: https://github.com/tischi/fiji-interactive-table-sandbox/blob/master/src/test/java/TableTestTischi.java#L6

Playground for you: https://github.com/tischi/fiji-interactive-table-sandbox/blob/master/src/main/java/de/embl/cba/metadata/table/InteractiveGenericTable.java#L8


#8

Hi @Christian_Tischer

that was a challenging one! I had to create/copy five classes in order to make this happen. I created a pull request to your repo.

The most important part of the code looks like this. I hope you like the API:

		ImageJ imagej = new ImageJ();
		imagej.ui().showUI();

		InteractiveTableDisplay tableDisplay = (InteractiveTableDisplay) imagej.display().createDisplay(defaultGenericTable);
		tableDisplay.addClickEventListener(new InteractiveTableClickEventListener() {
			@Override
			public void execute(Table t, int row, int column) {
				IJ.log("CLICK! table " + t + " row" + row + " column " + column);
			}
		});

The whole thing only works, because the InteractiveSwingTableDiplayViewerhas a higher priority than the SwingTableDiplayViewer. Thus, you may break other parts of your ImageJ. If anybody (@imagejan ?) has an idea of how to improve / simplify this implementation, I would be super happy to learn how to do things like that. To me it appears like “Von hinten durch die Brust ins Auge” but I found no other way.

Cheers,
Robert


#9

I think the road you guys are walking is fraught with peril. As @haesleinhuepf points out, you are relying on certain UI components being present in the environment, at higher priority than others. The whole point of the UIService is to abstract away which UI is active, such that various UI implementations can all work with the same code. Ripping into the UI to find the Swing components in order to add listeners violates this abstraction.

At minimum, I would strongly recommend checking the return values of things using instanceof, and only if the implementation matches your expectations do you proceed—and otherwise either fail silently, or issue a warning. Otherwise, all environments besides Swing will crash.

If you are ambitious, you can extend the UserInterface abstraction to support attaching input behaviors. This is a thing that @tpietzsch and I briefly touched on during the currently ongoing hackathon in Madison: whether to create a scijava-input component that is a generalization of things currently in ui-behaviour and scijava-common, such that you can write UI behaviors without using any UI-specific classes such as java.awt or javax.swing or JavaFX. But we did not do it yet…


#10

Hey @ctrueden

thanks for confirming my doubts. However, could you please suggest an alternate solution? The only thing we are trying to achieve is adding a listener to a window. That should be doable within a couple of lines.

I’m sorry, but the UserInterface abstraction solution I don’t understand by far, likely because I’m not so familiar with ImageJ2 architecture.

I was expecting if we implement a SpecialTable that we just need a SpecialTableDisplay. However, it turned out that we also need a SpecialTableSwingViewer and messing with priorities. I see the point for separating UI (SwingViewer) and data (Table). But what is then the Display abstraction layer?

Thanks,
Robert


#11

To be precise: @Christian_Tischer’s requirement is to attach a listener that responds when the user selects a certain row. I.e.: a ListSelectionListener attached to the SelectionModel of a JTable. Right?

Here is an Groovy example illustrating what I meant:

#@ TableService tableService
#@ DisplayService displayService
#@ UIService uiService

// NB: The following will change to org.scijava.table in the next release.
import net.imagej.table.*

// TODO: Make a function like this in the TableService. :-)
def makeTable(listOfMaps) {
	table = new DefaultGenericTable()
	for (columnHeader in listOfMaps[0].keySet()) {
		column = new GenericColumn(columnHeader)
		for (row in listOfMaps) {
			column.add(row.get(columnHeader))
		}
		table.add(column)
	}
	return table	
}

// Create an ImageJ Table.
table = makeTable([
	["Name": "Christian", "ID": "tischi",        "City": "Heidelberg"],
	["Name": "Jan",       "ID": "imagejan",      "City": "Basel"],
	["Name": "Robert",    "ID": "haesleinhuepf", "City": "Dresden"],
	["Name": "Curtis",    "ID": "ctrueden",      "City": "Madison"],
])

// Wrap it in a TableDisplay (UI-agnostic visualization object).
tableDisplay = displayService.createDisplay(table)
println(tableDisplay)

// Look up the DisplayViewer (UI-specific visualization object) that wraps the Display.
sleep(50) // HACK: Creation of DisplayViewer is asynchronous. :-(
tableViewer = uiService.getDisplayViewer(tableDisplay)
println(tableViewer)
if (tableViewer == null) {
	println("NO DisplayViewer")
	return
}

// Ask the DisplayViewer for its associated DisplayWindow.
window = tableViewer.getWindow()
println(window)

// Ensure the DisplayWindow is of a type we know how to handle.
if (!(window instanceof javax.swing.JFrame)) {
	println("DisplayViewer window is not a Swing frame!")
	return
}

// A function to extract a Swing component from a container recursively.
def findComponent(container, componentClass) {
	for (c in container.getComponents()) {
		if (componentClass.isInstance(c)) return c
		else if (c instanceof java.awt.Container) {
			result = findComponent(c, componentClass)
			if (result != null) return result
		}
	}
	return null
}

// Extract the JTable from the window.
swingTable = findComponent(window.getContentPane(), javax.swing.JTable.class)
println(swingTable)
if (swingTable == null) {
	println("No JTable found!")
	return
}

// Add a table selection listener.
swingTable.getSelectionModel().addListSelectionListener({event ->
	row = swingTable.getSelectedRow()
	println("Row selected = " + row)
	name = swingTable.getValueAt(row, 1)
	id = swingTable.getValueAt(row, 2)
	city = swingTable.getValueAt(row, 3)
	println("You selected row #" + row + ": name=" + name + ", id=" + id + ", city=" + city)
})

However: I would like to discuss & design a way of using SciJava’s UI-agnostic event mechanism for UI actions. That way, every table viewer implementation would fire the same SciJava events on the EventService, and you could listen for them, without worrying about whether it is a Swing-based UI or not. Whereas the above code only works for the Swing UI, and only under certain constraints (a single JTable inside a JFrame).

This is true. It is too complicated right now. There are plans to simplify the hierarchy. But for now, yes, a Display is the UI-agnostic representation of the visualization, whereas the DisplayViewer is a UI-specifici implementation.

I hope you (almost) never need to code your own SpecialTable nor SpecialTableDisplay nor SpecialTableSwingViewer. I’d like the built-in SciJava Table objects and associated class layers to support the events people need already.