英文:
drawPolyLine() vs drawLine() when plotting/graphing mathematical functions
问题
for(int o = 0; o < AnzahlanFunktionen; o++) {
g2d.setStroke(new BasicStroke(2));
int Genauigkeit = 1000;
ArrayList<Double> xcoords = new ArrayList<Double>();
ArrayList<Double> ycoords = new ArrayList<Double>();
for(int x3 = (-16 * Genauigkeit); x3 < (16 * Genauigkeit) + 1; x3++) {
double x2 = x3 / (Genauigkeit / 1.0);
xcoords.add(x2);
x.setArgumentValue(x2);
switch(o) {
case 0:
ycoords.add(fx1.getArgumentValue());
y = fx1.getArgumentValue();
g2d.setColor(new Color(200, 35, 35));
g.drawString("f(x) = " + text2.getText(), 60, 100);
break;
case 1:
ycoords.add(fx2.getArgumentValue());
y = fx2.getArgumentValue();
g2d.setColor(new Color(55, 43, 156));
g.drawString("f(x) = " + text.getText(), 60, 120);
break;
case 2:
ycoords.add(fx3.getArgumentValue());
y = fx3.getArgumentValue();
g2d.setColor(new Color(210, 233, 57));
g.drawString("f(x) = " + text3.getText(), 60, 140);
break;
case 3:
ycoords.add(fx4.getArgumentValue());
y = fx4.getArgumentValue();
g2d.setColor(new Color(26, 151, 7));
g.drawString("f(x) = " + text4.getText(), 60, 160);
break;
}
if(!(Double.isNaN(y))) g2d.draw(new Line2D.Double(Main.breite / 2 + x2 * (Main.höhe / 20),
Main.höhe / 2 - y * (Main.höhe / 20), Main.breite / 2 + x2 * (Main.höhe / 20),
Main.höhe / 2 - y * (Main.höhe / 20)));
}
}
英文:
I programmed a small program, that plots a mathematical function and gives the root of the function (Nullstelle/n) and the extreme points (Extrempunkte) and if there are any the discontinuity of a function(but only if the discontinuity is a number less exact than five decimal places). This all works, but the graph itself doesn't look that good. I used Graphics2D.Line2DDouble
for(int o =0; o<AnzahlanFunktionen;o++) {
//Funktionsgraphen
g2d.setStroke(new BasicStroke(2));
int Genauigkeit = 1000;
ArrayList<Double> xcoords = new ArrayList<Double>();
ArrayList<Double> ycoords = new ArrayList<Double>();
for(int x3 = (-16*Genauigkeit); x3<(16*Genauigkeit)+1; x3++) {
//Da x sehr groß ist und somit nicht bei der Zeichnung der Funktion eingesetzt werden kann, verwendete ich x2
double x2 = x3/(Genauigkeit/1.0);
xcoords.add(x2);
x.setArgumentValue(x2);
//y2 ist die Funktion
switch(o) {
case 0:
ycoords.add(fx1.getArgumentValue());
y = fx1.getArgumentValue();
g2d.setColor(new Color(200,35,35));
g.drawString("f(x) = "+text2.getText(),60,100);
break;
case 1:
ycoords.add(fx2.getArgumentValue());
y = fx2.getArgumentValue();
g2d.setColor(new Color(55,43,156));
g.drawString("f(x) = "+text.getText(),60,120);
break;
case 2:
ycoords.add(fx3.getArgumentValue());
y = fx3.getArgumentValue();
g2d.setColor(new Color(210,233,57));
g.drawString("f(x) = "+text3.getText(),60,140);
break;
case 3:
ycoords.add(fx4.getArgumentValue());
y = fx4.getArgumentValue();
g2d.setColor(new Color(26,151,7));
g.drawString("f(x) = "+text4.getText(),60,160);
break;
}
//Diese Zeile zeichnet den Graphen
if(!(Double.isNaN(y))) g2d.draw(new Line2D.Double(Main.breite/2+x2*(Main.höhe/20), Main.höhe/2-y*(Main.höhe/20),Main.breite/2+x2*(Main.höhe/20), Main.höhe/2-y*(Main.höhe/20)));
}
UI:
Plotting of the graphs:
If I used drawPolyLine
, would I get a more detailed and less "bricky" graph? Does it even make a difference? Is there another way to draw the graph, that looks better or is more efficiently?
Entire Code for those interested:
package graphikrechner.com;
import java.awt.*;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class Main extends JFrame {
private static final long serialVersionUID = 4648172894076113183L;
private static Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
public static double breite = screenSize.getWidth();
public static double höhe = screenSize.getHeight();
public Main() {
JFrame f = new JFrame("Graphikrechner by Visar Lumi");
f.setSize(new Dimension((int)breite,(int)höhe));
f.setMaximumSize(new Dimension((int)breite,(int)höhe));
f.setMinimumSize(new Dimension((int)breite,(int)höhe));
f.setPreferredSize(new Dimension((int)breite,(int)höhe));
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setAlwaysOnTop (true);
f.add(new GUI());
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new Main();
}
});
}
}
package graphikrechner.com;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Line2D;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.StringTokenizer;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import org.mariuszgromada.math.mxparser.Argument;
public class GUI extends JPanel implements ActionListener{
private static final long serialVersionUID = 7365905689502130889L;
private JTextField text = new JTextField(20);
private JTextField text2 = new JTextField(20);
private JTextField text3 = new JTextField(20);
private JTextField text4 = new JTextField(20);
private JButton button,button2,plusbutton,minusbutton;
private boolean FalscheEingabe = false;
private int AnzahlanFunktionen = 1;
private double y,z3;
private static String Error,number;
private static BigDecimal a;
private Argument x,fx1,fx2,fx3,fx4;
public static int GUI = 1;
public void createGUI() {
//Fonts
Font BigFont = new Font("Roboto",Font.BOLD,55);
Font RegularFont = new Font("Roboto",Font.BOLD,24);
Font TextFont = new Font("Latin Modern",Font.PLAIN,19);
Icon buttonImage = new ImageIcon(getClass().getResource("button_okay-_1_.jpg"));
Icon plusbuttonImage = new ImageIcon(getClass().getResource("button-_1_.jpeg"));
Icon minusbuttonImage = new ImageIcon(getClass().getResource("button-_2_.jpg"));
setLayout(null);
text.setBounds((int) Main.breite/2 , (int) Main.höhe/2 + 2,240,30);
text.setFont(TextFont);
text2.setBounds((int) Main.breite/2 , (int) Main.höhe/2 + 2 + 35,240,30);
text2.setFont(TextFont);
text3.setBounds((int) Main.breite/2 , (int) Main.höhe/2 + 2 + 35*2,240,30);
text3.setFont(TextFont);
text4.setBounds((int) Main.breite/2 , (int) Main.höhe/2 + 2 + 35*3,240,30);
text4.setFont(TextFont);
switch(AnzahlanFunktionen) {
case 1:
this.add(text);
break;
case 2:
this.add(text);
this.add(text2);
break;
case 3:
this.add(text);
this.add(text2);
this.add(text3);
break;
case 4:
this.add(text);
this.add(text2);
this.add(text3);
this.add(text4);
break;
}
if(AnzahlanFunktionen > 1) {
minusbutton = new JButton(minusbuttonImage);
minusbutton.setBounds((int) Main.breite/2 +245, (int) Main.höhe/2 + 2,40,30);
minusbutton.addActionListener(this);
this.add(minusbutton);
}
if(AnzahlanFunktionen != 4){
plusbutton = new JButton(plusbuttonImage);
plusbutton.setBounds((int) Main.breite/2 +245, (int) Main.höhe/2 + 2 + 35*(AnzahlanFunktionen-1),40,30);
plusbutton.addActionListener(this);
this.add(plusbutton);
}
button = new JButton(buttonImage);
button.setBounds((int) Main.breite/2 + 40 , (int) Main.höhe/2 + 40 + + 2 + 35*(AnzahlanFunktionen-1),167,40);
button.setFocusPainted(false);
button.addActionListener(this);
button.setToolTipText("Funktion eingeben");
this.add(button);
JLabel label = new JLabel("Gib eine Funktion ein:");
label.setFont(RegularFont);
label.setBounds(420, (int) Main.höhe/2 -10 ,500,50);
this.add(label);
JLabel label2 = new JLabel("Graphikrechner");
label2.setFont(BigFont);
label2.setForeground(new Color(60,147,205));
label2.setBounds(475,100,1000,70);
this.add(label2);
if(FalscheEingabe) {
JLabel label3 = new JLabel("Error-Funktion nicht vorhanden oder falsch eingegeben");
label3.setFont(new Font("Roboto",Font.BOLD,15));
label3.setForeground(Color.red);
label3.setBounds(500, (int) Main.höhe - 250 + 32*(AnzahlanFunktionen-1),1000,20);
this.add(label3);
}
}
public void createButton() {
Icon buttonImage2 = new ImageIcon(getClass().getResource("button_go-back_1.jpg"));
setLayout(null);
button2 = new JButton(buttonImage2);
button2.setBounds(0,0,167,48);
button2.addActionListener(this);
this.add(button2);
}
public GUI() {
if(GUI == 1) createGUI();
}
protected void paintComponent(Graphics g) {
if(GUI == 0) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
x = new Argument("x");
fx1 = new Argument("y ="+text.getText(),x);
fx2 = new Argument("y ="+text2.getText(),x);
fx3 = new Argument("y ="+text3.getText(),x);
fx4 = new Argument("y ="+text4.getText(),x);
//Die nächsten Zeile überprüfen ob die eingegebene Funktion richtig geschrieben wurde (z.B 3x => Error => FalscheEingabe=true; 3*x => no errors => FalscheEingabe=>false)
for(int i = 0; i<AnzahlanFunktionen; i++) {
switch(i) {
case 0:
fx1.checkSyntax();
Error = fx1.getErrorMessage();
break;
case 1:
fx2.checkSyntax();
Error = fx2.getErrorMessage();
break;
case 2:
fx3.checkSyntax();
Error = fx3.getErrorMessage();
break;
case 3:
fx4.checkSyntax();
Error = fx4.getErrorMessage();
break;
}
StringTokenizer st = new StringTokenizer(Error);
int counter = 0;
while (st.hasMoreTokens()) {
String token = st.nextToken();
if(counter == 0 && !(token.equals("no"))) {
FalscheEingabe = true;
counter = 1;
}
else if (counter==1 && token.equals("no")) FalscheEingabe = false;
}
if(FalscheEingabe) i=AnzahlanFunktionen;
}
if(FalscheEingabe) {
GUI = 1;
text.setText("");
text2.setText("");
text3.setText("");
text4.setText("");
createGUI();
revalidate();
repaint();
}
if(GUI == 0) {
createButton();
g.setFont(new Font("Monospaced",Font.BOLD,25));
//KoordinatenSystem und Graphen
//x-Achse
Polygon dreieck = new Polygon();
dreieck.addPoint((int)Main.breite/2, 0);
dreieck.addPoint(((int)Main.breite/2)-60,40);
dreieck.addPoint(((int)Main.breite/2)+60,40);
g2d.setColor(Color.black);
g2d.fill(dreieck);
g2d.setStroke(new BasicStroke(4));
g.setColor(Color.black);
g.drawLine((int)Main.breite/2, 42, (int)Main.breite/2,(int) Main.höhe);
//y-Achse
Polygon dreieck2 = new Polygon();
dreieck2.addPoint((int)Main.breite -15,(int)Main.höhe/2);
dreieck2.addPoint((int)Main.breite -60,(int)Main.höhe/2 -60);
dreieck2.addPoint((int)Main.breite -60,(int)Main.höhe/2 +60);
g2d.fill(dreieck2);
g.setColor(Color.black);
g.drawLine(0, (int) Main.höhe/2, (int)Main.breite-42,(int) Main.höhe/2);
//Nummerierung
//x-Achse
int xKoordinaten = 9;
for(int i = (int) (Main.höhe/20); i<(int) (Main.höhe*19/20); i+=(int)Main.höhe/20) {
g2d.setStroke(new BasicStroke(3));
g.drawLine((int)Main.breite/2+10,i+3,(int)Main.breite/2-10,i+3);
g2d.setStroke(new BasicStroke(1));
g.drawLine(0, i+3, (int)Main.breite, i+3);
if(xKoordinaten != 0) {
g.setFont(new Font("Monospaced",Font.BOLD,20));
g.drawString(""+xKoordinaten, (int)Main.breite/2-43, i+10);
}
xKoordinaten -= 1;
}
//y-Achse positiv
int yKoordinaten = 0;
for(int i = (int) Main.breite/2+3; i<(int) Main.breite-40; i+=(int)Main.höhe/20) {
g2d.setStroke(new BasicStroke(3));
g.drawLine(i-3,(int)Main.höhe/2+10,i-3,(int)Main.höhe/2-10);
g2d.setStroke(new BasicStroke(1));
g.drawLine(i-3, 0, i-3, (int)Main.höhe);
if(yKoordinaten != 0) {
g.setFont(new Font("Monospaced",Font.BOLD,20));
g.drawString(""+yKoordinaten, i-7, (int) Main.höhe/2+39);
}
yKoordinaten += 1;
}
//y-Achse negativ
int yminusKoordinaten = 0;
for(int i = (int) Main.breite/2+3; i>40; i-=(int)Main.höhe/20) {
g2d.setStroke(new BasicStroke(3));
g.drawLine(i-3,(int)Main.höhe/2+10,i-3,(int)Main.höhe/2-10);
g2d.setStroke(new BasicStroke(1));
g.drawLine(i-3,0,i-3,(int)Main.höhe);
if(yminusKoordinaten != 0 && yminusKoordinaten != -1) {
g.setFont(new Font("Monospaced",Font.BOLD,20));
g.drawString(""+yminusKoordinaten, i-7, (int) Main.höhe/2+39);
}
yminusKoordinaten -= 1;
}
int counter2 = 1;
//Zeichnung und Nullstellenbestimmungen aller eingegebenen Funktionen
for(int o =0; o<AnzahlanFunktionen;o++) {
//Funktionsgraphen
g2d.setStroke(new BasicStroke(2));
int Genauigkeit = 1000;
ArrayList<Double> xcoords = new ArrayList<Double>();
ArrayList<Double> ycoords = new ArrayList<Double>();
for(int x3 = (-16*Genauigkeit); x3<(16*Genauigkeit)+1; x3++) {
//Da x sehr groß ist und somit nicht bei der Zeichnung der Funktion eingesetzt werden kann, verwendete ich x2
double x2 = x3/(Genauigkeit/1.0);
xcoords.add(x2);
x.setArgumentValue(x2);
//y2 ist die Funktion
switch(o) {
case 0:
ycoords.add(fx1.getArgumentValue());
y = fx1.getArgumentValue();
g2d.setColor(new Color(200,35,35));
g.drawString("f(x) = "+text2.getText(),60,100);
break;
case 1:
ycoords.add(fx2.getArgumentValue());
y = fx2.getArgumentValue();
g2d.setColor(new Color(55,43,156));
g.drawString("f(x) = "+text.getText(),60,120);
break;
case 2:
ycoords.add(fx3.getArgumentValue());
y = fx3.getArgumentValue();
g2d.setColor(new Color(210,233,57));
g.drawString("f(x) = "+text3.getText(),60,140);
break;
case 3:
ycoords.add(fx4.getArgumentValue());
y = fx4.getArgumentValue();
g2d.setColor(new Color(26,151,7));
g.drawString("f(x) = "+text4.getText(),60,160);
break;
}
//Diese Zeile zeichnet den Graphen
if(!(Double.isNaN(y))) g2d.draw(new Line2D.Double(Main.breite/2+x2*(Main.höhe/20), Main.höhe/2-y*(Main.höhe/20),Main.breite/2+x2*(Main.höhe/20), Main.höhe/2-y*(Main.höhe/20)));
}
//Nullstellenbestimmung
int phase = 0;
double z4 = 0;
g.setFont(new Font("Monospaced",Font.BOLD,20));
for(int i=0; i<ycoords.size(); i++) {
if(i<(ycoords.size()-1)) {
double z = Math.abs(ycoords.get(i+1));
double z2 = Math.abs(ycoords.get(i));
//Die nächsten paar Zeile sorgen dafür, dass er bei rationalen Funktionen die richtigen Nst oder Extrempunkte erkennt
if(i>1) z4 = Math.abs(ycoords.get(i-1));
if(Double.isNaN(z4)) {
if(!(Double.isInfinite(z2)) && !(Double.isNaN(z2))) {
if(phase == 0) phase = 1;
else phase = 0;
g.drawString("Definitionslücke bei:"+ xcoords.get(i-1),800,counter2*50);
counter2++;
}
}
if(phase == 0 && !(Double.isNaN(z)) && !(Double.isNaN(z2)) && !(Double.isInfinite(z)) && !(Double.isInfinite(z2))) {
if(z>z2) {
if(i==0) {
//Damit er nicht bei exponentiallen Funktionen denkt, dass diese Nullstellen besitzen
x.setArgumentValue(-1*((ycoords.size()/2)+1));
switch(o) {
case 0:
fx1.getArgumentValue();
break;
case 1:
fx2.getArgumentValue();
break;
case 2:
fx3.getArgumentValue();
break;
case 3:
fx4.getArgumentValue();
break;
}
if(z3>z2) {
//Er findet die Anzahl an nachkommastellen heraus
number = Double.toString(z2);
a = new BigDecimal(number);
//Und rundet den Wert je nach Anzahl ab und wenn dieser 0 entspricht => Nullstelle sonst => Extrempunkt
if((Math.round(z2*10*a.scale())/(10*a.scale()/1.0)) == 0) {
g.drawString("Nullstelle bei: ("+xcoords.get(i)+"/"+Math.round(z2*10*a.scale())/(10*a.scale()/1.0)+")",800,counter2*50);
counter2++;
}
else {
if(!(Double.isNaN(z4))) {
g.drawString("Extrempunkt bei: ("+xcoords.get(i)+"/"+Math.round(z2*10*a.scale())/(10*a.scale()/1.0)+")",800,counter2*50);
counter2++;
}
}
}
}
else {
//Er findet die Anzahl an nachkommastellen heraus
number = Double.toString(z2);
a = new BigDecimal(number);
//Und rundet den Wert je nach Anzahl ab und wenn dieser 0 entspricht => Nullstelle sonst => Extrempunkt
if((Math.round(z2*10*a.scale())/(10*a.scale()/1.0)) == 0) {
g.drawString("Nullstelle bei: ("+xcoords.get(i)+"/"+Math.round(z2*10*a.scale())/(10*a.scale()/1.0)+")",800,counter2*50);
counter2++;
}
else {
if(!(Double.isNaN(z4))) {
g.drawString("Extrempunkt bei: ("+xcoords.get(i)+"/"+Math.round(z2*10*a.scale())/(10*a.scale()/1.0)+")",800,counter2*50);
counter2++;
}
}
}
phase = 1;
}
}
else if(phase == 1 && !(Double.isNaN(z)) && !(Double.isNaN(z2)) && !(Double.isInfinite(z)) && !(Double.isInfinite(z2))){
if(z < z2 ) {
phase = 0;
//Er findet die Anzahl an nachkommastellen heraus
number = Double.toString(z2);
a = new BigDecimal(number);
//Und rundet den Wert je nach Anzahl ab und wenn dieser 0 entspricht => Nullstelle sonst => Extrempunkt
if((Math.round(z2*10*a.scale())/(10*a.scale()/1.0)) == 0) {
g.drawString("Nullstelle bei: ("+xcoords.get(i)+"/"+Math.round(z2*10*a.scale())/(10*a.scale()/1.0)+")",800,counter2*50);
counter2++;
}
else {
if(!(Double.isNaN(z4))) {
g.drawString("Extrempunkt bei: ("+xcoords.get(i)+"/"+Math.round(z2*10*a.scale())/(10*a.scale()/1.0)+")",800,counter2*50);
counter2++;
}
}
}
}
}
}
}
}
}
else if(GUI == 1) {
g.setColor(Color.white);
g.fillRect(0,0,(int)Main.breite,(int)Main.höhe);
}
}
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource() == button) {
GUI = 0;
removeAll();
revalidate();
repaint();
}
else if(e.getSource() == button2) {
removeAll();
GUI = 1;
text.setText("");
text2.setText("");
createGUI();
revalidate();
repaint();
}
else if(e.getSource() == plusbutton && AnzahlanFunktionen != 4) {
removeAll();
AnzahlanFunktionen++;
createGUI();
revalidate();
repaint();
}
else if(e.getSource() == minusbutton) {
removeAll();
AnzahlanFunktionen--;
createGUI();
revalidate();
repaint();
}
}
}
If you want to try it out: https://www.transfernow.net/pQO5J9062020
答案1
得分: 1
你的问题在于你使用了g.draw(new Line(...)
来绘制图形,其中线的起始点和结束点是相同的。这会导致不良的行为。相反,你应该在图形的最后一个点和当前点之间绘制线条。实现这一点最简单的方法是使用Path2D
。Path2D
本质上是Line2D
的更通用版本。首先,你使用moveTo()
将路径移动到初始位置,然后每次调用lineTo()
都会从当前位置添加一条线到新指定的位置,并将当前位置更新为新位置。与单独绘制每条线(这还需要手动记住上一个点)不同,Path2D
可以执行一些绘制优化,以减少线条连接处的锐利边缘。
此外,设置g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON)
会告诉Swing对其绘制的内容进行“平滑处理”。参见抗锯齿。
以下是绘制正弦曲线的示例实现。注意如何在“屏幕空间”和“坐标轴空间”之间进行坐标转换。屏幕空间表示面板(屏幕)中的坐标。然而,坐标轴空间表示与您想要可视化的坐标轴相关的坐标。通过这样做,您的函数将始终在相同的范围内,无论窗口有多大。例如,在这里,可见的坐标轴将始终从x=-2*pi
到x=2*pi
,从y=-1.5
到y=1.5
。
screenToAxis
和axisToScreen
可以更轻松地在屏幕大小和实际要显示的坐标轴部分之间进行抽象。根据要绘制的函数,这些边界可能会有所不同。通过抽象,您只需要调整轴的大小和偏移量,而无需重写绘制方法。
请注意,您的代码已经做了类似的事情,只是使用了非常硬编码的值,并且缺乏清晰的操作说明。在计算x2
因为"x3 is too large"
时,实际上正在做相同的事情,但将屏幕转换为轴的计算与精度计算混合在一起。当您从x2,y
回到绘制在屏幕上的点时,同样的事情也适用。
根据您尝试绘制的图形,您可能需要调整precisionFactor
的值。随着图形的导数变大,x轴上紧邻点的y坐标变得更远,这最终会导致在点之间绘制的线条明显可见。尽管precisionFactor = 1
的因子对于大多数曲线都可以很好地工作。
以下是绘制正弦曲线的示例实现:
class SineCurve extends JPanel {
// ...(此处省略main方法等部分)
private final double precisionFactor = 1;
private final double axisWidth = Math.PI * 4;
private final double axisHeight = 3;
private final double axisShiftX = -axisWidth / 2;
private final double axisShiftY = -axisHeight / 2;
public SineCurve() {
setPreferredSize(new Dimension(500, 500));
}
@Override
protected void paintComponent(final Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int width = getWidth();
int height = getHeight();
g2d.setStroke(new BasicStroke(4));
g2d.setColor(Color.black);
g2d.drawLine(width / 2, 0, width / 2, height);
g2d.setColor(Color.black);
g2d.drawLine(0, height / 2, width, height / 2);
double precision = precisionFactor * width;
double stepSize = width / precision;
double xCurrent = 0;
double yCurrent = axisToScreen(Math.sin(-axisWidth / 2), height, axisHeight, axisShiftY);
Path2D path = new Path2D.Double(Path2D.WIND_EVEN_ODD, (int) (width / stepSize));
path.moveTo(xCurrent, yCurrent);
while (xCurrent <= width) {
xCurrent += stepSize;
double xInAxisSpace = screenToAxis(xCurrent, width, axisWidth, axisShiftX);
double yInAxisSpace = Math.sin(xInAxisSpace);
yCurrent = axisToScreen(yInAxisSpace, height, axisHeight, axisShiftY);
path.lineTo(xCurrent, yCurrent);
}
g2d.setStroke(new BasicStroke(2));
g2d.setColor(Color.RED);
g2d.draw(path);
}
private double screenToAxis(double valueInScreenSpace, double screenSize, double axisSize, double axisShift) {
return (valueInScreenSpace / screenSize) * axisSize + axisShift;
}
private double axisToScreen(double valueInAxisSpace, double screenSize, double axisSize, double axisShift) {
return screenSize * ((valueInAxisSpace - axisShift) / axisSize);
}
}
注意:上述内容只是你原始内容的翻译,不包含任何额外信息或回答。
英文:
Your problem is that you are drawing the graph using g.draw(new Line(...)
where the start and end point of the line are the same. This will result in undesired behaviour.
Instead you should paint lines between the last point of the graph and the current point of the graph. The easiest way to achieve this is by using a Path2D
. Path2D
is essentially a more general version of Line2D
. First you move the path to its initial position using moveTo()
and then each call of lineTo()
will add a line from the current position to the new specified position and updates the current position to the new one. In contrast to drawing each line by itself (which also requires to manually remember the last point) Path2D
may do some paint optimisations to reduce sharp edges where the lines join.
Additionally setting g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON)
will tell swing to "smooth out" the stuff it is drawing. See Antialiasing.
Here is a sample implementation of how to draw a sine curve. Notice how coordinates are translated from "screen space" to "axis space" and back. The screen space represent coordinates in the panel (the screen). Axis space however represents the coordinate relative to the coordinate axis you want to visualise. By doing this your function will always be in the same range regardless of how big your window is. For example here the visible coordinate axis will always go from x=-2*pi
to x=2*pi
and y=-1.5
to y=1.5
.
screenToAxis
and axisToScreen
allow for easier abstractions between your screen size and what part of the coordinate axis you actually want to display.
Depending on the function you want to plot these bounds may vary. By abstracting you only need to adjust the axis sizes and offsets without having to rewrite the paint method.
Note that your code already does something similar only with very hard coded values und missing clarity of what is happening. The part where you are calculating x2
because "x3 is too large"
is exactly doing the same thing but mixing the screen to axis calculation with the precision calculation into one. The same thing applies for when you do the transformation back from x2,y
to the point you are drawing on the screen.
Depending on the graph you are trying to paint you might need to adjust the value of precisionFactor
. As the derivative of the graph gets larger the y-coordinate of close by points on the x-axis become farther way, which eventually will lead to the lines drawn between the points be clearly visible as lines. Although a factor of precisionFactor = 1
will work fine for most curves.
class SineCurve extends JPanel {
public static void main(final String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setContentPane(new SineCurve());
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
});
}
private final double precisionFactor = 1;
private final double axisWidth = Math.PI * 4;
private final double axisHeight = 3;
private final double axisShiftX = -axisWidth / 2;
private final double axisShiftY = -axisHeight / 2;
public SineCurve() {
setPreferredSize(new Dimension(500, 500));
}
@Override
protected void paintComponent(final Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int width = getWidth();
int height = getHeight();
g2d.setStroke(new BasicStroke(4));
g2d.setColor(Color.black);
g2d.drawLine(width / 2, 0, width / 2, height);
g2d.setColor(Color.black);
g2d.drawLine(0, height / 2, width, height / 2);
double precision = precisionFactor * width;
double stepSize = width / precision;
double xCurrent = 0;
double yCurrent = axisToScreen(Math.sin(-axisWidth / 2), height, axisHeight, axisShiftY);
Path2D path = new Path2D.Double(Path2D.WIND_EVEN_ODD, (int) (width / stepSize));
path.moveTo(xCurrent, yCurrent);
while (xCurrent <= width) {
xCurrent += stepSize;
double xInAxisSpace = screenToAxis(xCurrent, width, axisWidth, axisShiftX);
double yInAxisSpace = Math.sin(xInAxisSpace);
yCurrent = axisToScreen(yInAxisSpace, height, axisHeight, axisShiftY);
path.lineTo(xCurrent, yCurrent);
}
g2d.setStroke(new BasicStroke(2));
g2d.setColor(Color.RED);
g2d.draw(path);
}
private double screenToAxis(double valueInScreenSpace, double screenSize, double axisSize, double axisShift) {
return (valueInScreenSpace / screenSize) * axisSize + axisShift;
}
private double axisToScreen(double valueInAxisSpace, double screenSize, double axisSize, double axisShift) {
return screenSize * ((valueInAxisSpace - axisShift) / axisSize);
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论