3

I'm newbie in the swing and have a question how better to draw this shape:

complicated shape

I thought in two ways

  1. to draw regular rectangle and to write custom border to it?
  2. to draw regular rectangle + compound border(which contains 2 or 3 borders). But here i do not succeed to draw border inside the shape, is is possible at all? Something like this : two borders figure.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createMatteBor‌​der(outside top,left,bottom, right, Color.WHITE), createMatteBorder(inside top,left,bottom, right, Color.WHITE)), where the inside border is small rectangle, and outside is big rectangle - not sure if it is possible???

Please advise and an examples will be highly appreciated!

  • What do you mean by "border"? – MadProgrammer Dec 15 '15 at 10:29
  • See also [Border with rounded corners & transparency](http://stackoverflow.com/questions/15025092/border-with-rounded-corners-transparency). – Andrew Thompson Dec 15 '15 at 10:33
  • thank you for the answers! – altavista23 Dec 15 '15 at 11:32
  • With border, I meant something like: figure.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(outside top,left,bottom, right, Color.WHITE), createMatteBorder(inside top,left,bottom, right, Color.WHITE)), where the inside border is small rectangle, and outside is big rectangle (i will add the pics to the question), but not sure it is possible. – altavista23 Dec 15 '15 at 11:32

4 Answers4

1

you can use the polygon class (java.awt.Polygon)

int xs = new int[]{1,2,3...7}; //your x-coordinates
int ys = new int[]{1,2,3...7}; //your y-coordinates
Shape irr = new Polygon(xs, ys, xs.length); 

if you want to use certain borders you can use Graphics2D

public void paintComponent(Graphics gr){ 
    Graphics2D g2d = (Graphics2D)gr;

    GradientPaint redToWhite = new GradientPaint(0,0,color.RED,100, 0,color.WHITE);
    g2d.setPaint(redtowhite)
    g2d.fill(irr); //fill special color

    Stroke customBorder = getCustomBorder();
    g2d.setStroke(customBorder);
    g2d.draw(irr); //draw 'special' borders

}

have a look at stroke and fill

note that Polygon implements the contains(double x, double y)method which lets you detect if you're inside or not

Martin Frank
  • 3,445
  • 1
  • 27
  • 47
  • i'm sorry, i don't have your coordinates for the points of your shape, insert them please on your own... – Martin Frank Dec 15 '15 at 10:21
  • Thanks a lot! The polygon is the very good option, but thought that will be easier for me to find out to play with 2 or more borders(to integrate into existing code, which supported only rectangles until now). – altavista23 Dec 15 '15 at 11:52
  • you can use Graphics2D - i'll add this to my anwer – Martin Frank Dec 15 '15 at 11:55
1

You could use a Area for example...

Area

public class TestPane extends JPanel {

    public TestPane() {
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(200, 200);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();
        Area area = new Area(new Rectangle(10, 10, getWidth() - 20, getHeight() - 20));
        area.subtract(new Area(new Rectangle(20, getHeight() / 2, getWidth() / 2, getHeight() - 10)));
        g2d.draw(area);
        g2d.dispose();
    }

}

You define a custom shape...

Path2D

public class TestPane extends JPanel {

    public TestPane() {
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(200, 200);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();
        Path2D path = new Path2D.Float();
        path.moveTo(10, 10);
        path.lineTo(getWidth() - 20, 10);
        path.lineTo(getWidth() - 20, getHeight() - 20);
        path.lineTo(getWidth() / 2, getHeight() - 20);
        path.lineTo(getWidth() / 2, getHeight() / 2);
        path.lineTo(20, getHeight() / 2);
        path.lineTo(20, getHeight() - 20);
        path.lineTo(10, getHeight() - 20);
        path.closePath();
        g2d.draw(path);
        g2d.dispose();
    }

}

Actually writing a custom border would be very, very difficult, because of the irregular style of shape, where would the components actually be contained?

It might be possible to create two or more borders, which could then be laid out so that the appeared as one

See Working with Geometry for more details

Updated with Border example...

