Rotate/Pan/Zoom

2004-04-23
2004-06-08
  • Nobody/Anonymous

    I am implementing a scheme where the user can rotate, pan, and zoom in on interested areas of a graph (we may often have too much data in tight clusters). Starting with Pan operations, simple affine transformations of the edges and vertices were no problem (with a cutomized renderer extending SettableRenderer). The problem that I am having is that I am translating the "Graphic" vertices and not the "real" vertices. So visually all my vertices move, but the location of the vertices in the Vertex UserDatum is unchanged. Can anyone suggest an efficient way to update all the vertice locations?

    Thanks, Shawn

     
    • D. Fisher

      D. Fisher - 2004-04-23

      shawn, i would *strongly* advise you to look at the current CVS, and in particular, the "transformer" mechanism under samples/newgraphdraw.

      we're still working out details of how the new graph draw system will work, but it is custom-designed for that sort of operation.

      trying to do this in the classic GraphDraw is -- to say the least -- a challenge.

      that said, i'm happy to have you work on the old system if you like. i'm not sure i understand, though, what problem you are running into. can you walk me through it in a little more detail?

      please feel free to email me

      danyel

       
    • Nobody/Anonymous

      Danyel,

      I am in a position where switching to the newgraphdraw may be possible so I will start learning what I can about it.

      Thanks. Shawn

       
    • Nobody/Anonymous

      danyel,

      I have switched over to the newgraphdraw examples found in the samples. The most noticable difference that I see is that there is VisVertex and VisEdge functionality. This has worked sufficiently, except for one detail which I am having trouble figuring out how to resolve (mostly because there are many different was to do it, but none that I like).
      I allow the user to "offset" visVertices through the graphLayout  panel, keeping track of the transformations that take place. I am currently using a Circle layout for no other reason than it is simple and static. My problem is that if I use EmittedLayout to grab the nearest Vertex to a mouse click (this is after a translation/offset of coordinate points) the visVertexMap in EmittedLayout has not been updated with the new coordinate, and thus getNearestVertex returns the wrong vertex.
      I understand that I can update the EmittedLayout visVertexMap manually, but then I am doubling up on  my work, having to keep track of updating the visVertex in 2 locations (EmittedLayout and Coordinates). What am I missing here? Is there a way that I can use the Coordinate offset, setX, and setY through visVertex, without having to manually update the layout?
      Also, part of my problem is that I have to get into the JUNG source code to figure out what's going on. The layouts are especially confusing. Do you expect to have documentation on this new stuff anytime soon?

      Thanks in advance, Shawn

       
      • D. Fisher

        D. Fisher - 2004-06-01

        Shawn,

        You've discovered the importance of the (underdocumented, yet crucial) TransformerPipeline. Specifically, a LayoutTransformer modifies an EmittedLayout. Each time that the EmittedLayout is regenerated (due to a screen-resizing, or the animation advancing), the Transformer changes it before showing it on screen.

        So I would think that the best way to let the user "offset" vertices is to store the offsets in a Transformer that will automatically offset them as the program runs.

        OffsetLayoutTransformer extends LayoutTransformer {

           public void setOffset( double x, double y ) {
               this.xOffset = x;
               this.yOffset = y;
            }

           public void setOffsetSpecificVertex( Double x, Vertex v ) {
                this.vertexOffset.put( v, x );
            }

           public EmittedLayout transform( EmittedLayout el ) {
            for (vertex v in vertices) {
                v.offset( x, y );
                if( vertexoffset.containskey( v ))
                    v.offset( vertexoffset.get( v ));
             }
        }

        }
        I'm sorry that the layouts are confusing. I'm afraid that my efforts on the new graph draw work have pretty much stopped for the moment as I work on finishing my dissertation. I'll try to get more documentation together--but feel free to ask questions as you discover confusing bits, please! I'm much better at answering questions than I am at writing the text from scratch.

         
    • Nobody/Anonymous

      That makes sense. One more piece of info and I think I'll be in good shape. I have been fighting resizeLayouts() in GraphLayoutPanel, and eventually extended GLP to override it. I think my lack of understanding of the pipeline was the issue. I also think my previous problem with visVertexMap is tied to my problem with resizeLayouts(). I think I was updating the VisVertex Coordinates too far down the pike. That is, I had extended VisVertexRenderer and performed the transformations there. This appears incorrect, right? Now I see that there are only 4 lines in resizeLayouts(), but I'm a little confused on what it is doing. It appears that it is always modifying the ORIGINAL layout, and not the LAST layout. Is that true, or have I misunderstood it? Also, I am including zoom operations so that I user that has a lot of data that may be grouped together can focus in on a cluster of vertices. resizeLayouts() was always returning all vertices to the screen. Is it possible that, after a transformation, only a portion of the "domain" can be shown.

      Thanks, Shawn

       
      • D. Fisher

        D. Fisher - 2004-06-02

        Shawn,

        Thanks for the note. I'm excited to see that you're wrestling with this.

        Your intuition that the VisVertexRenderer is a little late to do the resizing is correct: by the time you get there, every vertex has a location already.

        All that resizeLayouts does is churns through the pipeline with the latest emittedlayout. Unfortunately, I haven't made the data flow as clear as I should.

        1) An IterableLayout emits its layout (see LayoutIterator), and sends it to GraphLayoutPanel.setLayoutDisplay()

        2) GraphLayoutPanel calls that the "clean original." It copies it, then churns that copy through the pipeline. The copy is then called the "mLayout" and is displayed on screen.

        3) GraphLayoutPanel, in paintComponent(), then paints the mlayout.

        You may note that in the paintComponent code iterates only through those vertices that are in the mLayout.visVertexMap (and edges, similarly).

        So if you want to hide vertices or edges, the best way that I know of is to remove them from those various maps. I think the best place to do that is inside the Transformer.

         
    • Nobody/Anonymous

      Ok, it's working now. Now that I understand a little more about the layouts, I like them much better than what I was trying to implement. One note for future developers. While the custom Offset transformation operations worked, the graph would always "reset" to a centered location at the beginning of the next drag operation. There were 2 reasons for this:
      1. I was using LocalGraphDraw, which defines in it's constructor FitOnScreenTransformer, which is then always called when the screen is updated via resizeLayouts().
      2. cleanOriginal must be updated with mLayout in GraphLayoutPanel (i.e. getGraphLayoutPanel().setLayoutDisplay(getGraphLayoutPanel().getGraphLayout())
      otherwise the layout will always restart from the original layout settings. But setLayoutDisplay(...) calls resizeLayouts() which sends the layout through another transformation, so the transformation should be set to zero before this call, or the whole class can be extended and portions overwritten to do the same.

      JUNG folks, thanks so much for the help.
      Shawn

       
      • D. Fisher

        D. Fisher - 2004-06-03

        Shawn, glad you got it working. If you would be wiling to send me some of what you did so I can redistribute it as sample code, I'd be very excited to do so...

        1) it sounds like I should tweak LocalGraphDraw so the FitOnScreen is optional.

        2) Can you clarify the second point to me?  When do you invoke this code?

        getGraphLayoutPanel().setLayoutDisplay(getGraphLayoutPanel().getGraphLayout())

        -Danyel

         
    • Nobody/Anonymous

      Danyel,

      On the second point, I have a mouseListener on GraphLayoutPanel which looks like this,

      class PanMouseListener extends MouseInputAdapter
      {
          double lastx,lasty;

              public void mousePressed(MouseEvent e)
              {
              lastx = e.getX();
              lasty = e.getY();
          }

          public void mouseReleased(MouseEvent e)
              {
              panLayoutTransformer.setOffset(0,0);
              getGraphLayoutPanel().setLayoutDisplay(getGraphLayoutPanel().getGraphLayout());
          }

              public void mouseDragged(MouseEvent e)
              {
                  double deltax = e.getX() - lastx;
              double deltay = e.getY() - lasty;
              panLayoutTransformer.setOffset(deltax,deltay);
              getGraphLayoutPanel().resizeLayouts();
          }
          }

      The mousePressed method grabs the first x,y position. Next, mouseDragged calculates the change in position, notifies the transformer of the change, and kicks off resizeLayouts to iterate through the layout transformations. But resizeLayouts copies the "cleanOriginal" layout to perform the transformation on. This is fine if I only want to do one pan operation. But If I want to do more pans, then I am always starting from the original layout (cleanOriginal) on the next mousePressed operation, not the layout I just got done transforming, i.e. "mLayout". To fix this I include the mouseReleased method. When I finish my pan operation and release the mouse, my "cleanOriginal" becomes the last layout produced as a result of the transformation. Notice in mouseReleased that I set the offset to zero, because setLayoutDisplay calls resizeLayouts, which then goes through another transformation.

      I understand that I could have extended and overwritten this, but I am trying to work with the base API as much as possible to reduce the amount of code in my own package.

      The only other code that I think you might want is the custom LayoutTransformer that you suggested above. Methods and parameters are shown below.

          private double xOffset=0; //global offset parameter
          private double yOffset=0;

          private VisVertex singleOffsetVertex = null;
          private double singleOffsetx = 0;
          private double singleOffsety = 0;

          public void setOffset( double x, double y ) {
          this.xOffset = x;
          this.yOffset = y;
          }

          public void setVertexOffset( VisVertex vv, double x, double y ) {
          this.singleOffsetVertex = vv;
          this.singleOffsetx = x;
          this.singleOffsety = y;
          }

          public EmittedLayout transform( EmittedLayout el ) {

          /*There are two forms of offsets: 1. individual node offsets,
          and 2. global offsets for the pan operation. */
         
          //1. Once a vertex has been offset, null it
          if( singleOffsetVertex != null)
          {
             singleOffsetVertex.offset(singleOffsetx,singleOffsety);
             singleOffsetVertex = null;
          }

          //2.
          if(xOffset != 0 && yOffset != 0)
          {
             for (Iterator iter = el.visVertexMap.values().iterator(); iter
                      .hasNext();)
             {
                 VisVertex vv = (VisVertex) iter.next();
              vv.offset(xOffset,yOffset);
             }
          }
         
          return el;
          }

      I am interested in 2 possibilities, 1. a global pan operation of all nodes, or a pan operation on a single node (I don't have a need for the hash map because I only want to transform a single node or all nodes, not any combination in between, yet). The pan operation on all nodes works fine. This is what I was referring to in my last e-mail. But the single node transformation does not. Now I've only been working on it for an hour or so, but it's nearly identical to the global pan operation so I can't see why it doesn't work. The transformations (offsets) are taking place and vertices seem to shift a bit, but they never leave the circle (I'm using CirlceLayout to initialize the vertices). Any idea what's happening here? Anyway, no pressure, I still have to put some more time into it.

      That's all for now. I'll be back at it tomorrow morning.
      Shawn

       
      • D. Fisher

        D. Fisher - 2004-06-04

        Ah, I see what you're doing. You've got a staticlayout, so it stays constant. That way, you can push mousedragging through the pipeline, only as far as the LayoutPanel. Your strategy wouldn't quite work if the Layout was Iterative (or otherwise changing on its own) because the layout at the top of the pipeline would be overwritten... you'd need to push it a little further back.

        The one check that I'd do on the singleOffsetVertex is to confirm that it's the same instance as the one in the map. I'd be worried that the visvertex is coming from the cleanOriginal.

        In order to be unambiguous, use the VERTEX, which is a proper index into the data, rather than the VISVERTEX, which functions as metadata. Consider something like this:

            Vertex singleOffsetVertex;

        public void setVertexOffset( VisVertex vv, double x, double y ) {
        this.singleOffsetVertex = vv.getVertex();
        this.singleOffsetx = x;
        this.singleOffsety = y;
        }

        if( singleOffsetVertex != null)
        {
        vv = (Vertex) el.getVisVertex( singleOffsetVertx );
        vv.offset(singleOffsetx,singleOffsety);
        }

         
    • Nobody/Anonymous

      It's a great job you guys have done. And I'd like to know when these new features will be released?  Thx

      TC

       
      • D. Fisher

        D. Fisher - 2004-06-08

        I'd like more feedback from potential users who are willing to help me identify the rough spots before I'm ready to finalize it.

         

Log in to post a comment.