'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 1:[1]

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.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1