Java中用于数据和文本的旋转坐标平面

7
我需要做以下事情:
1.) 移动坐标系原点并旋转,使得x值向右,y值向上从新的原点开始 (新的原点需要是内部蓝色矩形的左下角)。这将使我能够在下面给出的代码中绘制x,y坐标对应的点。
2.) 在数据图y轴上绘制旋转后的刻度标签。
下面的代码设置了这个问题。它可以工作,但存在两个问题:
1.) 数据点以左上角为原点,并且y值向下降序排列
2.) y轴刻度标签没有显示在屏幕上
有没有人能够展示如何修复下面的代码,以解决这两个问题,并实现上面第一段所述的内容?
以下是相关的两个Java文件:
DataGUI.java
import java.awt.*;
import java.util.ArrayList;
import javax.swing.*;

class DataGUI extends JFrame{
DataGUI() {
    super("X,Y Plot");
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.setPreferredSize(new Dimension(800, 400));
    this.pack();
    this.setSize(new Dimension(800, 600));
    this.setLocationRelativeTo(null);


    setLayout(new GridLayout());
    ArrayList<Double> myDiffs = new ArrayList<Double>();
            myDiffs.add(25.0);
            myDiffs.add(9.0);
            myDiffs.add(7.0);
            myDiffs.add(16.0);
            myDiffs.add(15.0);
            myDiffs.add(6.0);
            myDiffs.add(2.0);
            myDiffs.add(8.0);
            myDiffs.add(2.0);
            myDiffs.add(27.0);
            myDiffs.add(14.0);
            myDiffs.add(12.0);
            myDiffs.add(19.0);
            myDiffs.add(10.0);
            myDiffs.add(11.0);
            myDiffs.add(8.0);
            myDiffs.add(19.0);
            myDiffs.add(2.0);
            myDiffs.add(16.0);
            myDiffs.add(5.0);
            myDiffs.add(18.0);
            myDiffs.add(23.0);
            myDiffs.add(9.0);
            myDiffs.add(4.0);
            myDiffs.add(8.0);
            myDiffs.add(9.0);
            myDiffs.add(3.0);
            myDiffs.add(3.0);
            myDiffs.add(9.0);
            myDiffs.add(13.0);
            myDiffs.add(17.0);
            myDiffs.add(7.0);
            myDiffs.add(0.0);
            myDiffs.add(2.0);
            myDiffs.add(3.0);
            myDiffs.add(33.0);
            myDiffs.add(23.0);
            myDiffs.add(26.0);
            myDiffs.add(12.0);
            myDiffs.add(12.0);
            myDiffs.add(19.0);
            myDiffs.add(14.0);
            myDiffs.add(9.0);
            myDiffs.add(26.0);
            myDiffs.add(24.0);
            myDiffs.add(13.0);
            myDiffs.add(19.0);
            myDiffs.add(2.0);
            myDiffs.add(7.0);
            myDiffs.add(28.0);
            myDiffs.add(15.0);
            myDiffs.add(2.0);
            myDiffs.add(5.0);
            myDiffs.add(17.0);
            myDiffs.add(2.0);
            myDiffs.add(16.0);
            myDiffs.add(19.0);
            myDiffs.add(2.0);
            myDiffs.add(31.0);
    DataPanel myPP = new DataPanel(myDiffs,this.getHeight(),this.getWidth());
    this.add(myPP);
    this.setVisible(true);// Display the panel.
}
public static void main(String[] args){
    DataGUI myDataGUI = new DataGUI();
    myDataGUI.setVisible(true);
}
}

DataPanel.java (注意:我编辑了下面的代码,包括trashgod的建议,但仍然不能正常工作。)

DataPanel.java 是一个Java类,它与数据面板相关联。尽管已经进行了编辑和优化,但它仍然无法正常工作。
import java.awt.*;
import java.awt.geom.AffineTransform;
import javax.swing.*;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.*;

