Java Swing-Repaint() no funciona correctamente
graphics paintcomponent (1)
Tomo el programa de este tema . Intento editar puntos en modo de tiempo real. Agrego MouseMotionListener al constructor y escribo algunas funciones básicas para acercarme al punto sobre el mouse y edito este punto. Cuando obtengo (x, y) puntos en el constructor, repaint()
funciona de manera extraña. Cuando obtengo (x, y) puntos en paintComponent
, repaint()
no funciona en absoluto. Entonces, estas son imágenes con get (x, y) en el constructor y en paintComponent
. ¿Dónde está mi error?
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();
}
});
}
}
El problema está en tu constructor cuando calculas 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());
//...
}
En el punto en que se construye el componente, el tamaño de los componentes es 0x0
. Debe esperar hasta que el componente cambie de tamaño de alguna manera significativa antes de calcular los puntos ...
Esto también es problemático, ya que el componente puede redimensionarse varias veces, en rápida sucesión, cuando se realiza por primera vez en la pantalla.
Lo que hice en el pasado es usar un Swing Timer
configurado para un pequeño retardo para permitir que el componente cambie de tamaño varias veces en una escala de tiempo pequeña. Una vez que todo esté establecido, se disparará el Timer
y podrá realizar las operaciones requeridas, por ejemplo ...
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();
}
}
});
}
Todo lo que hace es esperar hasta que el temporizador entre el evento compoentResized
sea más de 200 milisegundos (puede jugar con este valor para acercarlo más a lo que desea, pero he encontrado aproximadamente 250 milisegundos en un retraso razonable)
Deberá corregir paintComponent
para permitir que graphPoints
null
.