Graphing one function at a time - java

I have 3 different functions superimposed on each other and I'm trying to add radio buttons so that when button1 is selected, the function associated with it will draw, and the others will be invisible.
If there is a better way to do this, I'll do that.
import java.awt.*;
import java.awt.geom.Path2D;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.*;
public class DrawingStuff extends JComponent {
DrawingStuff(){
JRadioButton line1 = new JRadioButton("F(x)");
JRadioButton line2 = new JRadioButton("G(x)");
JRadioButton line3 = new JRadioButton("Cos(2x)");
ButtonGroup bG = new ButtonGroup();
bG.add(line1);
bG.add(line2);
bG.add(line3);
this.setLayout( new FlowLayout());
this.add(line1);
this.add(line2);
this.add(line3);
line1.setSelected(true);
this.setVisible(true);
}
public void paintComponent(Graphics g)
{
//w is x, and h is y (as in x/y values in a graph)
int w = this.getWidth()/2;
int h = this.getHeight()/2;
Graphics2D g1 = (Graphics2D) g;
g1.setStroke(new BasicStroke(2));
g1.setColor(Color.black);
g1.drawLine(0,h,w*2,h);
g1.drawLine(w,0,w,h*2);
g1.drawString("0", w - 7, h + 13);
Graphics2D g2 = (Graphics2D) g;
g2.setStroke(new BasicStroke(2));
g2.setColor(Color.red);
int scale = 4;
//Path2D path1 = new Path2D.Double();
//path1.moveTo(w, h);
Polygon p = new Polygon();
for (int x = 0; x <= 4; x++) {
//path1.lineTo(w+scale*x, h - scale*((x*x*x) + x - 3));
p.addPoint(w+scale*x, h - scale*((x*x*x) + x - 3));
}
//g2.draw(path1);
g2.drawPolyline(p.xpoints, p.ypoints, p.npoints);
Polygon p1 = new Polygon();
//Path2D path2 = new Path2D.Double();
//path2.moveTo(w, h);
for (int x = -10; x <= 10; x++) {
//path2.lineTo(w+scale*x, h - scale * ((x*x*x)/100) - x + 10);
p1.addPoint(w + scale * x, h - scale * ((x*x*x)/100) - x + 10);
}
//g2.draw(path2);
g2.drawPolyline(p1.xpoints, p1.ypoints, p1.npoints);
Path2D path = new Path2D.Double();
for (int i = 0; i < 100; i++) {
double theta = i * 2 * Math.PI / 100;
double r = Math.cos(2 * theta);
double dX = w * r * Math.cos(theta) + w;
double dY = h * r * Math.sin(theta) + h;
if (i == 0) {
path.moveTo(dX, dY);
} else {
path.lineTo(dX, dY);
}
}
path.closePath();
g2.draw(path);
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(800, 600);
frame.setTitle("Graphs");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
DrawingStuff draw = new DrawingStuff();
frame.add(draw);
frame.setVisible(true);
}
}
I'm not sure how to call the buttons from the constructor, and when I attempted to set a function aside for the buttons, they would replicate whenever I would maximize or restore down the window.
I also don't know how to erase a a line once plotted.
Thanks.

I also don't know how to erase a a line once plotted.
The secret is to draw only visible lines in your paintComponent() implementation. To do this, wrap your function in a class that maintains a notional visible field and give that class methods to get and set the attribute.
public void paintComponent(Graphics g) {
…
if (x. getFunctionVisible()) {
g2.drawPolyline(…);
}
}
In this example, VisibleAction uses getSeriesVisible() and setSeriesVisible() to effect a change; you implementation might declare getSeriesVisible() and setSeriesVisible() to effect a change; in contrast, your implementation might define getFunctionVisible() and set getFunctionVisible sVisible().
A relate example is seen here.

Related

How to re-size polygon in jPanel?

