I am updating/maintaining an existing graphing program. This is suppose to be a medium duty program (able to handle anything less than a million nodes + their transitions). In the GUI, there is a 'viewport' that visually shows the graph and there is a side panel that contains tabs that contain summaries on the nodes, transitions, etc...
The graphical part works phenominal and is quick but after running a profiler (YourKit) 96-99.8% of the time is spent creating the summary tab/table for the nodes. So for 10,000 nodes, it takes a second or two to generate the graph visually but minutes for it to populate the table!
A summary of the process is this: the tab gets notified that the model changed and gets the node list. If it needs more rows, it adds them, else it reuses or throws old ones away. Then after creating the rows and their cells, it fills them.
The population is one node per row, three cells (JPanel) per row (each contain some information). Each time a cell is created when a new row is added or the row is asked to check for updates, it calls the "positionPanel" method provided below. The layout manager is SpringLayout. According to the profiler, of the 90-odd percent to generate this table, 90-odd percent minus one is the "add(newPanel);" line.
Any suggestions on where the speed is being taken and how to improve it?
private void positionPanel(int row, int col) {
JPanel upPanel = this;
JPanel leftPanel = this;
String upSpring = SpringLayout.NORTH;
String leftSpring = SpringLayout.WEST;
if (row != 0) {
upPanel = cells.get(row - 1)[col];
upSpring = SpringLayout.SOUTH;
}
if (col != 0) {
leftPanel = cells.get(row)[col-1];
leftSpring = SpringLayout.EAST;
}
Cell newPanel = cells.get(row)[col];
//cells.get(row).set(col, newPanel);
add(newPanel);
layout.putConstraint(SpringLayout.NORTH, newPanel, cellSpacing, upSpring, upPanel);
layout.putConstraint(SpringLayout.WEST, newPanel, cellSpacing, leftSpring, leftPanel);
}
The suggestion to consider JTable hinges on it's use of the flyweight pattern to implement rendering. The benefit comes from rendering only visible/altered nodes, while ignoring others. JGraph uses a similar approach. The essential mechanism is outlined here. Note that the benefit accrues only to the view, but your profiling suggests it may be worthwhile.
Related
I need to show multi-line content in a JTable. The actual content is a collection of objects maintained in a custom model, which extends DefaultTableModel and generates cell content on the fly by overriding getValueAt().
In order to have multi-line content, I have implemented a custom TableCellRenderer:
private class MultiLineCellRenderer extends JTextArea implements TableCellRenderer {
public MultiLineCellRenderer() {
setLineWrap(true);
setWrapStyleWord(true);
setOpaque(true);
setBorder(new EmptyBorder(-1, 2, -1, 2));
setRows(1);
}
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
String text = value == null ? "" : value.toString();
if (!getText().equals(text)) {
setText(text);
int newHeight = table.getRowHeight() * getLineCount();
if (table.getRowHeight(row) != newHeight)
table.setRowHeight(row, newHeight);
}
if (isSelected) {
setForeground(table.getSelectionForeground());
setBackground(table.getSelectionBackground());
} else {
setForeground(table.getForeground());
setBackground(table.getBackground());
}
return this;
}
}
Now if I populate the table with a few hundred rows (column count is 2), I see the AWT worker thread starting to max out one CPU core. At the same time, memory consuption goes up from ~100 MB to ten times that amount and further. That happens even if the application is not actually doing anything (no data loaded in the background, no user interaction) and stops only when I clear the collection from which the table gets its content.
By commenting out select sections of code, I have identified these lines as the culprit:
int newHeight = table.getRowHeight() * getLineCount();
if (table.getRowHeight(row) != newHeight)
table.setRowHeight(row, newHeight);
If I comment out this section, all table rows have the same height (1 row of text), but memory consumption stays around ~100 MB.
If I replace these lines with a single call to table.setRowHeight(row, 32), i.e. with a fixed value, memory consumption starts going up again indefinitely.
The following modification works, at the expense of all rows having the same height:
int newHeight = getRowHeight() * getLineCount();
if (table.getRowHeight() < newHeight)
table.setRowHeight(newHeight);
Bottom line: it seems setting individual row heights in a JTable creates a massive memory leak. Am I doing something wrong, or have I encountered an actual bug? In the latter case, are there any known fixes/workarounds?
Setting the row height triggers a redraw, which in turn triggers another call to the renderer. Therefore, it is important to set the row height only if it is different from the current one, in order to avoid an endles loop. This is what happens when you call setRowHeight() unconditionally, even with a fixed value.
A second issue is that each row comprises two cells, which may have different heights. The code above will set the row height to match the cell being rendered right now. When the other cell of that row gets rendered and has a different height, the row height gets changed again. That will trigger a redraw, also of the first column in the row. Since that will result in another height change, there’s the infine loop again.
Proof: the following code fixes this:
int newHeight = table.getRowHeight() * getLineCount();
if (table.getRowHeight(row) < newHeight)
table.setRowHeight(row, newHeight);
Now the row height will only increase, but never decrease, thus breaking the infinite loop. Side effect: if the cell contents change and now occupy fewer rows than before, the row height will not change to reflect this.
Bottom line: rendering a JTable with multiline cells is non-trivial, and SO has quite a few buggy examples. The only working example I found (thanks to another SO post) is at https://www.javaspecialists.eu/archive/Issue106.html.
Their solution is to store cell heights internally in the renderer (though this could also be done in the table model, whichever works best for your implementation). When calculating the height of a cell, store it, then get the maximum height of any cell in the row and use that. Also be sure to set the row height only if it differs from the current one.
That has fixed the memory leak/processor consumption issue, in addition to finally giving me a working example of how to calculate cell height properly.
The simple version is that I'm drawing Graphics2D 60 times a second on a JPanel and it uses the drawstring method to create a bunch of labels. How would I go about making so I can click on those labels and have something happen?
Explanation: As it currently stands I have a system setup that says for every object in the world draw a string to the side (So I can see a list of all objects in the world). This is done with a for loop and the Graphics2D drawstring method.
The JPanel is being updated 60 times a second so this is being redrawn 60 times a second. I want to be able to click on these object labels so I can select the items, how would I go about turning them into buttons?
I messed around with JButton for awhile but it didn't seem to do me any good because whenever I added it the JPanel would go blank and only the button would render (Plus it didn't render to the right size).
More Details:
I use a
for(int I=0; I < sceneObjects.size(); I++) {
}
loop to grab every object in an object ArrayList. Each object has a String variable "Name". Before the loop class I sent an int called YPosition, and for every object the YPosition goes up by 20 so that the labels don't all stack on top of each other. I'm using the g2d.DrawString method to achieve this. But I need to be able to select the object labels.
I apologize if I forgot something in my question, let me know.
For those who are curious, the code looks exactly like this (Can't be compiled as is):
g2d.setFont(new Font("Arial", Font.PLAIN, hierarchyWidth / 26));
g2d.setColor(Color.black);
int oYPos = 20;
// For every object in existence
for(int i=0; i < engine.sceneObjects.activeObjects.size(); i++) {
GameObject theObject = engine.sceneObjects.activeObjects.get(i);
// If the scrollbar is within range of the hierarchy
// (Based on HierarchyHeight so that it's resolution friendly)
if(oYPos >= hierarchyScroll && oYPos < hierarchyScroll + hierarchyHeight) {
// If the object has no parent
if(theObject.transform.parent == null) {
g2d.drawString(theObject.name, hierarchyPosition.x + 5, hierarchyPosition.y + oYPos);
} else { // If the object has a parent
}
}
oYPos += 20;
}
// Track the last oYPos so that the scrollbar can adjust itself accordingly
lastOYPos = oYPos;
My guess would be some sort of class create for each of these labels and a Boolean stored called isSelected, and then rendering the label according to the class, but this seems a bit more complicated than I'd like to do
How can frozen columns be implemented in GXT 3.x (from Sencha)? Ext-JS, another product from Sencha seems to implement this, but I can't see where the Java based GXT implement the same thing:
http://dev.sencha.com/deploy/ext-4.0.0/examples/grid/locking-grid.html
The basic idea is that you need two different scrolling containers, one with the fixed column(s), one with the scrolling columns. Each of these need to be in a distinct viewport, so the standard Grid/GridView doesn't work well with this - they make assumptions about how scrolling should behave, so simply subclassing one or both is likely to be fairly involved.
Instead, you can build two grids, one for the locked columns, one for the scrolling ones. Each can deal with their own ColumnConfig classes, to draw headers and rows, and will be linked to the same ListStore to ensure their data is in sync - changes in the store will be passed along to both listening grids.
To get the full effect, some additional wiring will be needed:
Linking scrolling. Listen to the BodyScrollEvent from each Grid, and scroll the other to the same place (changing only top, not left, since you don't want one to control the other).
Sizing is the second big piece - both grids need their scrollable height to be the same, but the horizontal scrolling one needs a buffer on the bottom when it is actually showing that scrollbar. Usually the Grid is told to size based on its parent's instructions, though sometimes you directly size the Grid - in this case, this step is not needed, just size the two grids slightly differently. Otherwise, you'll need to structure the layout to properly configure this.
Finally, the locked column needs its vertical scrollbar to be hidden away - the user has no need to see two vertical scrollbars.
This covers the basic use case, but doesn't deal with things like alternate GridView implementations - GroupingView and subclasses will need to link expanding (and to hide the group headings so they don't appear twice, plus deal with the fact that the group row shouldn't get split when the second half scrolls sideways), TreeGridView and TreeGrid will need to link expanding nodes and hide the tree +/- icons from the second grid.
Here's this basic set of modifications applied to the basic grid example at http://www.sencha.com/examples/#ExamplePlace:basicgrid. To help avoid confusing the issue, I've deleted a number of other features in that grid such as tooltips and changing the selection model:
public class GridExample implements IsWidget, EntryPoint {
private static final StockProperties props = GWT.create(StockProperties.class);
private ContentPanel root;
#Override
public Widget asWidget() {
if (root == null) {
final NumberFormat number = NumberFormat.getFormat("0.00");
ColumnConfig<Stock, String> nameCol = new ColumnConfig<Stock, String>(props.name(), 50, SafeHtmlUtils.fromTrustedString("<b>Company</b>"));
ColumnConfig<Stock, String> symbolCol = new ColumnConfig<Stock, String>(props.symbol(), 100, "Symbol");
ColumnConfig<Stock, Double> lastCol = new ColumnConfig<Stock, Double>(props.last(), 75, "Last");
ColumnConfig<Stock, Double> changeCol = new ColumnConfig<Stock, Double>(props.change(), 100, "Change");
changeCol.setCell(new AbstractCell<Double>() {
#Override
public void render(Context context, Double value, SafeHtmlBuilder sb) {
String style = "style='color: " + (value < 0 ? "red" : "green") + "'";
String v = number.format(value);
sb.appendHtmlConstant("<span " + style + " qtitle='Change' qtip='" + v + "'>" + v + "</span>");
}
});
ColumnConfig<Stock, Date> lastTransCol = new ColumnConfig<Stock, Date>(props.lastTrans(), 100, "Last Updated");
lastTransCol.setCell(new DateCell(DateTimeFormat.getFormat("MM/dd/yyyy")));
List<ColumnConfig<Stock, ?>> l = new ArrayList<ColumnConfig<Stock, ?>>();
//Remove name from main set of columns
// l.add(nameCol);
l.add(symbolCol);
l.add(lastCol);
l.add(changeCol);
l.add(lastTransCol);
//create two column models, one for the locked section
ColumnModel<Stock> lockedCm = new ColumnModel<Stock>(Collections.<ColumnConfig<Stock, ?>>singletonList(nameCol));
ColumnModel<Stock> cm = new ColumnModel<Stock>(l);
ListStore<Stock> store = new ListStore<Stock>(props.key());
store.addAll(TestData.getStocks());
root = new ContentPanel();
root.setHeadingText("Locked Grid Sample");
root.setPixelSize(600, 300);
final Resizable resizable = new Resizable(root, Dir.E, Dir.SE, Dir.S);
root.addExpandHandler(new ExpandHandler() {
#Override
public void onExpand(ExpandEvent event) {
resizable.setEnabled(true);
}
});
root.addCollapseHandler(new CollapseHandler() {
#Override
public void onCollapse(CollapseEvent event) {
resizable.setEnabled(false);
}
});
//locked grid
final Grid<Stock> lockedGrid = new Grid<Stock>(store, lockedCm) {
#Override
protected Size adjustSize(Size size) {
//this is a tricky part - convince the grid to draw just slightly too wide
//and so push the scrollbar out of sight
return new Size(size.getWidth() + XDOM.getScrollBarWidth() - 1, size.getHeight());
}
};
lockedGrid.setView(new GridView<Stock>(){{
this.scrollOffset=0;
}});
//require columns to always fit, preventing scrollbar
lockedGrid.getView().setForceFit(true);
//main grid, with horiz scrollbar
final Grid<Stock> grid = new Grid<Stock>(store, cm);
//don't want this feature, want to encourage horizontal scrollbars
// grid.getView().setAutoExpandColumn(nameCol);
grid.getView().setStripeRows(true);
grid.getView().setColumnLines(true);
grid.setBorders(false);
grid.setColumnReordering(true);
grid.setStateful(true);
grid.setStateId("gridExample");
//link scrolling
lockedGrid.addBodyScrollHandler(new BodyScrollHandler() {
#Override
public void onBodyScroll(BodyScrollEvent event) {
grid.getView().getScroller().scrollTo(ScrollDirection.TOP, event.getScrollTop());
}
});
grid.addBodyScrollHandler(new BodyScrollHandler() {
#Override
public void onBodyScroll(BodyScrollEvent event) {
lockedGrid.getView().getScroller().scrollTo(ScrollDirection.TOP, event.getScrollTop());
}
});
HorizontalLayoutContainer gridWrapper = new HorizontalLayoutContainer();
root.setWidget(gridWrapper);
//add locked column, only 300px wide (in this example, use layouts to change how this works
HorizontalLayoutData lockedColumnLayoutData = new HorizontalLayoutData(300, 1.0);
//this is optional - without this, you get a little offset issue at the very bottom of the non-locked grid
lockedColumnLayoutData.setMargins(new Margins(0, 0, XDOM.getScrollBarWidth(), 0));
gridWrapper.add(lockedGrid, lockedColumnLayoutData);
//add non-locked section, taking up all remaining width
gridWrapper.add(grid, new HorizontalLayoutData(1.0, 1.0));
}
return root;
}
#Override
public void onModuleLoad() {
RootPanel.get().add(asWidget());
}
}
There are a handful of issues (no line between locked and unlocked column, locked column header menu context icon is slightly out of place), but it covers most of the details without much hassle, and leaves almost all of it open to configuration - want the lock at the end? Just move the modifications around - want more than one locked column? just add more to the lockedCm.
That functionality is not implemented in GXT, but there is an user called The_Jackal who made a workaround for this problem in the Sencha Forum - Thread. I haven't tried yet, but I hope it can help you.
GXT - Freeze grid workaround Download
I have the following,
GridLayout layout = new GridLayout(4, 0);
In the event that I have 5 items, this will create 2 columns, where the first contains 3 rows and the second contains 2 rows. This is not what I want, nor is this what I expected. I expected the first column to contain 4 rows and the second column to contain 1 row.
Why isn't this layout manager respecting the number of rows I want per column? Or better yet, how do I make this layout manager respect this?
The result seems expected: "Specifying the number of columns affects the layout only when the number of rows is set to zero."
You can get the desired effect using JList, as shown here.
private static final int N = 5;
...
list.setLayoutOrientation(JList.VERTICAL_WRAP);
list.setVisibleRowCount(N - 1);
Disclaimer: This is not provided as a concrete answer, but rather to prove a point of the helpfulness of a SSCCE...
GridLayout constructor is GridLayout(int rows,int cols) (The reason I mention it is as #AndrewThompson said in his answer seems like you might have mixed up the parameters of rows/cols for the LayoutManager). Thus 4,0 will give us 4 rows and a variable amount of columns.
When I add 4 labels I get 4 rows and 1 coloumn (as expected):
when you add 5 labels I get 3 rows and 2 coloumns each having 2 items except for the last which has 1:
IMO this is expected GridLayout must honor column/row count >0 (anything less than 0 and the LayoutManager calculates the amount), thus if we add more components than the rows allowed it creates a new coloumn >0 but also >1 as 0 and 1 perform the same. Thus it creates 2 coloums now when we fill a Grid thats 4x0 with 5 components, we expect the 2 components on each line ( for each coloums) and the remaining on the last row (not necessarily the last row depending on the amount of components i.e 8 would fill it to the last row as now its 4x2 but adding 9 components would cause a GridLayout of 4x3 - filling all coloumns of the row before going to the new row)
Please post an SSCCE which re-inacts the problem or else we are just guessing here is my example I made which shows different behavior than what you said/get:
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class Test {
public Test() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
GridLayout layout = new GridLayout(4, 0);
frame.setLayout(layout);
for (int i = 0; i < 5; i++) {
frame.add(new JLabel(String.valueOf(i + 1)));
}
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
//Create Swing components on EDT
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Test();
}
});
}
}
The GridLayout you specified has number of columns as 0. That means number of columns is to be decided by the layout. In which case it tries to compute number of columns required based on number of items.
If you added 4 components you would have got 4 rows with one component each. If you add 5 components two columns are required and they are filled with the components serially that means only 3 rows will be consumed.
So depending on your number of components you may see all specified rows utilized or not. For instance try with 7 components.
I am writing a program which among other things takes a folder of images (Typically around 2000 jpeg images) resizes them, and adds them to a timeline of images. The result of this being as follows:
This works fine, however the way I have done this seems very inefficient. The code which processes these images is shown below:
public void setTimeline(Vector<String> imagePaths){
int numberOfImages = imagePaths.size();
JLabel [] TotalImages = new JLabel[numberOfImages];
setGridPanel.setLayout(new GridLayout(1, numberOfImages, 10, 0));
Dimension image = new Dimension(96, 72);
if (imagePaths != null){
for(int i = 0; i <numberOfImages; i++){
TotalImages[i] = new JLabel("");
TotalImages[i].setPreferredSize(image);
ImageIcon tempicon = new ImageIcon(imagePaths.elementAt(i));
Image tempimage = tempicon.getImage();
Image newimg = tempimage.getScaledInstance(96, 72, java.awt.Image.SCALE_SMOOTH);
ImageIcon newIcon = new ImageIcon(newimg);
TotalImages[i].setIcon(newIcon);
setGridPanel.add(TotalImages[i]);
}
}
}
As can be seen, this code loops through each image path, adds it to a label and adds it to the panel - performing exactly as it should with the correct output.
However, the time taken to do this is substantial. Typically around 5 minutes for 2000 images (depending on the machine). I wondered if there is any way I could improve this performance by using different techniques?
Any help is greatly appreciated.
Save your scaled instances and load them direct. Hard drive space is cheap. This won't get around the initial cost of generating the thumbs, but any subsequent appearances will be lightning-fast.
takes a folder of images
with processes by using tempimage.getScaledInstance(96, 72, java.awt.Image.SCALE_SMOOTH);
use JTable, with reduced funcionality you can use JList too
Typically around 5 minutes for 2000 images
Image.getScaledInstance is simple asynchonous, witouth guarantee an fast and performance, then you have to redirect loading of images to the Background task
advantage first part of images are available immediatelly
dis_advantage required dispalying statuses of loading for user, very good knowledge about Swing and Event Dispatch Thread
I'd suggest to look at Runnable#Thread, and output put to the DefaultTableModel, notice this output must be wrapped into invokeLater
another and most complex way is use SwingWorker, but required very good knowledge about Java and Swing too
To add to mKorbel's excellent answer, I would definitely use a background thread such as a SwingWorker. This may not make the program any faster, but it will seem a lot faster, and that can make all the difference. Something like:
// use List<String> not Vector<String> so you can use Vector now, or change your
// mind and use ArrayList later if desired
// pass dimensions and components in as parameters to be cleaner
public void setTimeLine2(List<String> imagePaths, Dimension imgSize,
JComponent imgDisplayer) {
if (imagePaths != null && imgSize != null && imgDisplayer != null) {
// are you sure you want to set the layout in here?
imgDisplayer.setLayout(new GridLayout(1, 0, 10, 0));
// create your SwingWorker, passing in parameters that it will need
ImageWorker imgWorker = new ImageWorker(imagePaths, imgSize,
imgDisplayer);
imgWorker.execute(); // then ask it to run doInBackground on a background thread
} else {
// TODO: throw exception
}
}
private class ImageWorker extends SwingWorker<Void, ImageIcon> {
private List<String> imagePaths;
private JComponent imgDisplayer;
private int imgWidth;
private int imgHeight;
public ImageWorker(List<String> imagePaths, Dimension imgSize,
JComponent imgDisplayer) {
this.imagePaths = imagePaths;
this.imgDisplayer = imgDisplayer;
imgWidth = imgSize.width;
imgHeight = imgSize.height;
}
// do image creation in a background thread so as not to lock the Swing event thread
#Override
protected Void doInBackground() throws Exception {
for (String imagePath : imagePaths) {
BufferedImage bImg = ImageIO.read(new File(imagePath));
Image scaledImg = bImg.getScaledInstance(imgWidth, imgHeight,
Image.SCALE_SMOOTH);
ImageIcon icon = new ImageIcon(scaledImg);
publish(icon);
}
return null;
}
// but do all Swing manipulation on the event thread
#Override
protected void process(List<ImageIcon> chunks) {
for (ImageIcon icon : chunks) {
JLabel label = new JLabel(icon);
imgDisplayer.add(label);
}
}
}
Use tiles. Which means than rather than operating on images which are not shown in the screen, you only operated when the image has to be shown on the screen.
You need to maintain the logical position of the timeline, as well as displayed images.
When the user move the cursor to a previously hidden position, you compute which image(s) need to be shown next. If the images are not already processed, you process them. That's the same technique web-browsers use for performance.
A first thing you could do would be to add the images asynchronously, instead of trying to add all of them at once. Loop over them as you do, add them to the panel and render it every few images or so the user doesn't need to wait for a long initialization time.
Reuse image objects. A flyweight pattern would come to mind, and possibly limit the screen redraws to only the portions where you add a new image in your asynchronous loading.
If you are likely to have the same images redrawn (or to reload the same folders) in the future, you might want to consider caching some of the image objects, and maybe to save to file the resized thumbnails (many photo viewers do this and will store thumbnails versions - and some useful metadata - in hidden files or folders, so they can reload them faster the next time around.
what you could do to make it faster is by making 4 threads, and have them computing simultaneously the images. i dont know if the vm will spread them over multiple cpu cores though. something to look into because that would boost perfotrmace on a multicore pc