I want to draw a piechart with a triangle in the middle of the piechart slice. At the moment I draw a piechat with slices and triangles in the middle of the slices, but the triangles are not in the right angle. I need to know how to position the triangles in the right way. My Code and the result:
import java.awt.*;
import java.awt.geom.Ellipse2D;
import javax.swing.*;
class Slice {
double value;
Color color;
public Slice(double value, Color color) {
this.value = value;
this.color = color;
}
}
class PieChart extends JPanel {
private Color a = Color.RED;
private Color b = Color.BLUE;
private Color c = Color.YELLOW;
Slice[] slices = {
new Slice(60, a),
new Slice(100, b),
new Slice(200, c)
};
public PieChart(){
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
super.paintComponent(g2d);
this.setBackground(new Color(255,255,255));
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
double total = 0.0D;
for (int i = 0; i < slices.length; i++) {
total += slices[i].value;
}
double curValue = 90.0D;
int startAngle = 0;
for (int i = 0; i < slices.length; i++) {
startAngle = (int) (curValue * 360 / total);
int arcAngle = (int) (slices[i].value * 360 / total);
g2d.setColor(slices[i].color);
g2d.fillArc(20, 20, 200, 200, startAngle, arcAngle);
g2d.setPaint(Color.BLACK);
int x = (int)(110+100*Math.cos(((-(startAngle+(arcAngle/2)))*Math.PI)/180));
int y = (int)(110+100*Math.sin(((-(startAngle+(arcAngle/2)))*Math.PI)/180));
Polygon p = new Polygon(new int[] {x, x+14, x+7}, new int[] {y, y, y-14}, 3); // this values seems to be important
g2d.draw(p);
g2d.fill(p);
curValue += slices[i].value;
}
}
}
Edit: should look like this:
I made the first arc to start from 0 o'clock (I think you meant to do this).
Since you are using fillArc
which takes int
s, the rounded down double
s might not add up to the full amount and you will have gaps between the slices:
Instead, use Arc2D.Double
to get better precision:
class Slice {
double value;
Color color;
public Slice(double value, Color color) {
this.value = value;
this.color = color;
}
public Color getColor() {
return color;
}
public double getValue() {
return value;
}
}
class PieChart extends JPanel {
private final int SIZE = 500, START = 40, START_DEG = 90;
private final int TRIG_HBASE = 66, TRIG_HEIGHT = 36;
private final int x0 =(START + SIZE / 2), y0 = START;
private final Polygon poly;
private Color a = Color.RED;
private Color b = Color.BLUE;
private Color c = Color.YELLOW;
Slice[] slices = {new Slice(65, a), new Slice(123, b), new Slice(212, c)};
PieChart() {
setBackground(Color.WHITE);
int x1 = x0 + TRIG_HBASE, y1 = y0;
int x2 = x0 - TRIG_HBASE, y2 = y0;
int x3 = x0, y3 = y0 - TRIG_HEIGHT;
poly = new Polygon(new int[] {x1, x2, x3}, new int[] {y1, y2, y3}, 3);
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
super.paintComponent(g2d);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.LIGHT_GRAY);
g2d.fillRect(START, START, SIZE, SIZE);
double total = 0d;
for (Slice slice : slices) {
total += slice.getValue();
}
double startAngle = START_DEG;
double arcAngle, centerAngle;
double x, y;
for (Slice slice : slices) {
arcAngle = (slice.getValue() * 360 / total);
g2d.setColor(slice.getColor());
g2d.fill(new Arc2D.Double(START, START, SIZE, SIZE, startAngle, arcAngle, Arc2D.PIE));
centerAngle = Math.toRadians(((startAngle - START_DEG) + arcAngle / 2));
x = (START + SIZE / 2 * (1 - Math.sin(centerAngle)));
y = (START + SIZE / 2 * (1 - Math.cos(centerAngle)));
AffineTransform trans = AffineTransform.getTranslateInstance(x - x0, y - y0);
AffineTransform rot = AffineTransform.getRotateInstance(-centerAngle, x, y);
Shape s = trans.createTransformedShape(poly);
s = rot.createTransformedShape(s);
g2d.setColor(slice.getColor().darker());
g2d.fill(s);
startAngle += arcAngle;
}
}
@Override
public Dimension getPreferredSize() {
return new Dimension(START * 2 + SIZE, START * 2 + SIZE);
}
}
poly
serves as the basic triangle and is facing upwards with its base centered on the 0 o'clock point. Each arc translates and transforms (a copy of) this polygon so that its base is centered at the center of the arc length and so that it points outwards.
Notes:
setBackground
inside paintComponent
, call it outside. It causes the paint mechanism to automatically paint the background on each repaint. If you put it inside you are just overriding the instruction with every repaint. Alternatively, you can use g.clearRect
to set the background to white (or fillRect
for a different color).getPreferredSize
method to be compatible with its contents.final
) instead of inline numbers. This way you only need to change them in one place and all dependencies are accounted for.for each
loops.double
s and only convert to int
at the latest point, otherwise you lose precision (you convert your angles to int
and then use them as a double
argument).Math.toRadians
and Math.toDegrees
are worth being acquainted with.TRIG
constants to play with their sizes. I also colored them to know which triangle belongs to which arc.Here is the result with your parameters (and no special coloring):
private final int SIZE = 200, START = 20, START_DEG = 90;
private final int TRIG_HBASE = 7, TRIG_HEIGHT = 14;