在饼图切片中间绘制带三角形的饼图
Draw PieChart with triangle in the middle of PieChart slice
我想画一个饼图,饼图切片中间有一个三角形。
此刻我在切片中间画了一个带有切片和三角形的piechat,但是三角形不在直角。我需要知道如何以正确的方式放置三角形。我的代码和结果:
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;
}
}
}
编辑:应该是这样的:
这里我做了一个解决方案(稍微改变一下算法):
public class Chart {
public static void main(String[] args) {
JFrame frame= new JFrame();
frame.add(new PieChart());
frame.setSize(new Dimension(400, 400));
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
EventQueue.invokeLater( () ->{
frame.setVisible(true);
});
}
public static class Slice {
double value;
Color color;
public Slice(double value, Color color) {
this.value = value;
this.color = color;
}
}
public static class PieChart extends JPanel {
private static final long serialVersionUID = 1L;
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(Color.WHITE);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
int upperLeftX = 20;
int upperLeftY = 20;
int r = 100;
int startAngle = 0;
double curValue = 0.0;
for (int i = 0; i < slices.length; i++) {
startAngle = (int) curValue ;
int arcAngle = (int) slices[i].value ;
g2d.setColor(slices[i].color);
g2d.fillArc(upperLeftX, upperLeftY, r*2, r*2, startAngle, arcAngle);
g2d.setPaint(Color.BLACK);
double qi = curValue+slices[i].value/2;
int x = upperLeftX + r + (int)(Math.cos(qi*Math.PI/180)*r);
int y = upperLeftY + r - (int)(Math.sin(qi*Math.PI/180)*r);
//point touching the circle (x,y)->half point of the base
int x1 = x - (int)(7*Math.sin(qi*Math.PI/180));
int y1 = y - (int)(7*Math.cos(qi*Math.PI/180));
int x2 = x + (int)(7*Math.sin(qi*Math.PI/180));
int y2 = y + (int)(7*Math.cos(qi*Math.PI/180));
int x3 = upperLeftX + r + (int)(Math.cos(qi*Math.PI/180)*(r+12));
int y3 = upperLeftY + r - (int)(Math.sin(qi*Math.PI/180)*(r+12));
Polygon p = new Polygon(new int[] { x1, x2, x3 },
new int[] { y1, y2, y3 }, 3); // this values seems to
// be important
g2d.draw(p);
g2d.fill(p);
curValue += slices[i].value;
}
}
}
}
我制作了从 0 点钟开始的第一个弧线(我想你是故意这样做的)。
由于您使用的 fillArc
需要 int
s,向下舍入的 double
s 可能不会加起来等于全部数量,并且切片之间会有间隙:
相反,使用 Arc2D.Double
以获得更好的精度:
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
作为基本三角形,底面以0点为中心,面朝上。每条弧线平移和变换此多边形(的副本),使其底边以弧长的中心为中心并指向外。
备注:
- 不要在
paintComponent
里面调用setBackground
,在外面调用。它会导致绘制机制在每次重绘时自动绘制背景。如果你把它放在里面,你只是在每次重绘时覆盖指令。或者,您可以使用 g.clearRect
将背景设置为白色(或 fillRect
设置不同的颜色)。
- 覆盖面板的
getPreferredSize
方法以与其内容兼容。
- 使用常量 (
final
) 而不是行内数字。这样你只需要在一个地方改变它们,所有的依赖都被考虑在内。
- Slice 可以使用 getter 方法(通常优于直接字段访问),并且它还允许
for each
循环。
- 使用
double
s 并且只在最后一点转换为 int
,否则你会失去精度(你将你的角度转换为 int
然后将它们用作 double
参数).
Math.toRadians
和Math.toDegrees
值得结识
- 我将三角形变宽以显示它们如何与圆弧相交,更改
TRIG
常量以适应它们的大小。我还给它们上色以知道哪个三角形属于哪个弧。
- 我为弧线添加了背景,以便更好地查看周长。
这是使用您的参数的结果(没有特殊着色):
private final int SIZE = 200, START = 20, START_DEG = 90;
private final int TRIG_HBASE = 7, TRIG_HEIGHT = 14;
我想画一个饼图,饼图切片中间有一个三角形。 此刻我在切片中间画了一个带有切片和三角形的piechat,但是三角形不在直角。我需要知道如何以正确的方式放置三角形。我的代码和结果:
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;
}
}
}
编辑:应该是这样的:
这里我做了一个解决方案(稍微改变一下算法):
public class Chart {
public static void main(String[] args) {
JFrame frame= new JFrame();
frame.add(new PieChart());
frame.setSize(new Dimension(400, 400));
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
EventQueue.invokeLater( () ->{
frame.setVisible(true);
});
}
public static class Slice {
double value;
Color color;
public Slice(double value, Color color) {
this.value = value;
this.color = color;
}
}
public static class PieChart extends JPanel {
private static final long serialVersionUID = 1L;
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(Color.WHITE);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
int upperLeftX = 20;
int upperLeftY = 20;
int r = 100;
int startAngle = 0;
double curValue = 0.0;
for (int i = 0; i < slices.length; i++) {
startAngle = (int) curValue ;
int arcAngle = (int) slices[i].value ;
g2d.setColor(slices[i].color);
g2d.fillArc(upperLeftX, upperLeftY, r*2, r*2, startAngle, arcAngle);
g2d.setPaint(Color.BLACK);
double qi = curValue+slices[i].value/2;
int x = upperLeftX + r + (int)(Math.cos(qi*Math.PI/180)*r);
int y = upperLeftY + r - (int)(Math.sin(qi*Math.PI/180)*r);
//point touching the circle (x,y)->half point of the base
int x1 = x - (int)(7*Math.sin(qi*Math.PI/180));
int y1 = y - (int)(7*Math.cos(qi*Math.PI/180));
int x2 = x + (int)(7*Math.sin(qi*Math.PI/180));
int y2 = y + (int)(7*Math.cos(qi*Math.PI/180));
int x3 = upperLeftX + r + (int)(Math.cos(qi*Math.PI/180)*(r+12));
int y3 = upperLeftY + r - (int)(Math.sin(qi*Math.PI/180)*(r+12));
Polygon p = new Polygon(new int[] { x1, x2, x3 },
new int[] { y1, y2, y3 }, 3); // this values seems to
// be important
g2d.draw(p);
g2d.fill(p);
curValue += slices[i].value;
}
}
}
}
我制作了从 0 点钟开始的第一个弧线(我想你是故意这样做的)。
由于您使用的 fillArc
需要 int
s,向下舍入的 double
s 可能不会加起来等于全部数量,并且切片之间会有间隙:
相反,使用 Arc2D.Double
以获得更好的精度:
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
作为基本三角形,底面以0点为中心,面朝上。每条弧线平移和变换此多边形(的副本),使其底边以弧长的中心为中心并指向外。
备注:
- 不要在
paintComponent
里面调用setBackground
,在外面调用。它会导致绘制机制在每次重绘时自动绘制背景。如果你把它放在里面,你只是在每次重绘时覆盖指令。或者,您可以使用g.clearRect
将背景设置为白色(或fillRect
设置不同的颜色)。 - 覆盖面板的
getPreferredSize
方法以与其内容兼容。 - 使用常量 (
final
) 而不是行内数字。这样你只需要在一个地方改变它们,所有的依赖都被考虑在内。 - Slice 可以使用 getter 方法(通常优于直接字段访问),并且它还允许
for each
循环。 - 使用
double
s 并且只在最后一点转换为int
,否则你会失去精度(你将你的角度转换为int
然后将它们用作double
参数). Math.toRadians
和Math.toDegrees
值得结识- 我将三角形变宽以显示它们如何与圆弧相交,更改
TRIG
常量以适应它们的大小。我还给它们上色以知道哪个三角形属于哪个弧。 - 我为弧线添加了背景,以便更好地查看周长。
这是使用您的参数的结果(没有特殊着色):
private final int SIZE = 200, START = 20, START_DEG = 90;
private final int TRIG_HBASE = 7, TRIG_HEIGHT = 14;