Getting a Border to actually work is far more difficult, as the expectation is that the internal area of the border will be rectangular.

Based on the complex shape you've provided, one solution would be to actually create two borders, a left and right borer, which take care of generating a "safe" area for components to be laid out within, for example:

public class LeftBorder implements Border {

    private int offset;

    public LeftBorder(int offset) {
        this.offset = offset;
    }
    
    @Override
    public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
        Path2D path = new Path2D.Float();
        
        int xOffset = x + offset;
        int yOffset = y + offset;
        
        width -= offset;
        height -= offset * 2;
        
        float gap = width * 0.1f;
        
        path.moveTo(xOffset, yOffset);
        path.lineTo(xOffset + width, yOffset);
        path.moveTo(xOffset, yOffset);
        
        path.lineTo(xOffset, yOffset + height);
        path.lineTo(xOffset + gap, yOffset + height);
        path.lineTo(xOffset + gap, yOffset + (height - (height / 2)));
        path.lineTo(xOffset + width, yOffset + (height - (height / 2)));
        
        ((Graphics2D)g).draw(path);
    }

    @Override
    public Insets getBorderInsets(Component c) {
        
        int height = c.getHeight();
        height -= (height / 2);
        
        System.out.println(height);
        return new Insets(offset + 4, offset + 4, height + 4, 0);
    }

    @Override
    public boolean isBorderOpaque() {
        return false;
    }

}

public class RightBorder implements Border {

    private int offset;

    public RightBorder(int offset) {
        this.offset = offset;
    }
    
    @Override
    public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
        Path2D path = new Path2D.Float();
        
        int xOffset = x;
        int yOffset = y + offset;
        
        width -= offset;
        height -= offset * 2;
        
        path.moveTo(xOffset, yOffset);
        path.lineTo(xOffset + width, yOffset);
        path.lineTo(xOffset + width, yOffset + height);
        path.lineTo(xOffset, yOffset + height);
        
        path.lineTo(xOffset, yOffset + (height - (height / 2)));
        
        ((Graphics2D)g).draw(path);
    }

    @Override
    public Insets getBorderInsets(Component c) {
        
        return new Insets(offset + 4, 0, offset + 4, offset + 4);
    }

    @Override
    public boolean isBorderOpaque() {
        return false;
    }

}

This would then require you to provide at least two panels of equal height, for example:

Example layout

import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.Border;

public class Main {