class DataPanel extends JPanel {
Insets ins; // holds the panel's insets
ArrayList<Double> myDiffs;
double maxDiff = Double.NEGATIVE_INFINITY;
double minDiff = Double.POSITIVE_INFINITY;
double maxPlot;

DataPanel(ArrayList<Double> Diffs, int h, int w){
    setOpaque(true);// Ensure that panel is opaque.
    setPreferredSize(new Dimension(w, h));
    setMinimumSize(new Dimension(w, h));
    setMaximumSize(new Dimension(w, h));
    myDiffs = Diffs;
    repaint();
    this.setVisible(true);
}

protected void paintComponent(Graphics g){// Override paintComponent() method.
    super.paintComponent(g);
    //get data about plotting environment and about text
    int height = getHeight();
    int width = getWidth();
    ins = getInsets();
    Graphics2D g2d = (Graphics2D)g;
    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();
    String titleString ="Title of Graphic";
    int titleStrWidth = fontMetrics.stringWidth(titleString);
    int titleStrHeight = fontMetrics.getHeight();
    int leftMargin = ins.left;
    //set parameters for inner rectangle
    int hPad=10;
    int vPad = 6;
    int testLeftStartPlotWindow = ins.left+5+(3*yStrHeight);
    int testInnerWidth = width-testLeftStartPlotWindow-ins.right-hPad;
    getMaxMinDiffs();
    getMaxPlotVal();
    double increment = 5.0;
    int numTicks = (int)(maxPlot/increment);//will use numTicks for: remainder, leftStartPlotWindow, innerRectangle+labels+tickmarks
    int remainder = testInnerWidth%numTicks;
    int leftStartPlotWindow = testLeftStartPlotWindow-remainder;
    System.out.println("remainder is: "+remainder);
    int bottomPad = (3*xStrHeight)-vPad;
    int blueTop = ins.bottom+(vPad/2)+titleStrHeight;
    int blueHeight = height-bottomPad-blueTop;
    int blueWidth = blueHeight;
    int blueBottom = blueHeight+blueTop;

    //plot outer rectangle
    g.setColor(Color.red);
    int redWidth = width-leftMargin-1;
    g.drawRect(leftMargin, ins.bottom, redWidth, height-ins.bottom-1);
    //write top label
    g.setColor(Color.black);
    g.drawString(titleString, leftStartPlotWindow+((blueWidth/2)-(titleStrWidth/2)), titleStrHeight);
    // fill, then plot, inner rectangle
    g.setColor(Color.white);
    g.fillRect(leftStartPlotWindow, blueTop, blueWidth, blueHeight);
    g.setColor(Color.blue);
    g.drawRect(leftStartPlotWindow, blueTop, blueWidth, blueHeight);
    //scale the diffs to fit window
    double Scalar = blueWidth/maxPlot;
    ArrayList<Double> scaledDiffs = new ArrayList<Double>();
    for(int e = 0;e<myDiffs.size();e++){scaledDiffs.add(myDiffs.get(e)*Scalar);}
    //plot the scaled Diffs
    AffineTransform at = g2d.getTransform();//save the graphics context's transform
    g2d.translate(leftStartPlotWindow, blueTop);//translate origin to bottom-left corner of blue rectangle
    g2d.scale(1, -1);//invert the y-axis
    for(int w = 0;w<scaledDiffs.size();w++){
        if(w>0){
            double prior = scaledDiffs.get(w-1);
            int priorInt = (int)prior;
            double current = scaledDiffs.get(w);
            int currentInt = (int)current;
            g2d.drawOval(priorInt, currentInt, 4, 4);
        }
    }
    g2d.setTransform(at);//restore the transform for conventional rendering
    //write x-axis label
    g.setColor(Color.red);
    g.drawString(xString, leftStartPlotWindow+((blueWidth/2)-(xStrWidth/2)), height-ins.bottom-vPad);
    //write y-axis label
    g2d.rotate(Math.toRadians(-90), 0, 0);//rotate text 90 degrees counter-clockwise
    g.drawString(yString, -(height/2)-(yStrWidth/2), yStrHeight);
    g2d.rotate(Math.toRadians(+90), 0, 0);//rotate text 90 degrees clockwise
    // draw tick marks on x-axis
    NumberFormat formatter = new DecimalFormat("#0.0");
    double k = (double)blueWidth/(double)numTicks;
    double iteration = 0;
    for(int h=0;h<=numTicks;h++){
        int xval = (int)(h*k);
        g.setColor(Color.red);
        g.drawLine(leftStartPlotWindow+xval, blueBottom+2, leftStartPlotWindow+xval, blueBottom+(xStrHeight/2));//draw tick marks
        g.drawString(formatter.format(iteration),leftStartPlotWindow+xval-(fontMetrics.stringWidth(Double.toString(iteration))/2),blueBottom+(xStrHeight/2)+13);
        iteration+=increment;
    }
    // draw tick marks on y-axis
    iteration = 0;
    for(int h=0;h<=numTicks;h++){
        int yval = (int)(h*k);
        g.setColor(Color.red);
        g.drawLine(leftStartPlotWindow-2, blueBottom-yval, leftStartPlotWindow-(yStrHeight/2), blueBottom-yval);//draw tick marks
        g2d.rotate(Math.toRadians(-90), 0, 0);//rotate text 90 degrees counter-clockwise
        g.drawString(formatter.format(iteration),leftStartPlotWindow-2,blueBottom-(fontMetrics.stringWidth(Double.toString(iteration))/2));
        g2d.rotate(Math.toRadians(+90), 0, 0);//rotate text 90 degrees clockwise
        iteration+=increment;
    }
}
void getMaxMinDiffs(){// get max and min of Diffs
    for(int u = 0;u<myDiffs.size();u++){
        if(myDiffs.get(u)>maxDiff){maxDiff = myDiffs.get(u);}
        if(myDiffs.get(u)<minDiff){minDiff = myDiffs.get(u);}
    }
}
void getMaxPlotVal(){
    maxPlot = maxDiff;
    maxPlot += 1;//make sure maxPlot is bigger than the max data value
    while(maxPlot%5!=0){maxPlot+=1;}//make sure maxPlot is a multiple of 5
}
}

