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
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
ooxml and apache poi PPT
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:
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:
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.