I had this problem here: http://forum.jgraph.com/questions/3857/cell-overlays-huge-performance-impact
I tracked it back in the Validator.java example. The problem is that the overlays are not reset before the validation is performed. This has a massive performance impact. Every time you add a single cell, every cell in your graph with an overlay gets another instance of the same overlay added. Moreover for every cell in the graph the method drawCell gets called multiple times.
Example: you have 50 cells with overlays. If you add 1 single cell, then these 50 cells will get another overlay added => 100 overlays and the drawCell method gets called twice now for every cell. If you add another cell the same happens again which results in cpu load rising up until the graph becomes unusable.
Below is an example code for better explanation. I'd upload a screenshot, but I don't have enough karma for file uploads.
When you launch the example, please open the console. At first you'll see the following in the console:
com.mxgraph.model.mxCell@1befab0celloverlays: 1
0: com.mxgraph.model.mxCell@1befab0: com.mxgraph.swing.util.mxCellOverlay[,92,162,16x16,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=,minimumSize=,preferredSize=]
Calls to drawCell per Second: 24400
which means drawCell is being called 24400 times per second, a single cell has 1 overlay.
and after adding 6 vertices you'll get this for every cell with an overlay:
com.mxgraph.model.mxCell@1befab0celloverlays: 7
0: com.mxgraph.model.mxCell@1befab0: com.mxgraph.swing.util.mxCellOverlay[,92,162,16x16,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=,minimumSize=,preferredSize=]
1: com.mxgraph.model.mxCell@1befab0: com.mxgraph.swing.util.mxCellOverlay[,92,162,16x16,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=,minimumSize=,preferredSize=]
2: com.mxgraph.model.mxCell@1befab0: com.mxgraph.swing.util.mxCellOverlay[,92,162,16x16,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=,minimumSize=,preferredSize=]
3: com.mxgraph.model.mxCell@1befab0: com.mxgraph.swing.util.mxCellOverlay[,92,162,16x16,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=,minimumSize=,preferredSize=]
4: com.mxgraph.model.mxCell@1befab0: com.mxgraph.swing.util.mxCellOverlay[,92,162,16x16,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=,minimumSize=,preferredSize=]
5: com.mxgraph.model.mxCell@1befab0: com.mxgraph.swing.util.mxCellOverlay[,92,162,16x16,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=,minimumSize=,preferredSize=]
6: com.mxgraph.model.mxCell@1befab0: com.mxgraph.swing.util.mxCellOverlay[,92,162,16x16,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=,minimumSize=,preferredSize=]
Calls to drawCell per Second: 103489
which means drawCell is being called 103489 times per second, a single cell has 7 overlays.
By the way, using triple buffer helps, but doesn't solve the problem.
This example creates a graph with a lot of vertices (most are overlapped, doesn't matter). Take a look at the cpu load in the taskmanager. Whenever you press "Add 1 Dummy Cell" the cpu load rises. Same happens when you only move an existing vertex. When you click "Clear Overlays & Validate" everything will be fine again.
package com.mxgraph.examples.swing;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import com.mxgraph.canvas.mxICanvas;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.swing.handler.mxKeyboardHandler;
import com.mxgraph.swing.handler.mxRubberband;
import com.mxgraph.swing.util.mxICellOverlay;
import com.mxgraph.util.mxDomUtils;
import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxEventSource.mxIEventListener;
import com.mxgraph.view.mxGraph;
import com.mxgraph.view.mxMultiplicity;
public class Validation extends JFrame
{
int dummyCount = 0;
/**
*
*/
private static final long serialVersionUID = -8928982366041695471L;
public Validation()
{
super("Hello, World!");
Document xmlDocument = mxDomUtils.createDocument();
Element sourceNode = xmlDocument.createElement("Source");
Element targetNode = xmlDocument.createElement("Target");
Element subtargetNode = xmlDocument.createElement("Subtarget");
final mxGraph graph = new mxGraph();
Object parent = graph.getDefaultParent();
graph.getModel().beginUpdate();
try
{
for( int i=0; i < 20; i++) {
Object v1 = graph.insertVertex(parent, null, sourceNode, 20, 20,
80, 30);
Object v2 = graph.insertVertex(parent, null, targetNode, 200, 20,
80, 30);
Object v3 = graph.insertVertex(parent, null, targetNode
.cloneNode(true), 200, 80, 80, 30);
Object v4 = graph.insertVertex(parent, null, targetNode
.cloneNode(true), 200, 140, 80, 30);
graph.insertVertex(parent, null, subtargetNode, 200,
200, 80, 30);
Object v6 = graph.insertVertex(parent, null, sourceNode
.cloneNode(true), 20, 140, 80, 30);
}
// graph.insertEdge(parent, null, "", v1, v2);
// graph.insertEdge(parent, null, "", v1, v3);
// graph.insertEdge(parent, null, "", v6, v4);
//Object e4 = graph.insertEdge(parent, null, "", v1, v4);
}
finally
{
graph.getModel().endUpdate();
}
mxMultiplicity[] multiplicities = new mxMultiplicity[3];
// Source nodes needs 1..2 connected Targets
multiplicities[0] = new mxMultiplicity(true, "Source", null, null, 1,
"2", Arrays.asList(new String[] { "Target" }),
"Source Must Have 1 or 2 Targets",
"Source Must Connect to Target", true);
// Source node does not want any incoming connections
multiplicities[1] = new mxMultiplicity(false, "Source", null, null, 0,
"0", null, "Source Must Have No Incoming Edge", null, true); // Type does not matter
// Target needs exactly one incoming connection from Source
multiplicities[2] = new mxMultiplicity(false, "Target", null, null, 1,
"1", Arrays.asList(new String[] { "Source" }),
"Target Must Have 1 Source", "Target Must Connect From Source",
true);
graph.setMultiplicities(multiplicities);
// modify graphComponent to show problem
final mxGraphComponent graphComponent = new mxGraphComponent(graph) {
// multiple cell overlay problem
public mxICellOverlay addCellOverlay(Object cell, mxICellOverlay overlay) {
mxICellOverlay ov = super.addCellOverlay( cell, overlay);
// show number of overlays per cell:
mxICellOverlay[] arr = getCellOverlays(cell);
if( arr != null) {
System.out.println( cell + "celloverlays: " + arr.length);
for( int i=0; i < arr.length; i++)
System.out.println( i + ": " + cell + ": " + arr[i]);
}
// call super method
return ov;
}
// multiple calls to drawCell problem
protected mxGraphControl createGraphControl()
{
return new mxGraphControl() {
long previousMs = 0;
int callsPerSecond = 0;
public void drawCell(mxICanvas canvas, Object cell)
{
callsPerSecond++;
long currentMs = System.currentTimeMillis();
if( currentMs - previousMs > 1000) {
System.out.println( "Calls to drawCell per Second: " + callsPerSecond);
callsPerSecond = 0;
previousMs = currentMs;
}
super.drawCell( canvas, cell);
}
};
}
};
graph.setMultigraph(false);
graph.setAllowDanglingEdges(false);
graphComponent.setConnectable(true);
graphComponent.setToolTips(true);
// Enables rubberband selection
new mxRubberband(graphComponent);
new mxKeyboardHandler(graphComponent);
// Installs automatic validation (use editor.validation = true
// if you are using an mxEditor instance)
graph.getModel().addListener(mxEvent.CHANGE, new mxIEventListener()
{
public void invoke(Object sender, mxEventObject evt)
{
graphComponent.validateGraph();
}
});
// Initial validation
graphComponent.validateGraph();
getContentPane().setLayout( new BorderLayout());
getContentPane().add(graphComponent, BorderLayout.CENTER);
// add button which adds a vertex on click
JPanel toolbar = new JPanel();
JButton btAddVertex = new JButton( "Add 1 Dummy Cell");
btAddVertex.addActionListener( new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
mxIGraphModel model = graph.getModel();
try {
model.beginUpdate();
dummyCount++;
Object v0 = graph.insertVertex(graph.getDefaultParent(), null, "Dummy " + dummyCount, 60, 100, 80, 30);
} finally {
model.endUpdate();
}
}
});
toolbar.add( btAddVertex);
// clear overlays
JButton btClearOverlays = new JButton( "Clear Overlays & Validate");
btClearOverlays.addActionListener( new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
graphComponent.clearCellOverlays();
graphComponent.validateGraph();
}
});
toolbar.add( btClearOverlays);
JLabel callsPerSecondLabel = new JLabel();
toolbar.add( callsPerSecondLabel);
getContentPane().add( toolbar, BorderLayout.SOUTH);
JLabel infoLabel = new JLabel();
infoLabel.setPreferredSize( new Dimension( 100,160));
infoLabel.setText( "<html>Demo of performance problem. Multiple vertices are created automatically.<br/><br/>In the console logging you see the calls to drawCell per second.<br/>As soon as you move or add a cell (eg button below)" +
" <br/><br/>1. the number of calls to drawCell doubles(!) and hence CPU load doubles<br/>and<br/>2. all the overlays get another instance of the same overlay added." +
" <br/><br/>Solution would be a call to clearCellOverlays() before validateGraph() is called.");
getContentPane().add( infoLabel, BorderLayout.NORTH);
}
public static void main(String[] args)
{
Validation frame = new Validation();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(600, 600);
frame.setVisible(true);
}
}
Quick workaround to the problem is using this:
graphComponent.clearCellOverlays();
before calling this:
graphComponent.validateGraph();
JGraph 1.9.2.5 was used.