I take program from this topic. I try to edit points in real-time mode. I add MouseMotionListener to the constructor and write some basic functions to get near point to mouse and edit this point. When I get (x,y) points in the constructor, repaint()
works strange. When I get (x,y) points in paintComponent
, repaint()
doesn't work at all. So, this are images with getting (x,y) in the constructor and in paintComponent
. Where is my mistake?
package simplegrapher2;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import static java.lang.Math.sqrt;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SimpleGrapher2 extends JPanel {
private int width = 800;
private int heigth = 400;
private int padding = 25;
private int labelPadding = 25;
private Color lineColor = new Color(44, 102, 230, 180);
private Color pointColor = new Color(100, 100, 100, 180);
private Color gridColor = new Color(200, 200, 200, 200);
private static final Stroke GRAPH_STROKE = new BasicStroke(2f);
private int pointWidth = 4;
private int numberYDivisions = 10;
private List<Double> scores;
private static double serieX = 0;
public List<Point2D.Double> graphPoints;
public SimpleGrapher2(List<Double> scores) {
this.scores = scores;
addMouseMotionListener(new MouseMotionListener() {
@Override
public void mouseDragged(MouseEvent me) {
double x = me.getX();
double y = me.getY();
findNearPoint(x, y);
editSerie(x, y);
revalidate();
repaint();
}
@Override
public void mouseMoved(MouseEvent me) {}
});
double xScale = ((double) getWidth() - (2 * padding) - labelPadding) / (scores.size() - 1);
double yScale = ((double) getHeight() - 2 * padding - labelPadding) / (getMaxScore() - getMinScore());
graphPoints = new ArrayList<>();
for (int i = 0; i < scores.size(); i++) {
double x1 = (double) (i * xScale + padding + labelPadding);
double y1 = (double) ((getMaxScore() - scores.get(i)) * yScale + padding);
graphPoints.add(new Point2D.Double(x1, y1));
}
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
/*double xScale = ((double) getWidth() - (2 * padding) - labelPadding) / (scores.size() - 1);
double yScale = ((double) getHeight() - 2 * padding - labelPadding) / (getMaxScore() - getMinScore());
graphPoints = new ArrayList<>();
for (int i = 0; i < scores.size(); i++) {
double x1 = (double) (i * xScale + padding + labelPadding);
double y1 = (double) ((getMaxScore() - scores.get(i)) * yScale + padding);
graphPoints.add(new Point2D.Double(x1, y1));
}*/
// draw white background
g2.setColor(Color.WHITE);
g2.fillRect(padding + labelPadding, padding, getWidth() - (2 * padding) - labelPadding, getHeight() - 2 * padding - labelPadding);
g2.setColor(Color.BLACK);
// create hatch marks and grid lines for y axis.
for (int i = 0; i < numberYDivisions + 1; i++) {
int x0 = padding + labelPadding;
int x1 = pointWidth + padding + labelPadding;
int y0 = getHeight() - ((i * (getHeight() - padding * 2 - labelPadding)) / numberYDivisions + padding + labelPadding);
int y1 = y0;
if (scores.size() > 0) {
g2.setColor(gridColor);
g2.drawLine(padding + labelPadding + 1 + pointWidth, y0, getWidth() - padding, y1);
g2.setColor(Color.BLACK);
String yLabel = ((int) ((getMinScore() + (getMaxScore() - getMinScore()) * ((i * 1.0) / numberYDivisions)) * 100)) / 100.0 + "";
FontMetrics metrics = g2.getFontMetrics();
int labelWidth = metrics.stringWidth(yLabel);
g2.drawString(yLabel, x0 - labelWidth - 5, y0 + (metrics.getHeight() / 2) - 3);
}
g2.drawLine(x0, y0, x1, y1);
}
// and for x axis
for (int i = 0; i < scores.size(); i++) {
if (scores.size() > 1) {
int x0 = i * (getWidth() - padding * 2 - labelPadding) / (scores.size() - 1) + padding + labelPadding;
int x1 = x0;
int y0 = getHeight() - padding - labelPadding;
int y1 = y0 - pointWidth;
if ((i % ((int) ((scores.size() / 20.0)) + 1)) == 0) {
g2.setColor(gridColor);
g2.drawLine(x0, getHeight() - padding - labelPadding - 1 - pointWidth, x1, padding);
g2.setColor(Color.BLACK);
String xLabel = i + "";
FontMetrics metrics = g2.getFontMetrics();
int labelWidth = metrics.stringWidth(xLabel);
g2.drawString(xLabel, x0 - labelWidth / 2, y0 + metrics.getHeight() + 3);
}
g2.drawLine(x0, y0, x1, y1);
}
}
// create x and y axes
g2.drawLine(padding + labelPadding, getHeight() - padding - labelPadding, padding + labelPadding, padding);
g2.drawLine(padding + labelPadding, getHeight() - padding - labelPadding, getWidth() - padding, getHeight() - padding - labelPadding);
Stroke oldStroke = g2.getStroke();
g2.setColor(lineColor);
g2.setStroke(GRAPH_STROKE);
for (int i = 0; i < graphPoints.size() - 1; i++) {
double x1 = graphPoints.get(i).x;
double y1 = graphPoints.get(i).y;
double x2 = graphPoints.get(i + 1).x;
double y2 = graphPoints.get(i + 1).y;
g2.draw(new Line2D.Double(x1, y1, x2, y2));
}
g2.setStroke(oldStroke);
g2.setColor(pointColor);
for (int i = 0; i < graphPoints.size(); i++) {
double x = graphPoints.get(i).x - pointWidth / 2;
double y = graphPoints.get(i).y - pointWidth / 2;
int ovalW = pointWidth;
int ovalH = pointWidth;
//g2.fillOval(x, y, ovalW, ovalH);
}
}
public void findNearPoint(double x, double y) {
double delta = 0;
for (int j = 0; j < graphPoints.size(); j++) {
double sx = graphPoints.get(j).x;
double sy = graphPoints.get(j).y;
double root = sqrt((x-sx)*(x-sx) + (y-sy)*(y-sy));
if (j == 0) {
delta = root;
serieX = sx;
}
else if (root < delta) {
serieX = sx;
delta = root;
}
}
}
public void editSerie(double x, double y) {
double tmpSerieX = serieX;
int ind = 0;
for (int i = 0; i < graphPoints.size(); i++) {
if (graphPoints.get(i).x == tmpSerieX) {
ind = i;
}
}
graphPoints.remove(ind);
graphPoints.add(ind, new Point2D.Double(x, y));
serieX = x;
}
private double getMinScore() {
double minScore = Double.MAX_VALUE;
for (Double score : scores) {
minScore = Math.min(minScore, score);
}
return minScore;
}
private double getMaxScore() {
double maxScore = Double.MIN_VALUE;
for (Double score : scores) {
maxScore = Math.max(maxScore, score);
}
return maxScore;
}
public void setScores(List<Double> scores) {
this.scores = scores;
invalidate();
this.repaint();
}
public List<Double> getScores() {
return scores;
}
private static void createAndShowGui() {
List<Double> scores = new ArrayList<>();
Random random = new Random();
int maxDataPoints = 40;
int maxScore = 10;
for (int i = 0; i < maxDataPoints; i++) {
scores.add((double) random.nextDouble() * maxScore);
}
SimpleGrapher2 mainPanel = new SimpleGrapher2(scores);
mainPanel.setPreferredSize(new Dimension(800, 600));
JFrame frame = new JFrame("DrawGraph");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
The problem is in your constructor when you calculate the x/yScale
...
public SimpleGrapher2(List<Double> scores) {
//...
double xScale = ((double) getWidth() - (2 * padding) - labelPadding) / (scores.size() - 1);
double yScale = ((double) getHeight() - 2 * padding - labelPadding) / (getMaxScore() - getMinScore());
//...
}
At the point the component is constructed, the components size is 0x0
. You need to wait until the component is resized in some meaningful way before calculating the points...
This is also problematic, as the component may be resized a number of times, in quick succession, when it is first realised onto the screen.
What I've done in the past is used a Swing Timer
set to a smallish delay to allow the component to be resized a number of times over a small time scale. Once it's all settled down, the Timer
will trigger and you can perform you required operations, for example...
public SimpleGrapher2(List<Double> scores) {
//...
addComponentListener(new ComponentAdapter() {
private Timer initTimer = new Timer(200, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
initTimer.stop();
double xScale = ((double) getWidth() - (2 * padding) - labelPadding) / (scores.size() - 1);
double yScale = ((double) getHeight() - 2 * padding - labelPadding) / (getMaxScore() - getMinScore());
graphPoints = new ArrayList<>();
for (int i = 0; i < scores.size(); i++) {
double x1 = (double) (i * xScale + padding + labelPadding);
double y1 = (double) ((getMaxScore() - scores.get(i)) * yScale + padding);
graphPoints.add(new Point2D.Double(x1, y1));
}
repaint();
}
});
@Override
public void componentResized(ComponentEvent e) {
if (graphPoints == null) {
initTimer.restart();
}
}
});
}
All this does is waits until the timer between the compoentResized
event is more then 200 milliseconds (you can play with this value to get it closer to what you want, but I've found roughly 250 milliseconds to be a reasonable delay)
You will need to correct the paintComponent
to allow for graphPoints
been null
as well.