Ruben Laguna's blog

May 7, 2009 - 3 minute read - Comments - connectionwidget graph illegalstateexception java labelwidget layerwidget library netbeans order visual visualization widget

Painting LabelWidgets on top of the ConnectionWidget - Netbeans Visual Library

I was fiddling with the Netbeans Visual Library again, trying to show a complex graph on screen and I run into a problem: There were so many connection in the graph that some widgets were hard to read because the ConnectionWidget arrows were printed over them.

LabelWidget hidden under ConnectionWidget

So I tried to I try to rearrange the order of my LayerWidgets to paint the LabelWidgets on top of ConnectionWidgets. A call to mainLayer.bringToFront() should be enough, but this is what I got when I tried that:

java.lang.IllegalStateException: Widget (org.netbeans.api.visual.widget.LabelWidget@10014f0) was not added into the scene
at org.netbeans.api.visual.anchor.Anchor.getRelatedSceneLocation(Anchor.java:213)
at org.netbeans.modules.visual.anchor.RectangularAnchor.compute(RectangularAnchor.java:111)
at org.netbeans.modules.visual.router.DirectRouter.routeConnection(DirectRouter.java:65)
at org.netbeans.api.visual.widget.ConnectionWidget.calculateRouting(ConnectionWidget.java:527)
at org.netbeans.modules.visual.layout.ConnectionWidgetLayout.layout(ConnectionWidgetLayout.java:109)
at org.netbeans.api.visual.widget.Widget.layout(Widget.java:1350)
at org.netbeans.api.visual.widget.Widget.layout(Widget.java:1342)
at org.netbeans.api.visual.widget.LayerWidget.layout(LayerWidget.java:86)
at org.netbeans.api.visual.widget.Widget.layout(Widget.java:1342)
at org.netbeans.api.visual.widget.Scene.layoutScene(Scene.java:312)
at org.netbeans.api.visual.widget.Scene.validate(Scene.java:393)
at org.netbeans.api.visual.widget.SceneComponent.addNotify(SceneComponent.java:92)
at java.awt.Container.addImpl(Container.java:1039)
at java.awt.Container.add(Container.java:896)
at com.rubenlaguna.modules.mainmodule.GraphTopComponent.setDotFile(GraphTopComponent.java:80)
at com.rubenlaguna.modules.mainmodule.DotOpenSupport.createCloneableTopComponent(DotOpenSupport.java:31)
at org.openide.windows.CloneableOpenSupport.openCloneableTopComponent(CloneableOpenSupport.java:197)
at org.openide.windows.CloneableOpenSupport$1.run(CloneableOpenSupport.java:98)
at org.openide.util.Mutex.doEvent(Mutex.java:1335)
at org.openide.util.Mutex.writeAccess(Mutex.java:452)
at org.openide.windows.CloneableOpenSupport.open(CloneableOpenSupport.java:95)
at org.openide.actions.OpenAction.performAction(OpenAction.java:81)
at org.openide.util.actions.NodeAction$DelegateAction$1.run(NodeAction.java:589)
at org.netbeans.modules.openide.util.ActionsBridge.doPerformAction(ActionsBridge.java:77)
at org.openide.util.actions.NodeAction$DelegateAction.actionPerformed(NodeAction.java:585)
at org.openide.explorer.view.TreeView$PopupSupport.mouseClicked(TreeView.java:1515)
at java.awt.AWTEventMulticaster.mouseClicked(AWTEventMulticaster.java:253)
at java.awt.AWTEventMulticaster.mouseClicked(AWTEventMulticaster.java:252)
at java.awt.AWTEventMulticaster.mouseClicked(AWTEventMulticaster.java:252)
at java.awt.AWTEventMulticaster.mouseClicked(AWTEventMulticaster.java:252)
at java.awt.Component.processMouseEvent(Component.java:6137)
at javax.swing.JComponent.processMouseEvent(JComponent.java:3265)
at java.awt.Component.processEvent(Component.java:5899)
at java.awt.Container.processEvent(Container.java:2023)
at java.awt.Component.dispatchEventImpl(Component.java:4501)
at java.awt.Container.dispatchEventImpl(Container.java:2081)
at java.awt.Component.dispatchEvent(Component.java:4331)
at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4301)
at java.awt.LightweightDispatcher.processMouseEvent(Container.java:3974)
at java.awt.LightweightDispatcher.dispatchEvent(Container.java:3895)
at java.awt.Container.dispatchEventImpl(Container.java:2067)
at java.awt.Window.dispatchEventImpl(Window.java:2458)
at java.awt.Component.dispatchEvent(Component.java:4331)
[catch] at java.awt.EventQueue.dispatchEvent(EventQueue.java:599)
at org.netbeans.core.TimableEventQueue.dispatchEvent(TimableEventQueue.java:104)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)

So I posted a question on netbeans graph mailing list but the suggestions there didn’t resolved my problem.

After digging a little bit into the Visual Library source code I come up with a really easy solution. Just call mainLayer.bringToFront() AFTER the scene was painted for the first time on the screen. I should have tried that first!. The result shows LabelWidgets painted overt the ConnectionWidgets so the text in the LabelWidgets is always readable.

class GraphTopComponent extends CloneableTopComponent {
  //.... omitted ....
  private showScene() {
    JComponent c = scene.createView();
    GraphLayout graphLayout = GraphLayoutFactory.createHierarchicalGraphLayout(scene, false);
    ForceDirectedLayout forceDirectedGraphLayout = new ForceDirectedLayout(scene);
    SceneLayout sceneGraphLayout = LayoutFactory.createSceneGraphLayout(scene, graphLayout);
    sceneGraphLayout.invokeLayout();
    this.forceDirectedSceneLayout = LayoutFactory.createSceneGraphLayout(scene, forceDirectedGraphLayout);
    jPanel1.removeAll();
    jPanel1.add©;
    scene.bringMainLayerToFront();
  }
}
class DotGraphScene extends GraphScene.StringGraph {
  //... omitted ...
  void bringMainLayerToFront() {
    mainLayer.bringToFront();
  }
}

LabelWidgets on top of ConnectionWidgets

Anchor will throw an IllegalStateException if the relatedWidget has no location in the scene. Those widget will not get a position until the LayerWidget that contains them is processed. So you keep the original order of the LayerWidgets until the scene is painted for the first time, by then all widgets have a location in the scene and then it’s posible to change the relative order of the LayerWidgets using LayerWidget.bringToFront().

The only problem with this approach is that is your are using a dynamic layout like me there is a chance that the layout changes the position of the LabelWidget by a noticiable amount and the Anchor will not be updated accordingly until the next iteration of the dynamic layout. Which lead to the artifact marked in red in the previous figure. This is only a real problem if your dynamic layout doesn’t stabilize or if is not very smooth.