javaswingshapesjgraphx

How to make a parallelogram shaped vertex in JGraphx?


I'm trying to display a flowchart via JGraphx, and I need a parallelogram for input/output block. But JGraphx doesn't seem to know "shape=parallelogram". It seems odd not to have a parallelogram in a library for graphs and flowcharts (and it even has "actor" shape, how come it doesn't have a parallelogram?). Maybe it's only named some other way? Or in the case there is indeed no predefined parallelogram shape, what do I do to make a vertex into parallelogram?


Solution

  • Finally, I've found a way to make a parallelogram, yay! Here is how I made it work.
    First of all, to make a custom shape I had to create my own class extending mxBasicShape and override the createShape method.

    public class Parallelogram extends mxBasicShape {
    public Shape createShape(mxGraphics2DCanvas canvas, mxCellState state){
        mxCell cell = (mxCell)state.getCell();
        Polygon polygon = new Polygon();
        if(cell != null && cell.getGeometry() != null) {
            mxGeometry g = cell.getGeometry();
            int dx = (int) (cell.getGeometry().getHeight()/4.0);
            polygon.addPoint((int)(g.getX()+dx), (int)g.getY());
            polygon.addPoint((int)(g.getX()+g.getWidth()+dx), (int)g.getY());
            polygon.addPoint((int)(g.getX()+g.getWidth()-dx), (int)(g.getY()+g.getHeight()));
            polygon.addPoint((int)((int)g.getX()-dx), (int)(g.getY()+g.getHeight()));
        }
        return polygon;
    
    }
    

    }

    The second step is adding it to a list of shapes which appeared to be stored in mxGraphics2DCanvas.

    mxGraphics2DCanvas.putShape("parallelogram", new Parallelogram());
    

    And now "shape=parallelogram" works just fine!

    UPD
    It appeared creating just a shape is not enough and a perimeter is also to be created. This is how I've done it:

    public class ParallelogramPerimeter implements mxPerimeterFunction {
        @Override
        public mxPoint apply(mxRectangle bounds, mxCellState vertex, mxPoint next,
                boolean orthogonal) {
            double cx = bounds.getCenterX();
            double cy = bounds.getCenterY();
            double nx = next.getX();
            double ny = next.getY();
            double pi = Math.PI;
            double pi2 = Math.PI/2.0;
            double h = bounds.getHeight();
            double w = bounds.getWidth();
            double alpha = Math.atan2(h/2.0, w/2.0+h/4.0);
            double beta = Math.atan2(h/2.0, w/2.0-h/4.0);
            double t = Math.atan2(ny-cy, nx-cx);
    
            mxPoint p = new mxPoint();
    
            //Left
            if (t > pi - alpha || t < (-pi)+beta){
                Line border = new Line(cx-w/2.0+h/4.0, cy-h/2.0, cx-w/2.0-h/4.0, cy+h/2.0);
                Line line = new Line(cx, cy, nx, ny);
                p = Line.intersection(border, line);
            }
            //Top
            else if (t > (-pi)+beta && t < (0-alpha)){
                p.setY(cy-h/2.0);
                p.setX(cx + h/2.0*Math.tan(pi2+t));
            }
            //Right
            else if (t > (0-alpha) && t < beta){
                Line border = new Line(cx+w/2.0+h/4.0, cy-h/2.0, cx+w/2.0-h/4.0, cy+h/2.0);
                Line line = new Line(cx, cy, nx, ny);
                p = Line.intersection(border, line);
            }
            //Bottom
            else {
                p.setY(cy+h/2.0);
                p.setX(cx + h/2.0*Math.tan(pi2-t));
            }
    
            if (orthogonal){
                //Top
                if (nx >= cx-w/2.0+h/4.0 && nx <= cx+w/2.0+h/4.0 && ny <= cy-h/2.0){
                    p.setX(nx);
                }
                //Bottom
                else if (nx >= cx - w/2.0-h/4.0 && nx <= cx+w/2.0-h/4.0 && ny >= cy+h/2.0){
                    p.setX(nx);
                }
                //Left or right
                else{
                    Line left = new Line(cx-w/2.0+h/4.0, cy-h/2.0, cx-w/2.0-h/4.0, cy+h/2.0);
                    Line right = new Line(cx+w/2.0+h/4.0, cy-h/2.0, cx+w/2.0-h/4.0, cy+h/2.0);
                    p = left.projection(nx, ny);
                    mxPoint p1 = right.projection(nx, ny);
                    boolean r = false;
                    if (distance(nx, ny, p.getX(), p.getY()) > distance(nx, ny, p1.getX(), p1.getY()))
                        {
                            p = p1;
                            r = true;
                        }
                    //Upper corners
                    if (p.getY() < cy-h/2.0){
                        p.setY(cy-h/2.0);
                        if(r){
                            p.setX(cx+w/2.0+h/4.0);
                        }
                        else
                        {
                            p.setX(cx-w/2.0+h/4.0);
                        }
                    }
                    //Lower corners
                    else if (p.getY() > cy+h/2.0){
                        p.setY(cy+h/2.0);
                        if(r){
                            p.setX(cx+w/2.0-h/4.0);
                        }
                        else
                        {
                            p.setX(cx-w/2.0-h/4.0);
                        }
                    }
                }
            }
    
            return p;
    
        }
    
        private double distance(double x1, double y1, double x2, double y2){
            return Math.sqrt(Math.pow(x2-x1, 2)+Math.pow(y2-y1, 2));
        }
    
    }
    
    class Line{
    private double a;
    private double b;
    private double c;
    
    Line(double x1, double y1, double x2, double y2){
        a = y1-y2;
        b = x2-x1;
        c = x1*y2-x2*y1;
    }
    
    private Line(double a, double b, double c){
        this.a = a;
        this.b = b;
        this.c = c;
    }
    
    static private double determinant(double i, double j, double k, double l){
        return i*l - k*j;
    }
    
    static mxPoint intersection(Line first, Line second){
        double x,y;
        double denominator = determinant(first.a, first.b, second.a, second.b);
        x = 0 - determinant(first.c, first.b, second.c, second.b)/denominator;
        y = 0 - determinant(first.a, first.c, second.a, second.c)/denominator;
        return new mxPoint(x,y);
    }
    
    mxPoint projection(double x, double y){
        double a,b,c;
        if (this.b!=0){
            a=1;
            b=-(this.a*a)/this.b;
        }
        else{
            b = 1;
            a=-(this.b*b)/this.a;
        }
        c = -(a*x+b*y);
        Line line = new Line(a,b,c);
        return intersection(this, line);
    }
    }
    

    Then I had to add it to the perimeters in use, whose list appeared to be not in the same place as with shapes, but in mxStyleRegistry:

    mxStyleRegistry.putValue("parallelogramPerimeter", new ParallelogramPerimeter());
    

    And finally I've used "shape=parallelogram;perimeter=parallelogramPerimeter" for a style, which now works not only for displaying the parallelogram, but also for connecting edges to it properly.