rotating coordinate plane for data and text in Java
Asked Answered
L

2

7

I need to:
1.) move the origin and also rotate the coordinate plane so that x-values progress rightward and y-values progress upward from the new origin(which needs to be the bottom left corner of the inner, blue rectangle in the code below). This will enable me to plot points at x,y coordinate pairs in the code below.
2.) plot rotated labels for the tic marks on the y-axis of the data plot.

The code below sets up this problem. It works, except for two problems:
1.) the data points are being plotted with the upper left hand corner as the origin and y-values descending downward
2.) the labels for the tic marks on the y-axis are not being drawn on the screen

Can anyone show me how to fix the code below so that it fixes these two problems and does what the first paragraph above describes?

The code is in the following two java files:

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 (Note: I edited the code below to include trashgod's suggestions, but it still does not work.)

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
}
}

Also, as always, links to articles or tutorials on the topic are much appreciated.

Lynnet answered 21/2, 2012 at 3:54 Comment(4)
Unrelated to the problem at hand, but the data added to the ArrayList should be held in a data file, not hard-coded in the program.Lean
@Hovercraft Full Of Eels, Yes, I know. In fact, the data is the result of some array operations and is much longer than the dataset above. However, I loaded it the way I did above so that it would be more easily reproducable for people on this site.Lynnet
Consider List<Double> myDiffs = new ArrayList<Double>(Arrays.asList(25.0, 9.0, 7.0, …)).Preamble
I have been doing some research online and I found this ( youtube.com/watch?v=mZ0qBfEc0fg ) tutorial regarding the affine transform, among a few others that were more complicated. This material is hard for me to grasp because I have not taken linear algebra yet. However, if people can show me how to fix my code when this comes up, I imagine that I can learn by decomposing the suggestions that I receive.Lynnet
P
12

One approach is shown in SineTest. In outline,

  1. Save the graphics context's transform.

    Graphics2D g2d = (Graphics2D) g;
    AffineTransform at = g2d.getTransform();
    
  2. Translate the origin to the center.

    g2d.translate(w / 2, h / 2);
    
  3. Invert the y-axis.

    g2d.scale(1, -1);
    
  4. Render using cartesian coordinates.

  5. Restore the transform for conventional rendering.

    g2d.setTransform(at);
    

enter image description here

Preamble answered 21/2, 2012 at 6:32 Comment(3)
+1 Thank you. I will carefully go through your suggestion. Please understand that I have been exploring translations, inversions, affine transforms, and the like as needed for a while now, and they are always confusing to me. When I figure it out for one application, I find I am confused a few weeks later when the need for the next application arises. Are you willing to incorporate your suggestions into working code in my DataPanel.java file above? If you were willing to do that, I would be able to carefully review your code with the java api and better understand how it works.Lynnet
The example cited is complete, and it illustrates an essential feature of the graphics transform—_order counts_. Your range marks disappear because they have been rotated out of view. More here.Preamble
I reviewed your code and the references you cited. I also made your suggested changes, but now it is simply not plotting all but one of the points. I have shown the revised code in my original posting above, including your suggestions, so that the currently remaining problem can be re-created. Are you willing to show me how to fix my revised code above so that it does what my original posting states that I am trying to do? Thank you.Lynnet
P
2

Apologies for somewhat incomplete answer, but this may get your gears turning. Java draws things the way you described them: It considers the top left corner of the screen to be 0, 0 and draws x increasing to the right and y increasing downwards. If you make the line that states

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

into

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

it should yield the correct results for your first issue. I need a bit more info on the second problem to help you with that one though. Are they just off the screen or are the getting drawn over by something else? Try flipping +s and -s around to see if you can get the correct result if that is the case.

Prompt answered 21/2, 2012 at 4:37 Comment(3)
Thanks for the suggestion. I made the change and tried to run the code, but it plots one outside the blue rectangle, which shows that it is not properly re-locating the data points. You can see for yourself that, if you run the code with your change, there is a data point to the left of the left edge of the blue rectangle, about 70% of the way up the screen. That means that every point is also being incorrectly plotted, even if they are inside the blue box. As for the tic mark labels, they are supposed to be placed similarly to the x-axis labels. +1 for trying to help.Lynnet
Try using g2d.drawOval (priorInt, -currentInt, 4, 4); I'm sort of running blind because I'm not running the code I'm suggesting. Sorry about that, but I'm to lazy to write a JPanel wrapper =P.Prompt
The only thing I can see wrong with your y axis label drawing code is that you may just be drawing inside your red box. Try changing the g2d.setColor (Color.red) to g2d.setColor (Color.green)Prompt

© 2022 - 2024 — McMap. All rights reserved.