Drawing strings inscribed in a circle - java

I am aware of Java's drawString(String str, int x, int y) method; however, sometimes all I want is to label a circle with an appropriate string in the middle of it, given a font size (either in pts or in accordance to the circle's size). Is there an easy way to do it, or does one have to make the math on one's own? And if so, how can the width of a string be calculated, as a function of the font size (int pts)?

Here's an example. The text drawing is a modified version of the answer listed by Software Monkey, to use given coordinates, instead of drawing in the center of a component.
If you wanted to draw a circle that didn't cover the entire component, you would then have to calculate the center of the circle, and pass it into drawCenteredText.
The business with the Random class is there to demonstrate how this method will work with any font size.
public class LabledCircle {
public static void main(String args[]) {
JFrame frame = new JFrame();
// Create and add our demo JPanel
// I'm using an anonymous inner class here for simplicity, the paintComponent() method could be implemented anywhere
frame.add(new JPanel() {
public void paintComponent(Graphics g) {
super.paintComponent(g);
// Draw a circle filling the entire JPanel
g.drawOval(0, 0, getWidth(), getHeight());
Random rad = new Random();// For generating a random font size
// Draw some text in the middle of the circle
// The location passed here is the center of where you want the text drawn
drawCenteredText(g, getWidth() / 2, getHeight() / 2, rad.nextFloat() * 30f, "Hello World!");
}
});
frame.setSize(200, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
public static void drawCenteredText(Graphics g, int x, int y, float size, String text) {
// Create a new font with the desired size
Font newFont = g.getFont().deriveFont(size);
g.setFont(newFont);
// Find the size of string s in font f in the current Graphics context g.
FontMetrics fm = g.getFontMetrics();
java.awt.geom.Rectangle2D rect = fm.getStringBounds(text, g);
int textHeight = (int) (rect.getHeight());
int textWidth = (int) (rect.getWidth());
// Find the top left and right corner
int cornerX = x - (textWidth / 2);
int cornerY = y - (textHeight / 2) + fm.getAscent();
g.drawString(text, cornerX, cornerY); // Draw the string.
}
}

Related

Rounded LineBorder - not all corners are rounded

I'm using JFreeChart and I want to customise the ToolTip by creating my own Class which extends ChartPanel and override createToolTip().
static private class PrivateChartPanel extends ChartPanel{
//constructors
#Override
public JToolTip createToolTip() {
JToolTip jtt = super.createToolTip();
jtt.setBackground(Color.WHITE);
jtt.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1, true));
return jtt;
}
}
The problem is at Border. It is not rounded on all corners.
Why it is not rounded on all corners and how I could done it?
P.S.: I created a new simple project
import java.awt.Color;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class HelloWorld {
public static void main(String[] args) {
JFrame a = new JFrame();
a.setBounds(100, 100, 100, 100);
a.setLayout(null);
JPanel b = new JPanel();
b.setBounds(5, 5, 50, 50);
b.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1, true));
a.add(b);
a.setVisible(true);
}
}
and Border of JPanel is with same problem. I'm using Java 10
The effect of rounded corners depends on the size of these rounded corners. In case of LineBorder, it is determined by the thickness property. This is how the relevant implementation code looks like:
int offs = this.thickness;
int size = offs + offs;
if (this.roundedCorners) {
float arc = .2f * offs;
outer = new RoundRectangle2D.Float(x, y, width, height, offs, offs);
inner = new RoundRectangle2D.Float(x + offs, y + offs, width - size, height - size, arc, arc);
}
else {
outer = new Rectangle2D.Float(x, y, width, height);
inner = new Rectangle2D.Float(x + offs, y + offs, width - size, height - size);
}
Path2D path = new Path2D.Float(Path2D.WIND_EVEN_ODD);
path.append(outer, false);
path.append(inner, false);
g2d.fill(path);
So it differentiates between inner and outer corner, which is not much meaningful for a line size of one. But even worse, the outer corner size is just offs, which is identical to thickness (one in your case) and the size of the inner rounded corner is determined by arc, which is .2f * offs. For your thickness of one, the resulting inner corner size is 0.2. So it seems to be a pure coincidence (rounding issue of these two different corners) that we see an effect in the upper left corner, as even the bigger outer corner size of one is not enough to create a visible rounded effect.
Here is how it looks like with a thickness of 20, which results in an outer corner size of 20 and a whopping inner corner size of 4:
It don’t know which actual use case the Swing developers had in mind when they added the rounded corner support in this class. I can’t imagine any scenario where this strategy is useful.
Implementing a meaningful Border is not that hard. One possible implementation looks like:
public class RoundedLineBorder extends AbstractBorder {
int lineSize, cornerSize;
Paint fill;
Stroke stroke;
private Object aaHint;
public RoundedLineBorder(Paint fill, int lineSize, int cornerSize) {
this.fill = fill;
this.lineSize = lineSize;
this.cornerSize = cornerSize;
stroke = new BasicStroke(lineSize);
}
public RoundedLineBorder(Paint fill, int lineSize, int cornerSize, boolean antiAlias) {
this.fill = fill;
this.lineSize = lineSize;
this.cornerSize = cornerSize;
stroke = new BasicStroke(lineSize);
aaHint = antiAlias? RenderingHints.VALUE_ANTIALIAS_ON: RenderingHints.VALUE_ANTIALIAS_OFF;
}
#Override
public Insets getBorderInsets(Component c, Insets insets) {
int size = Math.max(lineSize, cornerSize);
if(insets == null) insets = new Insets(size, size, size, size);
else insets.left = insets.top = insets.right = insets.bottom = size;
return insets;
}
#Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
Graphics2D g2d = (Graphics2D)g;
Paint oldPaint = g2d.getPaint();
Stroke oldStroke = g2d.getStroke();
Object oldAA = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
try {
g2d.setPaint(fill!=null? fill: c.getForeground());
g2d.setStroke(stroke);
if(aaHint != null) g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, aaHint);
int off = lineSize >> 1;
g2d.drawRoundRect(x+off, y+off, width-lineSize, height-lineSize, cornerSize, cornerSize);
}
finally {
g2d.setPaint(oldPaint);
g2d.setStroke(oldStroke);
if(aaHint != null) g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAA);
}
}
}
Now, when I change the line
b.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1, true));
in your example to
b.setBorder(new RoundedLineBorder(Color.BLACK, 1, 10, true));
I get