另外,一如既往地,关于该主题的文章或教程链接将受到高度赞赏。

1
与手头问题无关,但添加到ArrayList中的数据应该保存在数据文件中,而不是硬编码在程序中。 - Hovercraft Full Of Eels
3
是的,我知道。实际上,这些数据是一些数组操作的结果,比上面的数据集要长得多。不过,我按照上面的方式加载它,这样就更容易为这个网站上的人再现它。 - CodeMed
2
考虑 List<Double> myDiffs = new ArrayList<Double>(Arrays.asList(25.0, 9.0, 7.0, …)) - trashgod
我在网上做了一些研究,发现了这个(http://www.youtube.com/watch?v=mZ0qBfEc0fg)关于仿射变换的教程,还有一些更复杂的教程。由于我还没有学过线性代数,所以这些材料对我来说很难理解。然而,如果有人能够在出现问题时向我展示如何修复我的代码,我想我可以通过分解接收到的建议来学习。 - CodeMed
2个回答

11

有一种方法是在SineTest中展示的。简要来说,

  1. 保存图形上下文的变换。

Graphics2D g2d = (Graphics2D) g;
AffineTransform at = g2d.getTransform();
  • 将原点转移到中心。

  • g2d.translate(w / 2, h / 2);
    
  • 反转y轴。

  • g2d.scale(1, -1);
    
  • 使用笛卡尔坐标进行渲染。

  • 还原传统渲染的变换。

  • g2d.setTransform(at);
    

    在此输入图片描述


    +1 谢谢。我会认真阅读你的建议。请理解,我一直在根据需要探索翻译、反转、仿射变换等技术,但它们总是让我感到困惑。当我为一个应用程序弄清楚时,几周后需要下一个应用程序时,我又感到困惑。你愿意将你的建议合并到我的DataPanel.java文件中吗?如果你愿意这样做,我就能够使用Java API仔细审查你的代码,并更好地理解它的工作原理。 - CodeMed
    所引用的示例是完整的,并且它展示了图形变换的一个重要特征——顺序很重要。你的范围标记消失了,因为它们已经旋转到视野之外。更多信息请参见这里 - trashgod
    我已经审查了您的代码和引用。 我也进行了您建议的更改,但现在它只是没有绘制除一个点以外的所有点。 我已在我的原始帖子中展示了修改后的代码,包括您的建议,以便可以重新创建当前剩余的问题。 您是否愿意向我展示如何修复我的修改后的代码,使其执行我的原始发布所述的操作?谢谢。 - CodeMed

    2

    很抱歉回答不是十分全面,但这可以让您开始思考。Java的绘图方式与您所描述的相同:它将屏幕的左上角视为0,0,并绘制x向右增加,y向下增加。如果您使声明线为

    g2d.drawOval(priorInt, currentInt, 4, 4);
    

    转换为

    g2d.drawOval(blueWidth - priorInt, blueHeight - currentInt, 4, 4);
    

    对于您的第一个问题,它应该可以产生正确的结果。但是,要帮助您解决第二个问题,我需要更多的信息。它们只是超出了屏幕范围还是被其他东西覆盖了?如果是这种情况,请尝试翻转 + 和 - 的符号以查看是否可以获得正确的结果。


    谢谢你的建议。我已经做了更改并尝试运行代码,但是它将一个数据点绘制在蓝色矩形框外面,这表明它没有正确地重新定位数据点。如果您按照您的更改运行代码,您会发现一个数据点在屏幕左侧蓝色矩形框左边的位置,大约在屏幕高度的70%处。这意味着即使它们在蓝色框内,每个点也被错误地绘制出来。至于刻度标签,它们应该与X轴标签类似地放置。+1为你的帮助。 - CodeMed
    尝试使用g2d.drawOval(priorInt,-currentInt,4,4); 我有点盲目,因为我没有运行我建议的代码。对此很抱歉,但我太懒了,不想写一个JPanel包装器 =P。 - bob_twinkles
    你的y轴标签绘制代码唯一可能存在的问题在于,你可能只是在你的红色框中绘制。尝试将g2d.setColor(Color.red)更改为g2d.setColor(Color.green) - bob_twinkles

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接