I have to make a program that generates stars in random locations of random size. My code already plots the stars in random locations, but I can't manage to randomly change their sizes. I tried assigning a size factor to each point to alter the distance between them but the stars came out all messed up. Is there a scaling method I can use?
Here is what I have so far, it plots the stars in random locations.
final int MID = WIDTH / 2;
final int TOP = 50;
//sky
Color skyColor = new Color(0, 0, 0);
page.fillRect(0,0,getWidth(),getHeight());
//ground
Color groundColor = new Color(95,95,95);
page.setColor(groundColor);
page.fillRect(0,HEIGHT-20,getWidth(),getHeight());
//star
for (int i = 1; i <= starCount; i++)
{
int ranLocX = gen.nextInt(700 - 100) + 100;
int ranLocY = gen.nextInt(300 - 75) + 75;
int ranSize = gen.nextInt(8 - 1) + 1;
int sizeXA = (-10 * ranSize);
int sizeXB = (10 * ranSize);
int sizeXC = (-5 * ranSize);
int sizeXD = (-10 * ranSize);
int sizeXE = (-10 * ranSize);
int sizeXF = (-10 * ranSize);
int sizeYC = (10 * ranSize);
int sizeYD = (-10 * ranSize);
int sizeYE = (10 * ranSize);
page.drawPolygon(new int[] {xa + ranLocX, xb + ranLocX, xc + ranLocX, xd + ranLocX, xe + ranLocX, xf + ranLocX}, new int[] {ya + ranLocY, yb + ranLocY, yc + ranLocY, yd + ranLocY, ye + ranLocY, yf + ranLocY}, 6);
}
Here is a simple method you can use to create a Shape with any given number of points and radius:
public static Shape radiusShape(int points, int... radii)
{
Polygon polygon = new Polygon();
for (int i = 0; i < points; i++)
{
double radians = Math.toRadians(i * 360 / points);
int radius = radii[i % radii.length];
double x = Math.cos(radians) * radius;
double y = Math.sin(radians) * radius;
polygon.addPoint((int)x, (int)y);
}
Rectangle bounds = polygon.getBounds();
polygon.translate(-bounds.x, -bounds.y);
return polygon;
}
To create your 5 point star you would use code like:
Shape star = ShapeUtils.radiusShape(10, 30, 12);
It will create a star with 5 outer points and 5 inner points to give the star shape.
So to randomize the size of the star you would randomize the radius.
Check out Playing With Shapes for more examples of the types of Shapes you can create using this method. The above radiusShape(...) method was taken from the ShapeUtils class found in the above link.
I would then suggest you create a custom class with the properties 1) Shape 2) Point so you can paint the Star at different locations on the panel. Then you create an ArrayList to hold instances of the class. In your painting method you iterate through this ArrayList to paint each Shape. The above link will also provide basic code for this concept.
Here's an example of how to change the size of a Polygon. I drew squares but any Shape will work.
just create a scale instance of an AffineTransform and use that to scale the Shape.
I used ThreadLocalRandom to randomly choose the scale to be applied. I always copy the original polygon(template) and then scale that.
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Polygons extends JPanel {
static int WIDTH = 500;
static int HEIGHT = 500;
JFrame f = new JFrame();
Polygon b =new Polygon();
ThreadLocalRandom r = ThreadLocalRandom.current();
List<Shape> polys = new ArrayList<>();
public static void main(String[] args) {
SwingUtilities.invokeLater(()-> new Polygons().start());
}
public void start() {
f.add(this);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
Polygon template = new Polygon();
template.addPoint(0,0);
template.addPoint(0,100);
template.addPoint(100,100);
template.addPoint(100,0);
// AffineTransform rotate = AffineTransform.getRotateInstance(Math.toRadians(72.), )
for (int i = 0; i < 20; i++) {
Polygon p = new Polygon(template.xpoints,template.ypoints, template.npoints);
p.translate(r.nextInt(WIDTH), r.nextInt(HEIGHT));
double scale = r.nextInt(10,90)/100.;
AffineTransform scaleIt = AffineTransform.getScaleInstance(scale,scale);
polys.add(scaleIt.createTransformedShape(p));
}
}
public Dimension getPreferredSize() {
return new Dimension(WIDTH,HEIGHT);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
for (Shape shape : polys) {
g2d.draw(shape);
}
}
}
Here is something I wrote a long time ago to create a 5 point star. It draws a single arm and then rotates 72 degrees and draws another, repeating the process.
Because it was written to allow the base to be changed, hence the star size, this might be as better option for scaling the size of your stars rather than using the AffineTransform mentioned above:
for (int i = 0; i < 50; i++) {
// get the base of the next star between 5 and 29 inclusive
int base = r.nextInt(5,30);
Polygon star = createStar(base);
// now randomly position it.
star.translate(r.nextInt(0,400),r.nextInt(0,400));
// and add to the list
polys.add(star);
}
Creating a 5 point star
int startx = 250; // arbitrary starting points
int starty = 250;
public Polygon createStar(int armBase) {
Polygon star = new Polygon();
// The armBase is equal to one side of the inner
// pentagon of the star
// The height of the arm is the distance from the middle of the
// base to the tip of the stars arm. Since the tangent computes
// ratio of the sides of a right triangle, multiplying by half
// the base gives the other side, hence the height.
int armHeight =
(int) (armBase / 2 * Math.tan(Math.toRadians(72)));
// The center offset is the distance from the middle of a given
// base to the center of the inner pentagon.
int centerOffset =
(int) (armBase / 2 * Math.tan(Math.toRadians(54)));
// this works by creating the first arm, rotating 72 degrees
// and then adding the other two coodinates of succeeding arms.
star.addPoint(startx, starty);
star.addPoint(startx + armBase / 2, starty - armHeight);
star.addPoint(startx + armBase, starty);
for (int j = 0; j < 4; j++) {
rotatePolygon(-Math.PI / 5 * 2, startx + armBase / 2,
starty + centerOffset, star);
star.addPoint(startx + armBase / 2, starty - armHeight);
star.addPoint(startx + armBase, starty);
}
star.npoints--;
star.translate(-star.getBounds().x,-star.getBounds().y);
return star;
}
// This is general purpose rotation that rotates about a center
// point. This can be derived using the double angle identities of
// for sin and cosine.
private void rotatePolygon(double ang, double sx, double sy,
Polygon poly) {
for (int j = 0; j < poly.npoints; j++) {
double x = poly.xpoints[j];
double y = poly.ypoints[j];
double xx = sx + (x - sx) * Math.cos(ang)
- (y - sy) * Math.sin(ang);
double yy = sy + (x - sx) * Math.sin(ang)
+ (y - sy) * Math.cos(ang);
poly.xpoints[j] = (int) xx;
poly.ypoints[j] = (int) yy;
}
}
Here's a GUI to draw one five-pointed star.
Here's the complete runnable code. I used polar coordinates to calculate the 10 points I needed to draw a star. I guessed the fraction to get the intermediate points correct.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Polygon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class StarryNight2GUI implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new StarryNight2GUI());
}
#Override
public void run() {
JFrame frame = new JFrame("Starry Night");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new DrawingPanel(), BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
public DrawingPanel() {
this.setBackground(Color.BLACK);
this.setPreferredSize(new Dimension(640, 480));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Color groundColor = new Color(95, 95, 95);
g.setColor(groundColor);
g.fillRect(0, getHeight() - 30, getWidth(), 30);
Polygon polygon = createStar(new Point(320, 240), 80);
g.setColor(Color.YELLOW);
g.fillPolygon(polygon);
}
private Polygon createStar(Point centerPoint, int radius) {
Polygon polygon = new Polygon();
// 72, 144, 216, 288, 360
// 36, 108, 180, 252, 324
// 54, 126, 198, 270, 342
// 18, 54, 90, 126, 162, 198, 234, 270, 306, 342
for (int angle = 18; angle < 360; angle += 72) {
double r = 0.42 * radius;
Point point = toCartesian(centerPoint, angle, r);
polygon.addPoint(point.x, point.y);
point = toCartesian(centerPoint, angle + 36, radius);
polygon.addPoint(point.x, point.y);
}
return polygon;
}
private Point toCartesian(Point centerPoint, int angle, double radius) {
double theta = Math.toRadians(angle);
int x = centerPoint.x + (int) Math.round(Math.cos(theta) * radius);
int y = centerPoint.y + (int) Math.round(Math.sin(theta) * radius);
return new Point(x, y);
}
}
}

Swing draw string centeralised

