I'm working on something that involves clicking specific points on a buffered image in a JPanel. I had issues with this earlier in the project (affine transform translation not working properly), but nothing I found fixed it so I decided I would come back to it later.
I'm not entirely sure how to trouble shoot it since I'm a novice, but I think it's reading my y coordinates too low. I made a mouse input listener that tracks the number of times the user has clicked and gets the mouse pointer's location for functions I haven't made yet. For testing I have it output the coordinates and number of clicks then make a circle centered where the mouse clicks.
#Override
public void mouseClicked(MouseEvent e) {
Point mouseCursor = MouseInfo.getPointerInfo().getLocation();
panel.drawCenteredCircle(mouseCursor.getX(), mouseCursor.getY(), 100);
System.out.println(String.valueOf(mouseCursor));
System.out.println(String.valueOf(clickCount));
clickCount++;
}
Here is drawCenteredCircle in my custom panel class:
public void drawCenteredCircle(double x, double y, int r) {
imgG2 = image.createGraphics();
imgG2.setPaint(Color.RED);
x = (x-r/2.0);
y = (y-r/2.0);
imgG2.fillOval((int)Math.round(x), (int)Math.round(y), r, r);
this.repaint();
imgG2.dispose();
}
I tried taking a screenshot to show what happens, but the circle properly centers on the x coordinate, but not the y coordinate. Instead it draws the circle with the pointer at the top center edge.
I overrided the paintComponent of my JPanel to implement a zoom feature:
#Override
protected void paintComponent(Graphics g) {
//Implimenting zoom
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g.create();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
/*Supposed to counter the movement from the scale, not working properly
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
double x = (w - scale * imageWidth)/2;
double y = (h - scale * imageHeight)/2;*/
AffineTransform at = new AffineTransform()/*.getTranslateInstance(x, y) */;
at.scale(scale, scale);
g2.drawRenderedImage(image, at);
//g2.dispose(); I was told to put this, but I'm not sure if it's necessary or what it does entirely
}
My confused notes are because I got this code from an example someone made and, as I said earlier, the affine translation wasn't working (I took the actual translation out). They're irrelevant to the question.
The reason I put this is because I initially had code that was meant to fit the image to the screen/frame depending if it was fullscreen or not:
int x = image.getWidth();
int y = image.getHeight();
double frameW = frame.getBounds().getWidth();
double frameH = frame.getBounds().getHeight();
//Rectangle winSize = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
double screenW = Toolkit.getDefaultToolkit().getScreenSize().getWidth();
double screenH = Toolkit.getDefaultToolkit().getScreenSize().getHeight();
if (!isFullScreen) {
if (x/y > frameW/frameH) {
scale = frameW/x;
} else {
scale = frameH/y;
}
} else {
if (x/y > screenW/screenH) {
scale = screenW/x;
} else {
scale = screenH/y;
}
}
It uses my zoom function which scales the image with the double "scale." I noticed that when I zoomed in or out, it would change where the dots would appear relative to the pointer. It wasn't until I removed the code for the image to start fitted to the window and had it start at 100% that I received the result of the pointer being at the top center of the circle.
I also tried removing the part that's supposed to center the circle and the result was the pointer being on the left side and having a gap between it and the top of the circle.
Sorry if this is too much stuff. I'm pretty novice and learned just as much about java (the only coding language I know) working on this project as I knew when I first started it. I'm not sure what information I have that could be helpful in this, so I just threw in everything I thought could help. I appreciate any help, even irrelevant to my question.
I'm trying to make a graphic project where a Ball runs away from my cursor, I already did the other way around where the ball seeks my cursor and when she arrives she loses velocity so it's like she's running fast until she comes around a range of 10 pixels and then she loses velocity until she touches the cursor.
The thing is, I can't find a way to make the ball run away from the cursor in a way that when I enter a diameter(from the ball), she runs slow, if I approach more she starts to run faster to get away but when my cursor leaves the diameter, she runs slow until she stops once again.
I hope I made it clear, I thought about a solution but I don't know if there's a library or some built function in Java that I could use guys:
-have like a percentage from 0 to 100 where the distance between my cursor and the ball fits inside, 0% is velocity=0, 100% is velocity=4 for example, do you have any idea if there is such thing that I could implement?
Thank you in advance!
I've made a Vector class where I change it and access the X and Y coordinates to make the ball move, I used basic trigonometry to make the vector ALWAYS the same length.
code of my ball (Chaser) class:
public class Chaser {
private double x;
private double y;
private double vel = 1;
private double hyp;
private Vector vector = new Vector(0, 0);
private double distance;
public Chaser(int width, int height){
x = width/2;
y = height/2;
}
public void setVel(Point m){
if(m.x != x)
hyp = Math.sqrt(Math.pow(Math.abs(m.x - x), 2) + Math.pow(Math.abs(m.y - y), 2));
else
hyp = Math.abs(m.y - y);
}
public void setDirection(Point m){
if(hyp == 0) return;
vector.change((m.x - x)/hyp, (m.y - y)/hyp);
}
public void draw(Graphics g){
g.setColor(Color.RED);
g.fillOval((int)x - 10, (int)y - 10, 20, 20);
g.setColor(Color.BLACK);
g.drawLine((int)x, (int)y, (int)(vector.getX()*15*vel) + (int)x, (int)(vector.getY()*15*vel) + (int)y);
}
public void move(Point m){
setVel(m);
setDirection(m);
useVector();
}
public void useVector(){
if(vector == null) return;
x -= vector.getX() * vel;
y -= vector.getY() * vel;
}
public void calculateVelocity(Point m){
if(vector == null) return;
// I don't know what to do yet
}
}
If you want to just push the ball around you can do something simple. Let's use vectors to make it easier to understand. Say ball holds the ball's center (x,y) and mouse contains the mouse position (x,y).
You can compute the distance between ball and mouse, that is (mouse - ball).length() to get how far away the mouse is from the ball.
If the distance > ball radius then the mouse is outside.
Otherwise you can do:
tmp = ball - mouse // get the vector from mouse to the ball.
tmp = tmp / tmp.length() * ball_radious // change the vector's length to match the radious of the ball.
ball = mouse + tmp // Move the ball such that the mouse will be on the edge.
As you move the mouse the ball will get pushed by the mouse.
If you want a bit of inertia, so the ball doesn't just stop when you don't push it anymore then you need to keep an additional vector speed and use tmp to get an acceleration.
Something like this:
tmp = ball - mouse // get the vector from mouse to the ball.
force = max(0, ball_radious - tmp.length()) // how strong we push the ball.
acceleration = tmp / tmp.legnth() * f(force) // compute the acceleration vector. f(force) is some function based on force, try k*f or k*f*f and see what looks better for your setup.
speed = speed * kDrag + acceleration // update the speed, kDrag should be between 0 and 1, start with something like 0.8 and try different values.
ball = ball + speed * time_delta // Update the ball's position.
You can play with the constants to get the right feel that you're looking for. time_delta is meant to normalize the speed between frams so you don't need to worry too much if there's some inconsistency between them. You can use a constant as well, but the movement might become jerky at times.
All operations above are vector operations.
Occasionally I have to display a popup or dialog relative to an existing component (prime example is a date input control with a calendar button beside it).
It worked beautifully for years, but always had the bug that the calendar could partially appear outside the screen (it was hardcoded to appear just to the right of the field). Just nobody ever noticed because there was never a date control at the far right in a window. Well that changed recently with the addition of a new window.
Well then, I thought, lets just fix a windows position (after I positioned it where it should be) to be completely on screen. I wrote a simple utility method to do just that:
public static void correctWindowLocationForScreen(Window window) {
GraphicsConfiguration gc = window.getGraphicsConfiguration();
Rectangle screenRect = gc.getBounds();
Rectangle windowRect = window.getBounds();
Rectangle newRect = new Rectangle(windowRect);
if (windowRect.x + windowRect.width > screenRect.x + screenRect.width)
newRect.x = screenRect.x + screenRect.width - windowRect.width;
if (windowRect.y + windowRect.height > screenRect.y + screenRect.height)
newRect.y = screenRect.y + screenRect.height - windowRect.height;
if (windowRect.x < screenRect.x)
newRect.x = screenRect.x;
if (windowRect.y < screenRect.y)
newRect.y = screenRect.y;
if (!newRect.equals(windowRect))
window.setLocation(newRect.x, newRect.y);
}
Problem solved. Or not. I position my window using the on-screen coordinates from the triggering component (the button that makes the calendar appear):
JComponent invoker = ... // passed in from the date field (a JButton)
Window owner = SwingUtilities.getWindowAncestor(invoker);
JDialog dialog = new JDialog(owner);
dialog.setLocation(invoker.getLocationOnScreen());
correctWindowLocationForScreen(dialog);
Havoc breaks out if the "invoker" component is located in a window that spans two screens. Apparently "window.getGraphicsConfiguration()" returns whatever graphic configuration the windows top left corner happens to be in. Thats not necessarily the screen where the date component within the window is located.
So how can I position my dialog properly in this case?
One can iterate over all devices, and find the monitor where the point is in. Then keep to that Rectangle.
See GraphicsEnvironment.getScreenDevices.
This will not use the current Window, but you already found out that a window may be shown in several monitors.
Useful might be Component.getLocationOnScreen.
Ok, here is what I ended up with (a wall of code to handle the odd edge case).
correctWindowLocationForScreen() will reposition a window if it is not completely within the visible screen area (simplest case, its completely on one screen. Hard case, it spans multiple screens). If the window leaves the complete screen area by just one pixel, it is repositioned using the first screen rectangle found. If the window doesn't fit the screen, its positioned at the top left and extends over the screen to bottom right (its implied by the order in which positionInsideRectangle() checks/alters coordinates).
Its quite complicated considering the requirement is pretty simple.
/**
* Check that window is completely on screen, if not correct position.
* Will not ensure the window fits completely onto the screen.
*/
public static void correctWindowLocationForScreen(final Window window) {
correctComponentLocation(window, getScreenRectangles());
}
/**
* Set the component location so that it is completely inside the available
* regions (if possible).
* Although the method will make some effort to place the component
* nicely, it may end up partially outside the regions (either because it
* doesn't fit at all, or the regions are placed badly).
*/
public static void correctComponentLocation(final Component component, final Rectangle ... availableRegions) {
// check the simple cases (component completely inside one region, no regions available)
final Rectangle bounds = component.getBounds();
if (availableRegions == null || availableRegions.length <= 0)
return;
final List<Rectangle> intersecting = new ArrayList<>(3);
for (final Rectangle region : availableRegions) {
if (region.contains(bounds)) {
return;
} else if (region.intersects(bounds)) {
// partial overlap
intersecting.add(region);
}
}
switch (intersecting.size()) {
case 0:
// position component in the first available region
positionInsideRectangle(component, availableRegions[0]);
return;
case 1:
// position component in the only intersecting region
positionInsideRectangle(component, intersecting.get(0));
return;
default:
// uuuh oooh...
break;
}
// build area containing all detected intersections
// and check if the bounds fall completely into the intersection area
final Area area = new Area();
for (final Rectangle region : intersecting) {
final Rectangle2D r2d = new Rectangle2D.Double(region.x, region.y, region.width, region.height);
area.add(new Area(r2d));
}
final Rectangle2D boundsRect = new Rectangle2D.Double(bounds.x, bounds.y, bounds.width, bounds.height);
if (area.contains(boundsRect))
return;
// bah, just place it in the first intersecting region...
positionInsideRectangle(component, intersecting.get(0));
}
/**
* Position component so that its completely inside the rectangle.
* If the component is larger than the rectangle, component will
* exceed to rectangle bounds to the right and bottom, e.g.
* the component is placed at the rectangles x respectively y.
*/
public static void positionInsideRectangle(final Component component, final Rectangle region) {
final Rectangle bounds = component.getBounds();
int x = bounds.x;
int y = bounds.y;
if (x + bounds.width > region.x + region.width)
x = region.x + region.width - bounds.width;
if (y + bounds.height > region.y + region.height)
y = region.y + region.height - bounds.height;
if (region.x < region.x)
x = region.x;
if (y < region.y)
y = region.y;
if (x != bounds.x || y != bounds.y)
component.setLocation(x, y);
}
/**
* Gets the available display space as an arrays of rectangles
* (there is one rectangle for each screen, if the environment is
* headless the resulting array will be empty).
*/
public static Rectangle[] getScreenRectangles() {
try {
Rectangle[] result;
final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
final GraphicsDevice[] devices = ge.getScreenDevices();
result = new Rectangle[devices.length];
for (int i=0; i<devices.length; ++i) {
final GraphicsDevice gd = devices[i];
result[i] = gd.getDefaultConfiguration().getBounds();
}
return result;
} catch (final Exception e) {
return new Rectangle[0];
}
}
I'll be candid about this; it is a homework assignment, but can someone guide me in the right direction and explain to me how some parts of the code are supposed to work? The directions are below the code and the questions.
This is my code so far:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Container;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Rainbow extends JPanel
{
// Declare skyColor:
private final Color skyColor = Color.CYAN;
public Rainbow()
{
setBackground(skyColor);
}
// Draws the rainbow.
public void paintComponent(Graphics g)
{
super.paintComponent(g);
int width = getWidth();
int height = getHeight();
// Declare and initialize local int variables xCenter, yCenter
// that represent the center of the rainbow rings:
int xCenter = width/2;
int yCenter = (height * 3) /4;
// Declare and initialize the radius of the large semicircle:
int largeRadius = width/4;
g.setColor(Color.RED);
// Draw the large semicircle:
g.fillArc(xCenter,yCenter,largeRadius,height,0,180);
// Declare and initialize the radii of the small and medium
// semicircles and draw them:
int smallRadius = height/4;
g.setColor(Color.MAGENTA);
g.fillArc(xCenter,yCenter,width,height,0,180);
int mediumRadius = (int) Math.sqrt(smallRadius * largeRadius);
g.setColor(Color.GREEN);
g.fillArc(xCenter,yCenter,width,height,0,180);
// Calculate the radius of the innermost (sky-color) semicircle
// so that the width of the middle (green) ring is the
// arithmetic mean of the widths of the red and magenta rings:
// Draw the sky-color semicircle:
g.fillArc(xCenter,yCenter,width,height,0,180);
}
public static void main(String[] args)
{
JFrame w = new JFrame("Rainbow");
w.setBounds(300, 300, 300, 200);
w.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container c = w.getContentPane();
c.add(new Rainbow());
w.setVisible(true);
}
}
My questions: How exactly does fillArc work; I understand what goes in the parameter, but what must one do so each arc differs from one another?
How does one set one color for each arc? I tried doing so, and I ended up with the color listed closest the end showing up and overriding the others.
I'll probably have more as a I continue to code.
These were the directions:
![enter image description here][1]
The “rainbow” is made of four overlapping semicircles. The outer ring is red (Color.RED), the middle one is green (Color.GREEN), and the inner ring has the magenta color (Color.MAGENTA). The innermost semicircle has the same color as the background.
Follow the instructions below and fill in the blanks in Rainbow.java.
Start the Rainbow project.
Add a complete comment header with your name before the class declaration at the top of the file.
Add to the Rainbow class a declaration of a private final field skyColor of the type Color, initialized to Color.CYAN (the color of the sky). In Rainbow’s constructor, set the window’s background to skyColor rather than Color.WHITE.
In the paint method, declare local integer variables xCenter and yCenter that represent the coordinates of the center of the rings. Initialize them to 1/2 width and 3/4 height (down) of the content pane, respectively. (Recall that the origin of graphics coordinates in Java is at the upper left corner of the content pane with the y-axis pointing down.) Do not plug in fixed numbers from the window’s dimensions.
Declare a local variable largeRadius that represents the radius of the largest (red) semicircle and initialize it to 1/4 of width.
A method call g.fillArc(x, y, size, size, from, degrees) (with all integer arguments) draws a sector of a circle. x and y are the coordinates of the upper left corner of the rectangle (in this case a square) into which the oval is (logically) inscribed; size is the side of the square (and the diameter of the circle); from is the starting point of the arc in degrees (with 0 at the easternmost point of the horizontal diameter), and degrees (a positive number) is the measure of the arc, going counterclockwise. Add a statement to the paint method to draw the largest (red) semicircle. Test your program.
Add statements to display the medium (green) and small (magenta) semicircles. The radius of the magenta semicircle should be 1/4 of height. The radius of the green one should be the geometric mean (the square root of the product) of the radius of the red semicircle and the radius of the magenta semicircle, rounded to the nearest integer. (A call to Math.sqrt(x) returns the value of square root of x, a double.) Retest your program.
Add statements to display the innermost semicircle of the background (“sky”) color to complete the rainbow. Use the skyColor constant for this semicircle’s color. Choose the radius of the sky-color semicircle in such a way that the width of the middle (green) ring is the arithmetic mean of the widths of the red and magenta rings.
Test your program.
Submit your completed program and run output. Your run output (the rainbow picture) can be included by capturing the screen output (Alt-PrintScrn), pasting it into a graphics program (such as MS Paint) and then saving the image to your Eclipse project directory.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Container;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Rainbow extends JPanel
{
//Declare skyColor:
private final Color skyColor = Color.CYAN;
public Rainbow()
{
setBackground(skyColor);
}
// Draws the rainbow.
public void paintComponent(Graphics g)
{
super.paintComponent(g);
int width = getWidth();
int height = getHeight();
// Declare and initialize local int variables xCenter, yCenter
// that represent the center of the rainbow rings:
int xCenter = width/2;
int yCenter = (height * 3) /4;
// Declare and initialize the radius of the large semicircle:
int largeRadius = width/4;
g.setColor(Color.RED);
// Draw the large semicircle:
g.fillArc(xCenter - largeRadius,yCenter - largeRadius ,largeRadius,largeRadius,0,180);
// Declare and initialize the radii of the small and medium
//semicircles and draw them:
int smallRadius = height/4;
int mediumRadius = (int) Math.sqrt(smallRadius * largeRadius);
g.setColor(Color.GREEN);
g.fillArc(xCenter-(largeRadius+mediumRadius)/2,yCenter- (largeRadius+mediumRadius)/2,mediumRadius,mediumRadius,0,180);
g.setColor(Color.MAGENTA);
g.fillArc(xCenter-(largeRadius+smallRadius)/2,yCenter-(largeRadius+smallRadius)/2,smallRadius,smallRadius,0,180);
// Calculate the radius of the innermost (sky-color) semicircle
// so that the width of the middle (green) ring is the
// arithmetic mean of the widths of the red and magenta rings:
int skyRadius = (int)((2 * Math.sqrt(smallRadius * largeRadius)) - width/4);
// Draw the sky-color semicircle:
g.setColor(skyColor);
g.fillArc(xCenter-skyRadius,yCenter-skyRadius,skyRadius,skyRadius,0,180);
}
public static void main(String[] args)
{
JFrame w = new JFrame("Rainbow");
w.setBounds(300, 300, 300, 200);
w.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container c = w.getContentPane();
c.add(new Rainbow());
w.setVisible(true);
}
}
fillArc() fills in a section of the circle based upon the parameters you gave it. For example your first arc.
You're drawing the fill arc, which in this case is a semi-circle, of the color red.
//Set the arc color
g.setColor(Color.RED);
// Draw the large semicircle:
g.fillArc(xCenter,yCenter,largeRadius,height,0,180);
There's our fillArc. That doesn't look anything like a rainbow. In order to get the rainbow shape, we have to draw a smaller arc inside of it. In your case the next one is green. So we do fillArc again after setting the color to green. But we shrunk the radius a little bit so the green doesn't cover the entire red section.
Keep in mind when we draw, we're drawing on top, so if you drew the green on first it would be covered by the red one.
Then we draw another arc inside this once more, but make this one the color of the sky (white in this case). This creates the final rainbow shape. So we do fillArc again, but with a slightly smaller radius and the color white.
And there, we drew a rainbow.
To center this beautiful creation, we have to understand a few things about the fillArc function.
The parameters are:
public abstract void fillArc(int x,
int y,
int width,
int height,
int startAngle,
int arcAngle)
int x and int y represent the coordinates for the upper left hand corner of the arc that you are drawing. The reason your code isn't centering is because of how you're drawing the arc.
g.fillArc(xCenter - largeRadius,yCenter - largeRadius,largeRadius,largeRadius,0,180);
g.fillArc(xCenter-(largeRadius+mediumRadius)/2,yCenter-(largeRadius+mediumRadius)/2,mediumRadius,mediumRadius,0,180);
g.fillArc(xCenter-(largeRadius+smallRadius)/2,yCenter-(largeRadius+smallRadius)/2,smallRadius,smallRadius,0,180);
I took out a few of the excess stuff. You see how you're subtracting the (largeRadius+smallRadius)/2 and (largeRadius+mediumRadius)/2? This is shifting the rainbow to make it off center. What you should have is instead:
g.fillArc(xCenter - largeRadius/2,yCenter - largeRadius,largeRadius,largeRadius,0,180);
g.fillArc(xCenter-(mediumRadius)/2,yCenter-(largeRadius+mediumRadius)/2,mediumRadius,mediumRadius,0,180);
g.fillArc(xCenter-(smallRadius)/2,yCenter-(largeRadius+smallRadius)/2,smallRadius,smallRadius,0,180);
This will properly center the rainbow. Here's why.
That's the point where they will start the drawing the arc from. If you want to center the entire rainbow, you'd shift it over by half of it's entire width. So if you want to center the red arc, you'd do
xCenter - (largeRadius/2)
As this is setting the x start to the left by half. You wouldn't include largeRadius in the other arcs, as you're centering them around this point. Thus you'd want to shift them over by half of their individual widths, which is why their x positions are
xCenter-(mediumRadius)/2
xCenter-(smallRadius)/2
Centering on the Y-axis works differently. You have to consider that the height of the rainbow overall is 1/4 the largeRadius. Your code uses yCenter = 3/4 * height, so that changes it a bit.
This is my solution
g.fillArc(xCenter - largeRadius/2,yCenter - largeRadius/2 + largeRadius/4 -height/4,largeRadius,largeRadius,0,180);
g.fillArc(xCenter-(mediumRadius)/2,yCenter-(mediumRadius)/2 + largeRadius/4 -height/4,mediumRadius,mediumRadius,0,180);
g.fillArc(xCenter-(smallRadius)/2,yCenter-(smallRadius)/2 + largeRadius/4 -height/4,smallRadius,smallRadius,0,180);
Let's take a look. I subtracted the largeRadius/2 (and respective radiuses) for the same principle as in x. But then I added largeRadius/4 because we have to shift the entire rainbow down. This is because subtracting the respective radius/2 only centers the rainbow as if it were an entire circle, not semi-circles.
Adding largeRadius/4 shifts the rainbow down by overall half of it's height, centering it correctly for a semi-circle. Finally, subtracting height/4 makes changes the yCenter to height/2, since 3/4 * height is a requirement in your assignment.
Sorry about all the problems in the comments, hope this cleared it up.
I've modified part of your code so you could get an idea. Remember that your xCenter and yCenter represent the center of your circle, not the coordinates you need to use in the fillArc method. The instructions you provided explain it pretty well. You can get an idea from what I did here and figure the rest by yourself.
// First declare and initialize all radiuses
int largeRadius = width/4;
int smallRadius = height/4;
int mediumRadius = (int) Math.sqrt(smallRadius * largeRadius);
//Then draw each arc in descending order from the largest one
g.setColor(Color.RED);
g.fillArc(xCenter-largeRadius,yCenter-largeRadius,largeRadius,largeRadius,0,180);
g.setColor(Color.GREEN);
g.fillArc(xCenter-(largeRadius+mediumRadius)/2,yCenter-(largeRadius+mediumRadius)/2,mediumRadius,mediumRadius,0,180);
g.setColor(Color.MAGENTA);
g.fillArc(xCenter-(largeRadius+smallRadius)/2,yCenter-(largeRadius+smallRadius)/2,smallRadius,smallRadius,0,180);
// Calculate the radius of the innermost (sky-color) semicircle
For you skyRadius consider:
Red width = large radius - medium radius
green width = medium - small
magenta width = small radius - skyradius
if I did the math rightyou get: skyRadius = smallRadius - 2*(mediumRadius-smallRadius)+largeRadius-mediumRadius
int skRadius=smallRadius-2*(mediumRadius-smallRadius)+largeRadius-mediumRadius;
g.setColor(skyColor);
g.fillArc(xCenter-(largeRadius+skRadius)/2,yCenter-(largeRadius+skRadius)/2,skRadius,skRadius,0,180);
A much simpler equation for skyRadius is:
int skyRadius = largeRadius - 3 * mediumRadius + 3 * smallRadius;
The code below plots some simple x-y data, but it has two problems that I do not know how to fix.
First, it plots negative values for some of the data points, which means lines extending southward below the x-axis. Since the data points are selected at random, you may have to resize the frame a bit in order to view new random numbers to be plotted in a way that shows this bug. All data values will be positive, so I want all deflections to project northward above the blue bottom marker line, and I need to make sure that no deflections extend southward below the blue bottom marker line.
Second, the y-axis label takes up too much real estate on the screen. It needs to be rotated -90 degrees. However, all the examples I have seen for this involve rotating the entire panel using a graphics2d object. I do not want to rotate the entire panel. Instead, I just want to rotate the text of the y-axis label.
Can anyone show me how to change the code below to fix these two specific problems?
The code is in the following two files:
GUI.java
import java.awt.*;
import javax.swing.*;
class GUI{
GUI() {
// Create a new JFrame container.
JFrame jfrm = new JFrame("X-Y Plot");
// Specify FlowLayout for the layout manager.
jfrm.getContentPane().setLayout(new FlowLayout());
int frameHeight = 400;
int frameWidth = 300;
// Give the frame an initial size.
jfrm.setSize(frameWidth, frameHeight);
// Terminate the program when the user closes the application.
jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Create a text-based label.
JVertLabel myVLabel = new JVertLabel("y-axis label");
int width = myVLabel.WIDTH;
PaintPanel myPP = new PaintPanel(frameWidth-width-50-20,frameHeight-70);
jfrm.add(myPP);
jfrm.add(myVLabel);// Add the label to the frame.
// Display the frame.
jfrm.setVisible(true);
}
public static void main(String args[]) {
// Create the frame on the event dispatching thread.
SwingUtilities.invokeLater(new Runnable() {public void run(){new GUI();}});
}
public class JVertLabel extends JComponent {
private String text;
public JVertLabel(String s) {
text = s;
}//constructor
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.rotate(Math.toRadians(-90));
g2d.drawString(text, 0, 0);
}
}
}
PaintPanel.java
import java.awt.*;
import javax.swing.*;
import java.util.*;
class PaintPanel extends JPanel {
Insets ins; // holds the panel's insets
Random rand; // used to generate random numbers
PaintPanel(int w, int h) {
setOpaque(true);// Ensure that panel is opaque.
setPreferredSize(new Dimension(w, h));// Set preferred dimension as specfied.
rand = new Random();
}
protected void paintComponent(Graphics g) {// Override paintComponent() method.
super.paintComponent(g);// Always call superclass method first.
int height = getHeight();// Get height of component.
int width = getWidth();// Get width of component.
ins = getInsets();// Get the insets.
// Get dimensions of text
Graphics2D g2d = (Graphics2D) g;
Font font = new Font("Serif", Font.PLAIN, 12);
FontMetrics fontMetrics = g2d.getFontMetrics();
String xString = ("x-axis label");
int xStrWidth = fontMetrics.stringWidth(xString);
int xStrHeight = fontMetrics.getHeight();
String yString = "y-axis-label";
int yStrWidth = fontMetrics.stringWidth(yString);
int yStrHeight = fontMetrics.getHeight();
int leftStartPlotWindow = ins.left + 5 + yStrWidth;
int hPad = 3;
// Fill panel by plotting random data in a bar graph.
for (int i = leftStartPlotWindow + hPad; i <= width - leftStartPlotWindow - hPad + yStrWidth + 1; i += 4) {
int h = Math.abs(rand.nextInt(height - ins.bottom));//Get rand# betw/0 and max height of drawing area.
// If generated value w/in or too close to border, change it to just outside border.
if (h <= ins.top) {
h = ins.top + 1;
}
g.drawLine(i, Math.abs(height - ins.bottom - xStrHeight - 5), i, h);// Draw a line that represents data.
}
g.setColor(Color.blue);
g.drawRect(leftStartPlotWindow, ins.bottom + 2, width - leftStartPlotWindow - ins.right - hPad, height - xStrHeight - 6);
g.setColor(Color.red);
g.drawRect(ins.left, ins.bottom, width - ins.left - 1, height - ins.bottom - 1);
g.drawString(xString, (width / 2) - (xStrWidth / 2), height - ins.bottom - 6);
g.drawString(yString, ins.left, height / 2);
}
}
All data values will be positive, so I want all deflections to project northward above the blue bottom marker line, and I need to make sure that no deflections extend southward below the blue bottom marker line.
You need to calculate the random height so that all values fit into the space available. So the calculation would be something like:
int randomHeight = panelHeight - offset.top - offset.bottom - heightForTheXAxisText;
Then you don't have to worry about negative values or the top of the line extending outside the bounds of the panel.
all the examples I have seen for this involve rotating the entire panel using a graphics2d object. I do not want to rotate the entire panel. Instead, I just want to rotate the text of the y-axis label.
Set the rotation of the Graphics object, the draw the text, then restore the rotation of the Graphics object back to 0.
Or, you create create a new Graphcis object from the current Graphics object, then apply the rotation, draw the text and then dispose of the temporaray Graphics object.
JFreeChart addresses both issues by default, as shown in this example.
In your example,
You'll have to create the data model before trying to render it. Then you can scan it for min and max to determine the limits of your range axis. List<Double> may be a suitable choice.
You can rotate the range label by altering the graphics context's AffineTransform, as shown in RotateText.