Does anyone have experience with the prefuse graph toolkit? Is it possible to change an already displayed graph, ie. add/remove nodes and/or edges, and have the display correctly adapt?
For instance, prefuse comes with an example that visualizes a network of friends:
http://prefuse.org/doc/manual/introduction/example/Example.java
What I would like to do is something along the lines of this:
// -- 7. add new nodes on the fly -------------------------------------
new Timer(2000, new ActionListener() {
private Node oldNode = graph.nodes().next(); // init with random node
public void actionPerformed(ActionEvent e) {
// insert new node //
Node newNode = graph.addNode();
// insert new edge //
graph.addEdge(oldNode, newNode);
// remember node for next call //
oldNode = newNode;
}
}).start();
But it doesn't seem to work. Any hints?
You should be aware the several layers of prefuse:
Data
Visualization
Display
To be short, the three layers can be linked this way:
Graph graph = new Graph(eg. yourXML_file);
Visualization viz = new Visualization();
viz.add(GRAPH, graph);
Display disp = new Display();
disp.setVisualization(viz);
Display is a graphic component that you add to a panel as usual.
Here you only modify the data layer.
Node newNode = graph.addNode();
graph.addEdge(oldNode, newNode);
You need now to update the visual layer:
viz.run("repaint");
The repaint action has to be defined.
ActionList repaint = new ActionList();
repaint.add(new RepaintAction());
viz.putAction("repaint", repaint);
I really advise you to read the prefuse doc.
And you can find a lot a resources on the official forum
At least, I can say you that prefuse is for the moment not really efficient for live graph update.
But it should not be enough, as you modified the graph structure, you have to regenerate it in the visualization (ie. recalculate the node placements etc...). There are two actions already defined in your sample code. Run them at the end of your actionPerformed.
viz.run("color");
viz.run("layout");
This method is not very efficient, because it adds a lot of computation each time you add a node, but there are not any others for the moment with prefuse.
As pointed out in my other post, the reason new nodes and edges are not visible in the original example is that the colors etc. for the nodes are not set correctly. One way to fix this is to explicitly call vis.run("color"); whenever a node or edge was added.
Alternatively, we can ensure that the color action is always running, by initializing the ActionList to which we add it (called "color" in the original example) slightly differently:
instead of
ActionList color = new ActionList();
we could write
ActionList color = new ActionList(Activity.INFINITY);
This keeps the action list running indefinitely, so that new nodes/edges will automatically be initialized for their visual appearance.
However, it is unclear to me whether this would actually be the preferred method - for things like a dynamic layout action (e.g. ForceDirectedLayout), such a declaration makes perfect sense, but for colors it seems to me that a constantly running coloring action is mostly overhead.
So, perhaps the previously posted solution of just running the "color" action explicitly (but only once) whenever the graph gets extended, might be the better choice...
Okay, after digging a bit through the prefuse sources, I now have a better understanding of how things work under the hood. I found out that actually the new nodes I create with the code above are not only added correctly to the graph, the visualization also takes note of it!
So, unlike Jerome suggests, it is not necessary to call vis.run("layout") explicitly.
The reason I thought the nodes were not added correctly was the fact that they are drawn with white background-, border- and text color - on white background that is. Not astonishing that they are a bit difficult to spot.
To fix that one has to call the color action after a new node is inserted, like this:
// insert new edge //
graph.addEdge(oldNode, newNode);
vis.run("color"); // <- this is new
(Note that this action is defined further up in the code of Example.jar under //-- 4.)
One last thing I am unsure about now is whether calling this action will make prefuse go over all graph nodes again and set their color - for very large graphs that would be undesired, of course.
You need to tell the control container ('d', in example.java) do get redrawn. Calling invalidate() should be enough (not sure, though).
Anyway, this might help you.
Related
I've a graph in Jung shown using a JFrame.
After I remove a vertex from the graph,
the shown graph automatically redrawn and presented without the removed vertex.
How can I disable it, so that only when I call the repaint method
the graph would be redrawn ?
Thank you
The simple way to do this is extend your graph by some class and add toRemove() method, where you can signify your vertex to delete in boolean array. And the second method deleteNow() which will use your boolean array and delete your vertexes - it will be alike repaint() now. The second way is add boolean value to your vertex instead of array in your extended class. I can't find any other way. Sorry if it's not helpful.
You haven't really given enough information to be able to advise you precisely, but here are some general observations.
The answer to your question is going to depend in part on how you're removing the vertex: interactively or programmatically.
If it's programmatically, then you'll need to look at the code that calls VisualizationViewer.repaint(). It's been a while since I've looked at that part of the code, but the gist is that something is listening to changes to the graph model and triggering repaints (because this is what users generally want).
If it's interactively, then it's probably on the same thread as your visualization, and you should have a fair amount of control over when repaint() gets called (see the calls in the sample code to VV.repaint()).
Button fooBar = new Button("1");
Button zooBar = new Button("2");
for (Node child : scene.lookupAll("*")) {
child.getStyleClass().add(child.getClass().getName());
}
I have a lot of Nodes and all of them need individuals styling. Now instead of repeating: Node.getStyleClass().add() the entire time I thought I would loop through all nodes and apply a style class with the same name as the variable. However, how exactly can I do this?
Edit: How do I reduce the amount of styleClass adds? Right now I need to do it for every node. I have 12 nodes (Hboxes, buttons etc) and its constantly growing which means that with the instantiation (new button etc) + the adding of stylesheets I have crazy amount of code.
Edit 2: Maybe my approach was wrong. Trying to create the gui programmatically wasnt a good idea. Im thinking of using fxml for complex guis now.
I've built a desktop application based on an example shipped with Jung2 that displays graphs on a frame. The application lets the user move selected nodes around using the EditingModalGraphMouse class.
However, when the user clicks and drags a node from an area where there's a lot of nodes on top of each other, the application actually selects the node on the bottom of the pile (which is not visible) instead of picking the one on top (which is visible to the user).
I'm trying to dig through the classes to verify where the node picking is actually done, but I'm a bit confused. I think the action takes place at the PickingGraphMousePlugin.mousePressed(...) method with the pickSupport object returned by vv.getPickSupport().
My question is: How can I make my application move the node on top when the user picks a node from a pile? And what are the classes responsible for managing that?
I'm using Jung version 2.0.1.
To answer my own question, one just have to use the ShapePickSupport.Style.HIGHEST, as in the code below (generics parameters were changed for clarity):
VisualizationViewer<V, E> vv = new VisualizationViewer<V, E>(visualizationModel, preferredSize);
...
ShapePickSupport pickSupport = (ShapePickSupport) vv.getPickSupport();
pickSupport.setStyle(ShapePickSupport.Style.HIGHEST);
Also, here's useful resource that is related to the matter:
Understanding the JUNG Visualization System.
We have developed a little graph editor with jung where you can draw graph/networks with your mouse. We use the VisualizationViewer as the panel we draw on. The VisualizationViewer holds the graph it has to display via its containing GraphLayout. We also have a save function which saves the graph into a text file:
public void saveGraph(){
Graph<V, E> g = visualizationviewer.getGraphLayout.getGraph();
// save g into text file
}
I have now written a class that generates me a new graph object using some algorithms:
public Graph<V, E> generateGraph(){
Graph<V, E> g = new DirectedSparseGraph<V, E>();
// do some algorithms on g
return g
}
If I now want to display the generated graph via:
...
visualisationviewer.getGraphLayout.setGraph(generateGraph());
...
The new Graph is perfectly displayed and one can draw on it even further.
But the saving functions (and all other functions that want to use the underlying Graph object of the VisualizationViewer) are now not working properly anymore. Either only one node is recognized or no nodes (which results in a NullPointerException). Everytime we want to retrieve the underlying Graph data structure we use:
visualizationviewer.getGraphLayout.getGraph();
Am I something missing here? Or is there a known bug within the framework that hasn't been fixed? It seems weird that the optical part is working perfectly while the programmatically retrieving of the underlying objects is not.
The problem is that you added the vertices in two steps by adding them to an arrayList first and adding to the graph from that list. Since your program works dynamically in order to avoid null-pointer exceptions you have to add nodes like this:
Node node;
g.addVertex(node = nodefactory.create());
nodes.add(node);
This way you're still able to use your arrayList(nodes) but avoid errors!
The second error is that the visualization viewer does not recognize the nodes from the new graph therefore you have to compare the names of the nodes and take the position from the old node in order to get it right
I don't think that's helpful at all..
Just keep in mind to add PDEEdges and Nodes directly and not via ArrayList ;-)
This method works as expected - it creates a JTree with a root node and two child container nodes (each with a respective leaf node):
private JComponent createSideBar()
{
final DefaultMutableTreeNode top = new DefaultMutableTreeNode("Projects");
final JTree tree = new JTree(top);
DefaultMutableTreeNode project = new DefaultMutableTreeNode("project 1");
DefaultMutableTreeNode version = new DefaultMutableTreeNode("version 1");
project.add(version);
top.add(project);
TreePath treePath = new TreePath(project.getPath());
// tree.expandPath(treePath);
project = new DefaultMutableTreeNode("project 2");
version = new DefaultMutableTreeNode("version 2");
project.add(version);
top.add(project);
return tree;
}
In this case, the tree starts out closed. I'd like the application to start with all nodes fully expanded so I started by adding the following:
tree.expandPath(treePath);
but when I un-comment it from the code above, the second set of child nodes don't show up, ie: Project 2 and Version 2 do not show up. In fact, all subsequently added nodes never show up.
For what its worth, I'm using JDK 1.5. From the docs, I can't seem to see any restrictions or why this method would have such ill-effects ... I'm going to try to look at the source but was hoping someone might have a good idea what and why this is expected behavior. I'm wondering if each subsequent node 'add' is somehow disallowed somehow - but I can't imagine would work for most run-time use cases.
Thanks,
-Luther
Unfortunately, Swing is often "helpful". In this case, it is creating a model for you from the data supplied, much the same as a JList would create a model if you supplied a Vector.
JTree and accomplices (primarily the Pluggable Look & Feel) will add listeners to the model to keep informed of updates. If you just change the data behind the (implicit) model's back, nothing will get updated other than by chance.
So, what you should do is explicitly create a model. When the model data changes (always on the EDT, of course), cause the relevant event to be fired.
If nodes are added to a node which has already been expanded, you need to reload the model.
((DefaultTreeModel)tree.getModel()).reload();
or
((DefaultTreeModel)tree.getModel()).reload(top);
This second version is more useful if you want to reload only a small part of a large tree.
Ah ... the model.
Your answers are elaborated on a bit here and here .... and even here.
I ended up doing something like:
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
model.insertNodeInto(newNode, parent, index);
which keeps the model directly informed. In my case, that scales just fine.
Now, how to mark one of these as the answer!?!