I have a dataset that I am plotting within a rectangular space defined in the code below. The length of the dataset will almost always be different than the width in pixels of the screen space in which the dataset needs to be plotted.
My problem is that I need an effective way of spreading the graph of the dataset across the entire width of the plotting space.
As you can see from the code I am posting below, the data is being (incorrectly) plotted in a subset of the width of the rectangle. If you compile the code and manually re-size the frame a few times, you will see that the width of the datagraph relative to the width of the blue inner rectangle changes as the width of the frame changes. To illustrate this most clearly, the code below also plots two green vertical lines, one at the beginning and the other at the end of the graph of the dataset. If the code were working correctly, the vertical green lines should be located only at the beginning and end of the blue inner rectangle, and the spikes between them should be at the appropriate percentage distances from the start and end of the inner blue rectangle (30% and 70%, corresponding with indices 120/400 and 280/400, respectively). But, as you can see, the second green vertical line is always out of place while the first green line seems to always be in its proper location, and the positions of the dataspikes are relative to the (misplaced) green vertical markers rather than being correctly placed relative to the left and right edges of the inner blue rectangle, which should overlap with the (correctly placed) green vertical lines.
Can anyone show me how to fix the code below so that it evenly distributes the width of the datagraph across the width of the inner blue rectangle?
I think the problem is in the hstep variable, and I mark the place in the DataPanel.java code below where I think the problem code is with ALL CAPS in a comment.
The code is in the following two files:
DataGUI.java
import java.awt.*;
import javax.swing.*;
class DataGUI{
DataGUI() {
JFrame jfrm = new JFrame("X-Y Plot");// Create a new JFrame container.
jfrm.getContentPane().setLayout(new GridLayout(1,1));// Specify FlowLayout for the layout manager.
int frameHeight = 500;
int frameWidth = 767;
jfrm.setSize(frameWidth, frameHeight);// Give the frame an initial size.
jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// Terminate program when user closes application.
int len = 400;
double[] data = new double[len];
for(int d=0;d<data.length;d++){
if(d==120||d==280){data[d]=45;}
else{data[0]=0;}
}
DataPanel myDataPanel = new DataPanel(data);
jfrm.add(myDataPanel);
jfrm.setVisible(true);// Display the frame.
}
public static void main(String args[]) {
// Create frame on event dispatching thread.
SwingUtilities.invokeLater(new Runnable() {public void run(){new DataGUI();}});
}
}
DataPanel.java
import java.awt.*;
import javax.swing.*;
import java.text.NumberFormat;
class DataPanel extends JPanel {
Insets ins; // holds the panel's insets
int len = 400;// for testing only. will throw error if you do not link this to an array length.
double[] plotData;
double xScale;
DataPanel(double[] myData) {
setOpaque(true);// Ensure that panel is opaque.
plotData = myData;
}
protected void paintComponent(Graphics g){// Override paintComponent() method.
super.paintComponent(g);// Always call superclass method first.
int height = getHeight();// Get height of component.
int width = getWidth();// Get width of component.
ins = getInsets();// Get the insets.
// Get dimensions of text
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();
//get margins
int leftMargin = ins.left;
//set parameters for inner rectangle
int hPad=10;
int vPad = 6;
int numYticks = 10;
int testLeftStartPlotWindow = ins.left+5+(3*yStrHeight);
int testInnerWidth = width-testLeftStartPlotWindow-ins.right-hPad;
int remainder = testInnerWidth%numYticks;
int leftStartPlotWindow = testLeftStartPlotWindow-remainder;
System.out.println("remainder is: "+remainder);
int blueWidth = testInnerWidth-remainder;
int endIndex = leftStartPlotWindow+blueWidth;
int blueTop = ins.bottom+(vPad/2)+titleStrHeight;
int bottomPad = (3*xStrHeight)-vPad;
int blueHeight = height-bottomPad-blueTop;
int blueBottom = blueHeight+blueTop;
g.setColor(Color.red);
int redWidth = width-leftMargin-1;
//plot outer rectangle
g.drawRect(leftMargin, ins.bottom, redWidth, height-ins.bottom-1);
System.out.println("blueWidth is: "+blueWidth);
// fill inner rectangle
g.setColor(Color.white);
g.fillRect(leftStartPlotWindow, blueTop, blueWidth, blueHeight);
//write top label
g.setColor(Color.black);
g.drawString(titleString, (width/2)-(titleStrWidth/2), titleStrHeight);
//write x-axis label
g.setColor(Color.red);
g.drawString(xString, (width/2)-(xStrWidth/2), height-ins.bottom-vPad);
g2d.rotate(Math.toRadians(-90), 0, 0);//rotate text 90 degrees counter-clockwise
//write y-axis label
g.drawString(yString, -(height/2)-(yStrWidth/2), yStrHeight);
g2d.rotate(Math.toRadians(+90), 0, 0);//rotate text 90 degrees clockwise
// draw tick marks and data rectangles on x-axis
NumberFormat formatter = NumberFormat.getPercentInstance();
double k = blueWidth/numYticks;
double iteration = 0;
for(int h=0;h<=numYticks;h++){
int xval = (int)(h*k);
//draw tick marks
g.setColor(Color.red);
g.drawLine(leftStartPlotWindow+xval, blueBottom+2, leftStartPlotWindow+xval, blueBottom+(xStrHeight/2));
g.drawString(formatter.format(iteration/10),leftStartPlotWindow+xval-(fontMetrics.stringWidth(Double.toString(iteration/10))/2),blueBottom+(xStrHeight/2)+13);
iteration+=1;
}
//plot data
// I THINK THE PROBLEM IS IN THE NEXT LINE OF CODE, BECAUSE hstep EVALUATES TO EITHER 1 OR 2.
// HOW DO I ALTER THIS SO THAT THE PLOT IS ABLE TO STRETCH ON A SCALAR THAT IS NOT AS COARSE?
double hstep = blueWidth/len;
System.out.println("hstep, len are: "+hstep+", "+len);
for(int h = 1;h<len;h++){
int x2 = leftStartPlotWindow+(int)(h * hstep);
int x1 = leftStartPlotWindow+(int) ((h - 1) * hstep);
int y1 = (blueBottom-(int)plotData[h - 1]);
int y2 = (blueBottom-(int)plotData[h]);
g.drawLine(x1, y1, x2, y2);
g.setColor(Color.green);
if(h==1){
g.drawLine(leftStartPlotWindow+(int)(h*hstep), blueBottom, leftStartPlotWindow+(int)(h*hstep), blueBottom-100);
}
if(h==len-1){
g.drawLine(leftStartPlotWindow+(int)(h*hstep), blueBottom, leftStartPlotWindow+(int)(h*hstep), blueBottom-100);
}
g.setColor(Color.red);
}
// plot inner rectangle
g.setColor(Color.blue);
g.drawRect(leftStartPlotWindow, blueTop, blueWidth, blueHeight);
}
}
@Tedil spotted the bug, or at least a very good guess.
Saying that, I’d have took my points and scaled and offset them.
Then just iterated through the collection in the paint code.
The nice thing about this way, is min and max X and Y give you the bounding rectangle, so initialiiy you just draw that, and get your scaling/resizing sorted, then draw the points and decorate based on the scaled coordinates.
Paint code is painful to debug, so I like to delegate as much of the math as possible, and get the ‘corners’ sorted before I clutter the code with decoration.
You can also get clever optimising it when there’s a lot of points or look at building ‘off screen’ if it very dynamic.
Not a java guy.
GIven you your can load the dataset from youyr set in to a collection of points, e.g.
Point is struct of two floats called X and Y
And Points is an ordered list (by X)
Then work out the bounds
Just loop through the points for min and max Y, you already have X as they are order, so it’s just first and last
Rationalise them if required ie Origin is 0,0 MaxX = 7658 so call it 8000, MaxY is 84, so call it a 100.
So now your points can be bounded by a rectangle(0,0,8000,100) so
ActtualHeight = 100, actualWidth = 8000 (calc this, to deal with negative axis)
Some where you’ve worked out that your plotting area is 755 X 384 and the origin will be plotted at 30, 45.
So XRatio = (Double)actualWidth / 755, and YRatio = …
loopp through actual points and produce another list of scaled ones, add teh offset, invert for Y
Then draw a line from ppoint 0 to 1, 1 to 2, n-1 to n)
It’s all dead simple stuff, just break the drawing of the whole graph in to some nice easy steps. Think about how you’d do it with some graph paper, a ruler and a HB pencil.
For instance ypou can work out a step size for your ticks dead easy.
Then use teh tick position and orientation to work out where the tick label is.
You can center your description, title etc.
Trick is relative positioning.
X axis label is say 20 pixels down from the X axis line, and centered between X AxisStart and end.
Don’t try and do it all in one loop using absolute coordinates.
PS first get it working, then look at getting clever…