javaapache-poipowerpointopenxmlecma

how to create bent connector by rotating 90degrees with exact coordinates using ooxml apache poi


Recently i am working with Apache POI and i am facing an issue while rotating bent connector with exact positioning. I want to represent bent connector as flow level between shapes, and i have a problem with visualizing So the basic problem of mine is while drawing shapes connector using ooxml apache poi, it is being depicted as below

enter image description here when we rotate to 90 degrees, exact position and coordinates keeps changing

I am expecting output as below, can anyone provide pointers to address this issue

enter image description here

ooxml and apache poi PPT


Solution

  • To get your wanted bent connection, the default BENT_CONNECTOR_3 needs to be flipped horizontally and then rotated 90 degrees counter-clockwise.

    Following complete example shows this. Note the comments in the code.

    import java.io.FileOutputStream;
    
    import org.apache.poi.xslf.usermodel.*;
    import org.apache.poi.sl.usermodel.*;
    import org.openxmlformats.schemas.presentationml.x2006.main.*;
    import org.openxmlformats.schemas.drawingml.x2006.main.*;
    
    import java.awt.Rectangle;
    import java.awt.geom.Rectangle2D;
    import java.awt.Color;
    
    public class CreatePPTXConnectorShapes {
        
     private static XSLFConnectorShape createConnector(XSLFSlide slide, XSLFAutoShape shape1, XSLFAutoShape shape2) {
      XSLFConnectorShape connector = slide.createConnector();
      connector.setShapeType(ShapeType.BENT_CONNECTOR_3); // bent connector having one handle
      connector.setAnchor(new Rectangle2D.Double( //connector is diagonal in a rectangle
       shape1.getAnchor().getCenterX(), // top left x of that rectangle is center x position of shape1
       shape1.getAnchor().getMaxY(), // top left y of that rectangle is bottom y of shape1
       shape2.getAnchor().getCenterX()-shape1.getAnchor().getCenterX(), // width of that rectanle is center x of shape2 minus center x of shape1
       shape2.getAnchor().getY()-shape1.getAnchor().getMaxY() // height of that rectanle is top y of shape2 minus bottom y of shape1
       ));
      // the rectangle needs to be flipped horizontally as the default bent connector having one handle is like so:
      // ----+
      //     |
      //     +----
      connector.setFlipHorizontal(true);
      // now it is like so:
      //     +----
      //     |
      // ----+
      // and needs to be rotated 90 deg counter-clockwise 
      connector.setRotation(-90.00);
      // now it is like so:
      // |
      // +---+
      //     |
      // now we fix the connecting points
      CTConnector ctConnector = (CTConnector)connector.getXmlObject();
      CTNonVisualConnectorProperties cx = ctConnector.getNvCxnSpPr().getCNvCxnSpPr();
      CTConnection start = cx.addNewStCxn();
      start.setId(shape1.getShapeId());
      start.setIdx(2); // connecting point 2 is center of bottom edge
      CTConnection end = cx.addNewEndCxn();
      end.setId(shape2.getShapeId());
      end.setIdx(0); // connecting point 0 is center of top edge
    
      return connector;  
     }
    
     public static void main(String[] args) throws Exception {
    
      XMLSlideShow slideShow = new XMLSlideShow();
    
      XSLFSlide slide = slideShow.createSlide();
    
      XSLFAutoShape shape1 = slide.createAutoShape();
      shape1.setShapeType(ShapeType.RECT);
      shape1.setFillColor(Color.BLUE);
      shape1.setAnchor(new Rectangle(50, 50, 150, 50));
      
      XSLFAutoShape shape2 = slide.createAutoShape();
      shape2.setShapeType(ShapeType.RECT);
      shape2.setFillColor(Color.BLUE);
      shape2.setAnchor(new Rectangle(150, 200, 150, 50));
      
      // create connector 
      XSLFConnectorShape connector1 = createConnector(slide, shape1, shape2);
     
      FileOutputStream out = new FileOutputStream("CreatePPTXConnectorShapes.pptx");
      slideShow.write(out);
      out.close();
     }
    }
    

    It produces:

    enter image description here


    It is clear that rotation only is that easy if the rectangle containing the connector as the diagonal is a square. That's why the above code works and shows the main principle. I will leave it in the answer as not to complicate it at first and to have code which shows the issue when the rectangle containing the connector as the diagonal is not a square.

    If the rectangle containing the connector as the diagonal is not a square, then the anchor needs to be set considering the change of the top left position and the dimension while rotating. We have TL1, the needed position after rotation, which can get calculated from the shape's position and dimension. We need TL0, which is the top left point before rotating. For dimension it needs to be considered that width and height are swapped after rotation.

               TL0*-------+
                  |   .   |
                  |   .   |
                  |   .   |
     TL1*---------------------------+
        |         |   .   |         |
        h- - - - - - -+rp- - - - - -|
        |         |   .   |         |
        +-------------w-------------+
                  |   .   |
                  |   .   |
                  |   .   |
                  +-------+
    we have TL1, which can get calculated from the shape position and dimension
    TL1X, TL1Y, w: width, h: height
    we need TL0, which is the top left point before rotating (rp = rotation point)
    TL0X = TL1X + (w/2 - h/2) = TL1X + (w - h) / 2
    TL0Y = TL1Y - (w/2 - h/2) = TL1Y - (w - h) / 2
    width and height simply get swapped
    

    The following should work if shape 2 is on the right below shape 1. The connector is from center of bottom edge of shape 1 to center of top edge of shape 2.

    import java.io.FileOutputStream;
    
    import org.apache.poi.xslf.usermodel.*;
    import org.apache.poi.sl.usermodel.*;
    import org.openxmlformats.schemas.presentationml.x2006.main.*;
    import org.openxmlformats.schemas.drawingml.x2006.main.*;
    
    import java.awt.Rectangle;
    import java.awt.geom.Rectangle2D;
    import java.awt.Color;
    
    public class CreatePPTXConnectorShapes2 {
        
     private static XSLFConnectorShape createConnector(XSLFSlide slide, XSLFAutoShape shape1, XSLFAutoShape shape2) {
      XSLFConnectorShape connector = slide.createConnector(); 
      // if shape2 is on the right below shape1; connector is from center of bottom edge of shape1 to center of top edge of shape2
      if (shape2.getAnchor().getX() >= shape1.getAnchor().getCenterX() && shape2.getAnchor().getY() >= shape1.getAnchor().getMaxY()) {
       connector.setShapeType(ShapeType.BENT_CONNECTOR_3); // bent connector having one handle
       // the rectangle needs to be flipped horizontally as the default bent connector having one handle is like so:
       // ----+
       //     |
       //     +----
       connector.setFlipHorizontal(true);
       // now it is like so:
       //     +----
       //     |
       // ----+
       // and needs to be rotated 90 deg counter-clockwise 
       connector.setRotation(-90.00);
       // now it is like so:
       // |
       // |
       // +---+
       //     |
       //     |
       // set the anchor after rotating
       //connector is diagonal in a rectangle
       //before rotating it would be:
       double topLeftX = shape1.getAnchor().getCenterX(); // top left x of that rectangle is center x position of shape1
       double topLeftY = shape1.getAnchor().getMaxY(); // top left y of that rectangle is bottom y of shape1
       double width = shape2.getAnchor().getCenterX()-shape1.getAnchor().getCenterX(); // width of that rectanle is center x of shape2 minus center x of shape1
       double height = shape2.getAnchor().getY()-shape1.getAnchor().getMaxY(); // height of that rectanle is top y of shape2 minus bottom y of shape1
       //considering the rotation it is:
       topLeftX = topLeftX + (width - height) / 2;
       topLeftY = topLeftY - (width - height) / 2;
       double w = width;
       width = height;
       height = w;  
       connector.setAnchor(new Rectangle2D.Double(topLeftX, topLeftY, width, height));
       // now it is like so:
       // |
       // +-------+
       //         |  
       // now we fix the connecting points
       CTConnector ctConnector = (CTConnector)connector.getXmlObject();
       CTNonVisualConnectorProperties cx = ctConnector.getNvCxnSpPr().getCNvCxnSpPr();
       CTConnection start = cx.addNewStCxn();
       start.setId(shape1.getShapeId());
       start.setIdx(2); // connecting point 2 is center of bottom edge
       CTConnection end = cx.addNewEndCxn();
       end.setId(shape2.getShapeId());
       end.setIdx(0); // connecting point 0 is center of top edge
      }
    
      return connector;  
     }
    
     public static void main(String[] args) throws Exception {
    
      XMLSlideShow slideShow = new XMLSlideShow();
    
      XSLFSlide slide = slideShow.createSlide();
    
      XSLFAutoShape shape1 = slide.createAutoShape();
      shape1.setShapeType(ShapeType.RECT);
      shape1.setFillColor(Color.BLUE);
      shape1.setAnchor(new Rectangle(50, 50, 150, 50));
      
      XSLFAutoShape shape2 = slide.createAutoShape();
      shape2.setShapeType(ShapeType.RECT);
      shape2.setFillColor(Color.BLUE);
      shape2.setAnchor(new Rectangle(150, 200, 150, 50));
      
      XSLFAutoShape shape3 = slide.createAutoShape();
      shape3.setShapeType(ShapeType.RECT);
      shape3.setFillColor(Color.BLUE);
      shape3.setAnchor(new Rectangle(400, 200, 150, 50));
      
      // create connectors
      XSLFConnectorShape connector1 = createConnector(slide, shape1, shape2);
      XSLFConnectorShape connector2 = createConnector(slide, shape1, shape3);
     
      FileOutputStream out = new FileOutputStream("CreatePPTXConnectorShapes.pptx");
      slideShow.write(out);
      out.close();
     }
    }
    

    It produces:

    enter image description here

    Disclaimer: As all of my code samples here, this is not a one fits all solution usable in productive environments but only a working draft to show the principles. In order to create a solution that can be used productively, you have to make your own efforts.