understanding difficulties java swing

im trying to paint random (not yet) circles on a JPanel through click on a JMenu.
Im using a JTextField (and have to keep this) for some output.
Here is my Class:
class RandomDrawer extends JPanel implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
//double x = Math.random();
//double y = Math.random();
Random generator = new Random();
int x = generator.nextInt(100)+1;
int y = generator.nextInt(100)+1;
//System.out.printf("x = %d y = %d\n", x, y);
status.setText(String.format("rnd draw x: %d y: %d", x, y));
Graphics2D gg = (Graphics2D) canvas.getGraphics();
gg.setColor(Color.BLACK);
gg.drawOval(50, 50, 50, 50);
}
}
As long i let the line
status.setText(String.format("rnd draw x: %d y: %d", x, y));
stay in there i get nothing drawn. Without it i get my circle, im not sure what the problem is. I cant figure out why nothing is drawn.
Thanks a lot
EDIT:
Hello, i tried to understand the given informations.
Sadly I have to draw using the Graphics2D class, so i guess i can not draw using shapes. So i tried this, sadly it wont draw yet, can u give me some tips?
I tried to create a new class DrawShape, my thought was that i could keep track with those objects.
In my understanding there should be a drawn oval right now
gg.drawOval(100,100,100,100);
Thank you.
class DrawShape {
public DrawShape(String string) {
// TODO Auto-generated constructor stub
}
}
class RandomDrawer extends JPanel implements ActionListener {
/* (non-Javadoc)
* #see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
private List<DrawShape> shapes = new ArrayList<DrawShape>();
public void addShape(DrawShape s) {
shapes.add(s);
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
final Graphics2D gg = (Graphics2D) g;
gg.setColor(Color.BLACK);
gg.drawOval(100, 100, 100, 100);
}
#Override
public void actionPerformed(ActionEvent e) {
Random generator = new Random();
int x = generator.nextInt(100)+100;
int y = generator.nextInt(100)+100;
if (e.getActionCommand()==("Draw RandomCircle")) {
System.out.printf("x = %d y = %d\n", x, y);
status.setText(String.format("rnd draw x:%d y:%d ", x, y));
DrawShape circle = new DrawShape("Circle");
addShape(circle);
int count = shapes.size();
System.out.printf("objects in array: %d\n", count);
}
else if (e.getActionCommand()==("Draw RandomRectangle")) {
System.out.printf("x = %d y = %d\n", x, y);
//status.setText(String.format("rnd draw x: y: "));
//Graphics2D gg = (Graphics2D) canvas.getGraphics();
//gg.setColor(Color.BLACK);
//gg.drawRect(x, y, generator.nextInt(x), generator.nextInt(y));
}
}
}
Painting and such happens event-driven. If a a piece of a component needs to be redrawn its paintComponent method is called.
This means you need a component that nows how to draw by for instance:
public class DrawShape {
public final String text;
public final Color color;
public final Shape shape;
public DrawShape(String text, Color color, Shape shape) {
this.text = text;
this.color = color;
this.shape = shape;
}
}
public class CanvasWithShapes extends JPanel {
private List<DrawShape> shapes = new ArrayList<>();
public void addShape(DrawShape shape) {
shapes.add(shape);
}
#Override
public void paintComponent(Graphics g) {
final Graphics2D gg = (Graphics2D) g;
// Java 8: shapes.stream().forEach((shape) -> gg.draw(shape));
for (DrawShape drawShape : shapes) {
gg.setColor(drawShape.color);
gg.draw(drawShape.shape);
Rectangle bounds = shape.getBounds();
gg.drawString(shape.text, bounds.x+ 10, bounds.y + 20);
}
}
}
And then just add shapes to be redrawn a bit later.
Shape oval = ...;
c.add(oval);
c.repaint(50L); // A bit later
More detailed
A Shape has many implementations of interest like rectangle and oval.
Graphics2D can draw and fill a Shape. So in your case it would be ideal to add such a Shape. Maybe together with color and text. So I took your DrawShape class to hold these properties.
Random generator = new Random();
int x = generator.nextInt(100)+100;
int y = generator.nextInt(100)+100;
if (e.getActionCommand().equals("Draw RandomCircle")) {
System.out.printf("x = %d y = %d\n", x, y);
status.setText(String.format("rnd draw x:%d y:%d ", x, y));
int w = generator.nextInt(100) + 10;
int h = w;
Shape circle = new Ellipse2D.Double(x, y, w, h);
addShape(new DrawShape(text, Color.BLACK, circle));
int count = shapes.size();
System.out.printf("objects in array: %d\n", count);
} else if (e.getActionCommand().equals("Draw RandomRectangle")) {
System.out.printf("x = %d y = %d\n", x, y);
generator.nextInt(y));
int w = generator.nextInt(100) + 10;
int h = generator.nextInt(100) + 10;
Shape rect = Rectangle2D.Double(x, y, w, h)
addShape(new DrawShape(text, Color.BLACK, rect));
}
Graphics2D gg = (Graphics2D) canvas.getGraphics();
Don't use the getGraphics() method to do painting. The painting is temporary. It will be lost if you resize the frame for example.
Instead you need to override the paintComponent() method of your panel.
If you want to paint multiple objects then you need to keep track of each object. Check out Custom Painting Approaches for the two common ways to do this:
keep of List of Objects to paint and then iterate through the List each time the component is repainted.
paint the Object directly to a BufferedImage and then just paint the BufferedImage.
The example paints Rectangles. Basically you need a method like the addRectangle(...) method to add a new object to paint. So every time you click your button you add the new random shape.
Presumably, your problem arises from the setText() invocation modifying the Graphics object in some unexpected way. It is rarely appropriate to use getGraphics() in your own code. Instead, paint with the Graphics that is given to you.
Your approach is anyway flawed. If you manage to draw on a GUI component only once, as you are trying to do, then whatever you have drawn will disappear when the component is next repainted. Repainting can happen for a wide variety of reasons, many of them unrelated to the program's own behavior.
What you need to do is store some kind of data that the component's paintComponent() method will rely upon to do your custom painting every time. It follows that you will need to override the paintComponent() method of the component on which you want the circles to be drawn. For example, you might create a class that records all the needed drawing details for one circle, and give RandomDrawer a List of those objects as a member variable. The action listener manipulates that list appropriately and schedules a repainting, and paintComponent() is overridden to perform the actual painting.

How to add a shape on button click?

I have the following code, I am trying to add a circle to my frame when the button is clicked, I tried calling circle class from my main function, but do not know how to add a circle after that. please help me!
public static void main(String[] args) {
// Create a frame and put a scribble pane in it
JFrame frame = new JFrame("FrameFormula");
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
final FrameFormula scribblePane = new FrameFormula();
JPanel shapePanel = new JPanel();
JButton horGap = new JButton("Add a circle");
horGap.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
int[] circleValues = generateRandomValues(300, 300, 50, 150);
int x = circleValues[0];
int y = circleValues[1];
int width = circleValues[2];
int height = width;
Circle circle = new Circle(x, y, width, height);
//scribblePane.addCircle(circle);
}
});
shapePanel.add(horGap);
frame.add(shapePanel, BorderLayout.SOUTH);
frame.getContentPane().add(scribblePane, BorderLayout.CENTER);
}
I have created separate classes for creating circle with x and y points.
private static int[] generateRandomValues(int maxX, int maxY,
int minSize, int maxSize) {
Random random = new Random();
int[] values = new int[3];
values[0] = random.nextInt(maxX);
values[1] = random.nextInt(maxY);
values[2] = Math.min(random.nextInt(maxSize) + minSize, maxSize);
return values;
}
static class Circle {
int x, y, width, height;
public Circle(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public void draw(Graphics g) {
g.drawOval(x, y, width, height);
}
}
it remains for a second and gets removed if something else we do on the panel
Check out Custom Painting Approaches for the two common ways to do custom painting:
Add objects to an ArrayList and then paint all the objects in the list
Paint the objects to a BufferedImage and then paint the BufferedImage
The demo code shows how to randomly add Rectangles using the mouse. Your code would obviously be slightly different because you would add the Rectangles with a button.
So start with the working code to get it working with a button. Then change the code to work for circles instead of rectangles.
What you do is creating a circle but not calling the draw-Method. You would use something like:
Circle circle = new Circle(x, y, width, height);
Graphics g = shapepanel.getGraphics();
circle.draw(g);
But that leads to problems so I would suggest you take a look at this thread: Drawing an object using getGraphics() without extending JFrame
There is explained why and how to draw something consistently in a JPanel.

GridBagLayout is not adding my panel

I am trying to add a three panels using GridBagLayout in JFrame. When the default layout is set, the panel is added without any problem, but when layout is changed to GridBagLayout and panel is added in JFrame, then the output is an empty frame.
Here is the code for MainFrame:
public class MainFrame extends JFrame {
CirclePanel[] cp;
MainFrame() throws IOException{
cp = new CirclePanel[3];
for(int i = 0 ;i <3 ; i ++)
cp[i]= new CirclePanel(i+1);
this.setSize(700, 700);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new GridBagLayout());//when this line is not set then
//add(cp[0]) adds the panel
GridBagConstraints gc = new GridBagConstraints();
gc.weightx=1;
gc.weighty=1;
gc.gridx=0;
gc.gridy=0;
add(cp[1],gc);
this.setVisible(true);
}
}
I tried different step but was unable to add the panel using GridBagLayout or at least I was unable to show panel.
I want to add 3 consecutive panels with equal width by incrementing gc.gridx.
The Main class contains only the main method, which is calling the MainFrame constructor.
In my custom panel class (CirclePanel) I used the paintComponent method to draw circles.
public class CirclePanel extends JPanel {
private int mainCirclex ;//= getWidth()/2;
private int mainCircley ;//= getHeight()/2;
private final int mainCircler =1200;
private final double ampRadius = 0.1;
// private MakeArrays ma;
private int[] firstCir;
public CirclePanel(int num) throws IOException{
firstCir = new int[175];
/* ma = new MakeArrays();
if(num == 1)
firstCir = ma.getFirstCir();
else if(num == 2)
firstCir = ma.getSecondCir();
else if(num == 3)
firstCir = ma.getThirdCir();
*/
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.BLACK);
g2d.fillRect(getX(), getY(), getWidth(), getHeight());
//////////main circle////////////////////
//amplifying radius and setting x & y
mainCircley = getHeight()/2;
mainCirclex = getWidth()/2;
int radius = (int) (mainCircler*ampRadius);
//drawing
g2d.setColor(Color.yellow);
g2d.drawOval(mainCirclex - radius, mainCircley - radius, 2 * radius, 2 * radius);
/////////end main circle///////////
//////////////sub circles//////////////
int min = Math.min(mainCirclex, mainCircley);
int r2 = Math.abs(min - radius) / 160;
int j =0;
for (int i = 0; i <360; i++) {
if(j>=175) break;
if(i%2==0){
double t = 2 * Math.PI * i / 360;
int r = (int) (firstCir[j]*ampRadius);
//System.out.println(firstCir[j]);
int x = (int) Math.round(mainCirclex + r * Math.cos(t));
int y = (int) Math.round(mainCircley - r * Math.sin(t));
g2d.setColor(Color.red);
g2d.fillOval(x - r2, y - r2, 2 * r2, 2 * r2);
j++;
}
}
}
}
I added all 3 panels by incrementing the gridx value. To make it work I did the following:
Added the line gc.fill = GridBagConstraints.BOTH to allow the panels to resize and fill extra space.
Called setBackground in the panels' constructor instead of fillRect in paintComponent. It saves you the trouble of calculating the size of the panel.
Overrode the getPreferredSize of the panels:
#Override
public Dimension getPreferredSize() {
int size = (int) (2 * mainCircler * ampRadius);
return new Dimension(size, size);
}
(You might want to redo the calculation.)
Called pack on the frame instead of setSize.
Explanation
The panels you are adding to the frame are empty from the layout manager's point of view - they don't have any child components. This means that panels will retain their default preferred is, which is 10x10 (System.out.println(cp[1].getPreferredSize())) and they will be added to the frame with those dimensions.
Custom painting is not taken into account automatically when computing the preferred size of a component. If the preferred size is too small for the painting, it will not be displayed (or only part of it will). Overriding the getPreferedSize method of the panels to give the correct dimensions will tell the frame how to display them when pack is called.
Edit: comment answer
When I call setBackground to color the panels in their constructor it works fine, but when I try to use fillRect in paintComponent then the 1st panel is colored, but the next 2 panels aren't. Why is that?
Because of a very common misunderstanding (which is why I recommended point 2).
The getXXX() (X, Y, Width, Height) do not state the following explicitly, but they are derived from getBounds(), which reads:
Gets the bounds of this component in the form of a Rectangle object. The bounds specify this component's width, height, and location relative to its parent.
(emphasis mine.) So the bounds are in the parent's coordinate system. However, Graphics.fillRect paints with the given bounds in the component's coordinate system (the Graphics object passed to paintComponent is for painting the component, not its parent).
Then in order to paint the panel in its own coordinate system, we need
g2d.fillRect(0, 0, getWidth(), getHeight());
because a component always starts at (0,0) in its own coordinate system. Using
g2d.fillRect(getX(), getY(), getWidth(), getHeight());
takes the position of the panel in the parent and passes it to some non-special position in the panel. Moreover, if the panel is located in the parent's (300,300), but the panel is less than (300,300) in size, then the drawing in the panel would in theory start outside of the panel. So, you see no painting.
The first panel paints because its (0,0) coincides with its parent's (0,0).