I have to draw n + 1 amount of circles horizontally and vertically in a GUI. Which I have successfully done as shown below. With this, a 2D array of strings will be printed between them, centralised.
How it currently stands
Now I want to draw numbers in a "square" of the dots.
How I want the final result
for (int i = 0; i < width; i++) {
for (int j = 0; j < width; j++) {
canvas.drawCircle( (j + 1) * 125, (i + 1) * 125, 15, Color.white);
}
}
for (int r = 0; r < size; r++) {
for (int c = 0; c < size; c++) {
canvas.drawString(events[r][c], (r + 1) * 150, (c + 1) * 150, Color.green );
}
}
Width in this case is 4, so basically (n-1) dots/circles in the picture.
Size is 3 which is just the length of the 2d array, as there are 4 circles in this case there will be 3 numbers between each one
Events is the 2D array with the data containing the numbers
The drawCircle method's signature is
(x, y, radius, color)
The drawString method's signature is
(text, x, y color)
I believe part of the problem is also the drawing of circles. Basically I think it has to do with the rubbish formula I have for determining the x, y coords for both circles and the text. Any help is appreciated, thank you.
Provided is some code which I believe does what you want. Some of the constants may be tweaked to fit your final requirements. I used a 2D array of numbers and converted to strings during paint. It also allows for the following:
Changing the ball diameter or number of balls will still provide a
correct graph (although it will change its location within the
panel).
The String numbers track along with the size of the balls
Anti-aliasing was turned on to smooth out the graphics.
FontMetrics was used to fine tune the location of the numbers.
One final note: Because this is not resource intensive, the coordinates are calculated within the paintComponent method. A more optimum solution would be to adopt the Flyweight design pattern and precalculate as much as possible before entering paintComponent.
import java.awt.*;
import javax.swing.*;
public class SwingMain extends JPanel {
final static int WIDTH = 700;
final static int HEIGHT = 700;
final static int SEPARATION = 100;
final static int DIAMETER = 25;
final static int NBALLS = 4;
final static int XSTART = WIDTH / (NBALLS + 2);
final static int YSTART = HEIGHT / (NBALLS + 2);
JFrame frame = new JFrame();
int[][] numbers = new int[NBALLS - 1][NBALLS - 1];
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new SwingMain().start());
}
public void start() {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setPreferredSize(new Dimension(WIDTH, HEIGHT));
frame.add(this);
setBackground(Color.RED);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
// populate numbers in 2D array.
for (int r = 0; r < NBALLS - 1; r++) {
for (int c = 0; c < NBALLS - 1; c++) {
numbers[r][c] = r * (NBALLS - 1) + c + 1;
}
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// Allow smoothing of the graphics.
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.white);
// Iterating by the number of balls is more consistent than
// by x and y due to round of errors.
int y = YSTART;
for (int r = 0; r < NBALLS; r++) {
int x = XSTART;
for (int c = 0; c < NBALLS; c++) {
g2d.fillOval(x, y, DIAMETER, DIAMETER);
x += SEPARATION;
}
y += SEPARATION;
}
// This is the same as above except that the start begins
// halfway between the first row and column
// have the size of the font track with the diameter of the balls
g2d.setFont(new Font("ARIAL", Font.BOLD, DIAMETER));
FontMetrics fm = g2d.getFontMetrics();
y = YSTART + SEPARATION / 2;
for (int r = 0; r < NBALLS - 1; r++) {
int x = XSTART + SEPARATION / 2;
for (int c = 0; c < NBALLS - 1; c++) {
String number = Integer.toString(numbers[r][c]);
// Do some final position adjustment with the font metrics to
// center the number
int strWidth = fm.stringWidth(number);
int strHeight = fm.getAscent();
g2d.drawString(number,
x - strWidth / 2 + DIAMETER / 2,
y + strHeight);
x += SEPARATION;
}
y += SEPARATION;
}
}
}
You could store the coordinates of the circles in a 2D array as well, and use that to find the location of the strings. One thing to note is that the drawCircle method for some reason wont draw the circle with the given center (the coordinates you give will be the top left corner actually).
Point[][] circleCoords = new Point[width][width]; //suppose Point class has x and y coords
for (int i = 0; i < width; i++) {
for (int j = 0; j < width; j++) {
//the -15 actually centers the circle to the coordianates
circleCoords[i][j] = new Point((j + 1) * 125 - 15, (i + 1) * 125 -15);
canvas.drawCircle(circleCoords[i][j].x , circleCoords[i][j].y, 15, Color.white);
}
}
for (int r = 0; r < width-1; r++) {
for (int c = 0; c < width-1; c++) {
//calculate coords from circleCoords array: halfway between them
int xCoord = (circleCoords[r][c].x + circleCoords[r+1][c].x)/2;
int yCoord = (circleCoords[r][c].y + circleCoords[r][c+1].y)/2;
//wont be out of bounds, becouse this runs to width-1
canvas.drawString(events[r][c], xCoord, yCoord, Color.green );
}
}
This still wont actually be perfectly centered, because the drawString will use the coordinates for top left point as well, and not centerpoint. Maybe I miscalculated something, but this should give you the idea: instead of calculating the coordinates independently, re-use the circle coordinates.
Consider a class that represents a single square. The value is represented by a JLabel which is placed at the bottom using straight-forward layout manager (BorderLayout in this example). You can change or manipulate the layout manager to change to position of the JLabel.
class Square extends JPanel{
private static int WIDTH = 100, HEIGHT = 100, DAIMETER = 30, GAP = 5;
private final JLabel label;
Square(int value) {
setPreferredSize(new Dimension(WIDTH, HEIGHT));
setLayout(new BorderLayout());
setBackground(Color.red);
setOpaque(true);
label = new JLabel(String.valueOf(value), JLabel.RIGHT);
label.setForeground (Color.white);
Border margin = new EmptyBorder(GAP,GAP,GAP,2*GAP); //top left bottom right
label.setBorder(margin);
add(label, BorderLayout.PAGE_END);
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.white);
g2d.fillOval(WIDTH/2 - DAIMETER/2, HEIGHT/2-DAIMETER/2, DAIMETER, DAIMETER);
}
}
Now add 16 instances of this Square to panel representing a board. Its layout manager is set to GridLayout:
class Board extends JPanel{
private static int ROWS = 4, COLS = 4;
Board(){
setLayout(new GridLayout(ROWS, COLS));
for(int index = 0; index < ROWS * COLS; index ++){
add(new Square(index)); //index is used as value for demonstration purposes
}
}
}
Putting it all together (this is a one-file mcve. Copy paste the entire code into SwingMain.java and run )
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
public class SwingMain {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocation(400,250);
frame.add(new Board());
frame.pack();
frame.setResizable(false);
frame.setVisible(true);
}
}
class Board extends JPanel{
private static int ROWS = 4, COLS = 4;
Board(){
setLayout(new GridLayout(ROWS, COLS));
for(int index = 0; index < ROWS * COLS; index ++){
add(new Square(index)); //index is used as value for demonstration purposes
}
}
}
class Square extends JPanel{
private static int WIDTH = 100, HEIGHT = 100, DAIMETER = 30, GAP = 5;
private final JLabel label;
Square(int value) {
setPreferredSize(new Dimension(WIDTH, HEIGHT));
setLayout(new BorderLayout());
setBackground(Color.red);
setOpaque(true);
label = new JLabel(String.valueOf(value), JLabel.RIGHT);
label.setForeground (Color.white);
Border margin = new EmptyBorder(GAP,GAP,GAP,2*GAP); //top left bottom right
label.setBorder(margin);
add(label, BorderLayout.PAGE_END);
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.white);
g2d.fillOval(WIDTH/2 - DAIMETER/2, HEIGHT/2-DAIMETER/2, DAIMETER, DAIMETER);
}
}