    public static void main(String args[]) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new GridBagLayout());
                frame.add(new LeftPane());
                frame.add(new RightPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class RightPane extends JPanel {

        public RightPane() {
            setBorder(new RightBorder(10));
            setLayout(new GridBagLayout());
            add(new JLabel("Righty"));
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

    }

    public class LeftPane extends JPanel {

        public LeftPane() {
            setBorder(new LeftBorder(10));
            setLayout(new GridBagLayout());
            add(new JLabel("Lefty"));
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

    }

}

This will also be relient on the layout manager been able to layout the two components next to each other

Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Thanks a lot! I thought of this option to derive from JPanel, but thought that will be easier for me to find out to play with 2 or more borders(to integrate into existing code, which supported only rectangles until now). – altavista23 Dec 15 '15 at 11:51
  • It might be possible to create two or more borders, which could then be laid out so that the appeared as one - yes, this what I wanted to(I added to the question) but not succeeded. The figure inherits from Jpanel and implemented as rectangular, with the lots of logic which I wanted to keep as is, so the playing with border I see the best option. The parent container of the figure is also JPanel. – altavista23 Dec 15 '15 at 12:41
1

Take a look at the Java 2D API. It helps you to draw complex shapes.

E.g.

class IrregularShape extends JComponent {

    private int strokeWidth;

    IrregularShape(int strokeWidth){
        this.strokeWidth = strokeWidth;
    }

    @Override
    protected void paintComponent(Graphics g) {
        Graphics2D newGraphics = (Graphics2D) g.create();

        Insets borderInsets = new Insets(0, 0, 0, 0);
        Border border = getBorder();
        if (border != null) {
            borderInsets = border.getBorderInsets(this);
        }

        BasicStroke basicStroke = new BasicStroke(strokeWidth);
        newGraphics.setStroke(basicStroke);

        int x = getX() + borderInsets.left + strokeWidth;
        int y = getY() + borderInsets.top + strokeWidth;
        int width = getWidth() - x - borderInsets.right - strokeWidth;
        int height = getHeight() - y - borderInsets.bottom - strokeWidth;

        Double outterRactangleDouble = new Rectangle2D.Double(x, y, width, height);

        Area outterRectangle = new Area(outterRactangleDouble);

        Area innerRectangle = new Area(outterRactangleDouble);
        AffineTransform affineTransform = new AffineTransform();
        affineTransform.scale(0.5, 0.5);
        affineTransform.translate(x + width * 0.10, y + height * 1.2);

        innerRectangle.transform(affineTransform);
        outterRectangle.subtract(innerRectangle);
        newGraphics.draw(outterRectangle);

    }

}

public class MainFrame {

    public static void main(String[] args) {
        JFrame frame = new JFrame("Irregular Shape");
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        Container contentPane = frame.getContentPane();
        contentPane.add(new IrregularShape(3));

        frame.setSize(640, 150);

        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

}

Result enter image description here

and it's also resizeable

enter image description here

René Link
  • 48,224
  • 13
  • 108
  • 140
  • Thanks a lot! I thought that will be easier for me to find out to play with 2 or more borders(to integrate into existing code, which supported only rectangles until now, I added more pics in the question) But if I do not succeed, i will use your idea, which will require more insertion in the existing code, thanks! – altavista23 Dec 15 '15 at 11:56
0

In addition to my first answer https://stackoverflow.com/a/34287251/974186

You can also implement it as a Border.

class IrregularBorder implements Border {

    private int thickness;

    public IrregularBorder(int thickness) {
        this.thickness = thickness;
    }

    @Override
    public void paintBorder(Component c, Graphics g, int x, int y, int width,
            int height) {
        Graphics2D graphics2d = (Graphics2D) g;

        BasicStroke basicStroke = new BasicStroke(thickness);
        graphics2d.setStroke(basicStroke);

        int halfThickness = thickness / 2;
        Double outterRactangleDouble = new Rectangle2D.Double(
                x + halfThickness, y + halfThickness, width - thickness,
                height - thickness);

        Area outterRectangle = new Area(outterRactangleDouble);

        Area innerRectangle = computeInnerRect(x, y, width, height,
                outterRactangleDouble);
        outterRectangle.subtract(innerRectangle);
        graphics2d.draw(outterRectangle);

    }

    private Area computeInnerRect(int x, int y, int width, int height,
            Double outterRactangleDouble) {
        Area innerRectangle = new Area(outterRactangleDouble);
        AffineTransform affineTransform = new AffineTransform();
        affineTransform.scale(0.5, 0.5);
        affineTransform.translate(x + width * 0.10, y + height * 1.2);

        innerRectangle.transform(affineTransform);
        return innerRectangle;
    }

    @Override
    public Insets getBorderInsets(Component c) {
        int left = (int) (thickness + (c.getWidth() * 0.6));
        return new Insets(thickness, left, thickness, thickness);
    }

    @Override
    public boolean isBorderOpaque() {
        return true;
    }

}

and use it as usual

public class MainFrame {

    public static void main(String[] args) {
        JFrame frame = new JFrame("Irregular Shape");
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        Container contentPane = frame.getContentPane();

        JPanel mainPanel = new JPanel(new BorderLayout());
        mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        contentPane.add(mainPanel);

        JPanel irregularShapeBorderedPanel = new JPanel(new BorderLayout());
        irregularShapeBorderedPanel.add(new JButton("Button"),
                BorderLayout.CENTER);
        irregularShapeBorderedPanel.setBorder(new IrregularBorder(2));

        mainPanel.add(irregularShapeBorderedPanel);

        frame.setSize(640, 150);

        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

}

enter image description here

Community
  • 1
  • 1
René Link
  • 48,224
  • 13
  • 108
  • 140