I am working on a pie chart with 4 different elements. I can get the elements to appear in the JFrame but I cannot complete the pie chart circle. These are the elements:
public static class PieChart extends JComponent {
IAPieChart[] pieValue = {new IAPieChart(5, Color.green),
new IAPieChart(4, Color.orange),
new IAPieChart(3, Color.blue),
new IAPieChart(2, Color.red)
};
Now that the elements are instantiated, and loaded into the array, I use this method to paint them:
public void paintComponent(Graphics g) {
drawPie((Graphics2D) g, getBounds(), pieValue);
}
This method puts the elements together on the JFrame but only gives me about 120 degrees of the circle. :
void drawPie(Graphics2D g, Rectangle area, IAPieChart[] pieValue){
double sum = 0.0;
for (int i = 0; i < pieValue.length; i++) {
sum += pieValue[i].arcValue;
}
double endPoint = 0.0D;
int arcStart = 0;
for (int i = 0; i < pieValue.length; i++){
endPoint = (int) (endPoint * 360 / sum);
int radius = (int) (pieValue[i].arcValue * 360/ sum);
g.setColor(pieValue[i].color);
g.fillArc(area.x, area.y, area.width, area.height, arcStart , radius);
radius += pieValue[i].arcValue;
}
}
}
I'm at a loss. I have about 10 weeks expereince with Java so this is mostly trial and error. I am looking for a way to complete the circle. I reduced the values to the lowest point that would make all of the colors show. Anything larger will eliminate one color or the other.
I hope you can help. Here is the full program IAPieChart:
package iapiechart;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import javax.swing.JComponent;
import javax.swing.JFrame;
class IAPieChart{
double arcValue; // passes a value for the calculation of the arc.
Color color; // holds value for color (expressed as an integer
public IAPieChart(double value, Color color){
this.arcValue = value;
this.color = color;
}
public static class PieChart extends JComponent {
IAPieChart[] pieValue = {new IAPieChart(5, Color.green),
new IAPieChart(4, Color.orange),
new IAPieChart(3, Color.blue),
new IAPieChart(2, Color.red)
};
public void paintComponent(Graphics g) {
drawPie((Graphics2D) g, getBounds(), pieValue);
}
void drawPie(Graphics2D g, Rectangle area, IAPieChart[] pieValue){
double sum = 0.0;
for (int i = 0; i < pieValue.length; i++) {
sum += pieValue[i].arcValue;
}
double endPoint = 0.0D;
int arcStart = 0;
for (int i = 0; i < pieValue.length; i++){
endPoint = (int) (endPoint * 360 / sum);
int radius = (int) (pieValue[i].arcValue * 360/ sum);
g.setColor(pieValue[i].color);
g.fillArc(area.x, area.y, area.width, area.height, arcStart , radius);
radius += pieValue[i].arcValue;
}
}
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.getContentPane().add(new PieChart());
frame.setSize(300, 300);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
If you just want to show pie chart where arc value is a weights for segments, made a change to your draw cycle.
int arcStart = 0;
for (int i = 0; i < pieValue.length; i++){
int radius = (int) (pieValue[i].arcValue * 360.0 / sum);
g.setColor(pieValue[i].color);
g.fillArc(area.x, area.y, area.width, area.height, arcStart, radius);
arcStart += radius;
}
The result is something like this:
As you see there is a small empty area, which, I suppose, caused by accumulated rounding error. Can be solved by drawing the last segment with arcValue that complement sum of all previos to 360.
There is a little mixup with arcStart calculation in the original method. It should start from the end of the previous arc. Consider this slightly modified method. The comments describe the updated lines.
void drawPie(Graphics2D g, Rectangle area, IAPieChart[] pieValue){
double sum = 0.0;
for (int i = 0; i < pieValue.length; i++) {
sum += pieValue[i].arcValue;
}
double endPoint = 0.0D;
int arcStart = 0;
for (int i = 0; i < pieValue.length; i++){
arcStart = (int) (endPoint * 360 / sum); //old line was: endPoint = (int) (endPoint * 360 / sum);
int radius = (int) (pieValue[i].arcValue * 360/ sum);
g.setColor(pieValue[i].color);
g.fillArc(area.x, area.y, area.width, area.height, arcStart , radius);
endPoint += pieValue[i].arcValue; //old line was: radius += pieValue[i].arcValue;
}
}
It replaces line:
endPoint = (int) (endPoint * 360 / sum);
with:
arcStart = (int) (endPoint * 360 / sum);
And replaces line:
radius += pieValue[i].arcValue;
with:
endPoint += pieValue[i].arcValue;
Here is the result:
Don't forget to call super.paintComponent(g); in paintComponent(). Also, you may want to add anti aliasing rendering hints to smooth the image a little bit, ie:
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
drawPie((Graphics2D) g, getBounds(), pieValue);
}
You have
double endPoint = 0.0D;
int arcStart = 0;
for (int i = 0; i < pieValue.length; i++){
endPoint = (int) (endPoint * 360 / sum);
...
However this will always give endPoint = 0 as your multiplying by zero everytime. Try making it
endPoint += (int) (endPoint * 360 / sum);
I learned that I am accumulating the wrong values from both replies from phcoding and mishadoff. With phcoding's recommendation however, I came up with additonal instances of the pieValue array. Both responses were enriching and my experience increased through your responses.
mishadoff: May I have your definition of 'weights' as you mentioned in your response? When I think of weights I imagine an anchoring point where the arc starts then the value given is how much it will increase in it's travel along the radius of the circle. Is that close?
phcoding & mishadoff : I intend to put this into an Applet do you forsee any issues with doing this, such as transfering this code into the applet? I ask because any recommendation you give me will take literally hours off of my programming time, and I am going to need them.
Thank you both very much!
Ed.
Related
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);
}
}
}
I have pie chart to display the rate of sales through out the year,now i want to draw a string at the center of each arc respectively,to specify the month in my pie chart
this is how my code looks like
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JPanel;
public class TopSectionPanel extends JPanel {
double[] sales = {4000, 3000, 2000, 6000 , 10000 , 2500,
3400 , 8700 , 6734 , 1200 , 4500 , 6700};
double[] angle = new double[sales.length];
Color[] color = {Color.RED, Color.BLACK, Color.BLUE, Color.DARK_GRAY, Color.GREEN,
Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE, Color.PINK, Color.WHITE,
Color.YELLOW, Color.GRAY};
double sum = 0;
/**
* Create the panel.
*/
public TopSectionPanel() {
setBackground(Color.CYAN);
setPreferredSize(new Dimension(400 , 400));
for(int l = 0 ; l < sales.length ; l++)
sum += sales[l];
for(int i = 0 ; i < angle.length ; i++)
angle[i] = sales[i] / sum * 360;
}
#Override
protected void paintComponent(Graphics g) {
// TODO Auto-generated method stub
super.paintComponent(g);
int radius;
// calculations to fit the circle in exact center
if(getHeight() < getWidth())
radius = (int) ((getHeight() / 2.) * 0.8) ;
else
radius = (int) ((getWidth() / 2.) * 0.8) ;
int diameter = radius * 2;
int x = (int)(getWidth() / 2) - radius;
int y = (int)(getHeight() / 2) - radius;
double a = 0;
for( int i = 0 ; i < angle.length ; i++ ) {
g.setColor(color[i]);
g.fillArc(x, y, diameter, diameter, (int)a, (int)angle[i]);
a = a + angle[i];
}
}
}
Not knowing minor details of the requirements such as distance from the arc or font or size I can only produce a raw sketch, using some elementary geometry. Add this after the loop drawing the pie chart.
int mx = x + radius;
int my = y + radius;
double b = 0;
int rad = (int)(radius*1.20);
for( int i = 0 ; i < angle.length ; i++ ) {
b += angle[i]/2;
double brad = b*Math.PI/180.0;
int ix = (int)(rad*Math.cos(brad));
int iy = (int)(rad*Math.sin(brad));
g.drawString( Integer.toString(i+1), mx+ix, my-iy );
b += angle[i]/2;
}
Note that the computed coordinates are the point on the baseline where the text starts. If you really want to center the text around this point you'll have to compute the bounding box for the text and modify the coordinates by these small amounts in x- and y-direction.
I'm making an application about space physics, so I do lots with orbits. Naturally, I encounter the Ellipse2D.Double to draw my orbits on the screen.
Whenever my JPanel refreshes, I draw the orbit of a body using an Ellipse2D, as well as the body itself with a different method.
Essentially, I discovered that when numbers get very large (whether it be the size of the orbits get large or the visualization is zoomed in very far), the position of the body and the Ellipse2D do not line up.
I calculate the position of the body using a conversion from polar coordinates to rectangular coordinates, and I leave the math for the Ellipse2D up to the geom package.
Take a look at this code sample. It's the most self-contained version of my problem that I can make, since scale of the circle has to be very large:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.math.BigDecimal;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class EllipseDemo extends JPanel {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.add(new EllipseDemo());
frame.setVisible(true);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// These values allow for a very zoomed in view of a piece of the circle
BigDecimal[] circleCenter = { new BigDecimal(-262842.5), new BigDecimal(-93212.8) };
BigDecimal circleRadius = new BigDecimal(279081.3);
// Draw the circle at the given center, with the given width and height
// x = centerx - radius, y = centery - radius, w = h = radius * 2
g2d.draw(new Ellipse2D.Double(circleCenter[0].subtract(circleRadius).doubleValue(),
circleCenter[1].subtract(circleRadius).doubleValue(), circleRadius.multiply(new BigDecimal(2)).doubleValue(),
circleRadius.multiply(new BigDecimal(2)).doubleValue()));
// Get a rectangular conversion of a point on the circle at this angle
BigDecimal angle = new BigDecimal(0.34117696217);
BigDecimal[] rectangular = convertPolarToRectangular(new BigDecimal[] {
circleRadius, angle });
// Draw a line from the center of the circle to the point
g2d.draw(new Line2D.Double(circleCenter[0].doubleValue(), circleCenter[1].doubleValue(),
circleCenter[0].add(rectangular[0]).doubleValue(), circleCenter[1]
.add(rectangular[1]).doubleValue()));
}
public BigDecimal[] convertPolarToRectangular(BigDecimal[] polar) {
BigDecimal radius = polar[0];
BigDecimal angle = polar[1];
BigDecimal x = radius.multiply(new BigDecimal(Math.cos(angle.doubleValue())));
BigDecimal y = radius.multiply(new BigDecimal(Math.sin(angle.doubleValue())));
return new BigDecimal[] { x, y };
}
}
The code above essentially draws a circle on the screen very far away with a large radius. I've picked the dimension so that a piece of the circle is visible in the small window.
Then it draws a line from the center of the circle to a point on the circle that's visible in the window: I picked an angle that was visible on the window and used geometry to convert that angle and the radius of the circle into rectangular coordinates.
This is what the program displays:
Notice that the line doesn't actually end up touching the ellipse. Now, I decided I had to find out whether it was the point I calculated or the ellipse that were incorrect. I did the math on my calculator, and found that the line was correct, and the ellipse incorrect:
Considering that the calculator is probably not wrong, I am led to believe the Ellipse2D is not drawing correctly. However, I tried many other angles, and this is the pattern I found:
And that leads me to believe the calculations are somehow wrong.
So that's my problem. Should I be using something other than Ellipse2D? Maybe Ellipse2D is not accurate enough? I used BigDecimals in my code sample because I thought it would give me more precision - is that the wrong approach? My ultimate goal is to be able to calculate the rectangular position of a point on an ellipse at a specific angle.
Thanks in advance.
You see this error because Ellipse2D is approximated by four cubic curves. To make sure just take a look at its path iterator defining shape border: http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/awt/geom/EllipseIterator.java#187
To improve quality we should approximate ellipse by higher number of cubic curves. Here is an extention of standard java implementation with changeable number of segments:
class BetterEllipse extends Ellipse2D.Double {
private int segments;
public BetterEllipse(int segments, double x, double y, double w, double h) {
super(x, y, w, h);
this.segments = segments;
}
public int getSegments() {
return segments;
}
#Override
public PathIterator getPathIterator(final AffineTransform affine) {
return new PathIterator() {
private int index = 0;
#Override
public void next() {
index++;
}
#Override
public int getWindingRule() {
return WIND_NON_ZERO;
}
#Override
public boolean isDone() {
return index > getSegments() + 1;
}
#Override
public int currentSegment(double[] coords) {
int count = getSegments();
if (index > count)
return SEG_CLOSE;
BetterEllipse ellipse = BetterEllipse.this;
double x = ellipse.getCenterX() + Math.sin(2 * Math.PI * index / count) * ellipse.getWidth() / 2;
double y = ellipse.getCenterY() + Math.cos(2 * Math.PI * index / count) * ellipse.getHeight() / 2;
if (index == 0) {
coords[0] = x;
coords[1] = y;
if (affine != null)
affine.transform(coords, 0, coords, 0, 1);
return SEG_MOVETO;
}
double x0 = ellipse.getCenterX() + Math.sin(2 * Math.PI * (index - 2) / count) * ellipse.getWidth() / 2;
double y0 = ellipse.getCenterY() + Math.cos(2 * Math.PI * (index - 2) / count) * ellipse.getHeight() / 2;
double x1 = ellipse.getCenterX() + Math.sin(2 * Math.PI * (index - 1) / count) * ellipse.getWidth() / 2;
double y1 = ellipse.getCenterY() + Math.cos(2 * Math.PI * (index - 1) / count) * ellipse.getHeight() / 2;
double x2 = x;
double y2 = y;
double x3 = ellipse.getCenterX() + Math.sin(2 * Math.PI * (index + 1) / count) * ellipse.getWidth() / 2;
double y3 = ellipse.getCenterY() + Math.cos(2 * Math.PI * (index + 1) / count) * ellipse.getHeight() / 2;
double x1ctrl = x1 + (x2 - x0) / 6;
double y1ctrl = y1 + (y2 - y0) / 6;
double x2ctrl = x2 + (x1 - x3) / 6;
double y2ctrl = y2 + (y1 - y3) / 6;
coords[0] = x1ctrl;
coords[1] = y1ctrl;
coords[2] = x2ctrl;
coords[3] = y2ctrl;
coords[4] = x2;
coords[5] = y2;
if (affine != null)
affine.transform(coords, 0, coords, 0, 3);
return SEG_CUBICTO;
}
#Override
public int currentSegment(float[] coords) {
double[] temp = new double[6];
int ret = currentSegment(temp);
for (int i = 0; i < coords.length; i++)
coords[i] = (float)temp[i];
return ret;
}
};
}
}
And here is how you can use it in your code instead of standard one (I use 100 segments here):
g2d.draw(new BetterEllipse(100, circleCenter[0].subtract(circleRadius).doubleValue(),
circleCenter[1].subtract(circleRadius).doubleValue(), circleRadius.multiply(new BigDecimal(2)).doubleValue(),
circleRadius.multiply(new BigDecimal(2)).doubleValue()));
I'm trying to draw a number of points on a circle such that each point is the same distance from the next. Eventually I'll be drawing lines between all of the points to make a little drawing. The current hurdle I am dealing with is the points don't come out on the circle uniformly. The way I am figuring the points is, for a given point with angle theta, the X coordinate and Y coordinate are calculated as follows
xc = radius * (Math.cos(theta)) + horizontal center
yc = radius * (Math.sin(theta)) + vertical center
I also have an extra variable so I can make a spinning animation. Here is the code:
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.util.Random;
import java.awt.event.*;
import java.lang.*;
import java.awt.geom.Line2D;
import java.awt.geom.Ellipse2D;
class JavaPaintUI extends JPanel{
int x,y,rad,i;
static Random r = new Random();
BufferedImage image;
Graphics2D g2d;
Timer timer;
double vertices = 24.0;
double angle = 0.0;
double delta = 0.01;
double [] xs = new double[24];
double [] ys = new double[24];
JavaPaintUI(){
image = new BufferedImage(600, 600, BufferedImage.TYPE_INT_ARGB);
g2d = (Graphics2D)image.getGraphics();
setBackground(Color.black);
g2d.setColor(Color.white);
i=0;
ActionListener listener = new ActionListener() {
public void actionPerformed(ActionEvent ae) {
angle += delta;
angle = angle % 360.0;
iterate();
}
};
timer = new Timer(100, listener);
timer.start();
}
public void iterate(){
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, 600, 600);
g2d.setColor(Color.WHITE);
for(double i = 0; i < vertices; i++){
xs[(int)i] = 250.0 * (Math.cos(((2*Math.PI)/360) * (angle+(i*(360.0/vertices))))) + 300.0;
ys[(int)i] = 250.0 * (Math.sin(((2*Math.PI)/360) * (angle+(i*(360.0/vertices))))) + 300.0;
}
for(double i = 0; i < vertices; i++){
g2d.draw(new Ellipse2D.Double(xs[(int)i], ys[(int)i], 8.0, 8.0));
}
/*
for(double i = 0; i < vertices; i++){
for(double j = 0; j < vertices; j++){
g2d.draw(new Line2D.Double(xs[(int)i],ys[(int)i],xs[(int)j],ys[(int)j]));
}
} */
repaint();
if (i==1000){timer.stop();}
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawImage(image,0,0,null);
}
}
I used code from another stack overflow post.Here is a picture of what the circle looks like as of now.
As you can see, there are some extra space between some of the points. Does anyone know what causes this?
EDIT
Thanks guys! It looks much better!
I believe the trig functions in Math take their parameters in radians, not degrees. So you need to do 2 * Math.PI instead of 360.0.
Math.sin() and Math.cos() take an angle in radians and you are providing angles in degrees.
A circle has 360 degrees.
So you need an
int numSteps = 32;
double angleStep = 360 / numStpes; // You also can do that with 2*Math.PI / numSteps
for (int i = 0; i < numSteps; i++) {
double angle = i * anglStep;
// now use the formula with x0 + r * sin(angle * toRadians);
}
I have a diagonal line and I have also a circles having a 100 meters in distance. The problem is that the circles are not really to the center of the line. I know this is quiet easy but I'm just confused on how to do it.. Could someone help me how to put the circles at the center of the line?
Here's what I've tried so far :
public void paint(Graphics g)
{
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setBackground(Color.white);
int x0_pixel = 0;
int y0_pixel = 0;
int x1_pixel = getWidth();
int y1_pixel = getHeight();
int x0_world = 0;
int y0_world = 0;
double x1_world = 2000; // meters
double y1_world = 1125; // meters
double x_ratio = (double) x1_pixel / x1_world;
double y_ratio = (double) y1_pixel / y1_world;
int xFrom = 0;
int yFrom = 0;
double xTo = x1_world;
double yTo = y1_world;
int FromX_pixel = convertToPixelX(xFrom, x_ratio);
int FromY_pixel = convertToPixelY(y1_pixel, yFrom, y_ratio);
int ToX_pixel = convertToPixelX((int) xTo, x_ratio);
int ToY_pixel = convertToPixelY(y1_pixel, (int) yTo, y_ratio);
g2d.setColor(Color.RED);
g2d.drawLine(FromX_pixel, FromY_pixel, ToX_pixel, ToY_pixel);
double theta = Math.atan(yTo / xTo);
int len = (int) Math.sqrt(xTo * xTo + yTo * yTo);
int interval = 100;
final double cosTheta = Math.cos(theta);
final double sinTheta = Math.sin(theta);
for (int distance = xFrom; distance <= len; distance += interval)
{
double distance_x = distance * cosTheta;
double distance_y = distance * sinTheta;
int x_circle_pixel = convertToPixelX(distance_x, x_ratio);
int y_circle_pixel = convertToPixelY(y1_pixel, distance_y, y_ratio);
g2d.drawOval(x_circle_pixel, y_circle_pixel, 50, 50);
g2d.setColor(Color.BLUE);
}
Toolkit.getDefaultToolkit().
sync();
g2d.dispose();
}
private static int convertToPixelY(int y_offset, double y_world, double y_ratio)
{
return (int) (y_offset - (y_world * y_ratio));
}
private static int convertToPixelX(double x_world, double x_ratio)
{
return (int) (x_world * x_ratio);
}
When you draw an oval, the first two parameters are the upper-left corner of the rectangle that holds the oval. The next two parameters are the width and height of this same bounding rectangle. Your current code places the upper-left corner on the line itself, but what you actually want is that the center of the bounding rectangle be placed on the line. The solution to your problem is to simply shift the upper-left corner over by 1/2 the diameter. Your code should have something like so:
public class GraphicsFoo extends JPanel {
// avoid using magic numbers:
private static final int CIRCLE_DIAMETER = 50;
//....
// override a JComponent's paintComponent method, not its paint method
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setBackground(Color.white);
// make your graphics smooth
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// ...
final double cosTheta = Math.cos(theta);
final double sinTheta = Math.sin(theta);
for (int distance = xFrom; distance <= len; distance += interval)
{
//....
// *** here's the key: ***
g2d.drawOval(
x_circle_pixel - CIRCLE_DIAMETER / 2,
y_circle_pixel - CIRCLE_DIAMETER / 2,
CIRCLE_DIAMETER, CIRCLE_DIAMETER);
g2d.setColor(Color.BLUE);
}