Repainting an instance of a class from an ArrayList

Ok so I am very new to Java Swing and a beginner in Java in general. My current problem is I have designed a "cityscape". I am working on a UFO flying around, but my randomly generated buildings continue to get regenerated. I am wondering if there is a way to save my instance of buildings to an ArrayList as I have attempted, and paint that selection from that list each time paint is called. I tried what I thought of and I believe it just crashed it when run, because it didn't even open a JFrame and instead produced errors upon errors. Here is what I have:
CityScape class (the main class):
import java.awt.*;
import javax.swing.*;
public class CityScape extends JPanel
{
Buildings a = new Buildings ();
UFO b = new UFO();
#Override
public void paint (Graphics g)
{
//RememberBuildings.buildingList.get(1).paint(g);
a.paint(g);
b.paint(g);
}
public void move()
{
b.move();
}
public static void main(String[] args) throws InterruptedException
{
JFrame frame = new JFrame("Frame");
CityScape jpe = new CityScape();
frame.add(jpe);
frame.setSize(800, 750);
frame.setBackground(Color.BLACK);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
System.out.println(frame.getContentPane().getSize());
while (true)
{
jpe.move(); //Updates the coordinates
jpe.repaint(); //Calls the paint method
Thread.sleep(10); //Pauses for a moment
}
}
}
Buildings class (the class that generates the buildings):
import java.awt.*;
public class Buildings
{
private int maxX = 784;
private int maxY = 712;
private int width = (int)(Math.random()*100+100);
private int height = (int)(Math.random()*350+100);
private int rows = Math.round((height)/25);
private int columns = Math.round(width/25);
public void addBuilding()
{
RememberBuildings.addBuilding();
}
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
Color transYellow = new Color (255, 255, 0, 59);
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, maxX, maxY);
g2d.setColor(Color.WHITE);
g2d.fillRect(5, 5, 25, 25);
int a = 0;
for (int i =10; i<634; i+=(a+10))//buildings
{
g2d.setColor(Color.GRAY);
g2d.drawRect(i, maxY-height, width, height);
g2d.fillRect(i, maxY-height, width, height);
rows = Math.round((height)/25);
columns = Math.round(width/25);
for (int j = 1; j<=columns; j++)//windows
{
for (int k = 1; k<=rows; k++)
{
g2d.setColor(Color.BLACK);
g2d.drawRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
if (Math.random()<0.7)
{
g2d.setColor(Color.YELLOW);
g2d.fillRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
}
else
{
g2d.setColor(Color.BLACK);
g2d.fillRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
g2d.setColor(transYellow);
g2d.fillRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
}
}
}
addBuilding();
a = width;
height = (int)(Math.random()*462+100);
width = (int)(Math.random()*100+100);
}
}
}
RememberBuildings class (the point of this is to add an instance to an ArrayList):
import java.util.*;
public class RememberBuildings
{
public static ArrayList<Buildings> buildingList = new ArrayList<Buildings>();
public static void addBuilding()
{
buildingList.add(new Buildings());
}
}
And finally my UFO class (creates the UFO flying by):
import java.awt.*;
import javax.swing.*;
public class UFO extends JPanel
{
private int x = 20; //x and y coordinates of the ball
private int y = 20;
private int xa = 1;
public void move() //Increase both the x and y coordinates
{
if (x + xa < 0) {
xa = 1;
}
if (x + xa > 784-75)
{
xa = -1;
}
x = x + xa;
}
public void paint(Graphics g)
{
super.paint(g); //Clears the panel, for a fresh start
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.LIGHT_GRAY);
g2d.fillOval(x,y,75,25); //Draw the ball at the desired point
}
}
Avoid overriding paint, use paintComponent instead. Always call the super paint method before you do any custom painting to ensure that the paint chain is maintained. See Painting in AWT and Swing and Performing Custom Painting for more details
Beware, Swing is not thread safe and it's unwise to update any component (or any variable that a component may rely on) from outside the context of the Event Dispatching Thread. A simple solution might be to use a Swing Timer instead of a while (true) loop and Thread.sleep. See How to use Swing Timers for more details.
You should also only create and modify UI components from within the context of the event dispatching thread, see Initial Threads for more details
If you have a problem with your code not working, you should consider providing a runnable example which demonstrates your problem. This is not a code dump, but an example of what you are doing which highlights the problem you are having. This will result in less confusion and better responses. Providing code which is not runnable and is missing classes makes it difficult to know why it's not working and how to fix it.
A few things here:
To address the paintComponent note and view an example, check out this other thread: Concerns about the function of JPanel: paintcomponent()
There seems to be a bit of a disconnect between the logic you've got going and the object-oriented programming logic that I think will help sort things out (for general info on OOP: https://en.wikipedia.org/wiki/Object-oriented_programming):
What You've Got:
The Structure you've got going is as follows:
CityScape :: here's where you've extended JPanel and setup the main function
UFO :: an object class that represents 1 UFO
Building :: a class that has methods for drawing randomized buildings and calling methods in RememberBuildings
RememberBuildings :: I think this is intended to track buildings that have been drawn
The issue here is that your Building class's paint method continually draws multiple newly randomized buildings instead of a set building that retains its structure.
My Suggestion:
There are plenty of solutions to this issue and different ways to implement each solution, but my recommendation is to remodel your Building class in an OOP fashion, meaning that it would represent 1 single building (truer to the name of the class). This would contain a constructor that initializes all of the randomized dimensions of that single building once and draws that single building on the jpanel. Then you would need to keep an array or list of some sort in the cityscape that contains buildings that are part of the cityscape, eliminating the need for a "RememberBuildings" class. so roughly:
CityScape extends JPanel:
variables:
Building[] buildings; //might be useful to use an arraylist/stack/queue instead of an array depending on implementation
UFO craft;
constructor:
setup new Building objects and add to list buildings
initialize craft to new UFO
paintComponent:
calls the paint methods for each building & the ufo craft
Building:
variables:
int x, y; // position of building
int height, width; // of this building
constructor:
initializes x, y // probably needs to be inputed from CityScape with this setup
calc height and width randomly // stored in this.height/width
paint:
paints single building based on it's variables
//side-note, you'll probably need getters for the x/y/width to build each building from CityScape
Everything else should be much the same.
Good Luck !
So, every time Buildings#paint is called, it regenerates all the builds, which is done randomly.
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
Color transYellow = new Color(255, 255, 0, 59);
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, maxX, maxY);
g2d.setColor(Color.WHITE);
g2d.fillRect(5, 5, 25, 25);
int a = 0;
for (int i = 10; i < 634; i += (a + 10))//buildings
{
g2d.setColor(Color.GRAY);
g2d.drawRect(i, maxY - height, width, height);
g2d.fillRect(i, maxY - height, width, height);
rows = Math.round((height) / 25);
columns = Math.round(width / 25);
for (int j = 1; j <= columns; j++)//windows
{
for (int k = 1; k <= rows; k++) {
g2d.setColor(Color.BLACK);
g2d.drawRect(i + 5 * j + 20 * (j - 1), (maxY - height) + 5 * k + 20 * (k - 1), 20, 20);
if (Math.random() < 0.7) {
g2d.setColor(Color.YELLOW);
g2d.fillRect(i + 5 * j + 20 * (j - 1), (maxY - height) + 5 * k + 20 * (k - 1), 20, 20);
} else {
g2d.setColor(Color.BLACK);
g2d.fillRect(i + 5 * j + 20 * (j - 1), (maxY - height) + 5 * k + 20 * (k - 1), 20, 20);
g2d.setColor(transYellow);
g2d.fillRect(i + 5 * j + 20 * (j - 1), (maxY - height) + 5 * k + 20 * (k - 1), 20, 20);
}
}
}
addBuilding();
a = width;
height = (int) (Math.random() * 462 + 100);
width = (int) (Math.random() * 100 + 100);
}
}
There's two ways you might be able to solve this, which you use will depend on what you want to achieve. You could render the buildings directly to a BufferedImage and simply paint that on each paint cycle or you could cache the information you need in order to re-create the buildings.
The BufferedImage approach is quicker, but can't be animated, so if you want to animate the buildings in some way (make the lights flicker), you will need to build up a series of information which allows you to simply repaint them.
I'm going for the second, as you've asked about painting assets from a ArrayList.
I started by translating your "paint" code into a single concept of a virtual building, which has also has information about it's own lights.
public class Building {
protected static final Color TRANS_YELLOW = new Color(255, 255, 0, 59);
private int x, y, width, height;
private List<Light> lights;
public Building(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
lights = new ArrayList<>(25);
int rows = Math.round((height) / 25);
int columns = Math.round(width / 25);
for (int j = 1; j <= columns; j++)//windows
{
for (int k = 1; k <= rows; k++) {
Color color = null;
if (Math.random() < 0.7) {
color = Color.YELLOW;
} else {
color = TRANS_YELLOW;
}
lights.add(new Light(x + 5 * j + 20 * (j - 1), y + 5 * k + 20 * (k - 1), color));
}
}
}
public void paint(Graphics2D g2d) {
g2d.setColor(Color.GRAY);
g2d.drawRect(x, y, width, height);
g2d.fillRect(x, y, width, height);
for (Light light : lights) {
light.paint(g2d);
}
}
public class Light {
private int x, y;
private Color color;
public Light(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public void paint(Graphics2D g2d) {
g2d.setColor(Color.BLACK);
g2d.fillRect(x, y, 20, 20);
g2d.setColor(color);
g2d.fillRect(x, y, 20, 20);
}
}
}
This allows you to generate the primary parameters for the Building and simple cache the results and when needed, simply paint it.
For example...
public class Buildings {
private int maxX = 784;
private int maxY = 712;
private List<Building> buildings;
public Buildings() {
buildings = new ArrayList<>(25);
for (int i = 10; i < 634; i += 10)//buildings
{
int width = (int) (Math.random() * 100 + 100);
int height = (int) (Math.random() * 350 + 100);
int x = i;
int y = maxY - height;
buildings.add(new Building(x, y, width, height));
}
}
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
for (Building building : buildings) {
building.paint(g2d);
}
}
}
I also changed your UFO class so it no longer extends from JPanel, as it just doesn't need to and is probably the primary cause of confusion with your painting.
I then updated your paint method in your CityScape to use paintComponent instead...
public class CityScape extends JPanel {
Buildings a = new Buildings();
UFO b = new UFO();
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
a.paint(g);
b.paint(g);
}
As a runnable example...
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class CityScape extends JPanel {
Buildings a = new Buildings();
UFO b = new UFO();
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); //To change body of generated methods, choose Tools | Templates.
a.paint(g);
b.paint(g);
}
public void move() {
b.move();
}
public static void main(String[] args) throws InterruptedException {
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("Frame");
CityScape jpe = new CityScape();
frame.add(jpe);
frame.setSize(800, 750);
frame.setBackground(Color.BLACK);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
System.out.println(frame.getContentPane().getSize());
Timer timer = new Timer(10, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
jpe.move(); //Updates the coordinates
jpe.repaint(); //Calls the paint method
}
});
timer.start();
}
});
}
public class Buildings {
private int maxX = 784;
private int maxY = 712;
private List<Building> buildings;
public Buildings() {
buildings = new ArrayList<>(25);
for (int i = 10; i < 634; i += 10)//buildings
{
int width = (int) (Math.random() * 100 + 100);
int height = (int) (Math.random() * 350 + 100);
int x = i;
int y = maxY - height;
buildings.add(new Building(x, y, width, height));
}
}
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
for (Building building : buildings) {
building.paint(g2d);
}
}
}
public static class Building {
protected static final Color TRANS_YELLOW = new Color(255, 255, 0, 59);
private int x, y, width, height;
private List<Light> lights;
public Building(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
lights = new ArrayList<>(25);
int rows = Math.round((height) / 25);
int columns = Math.round(width / 25);
for (int j = 1; j <= columns; j++)//windows
{
for (int k = 1; k <= rows; k++) {
Color color = null;
if (Math.random() < 0.7) {
color = Color.YELLOW;
} else {
color = TRANS_YELLOW;
}
lights.add(new Light(x + 5 * j + 20 * (j - 1), y + 5 * k + 20 * (k - 1), color));
}
}
}
public void paint(Graphics2D g2d) {
g2d.setColor(Color.GRAY);
g2d.drawRect(x, y, width, height);
g2d.fillRect(x, y, width, height);
for (Light light : lights) {
light.paint(g2d);
}
}
public class Light {
private int x, y;
private Color color;
public Light(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public void paint(Graphics2D g2d) {
g2d.setColor(Color.BLACK);
g2d.fillRect(x, y, 20, 20);
g2d.setColor(color);
g2d.fillRect(x, y, 20, 20);
}
}
}
public class UFO {
private int x = 20; //x and y coordinates of the ball
private int y = 20;
private int xa = 1;
public void move() //Increase both the x and y coordinates
{
if (x + xa < 0) {
xa = 1;
}
if (x + xa > 784 - 75) {
xa = -1;
}
x = x + xa;
}
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.LIGHT_GRAY);
g2d.fillOval(x, y, 75, 25); //Draw the ball at the desired point
}
}
}

