How would you go about displaying huge amount of rows in SWT table? Huge is something above 20K rows, 20 columns. Don't ask me why I need to show that much data, it's not the point. The point is how to make it work as fast as possible so that end user won't get bored waiting. Each row displays an instance of some object, columns are its properties (some). I thought to use JFace content/label provider pattern, but afraid it will be even slower than hitting the table directly with the data. This is how it goes:
Display.getDefault().asyncExec(new Runnable() {
public void run() {
List<MyObject> objects = model.getViewData();
for(MyObject object: objects){
TableItem item = new TableItem(table, SWT.NULL);
item.setImage(0, img1);
item.setBackground(color1);
item.setText(0, object.getProperty0());
item.setText(1, object.getProperty1());
item.setText(2, object.getProperty2());
.....
}
});
Drawing 20k records on my computer takes about 20 sec.
The biggest performance problem I've seen in Windows are caused by incredible amount of native windowing messages sent by SWT when new table item created and populated with text. I've found great workaround for this - hide table before populating, and then show it when done. Just calling table.setVisible(false) before the loop and table.setVisible(true) after the loop does wonders - the speed goes up six-seven times!
I'd like to speed it up even more.
What would you suggest ? Also, I wonder how this trick hiding the widget would work on non-windows implementations of SWT (aka Linux) ?
SWT can do that for you. When you use the SWT.VIRTUAL style flag, items are only created when scrolled into view. Here's how to do it:
Create the table with style SWT.VIRTUAL
Set the row count using Table#setItemCount()
Add a SWT.SetData Listener that fills the TableItems on demand.
Here's a code snippet:
public static void main( String[] args ) {
Display display = new Display();
Shell shell = new Shell( display );
shell.setLayout( new FillLayout() );
final Table table = new Table( shell, SWT.VIRTUAL );
table.setItemCount( 10000 );
table.addListener( SWT.SetData, new Listener() {
public void handleEvent( Event event ) {
TableItem item = (TableItem)event.item;
item.setText( "Item " + table.indexOf( item ) );
}
} );
shell.setSize( 300, 500 );
shell.open();
while( !shell.isDisposed() ) {
if( !display.readAndDispatch() ) {
display.sleep();
}
}
display.dispose();
}
You want to do lazy-loading on the table display. Basically, you keep all these objects offscreen in memory, then create only a handful of actual GUI table rows. As the user scrolls you re-render these rows with the objects at that scroll location.
See this article (EDIT: oops I meant this article) for a JFace example.
1 - use the setText(String[]) instead of setText(int, String) one call instead of several.
2 - use myTable.setRedraw(false) before and myTable.setRedraw(true) after the process to stop all redrawing opérations during loading data.
it's simpler and can improve performance !!
good luck.
on my side using this I load 2500 lines of 20 column in less than 300ms..... on a standard today PC.
Related
Suppose I have some data in a data base and I am retrieving that using query.
Example:
SELECT * FROM acsuserdetail where "+useranme+"= '"+arg+"' "
System.out.print(" FirstName = " + rs.getString("FirstName"));
This will return two result i.e.:
FirstName = Anurag
FirstName = Arvind
But when I am showing this data in UI in a JFrame then it is opening two frames having two details and if more details are there then that number of frame will open. This may be because the data which is coming from database are coming one by one not in single shot. I want all information to consolidate in a single frame. Code for UI is:-
public UIForShowingData(String data) {
frame = new JFrame("Showing Data");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
frame.setSize(300, 300);
button = new JButton("OK");
frame.setLayout(null);
button.setBounds(250, 250, 40, 50);
frame.add(button);
System.out.println(data.length());
tx = new JTextField(data);
frame.add(tx);
tx.setBounds(50, 50, 40, 50);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
frame.dispose();
}
});
}
Please note it is unclear what is happening given the code snippet in your question. However I'll take a shot in the dark and guess that UIForShowingData(String data) method is being called for each record in the result set obtained by querying the database.
Something like this:
while (rs.next()) {
...
UIForShowingData ui = new UIForShowingData(rs.getString("FirstName"));
...
}
This would explain why there are many frames opened as records are in the data base. To solve the problem you might consider use a Collection to store several values and use this collection to properly show data in a single JFrame. Probably using a JList or a JTable is a better choice than display records using text fields.
Off-topic
Please note your query is vulnerable to SQL injection attaks. To avoid this you may want to try PreparedStatement instead. For example:
String query = "SELECT * FROM acsuserdetail where useranme = ?";
PreparedStatement pst = connection.prepareStatement(query);
pst.setString(arg); // where arg is the argument to compare with
ResultSet rs = pst.executeQuery();
Swing is designed to work with Layout Managers and thus the use of null layout and methods such as setBounds(...), setLocation(...) and setXxxSize(...) are discouraged. From Swing tutorials:
Although it is possible to do without a layout manager, you should use
a layout manager if at all possible. A layout manager makes it easier
to adjust to look-and-feel-dependent component appearances, to
different font sizes, to a container's changing size, and to different
locales. Layout managers also can be reused easily by other
containers, as well as other programs.
See also these topics:
Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing?
Why is it frowned upon to use a null layout in Swing?
I am new in Java programming. I need to get the indices of selected column and row. I am getting -1 as selected indices for both the column and row. I have searched for a solution but didn't find anything satisfactory.
My code is following:
private void deleteProductButtonActionPerformed(java.awt.event.ActionEvent evt) {
DefaultTableModel tableModel = (DefaultTableModel) this.productDisplaTable.getModel();
JTable table = new JTable(tableModel);
int selectedRowIndex = table.getSelectedRow();
int selectedColIndex = table.getSelectedColumn();
System.out.println(selectedRowIndex );
System.out.println(selectedColIndex);
}
You're checking if a row is selected before the JTable has been displayed before the user can even interact with it.
Instead why not have that code in an ActionListener or some other listener so that the user at least has a chance to select something? This suggests that you might have a misunderstanding on how event-driven programming works and need to study the concepts a little bit more.
What makes you think that creating a a new JTable would have any selected rows or columns
JTable table = new JTable(tableModel); //???
Try using a table that is actually visible to the user instead
In your code you create a new JTable but you don't add this component to any container. Thus it won't never be visible and no row nor column could ever be selected.
Now, while we can add components dynamically in Swing we tipically place all our components before the top-level container (window) is made visible. In this case you should place the table when you initialize your components (don't forget the scroll pane) and do whatever you need to do when the button is pressed.
On the other hand I'm not sure what are you trying to achieve. I mean you already have a table called productDisplaTable. If you want to print the selected row and column in that table then make this little change:
private void deleteProductButtonActionPerformed(java.awt.event.ActionEvent evt) {
//DefaultTableModel tableModel = (DefaultTableModel) this.productDisplaTable.getModel();
//JTable table = new JTable(tableModel);
int selectedRowIndex = this.productDisplaTable.getSelectedRow();
int selectedColIndex = this.productDisplaTable.getSelectedColumn();
System.out.println(selectedRowIndex );
System.out.println(selectedColIndex);
}
Thanks all for taking time to reply.
I got the answer I was looking from #dic19's comment.
Now I clearly see the mistake I was doing. This was due to my lack of knowledge in Java programming.
I'm having troubles in my GWT app with a Google Visualization chart not showing up until after the user has some sort of interaction with the window (e.g. moves the mouse across the screen or presses a button). This would be fine except that the chart is suppose to show up as the first thing the user sees and since it's meant to be seen on a mobile device, it's likely they will not see the chart because their first interaction will be clicking a button that hides the chart to show other information.
Using the "Getting started tutorial" over at the Visualization code's page, the chart loads immediately fine (once some slight changes are made the fix the problems from the slightly out of date tutorial). After some trial and error to find where the difference between my code and the example code that was causing the problem, I found that it's happening because my code is using the newer layout panels instead of just regular panels in GWT.
The below code is the working tutorial code changed so that it uses a RootLayoutPanel.get() instead of a RootPanel.get(). With this, the chart doesn't load until you click to reload the page, then you can see the chart for an instant before the page reloads. This should be easily tested with the below code. To get the chart to show up for the entire time, simply change RootLayoutPanel.get() to RootPanel.get().
Something in my app is allowing the chart to load after user interaction (I'm not sure what). However, the layout panel is certainly the problem as if I change it to a regular panel it works fine. Unfortunately, my entire app is built using layout panels.
What's going on and how might I be able to make the chart show up from the start using layout panels? Thank you much!
package com.test.client;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.visualization.client.AbstractDataTable;
import com.google.gwt.visualization.client.VisualizationUtils;
import com.google.gwt.visualization.client.DataTable;
import com.google.gwt.visualization.client.Selection;
import com.google.gwt.visualization.client.AbstractDataTable.ColumnType;
import com.google.gwt.visualization.client.events.SelectHandler;
import com.google.gwt.visualization.client.visualizations.corechart.PieChart;
import com.google.gwt.visualization.client.visualizations.corechart.Options;
public class SimpleViz implements EntryPoint {
public void onModuleLoad() {
// Create a callback to be called when the visualization API
// has been loaded.
Runnable onLoadCallback = new Runnable() {
public void run() {
LayoutPanel panel = RootLayoutPanel.get();
// Create a pie chart visualization.
PieChart pie = new PieChart(createTable(), createOptions());
pie.addSelectHandler(createSelectHandler(pie));
panel.add(pie);
}
};
// Load the visualization api, passing the onLoadCallback to be called
// when loading is done.
VisualizationUtils.loadVisualizationApi(onLoadCallback, PieChart.PACKAGE);
}
private Options createOptions() {
Options options = Options.create();
options.setWidth(400);
options.setHeight(240);
options.setTitle("My Daily Activities");
return options;
}
private SelectHandler createSelectHandler(final PieChart chart) {
return new SelectHandler() {
#Override
public void onSelect(SelectEvent event) {
String message = "";
// May be multiple selections.
JsArray<Selection> selections = chart.getSelections();
for (int i = 0; i < selections.length(); i++) {
// add a new line for each selection
message += i == 0 ? "" : "\n";
Selection selection = selections.get(i);
if (selection.isCell()) {
// isCell() returns true if a cell has been selected.
// getRow() returns the row number of the selected cell.
int row = selection.getRow();
// getColumn() returns the column number of the selected cell.
int column = selection.getColumn();
message += "cell " + row + ":" + column + " selected";
} else if (selection.isRow()) {
// isRow() returns true if an entire row has been selected.
// getRow() returns the row number of the selected row.
int row = selection.getRow();
message += "row " + row + " selected";
} else {
// unreachable
message += "Pie chart selections should be either row selections or cell selections.";
message += " Other visualizations support column selections as well.";
}
}
Window.alert(message);
}
};
}
private AbstractDataTable createTable() {
DataTable data = DataTable.create();
data.addColumn(ColumnType.STRING, "Task");
data.addColumn(ColumnType.NUMBER, "Hours per Day");
data.addRows(2);
data.setValue(0, 0, "Work");
data.setValue(0, 1, 14);
data.setValue(1, 0, "Sleep");
data.setValue(1, 1, 10);
return data;
}
}
With the Layout panels the sizing of the widgets is done in JavaScript. When the initial page is loaded the initial sizing is done after everything else is finished. However in your case the pie is added when the library is loaded and that runs after the initial sizing. Therefor your widget isn't sized and won't show up. You need to call panel.forceLayout(); explicitly as the last method in you run method.
The google chart tools definately work with LayoutPanels. I am using it myself.
It's really difficult to say what's wrong but here are a couple of suggestions:
Check with Chrome Dev Tools (Console) if an exception is thrown.
Do you have standard mode enabled. That's important with LayoutPanels (make sure you have <!DOCTYPE html> in your HTML host page
You could try a 3rd party wrapper (supports automatic resizes)
I am using JFreeChart for the first time and I am using a TimeSeriesCollection() to create a TimeSeriesChart.
My reslutset from the DB query is app. aroung 1000 records. I am using org.jfree.date.time.Minute.Minute(int min.....) object to add it to a TimeSeries object.
I have a JFrame on which I add the ChartPanel directly. The user will provide new input parameters and reload the chart data with new dataset. So I clean up before every reload by calling the following in a method
dataset.removeAllSeries();
chart.removeLegend();
chart.getRenderingHints().clear();
cp.getChartRenderingInfo().setEntityCollection(null);
cp.removeAll();
cp.revalidate();
The output is perfect. But I noticed that after running the program 'several times in Eclipse' I see the below error message about Java heap space. Sometimes I also see in the Task Manager that the program hogs on the PC memory even though the dataset is very small (100 records).
Exception occurred during event dispatching:
java.lang.OutOfMemoryError: Java heap space
at sun.util.calendar.Gregorian.newCalendarDate(Gregorian.java:67)
at java.util.GregorianCalendar.<init>(GregorianCalendar.java:575)
at java.util.Calendar.createCalendar(Calendar.java:1012)
at java.util.Calendar.getInstance(Calendar.java:964)
at org.jfree.chart.axis.DateTickUnit.addToDate(DateTickUnit.java:238)
at org.jfree.chart.axis.DateAxis.refreshTicksHorizontal(DateAxis.java:1685)
at org.jfree.chart.axis.DateAxis.refreshTicks(DateAxis.java:1556)
at org.jfree.chart.axis.ValueAxis.reserveSpace(ValueAxis.java:809)
at org.jfree.chart.plot.XYPlot.calculateDomainAxisSpace(XYPlot.java:3119)
at org.jfree.chart.plot.XYPlot.calculateAxisSpace(XYPlot.java:3077)
at org.jfree.chart.plot.XYPlot.draw(XYPlot.java:3220)
at org.jfree.chart.JFreeChart.draw(JFreeChart.java:1237)
at org.jfree.chart.ChartPanel.paintComponent(ChartPanel.java:1677)
at javax.swing.JComponent.paint(JComponent.java:1029)
at javax.swing.JComponent.paintToOffscreen(JComponent.java:5124)
at javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1491)
at javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1422)
at javax.swing.BufferStrategyPaintManager.paint(BufferStrategyPaintManager.java:294)
at javax.swing.RepaintManager.paint(RepaintManager.java:1225)
at javax.swing.JComponent._paintImmediately(JComponent.java:5072)
at javax.swing.JComponent.paintImmediately(JComponent.java:4882)
at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:786)
at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:714)
at javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:694)
at javax.swing.RepaintManager.access$700(RepaintManager.java:41)
at javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1636)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:646)
at java.awt.EventQueue.access$000(EventQueue.java:84)
at java.awt.EventQueue$1.run(EventQueue.java:607)
at java.awt.EventQueue$1.run(EventQueue.java:605)
at java.security.AccessController.doPrivileged(Native Method)
My application is as follows:
I have a JFrame on which I directly add the ChartPanel after passing a Chart to it.
chart = ChartFactory.createTimeSeriesChart("Peak monitor", , "Time: Zoom in", "# of Requests Logged", createDataset(from,to), true, false, false);
chartpanel = new ChartPanel(chart);
FramePanel.this.add(cp);
validate();
Here createDataset(from, to) is a method
private TimeSeriesCollection createDataset(Date from, Date to) {
dataset.addSeries(controller.getStuff(from, to));
return dataset;
}
getStuff is called within a SwingWorker thread (DIBkgd method)
public TimeSeries getStuff(Date from, Date to) {
s1 = new TimeSeries("Log Requests");
final Date from1 = from;
final Date to1 = to;
progressDialog.setVisible(true);
sw = new SwingWorker<Void, Integer>() {
#Override
protected Void doInBackground() throws Exception {
if (db.getCon() == null) {
db.connect();
}
Arrlst2.clear();
Arrlst2= db.getDataDB(from1, to1);
for (Qryobjects x : Arrlst2) {
s1.add(new Minute(x.getMinute(), x.getHour(), x.getDay(), x.getMonth(), x.getYear()), x.getCount());
}
System.out.println("finished fetching data");
return null;
}
#Override
protected void done() {
progressDialog.setVisible(false);
}
};
sw.execute();
return s1;
}
Within my Database class the getDataDB is executed:
public List<Qryobjects> getDataDB(Date from, Date to) {
PreparedStatement select;
ResultSet rs;
String selectSql = "Select Sum(Cnt) Cid, Hr, Min, Dat from (Select count(H.Request_Id) Cnt , To_Char(H.Timestamp,'HH24') HR, To_Char(H.Timestamp,'mm') MIN, To_Char(H.Timestamp,'MM-dd-yyyy') DAT From Status_History H Where H.Timestamp Between ? And ? Group By H.Request_Id, H.Timestamp Order By H.Timestamp Asc) Group By Hr, Min, Dat order by Dat asc";
try {
select = con.prepareStatement(selectSql);
select.setDate(1, from);
select.setDate(2, to);
rs = select.executeQuery();
System.setProperty("true", "true");
while (rs.next()) {
int cnt = rs.getInt("cid");
int hour = Integer.parseInt(rs.getString("Hr"));
int min = Integer.parseInt(rs.getString("Min"));
int month = Integer.parseInt(rs.getString("dat").substring(0, 2));
int day = Integer.parseInt(rs.getString("dat").substring(3, 5));
int year = Integer.parseInt(rs.getString("dat").substring(6, 10));
Arrlst1.add(new Qryobjects(cnt, hour, min, day, month,year));
}
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
return Arrlst1;
}
For reference, I profiled two long running time series DTSCTest and MemoryUsageDemo. To exaggerate the scale, I used an artificially small heap, as shown below. In each case, I saw the typical saw-tooth pattern of periodic garbage collection return to baseline, as shown here. In contrast, this pathological example shows a secular rise in consumed memory from unrecoverable resources.
$ java -Xms32m -Xmx80m -cp build/classes:dist/lib/* chart.DTSCTest
$ java -Xms32m -Xmx80m -jar jfreechart-1.0.14-demo.jar
I resolved my problem.
I took the clue from #TrashGod to use dispose(). But it does not work directly for me.
I was adding the chart panel directly to my main JFrame container. And in my case, I wanted to keep creating the charts in the same JFrame container over and over.
I first tried clearing the dataset and called removeall() on the chart panel, but it did not help.
Then the solution I found was to create another JFrame and add the chart panel to it. And when I closed this JFrame, I again clear the dataset and called removeall() on the chart panel and also called dispose(). So everytime, I create a new chart, this JFrame and its children componenets are created and are completely disposed when I exit this JFrame.
So, when a chart is created a new JFrame is created and then disposed.
I should also add that after making this change I started to see the Saw Tooth pattern in the Java VisualVM profiler. I also used Jprofiler and I was shocked to see more than 100,000 objects were created while I was running my program. Now, I see 9000 objects created and it remains constant for the JFree package and based on my resultset retrieved the number of objects in my database package increases or decreases.
One more thing I did was to make my SQL do the parsing and convert it to a number. I wanted to reduce the number of objects created and also reduce the processing done by my program for each retrieved record.
Your solution is great! :)) Thanks to you, I have fixed my heap overflow problem. But, your solution can be even better. :)) Before you draw your graph onto panel, just call method panel.RemoveAll();
and everything that was before on your panel will be disposed. No other
JFrame
instances are necessary...
In my case, solution was:
for(...)
{
panel.RemoveAll();
drawData(listOfData);
}
Have a nice day! :)
In the method org.jfree.chart.axis.DateAxis.refreshTicksHorizontal, I added following extra lines to successfully avoided the OutOfmemoryError. the reason is for some circumstance, the variable tickDate is not increased, so the loop of "while (tickDate.before(upperDate))" becomes an infinite loop.
protected List refreshTicksHorizontal(Graphics2D g2,
Rectangle2D dataArea, RectangleEdge edge) {
List result = new java.util.ArrayList();
Font tickLabelFont = getTickLabelFont();
g2.setFont(tickLabelFont);
if (isAutoTickUnitSelection()) {
selectAutoTickUnit(g2, dataArea, edge);
}
DateTickUnit unit = getTickUnit();
Date tickDate = calculateLowestVisibleTickValue(unit);
Date upperDate = getMaximumDate();
boolean hasRolled = false;
Date previousTickDate=null; //added
while (tickDate.before(upperDate)) {
if(previousTickDate!=null && tickDate.getTime()<=previousTickDate.getTime()){ //added
tickDate=new Date(tickDate.getTime()+100L); //added
} //added
previousTickDate=tickDate; //added
//System.out.println("tickDate="+tickDate+" upperDate="+upperDate);** //add to see infinite loop
Please try by removing the tooltips and the legend from the chart (making them 'false' in the constructor). It should reduce the memory footprint
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.