Java GUI Rotation and Translation of Rectangle

I am trying to draw a rectangle in JPanel that would translate and then rotate itself to mimic the movement of a car. I have been able to make the rectangle translate and rotate, however it rotates around the origin of (0,0). I'm very pleased that I was able to have the rectangle move and rotate as I am very new to Java GUI, but I can not seem to get how to have the rectangle rotate around itself, because I experimented more with it, and when I initialized the rectangle and rotate it 45 degrees it's position was changed, which I would assume is the transform matrix that is appended from the rotate method.
I checked through the site on how would I solve this, however I only found how to rotate a rectangle and not on how to rotate and move like the movement of a simulated car. I would presume it is concerning about its transform matrix, but I'm only speculating. So my question is how would I be able to have the rectangle be able to rotate and move around itself and not against a point in JPanel.
Here's the code that I have come up so far:
public class Draw extends JPanel implements ActionListener {
private int x = 100;
private int y = 100;
private double theta = Math.PI;
Rectangle rec = new Rectangle(x,y,25,25);
Timer timer = new Timer(25,this);
Draw(){
setBackground(Color.black);
timer.start();
}
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
g2d.setColor(Color.white);
rec.x = 100;
rec.y = 100;
g2d.rotate(theta);
g2d.draw(rec);
g2d.fill(rec);
}
public void actionPerformed(ActionEvent e) {
x = (int) (x + (Math.cos(theta))*1);
y = (int) (y + (Math.sin(theta))*1);
theta = theta - (5*Math.PI/180);
repaint();
}
One of two approaches are commonly used:
Rotate the graphics context around the center (x, y) of the Shape, as shown here.
rotate(double theta, double x, double y)
Translate to the origin, rotate and translate back, as shown here.
g2d.translate(this.getWidth() / 2, this.getHeight() / 2);
g2d.rotate(theta);
g2d.translate(-image.getWidth(null) / 2, -image.getHeight(null) / 2);
Note the apparent reverse order of concatenation in the second example.
Addendum: Looking more closely at your example, the following change rotates the Rectangle around the panel's center.
g2d.rotate(theta, getWidth() / 2, getHeight() / 2);
Also, use the #Override annotation, and give your panel a reasonable preferred size:
#Override
public Dimension getPreferredSize() {
return new Dimension(640, 480);
}
Use affine transform to rotate the rectangle and convert it into the rotated polynomial. Check the code below:
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.white);
/* rotate rectnagle around rec.x and rec.y */
AffineTransform at = AffineTransform.getRotateInstance(theta,
rec.x, rec.y);
/* create the plunomial */
Polygon p = new Polygon();
/* path interator of the affine transformed polynomial */
PathIterator i = rec.getPathIterator(at);
while (!i.isDone()) {
double[] points = new double[2];
i.currentSegment(points);
p.addPoint((int) points[0], (int) points[1]);
i.next();
}
g2d.fill(p);
}

Categories