trying to draw a clock with ellipses in Java

I want there to be 12 ellipses so they represent 5 minute intervals around the clock. However every time I change the linApproxLen for the path iterator, it always either draws too many ellipses or 9 ellipses. How can I make these ellipses represent the 5 minute mark?
private Ellipse2D ellipse = new Ellipse2D.Double();
public void setClockContour(int w, int h)
{
size = (w > h) ? h/6f : w/6f;
ellipse.setFrame(w/2-size*2-4.5f,h/2-size*2-4.5f,size*4,size*4);
double linApproxLen = 0.75 * size * 0.258819; // sin(15 degree)
PathIterator pi = ellipse.getPathIterator(null, linApproxLen);
Point2D[] points = new Point2D[100];
int num_pts = 0;
while ( !pi.isDone() )
{
float[] pt = new float[6];
switch ( pi.currentSegment(pt) ) {
case FlatteningPathIterator.SEG_MOVETO:
case FlatteningPathIterator.SEG_LINETO:
points[num_pts] = new Point2D.Float(pt[0], pt[1]);
num_pts++;
}
pi.next();
}
pts = new Point2D[num_pts];
System.arraycopy(points, 0, pts, 0, num_pts);
}
I think that approach will not work regardless of the linApproxLen parameter. This parameter only specifies the "flatness" of the line. While this will certainly influence the number of line segments that are returned, the actual number will at least depend on the radius of the ellipse that you are painting. So even when you manage to return exactly 12 points in one case, changing the radius (that is, the size given as w and h) will affect the number of returned points.
Computing the positions of 12 points in a circular formation is particularly easy. You can compute the position on the circle with the sine and cosine of the respective angle. The resulting points may then be scaled or moved in order to obtain the desired shape.
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ClockCirclesTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new GridLayout(1, 0));
frame.getContentPane().add(new ClockCirclesPanel());
frame.setSize(400,400);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
class ClockCirclesPanel extends JPanel
{
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int w = getWidth() / 2;
int h = getHeight() / 2;
Point2D points[] = computePoints(w, h, 12);
int cx = getWidth() / 2;
int cy = getHeight() / 2;
g.translate(cx, cy);
for (Point2D p : points)
{
g.draw(new Ellipse2D.Double(
p.getX() - 5, p.getY() - 5,
10, 10));
}
}
private Point2D[] computePoints(double w, double h, int n)
{
Point2D points[] = new Point2D[n];
double angleDeltaRad = Math.PI * 2 / n;
for (int i=0; i<n; i++)
{
double angleRad = i * angleDeltaRad;
double ca = Math.cos(angleRad);
double sa = Math.sin(angleRad);
double x = sa * w/2;
double y = ca * h/2;
points[i] = new Point2D.Double(x,y);
}
return points;
}
}

Shapes and Segments in Java

I have a Shape. I'm basically trying to split an area into two areas using a segment as the bisection.
public Shape divide(Shape a, Point2D p1, Point2D p2) {
Shape str = new BasicStroke().createStrokedShape(new Line2D.Double(p1,p2));
Shape line = new Shape(str);
Shape temp = a;
line.intersect(temp);
temp.exclusiveOr(line);
// temp is the shape with the line intersecting it
AffineTransform t = new AffineTransform();
double angle = Math.atan2(p2.getY() - p1.getY(), p2.getX() - p1.getX());
t.rotate(angle, p1.getX(), p1.getY());
temp = temp.createTransformedArea(t);
return Shape ;
}
I want to bisect the shape into two using the segment, but not sure how to go about it, I was looking at the intersection methods:
http://docs.oracle.com/javase/7/docs/api/java/awt/geom/Area.html but still not sure how to get two Areas from one. I'm hoping to return something like:
return firstHalf secondHalf;
I'd do it something like this. Note the code has a bug for when the point that start out to the right and lower ends up to the left of the upper left point. Left as an exercise for the user.
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
class SplitArea {
int s = 100;
JPanel gui = new JPanel(new BorderLayout());
BufferedImage[] images = new BufferedImage[4];
Point p1 = new Point(s / 4, s / 4);
Point p2 = new Point(s * 3 / 4, s * 3 / 4);
Ellipse2D ellipse = new Ellipse2D.Float(
s / 5, s / 5, s * 3 / 5, s * 3 / 5);
Rectangle2D bg = new Rectangle2D.Float(0, 0, s, s);
SplitArea() {
JToolBar tb = new JToolBar();
gui.add(tb, BorderLayout.PAGE_START);
final JToggleButton tob = new JToggleButton("Primary Point");
tb.add(tob);
JPanel view = new JPanel(new GridLayout(1, 0, 4, 4));
gui.add(view, BorderLayout.CENTER);
for (int ii = 0; ii < images.length; ii++) {
BufferedImage bi = new BufferedImage(
s, s, BufferedImage.TYPE_INT_RGB);
images[ii] = bi;
JLabel l = new JLabel(new ImageIcon(bi));
if (ii == 0) {
l.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (tob.isSelected()) {
p1 = e.getPoint();
} else {
p2 = e.getPoint();
}
drawImages();
}
});
}
view.add(l);
}
drawImages();
}
public final void drawImages() {
Graphics2D g;
// image 0
g = images[0].createGraphics();
g.setColor(Color.BLACK);
g.fill(bg);
g.setColor(Color.CYAN);
g.fill(ellipse);
g.setColor(Color.WHITE);
g.draw(ellipse);
g.setColor(Color.red);
drawPoint(g, p1);
drawPoint(g, p2);
g.dispose();
int xDiff = p1.x - p2.x;
int yDiff = p1.y - p2.y;
Point2D xAxis;
Point2D xSAxis;
if (xDiff == 0) {
xAxis = new Point2D.Double(p1.x, 0);
xSAxis = new Point2D.Double(p1.x, s);
} else if (yDiff == 0) {
xAxis = new Point2D.Double(0, p1.y);
xSAxis = new Point2D.Double(s, p1.y);
} else {
System.out.println("Not vertical or horizontal!");
// will throw a NaN if line is vertical
double m = (double) yDiff / (double) xDiff;
System.out.println("m: " + m);
double b = (double) p1.y - (m * (double) p1.x);
System.out.println("b: " + b);
// crosses x axis at..
xAxis = new Point2D.Double(0d, b);
double pointS = (s - b) / m;
xSAxis = new Point2D.Double(pointS, s);
}
// image 1
g = images[1].createGraphics();
g.setColor(Color.BLACK);
g.fill(bg);
g.setColor(Color.CYAN);
g.fill(ellipse);
g.setColor(Color.WHITE);
g.draw(ellipse);
g.setColor(Color.YELLOW);
System.out.println(xAxis);
System.out.println(xSAxis);
g.drawLine(
(int) xAxis.getX(), (int) xAxis.getY(),
(int) xSAxis.getX(), (int) xSAxis.getY());
g.setColor(Color.red);
drawPoint(g, p1);
drawPoint(g, p2);
g.dispose();
// image 2
g = images[1].createGraphics();
g.setColor(Color.BLACK);
g.fill(bg);
g.setColor(Color.CYAN);
g.fill(ellipse);
g.setColor(Color.WHITE);
g.draw(ellipse);
g.setColor(Color.YELLOW);
System.out.println(xAxis);
System.out.println(xSAxis);
g.drawLine(
(int) xAxis.getX(), (int) xAxis.getY(),
(int) xSAxis.getX(), (int) xSAxis.getY());
g.setColor(Color.red);
drawPoint(g, p1);
drawPoint(g, p2);
g.dispose();
// split the regions
Rectangle2D.Double all = new Rectangle2D.Double(0, 0, s, s);
Area a1 = new Area(all);
Area a2 = new Area(all);
GeneralPath aPart = new GeneralPath();
aPart.moveTo(0, 0);
aPart.lineTo(0, s);
aPart.lineTo(xSAxis.getX(), xSAxis.getY());
aPart.lineTo(xAxis.getX(), xAxis.getY());
aPart.closePath();
a1.subtract(new Area(aPart));
a2.subtract(a1);
Area ellipsePartA = new Area(ellipse);
ellipsePartA.subtract(a1);
Area ellipsePartB = new Area(ellipse);
ellipsePartB.subtract(a2);
// image 3
g = images[2].createGraphics();
g.setColor(Color.BLACK);
g.fill(bg);
g.setColor(Color.CYAN);
g.fill(ellipsePartA);
g.setColor(Color.WHITE);
g.draw(ellipsePartA);
g.setColor(Color.red);
drawPoint(g, p1);
drawPoint(g, p2);
g.dispose();
// image 4
g = images[3].createGraphics();
g.setColor(Color.BLACK);
g.fill(bg);
g.setColor(Color.CYAN);
g.fill(ellipsePartB);
g.setColor(Color.WHITE);
g.draw(ellipsePartB);
g.setColor(Color.red);
drawPoint(g, p1);
drawPoint(g, p2);
g.dispose();
gui.repaint();
}
public final void drawPoint(Graphics g, Point2D p) {
g.setColor(new Color(255, 0, 0, 128));
int x = (int) p.getX();
int y = (int) p.getY();
g.drawLine(x - 1, y, x - 5, y);
g.drawLine(x + 1, y, x + 5, y);
g.drawLine(x, y - 1, x, y - 5);
g.drawLine(x, y + 1, x, y + 5);
}
public Area[] split(Area a, Point2D p1, Point2D p2) {
Shape str = new BasicStroke().createStrokedShape(new Line2D.Double(p1, p2));
Area line = new Area(str);
Area temp = a;
line.intersect(temp);
temp.exclusiveOr(line);
// temp is the shape with the line intersecting it
Area[] areas = {new Area(temp)};
return areas;
}
public JComponent getGui() {
return gui;
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
SplitArea sa = new SplitArea();
JOptionPane.showMessageDialog(null, sa.getGui());
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
}
Here is another https://stackoverflow.com/help/mcve (I started this "yesterday" at ~3:00 am, obviously Andrew Thompson is in a different time zone ;-))
The basic idea here is as follows:
The two given points define a line. That is, an infinite line, and not only a line segment. The corner points of the bounding box of the object are projected on this line and its perpendicular. This gives (an upper bound of) the extent of the object along these lines. These upper bounds can be used to define the "minimum half-spaces" above and below the line that are required to cover the respective half of the object. These half-spaces can then be intersected with the object to obtain the desired results.
The split method in this example receives a Graphics2D parameter. This is only used for "debugging" - that is, to show the intermediate results (extents, half-spaces) that are computed, and a preview of the final results. This Graphics g parameter (and the corresponding debugging output) can simply be removed (but it might also help to show the idea of the approach).
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ShapeSplit
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new ShapeSplitPanel());
f.setSize(1100,600);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class ShapeSplitPanel extends JPanel implements MouseMotionListener
{
private Shape inputShape = new Ellipse2D.Double(300,200,200,300);
private Point2D point0 = new Point2D.Double(200,300);
private Point2D point1 = new Point2D.Double(600,400);
ShapeSplitPanel()
{
addMouseMotionListener(this);
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setColor(Color.BLUE);
g.fill(inputShape);
g.setColor(Color.BLACK);
g.draw(new Line2D.Double(point0, point1));
g.fill(new Ellipse2D.Double(
point0.getX() - 3, point0.getY()-3, 6, 6));
g.fill(new Ellipse2D.Double(
point1.getX() - 3, point1.getY()-3, 6, 6));
split(new Area(inputShape), point0, point1, g);
}
private static Area[] split(Area a, Point2D p0, Point2D p1, Graphics2D g)
{
// Compute the direction of the line (L)
// and its perpendicular (P)
double dx = p1.getX() - p0.getX();
double dy = p1.getY() - p0.getY();
double length = Math.hypot(dx, dy);
double dirLx = dx / length;
double dirLy = dy / length;
double dirPx = -dirLy;
double dirPy = dirLx;
// Compute the minimum and maximum of all dot
// products that describe the distance of the
// projection of the corner points of the
// bounding box on on the line (L) and its
// perpendicular (P). These are upper limits
// for the extents of the object along these
// directions
double minDotL = Double.MAX_VALUE;
double maxDotL = -Double.MAX_VALUE;
double minDotP = Double.MAX_VALUE;
double maxDotP = -Double.MAX_VALUE;
Rectangle2D bounds = a.getBounds2D();
for (int i=0; i<4; i++)
{
Point2D corner = getCorner(bounds, i);
double pdx = corner.getX() - p0.getX();
double pdy = corner.getY() - p0.getY();
double dotL = dirLx * pdx + dirLy * pdy;
minDotL = Math.min(minDotL, dotL);
maxDotL = Math.max(maxDotL, dotL);
double dotP = dirPx * pdx + dirPy * pdy;
minDotP = Math.min(minDotP, dotP);
maxDotP = Math.max(maxDotP, dotP);
}
// Compute the start- and end points of
// the line segments describing the
// extent of the bounds along the line
// and the perpendicular
Point2D extentLmin = new Point2D.Double(
p0.getX() + minDotL * dirLx,
p0.getY() + minDotL * dirLy);
Point2D extentLmax = new Point2D.Double(
p0.getX() + maxDotL * dirLx,
p0.getY() + maxDotL * dirLy);
Point2D extentPmin = new Point2D.Double(
p0.getX() + minDotP * dirPx,
p0.getY() + minDotP * dirPy);
Point2D extentPmax = new Point2D.Double(
p0.getX() + maxDotP * dirPx,
p0.getY() + maxDotP * dirPy);
// Compute the two rectangles that cover
// each half of the object based on
// the given line
Path2D half0 = new Path2D.Double();
half0.moveTo(extentLmin.getX(), extentLmin.getY());
half0.lineTo(
extentLmin.getX() + minDotP * dirPx,
extentLmin.getY() + minDotP * dirPy);
half0.lineTo(
extentLmax.getX() + minDotP * dirPx,
extentLmax.getY() + minDotP * dirPy);
half0.lineTo(extentLmax.getX(), extentLmax.getY());
half0.closePath();
Path2D half1 = new Path2D.Double();
half1.moveTo(extentLmin.getX(), extentLmin.getY());
half1.lineTo(
extentLmin.getX() + maxDotP * dirPx,
extentLmin.getY() + maxDotP * dirPy);
half1.lineTo(
extentLmax.getX() + maxDotP * dirPx,
extentLmax.getY() + maxDotP * dirPy);
half1.lineTo(extentLmax.getX(), extentLmax.getY());
half1.closePath();
// Compute the resulting areas by intersecting
// the original area with both halves
Area a0 = new Area(a);
a0.intersect(new Area(half0));
Area a1 = new Area(a);
a1.intersect(new Area(half1));
// Debugging output
if (g != null)
{
g.setColor(Color.GRAY);
g.draw(bounds);
g.setColor(Color.RED);
g.draw(new Line2D.Double(extentLmin, extentLmax));
g.setColor(Color.GREEN);
g.draw(new Line2D.Double(extentPmin, extentPmax));
g.setColor(Color.YELLOW.darker());
g.draw(half0);
g.setColor(Color.MAGENTA);
g.draw(half1);
g.setColor(Color.BLUE);
g.fill(AffineTransform.getTranslateInstance(400, -20).
createTransformedShape(a0));
g.setColor(Color.BLUE);
g.fill(AffineTransform.getTranslateInstance(400, +20).
createTransformedShape(a1));
}
return new Area[] { a0, a1 };
}
private static Point2D getCorner(Rectangle2D r, int corner)
{
switch (corner)
{
case 0: return new Point2D.Double(r.getMinX(), r.getMinY());
case 1: return new Point2D.Double(r.getMinX(), r.getMaxY());
case 2: return new Point2D.Double(r.getMaxX(), r.getMaxY());
case 3: return new Point2D.Double(r.getMaxX(), r.getMinY());
}
return null;
}
#Override
public void mouseDragged(MouseEvent e)
{
point1.setLocation(e.getPoint());
repaint();
}
#Override
public void mouseMoved(MouseEvent e)
{
}
}
EDIT An aside: Technically, it could be easier (or even more elegant) to transform the original shape and the line so that the line matches the x-axis, then defining the half-spaces to be clipped against (which in this case could be simple Rectangle2Ds), and transforming the clipped results back into the original orientation. But I wanted to compute it "in-place", without having to create many transformed shapes.
EDIT2: Another snippet for the comment, to be inserted directly before the // Debugging output
AffineTransform t = new AffineTransform();
double angle = Math.atan2(p1.getY() - p0.getY(), p1.getX() - p0.getX());
t.rotate(-angle, p0.getX(), p0.getY());
a0 = a0.createTransformedArea(t);
a1 = a1.createTransformedArea(t);
EDIT3 The second approach, only the relevant method this time
private static Area[] split(Area a, Point2D p0, Point2D p1, Graphics2D g)
{
// Compute the angle of the line to the x-axis
double dx = p1.getX() - p0.getX();
double dy = p1.getY() - p0.getY();
double angleRadToX = Math.atan2(dy, dx);
// Align the area so that the line matches the x-axis
AffineTransform at = new AffineTransform();
at.rotate(-angleRadToX);
at.translate(-p0.getX(), -p0.getY());
Area aa = a.createTransformedArea(at);
// Compute the upper and lower halves that the area
// has to be intersected with
Rectangle2D bounds = aa.getBounds2D();
double half0minY = Math.min(0, bounds.getMinY());
double half0maxY = Math.min(0, bounds.getMaxY());
Rectangle2D half0 = new Rectangle2D.Double(
bounds.getX(), half0minY,
bounds.getWidth(), half0maxY-half0minY);
double half1minY = Math.max(0, bounds.getMinY());
double half1maxY = Math.max(0, bounds.getMaxY());
Rectangle2D half1 = new Rectangle2D.Double(
bounds.getX(), half1minY,
bounds.getWidth(), half1maxY-half1minY);
// Compute the resulting areas by intersecting
// the original area with both halves, and
// transform them back to their initial position
Area a0 = new Area(aa);
a0.intersect(new Area(half0));
Area a1 = new Area(aa);
a1.intersect(new Area(half1));
try
{
at.invert();
}
catch (NoninvertibleTransformException e)
{
// Always invertible
}
a0 = a0.createTransformedArea(at);
a1 = a1.createTransformedArea(at);
// Debugging output
if (g != null)
{
g.setColor(Color.GRAY);
g.draw(bounds);
g.setColor(Color.RED);
g.draw(aa);
g.setColor(Color.YELLOW.darker());
g.draw(half0);
g.setColor(Color.MAGENTA);
g.draw(half1);
g.setColor(Color.BLUE.darker());
g.fill(AffineTransform.getTranslateInstance(400, -20).
createTransformedShape(a0));
g.setColor(Color.BLUE.brighter());
g.fill(AffineTransform.getTranslateInstance(400, +20).
createTransformedShape(a1));
}
return new Area[] { a0, a1 };
}
Interesting question.
There are no methods that help you with this directly, but by calculating the bounding rectangle and intersecting with two opposing rectangles from your dividing line, you should be able to create such a method.
The general idea is to
Find the bounding rectangle of your original area: either getBounds() or getBounds2D().
Calculate two rectangles from your line that overlaps your area on both sides of the line. When doing this you will have to take several special cases into account (like is the line long enough, does it intersect the area at all, does the rectangles completely overlap each side of the original area, etc). The size of the rectangles should be decided by the bounding rectangle of your original area.
Get the two areas by intersecting each of the two rectangles with your original area, i.e. by using the intersect() method

Categories