Graphics and SwingWorker - java

I'm trying to paint a dynamic graph that has a few thousand points. I'm using SwingWorker class that gets the Graphics object as an argument. I initialize the graphics object in the constructor of the SwingWorker class and then call the main graphing function doGraph() in the done() method i.e, after all the calculations are done. I'm having issues with painting, not sure what is going on. The code works if I call doGraph() from the constructor of the SwingWorker class but not when I place it in done().
Any help is greatly appreciated. I have simplified the code for ease of understanding. I`m just trying to paint a rectangle in the doGraph() for simplicity.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.RenderingHints.Key;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
#SuppressWarnings("serial")
public class UIGraph extends JPanel {
private double minX, maxX;
private double minY, maxY;
private SWPaint swPaint; //Swing Worker object
private Graphics2D g2D;
public Key key = RenderingHints.KEY_ANTIALIASING;
public Object obj = RenderingHints.VALUE_ANTIALIAS_ON;
private int labelPadding = 25;
private int padding = 25;
private int padConst = (2 * padding) - labelPadding;
private double xScale;
private double yScale;
/**
* Crate Panel
*/
private void createUI() {
setBackground(new Color(245, 255, 250));
setLayout(new BorderLayout(0, 0));
}
/**
* Constructor
*
*/
public UIGraph() {
createUI();
getMinMax();
}
/**
* Paint Component. Do all the calculations and graphing
*/
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (swPaint != null) {
if (!swPaint.isDone()) { // The swing worker is busy with tasks. Set Visibility to false and return.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
setVisible(false);
}
});
return;
}
}
swPaint = new SWPaint(g); //Create a swing worker class, pass graphics object as argument
swPaint.execute();
}
}
/**
* Get the Min and Max values of Data
*/
private void getMinMax() {
//obtain min and max values
}
/**
* Main method for graphing
*
* #param g2D
*/
private void doGraph() {
xScale = (double) (getWidth() - padConst) / (maxX - minX);
yScale = (double) (getHeight() - padConst) / (maxY - minY);
g2D.setColor(Color.WHITE);
g2D.fillRect(padding + labelPadding, padding, getWidth() - (2 * padding) - labelPadding, getHeight() - 2 * padding
- labelPadding);
g2D.setColor(Color.BLACK);
}
/**
* Swing Worker to handle Paint
*
*/
public class SWPaint extends SwingWorker<Void, Void> {
/**
* Constructor
*
* #param g
*/
public SWPaint(Graphics g) {
g2D = (Graphics2D) g;
g2D.setRenderingHint(key, obj);
//doGraph() //This works
}
/**
* Do graphing calculations here
*/
#Override
protected Void doInBackground() throws Exception {
// do all calculations here
return null;
}
/*
* The SW is done. Paint the graph. Set Visibility to true
*/
protected void done() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
doGraph(); //This doesn`t work here.
setVisible(true);
}
});
}
}
}

Related

Why does the locaton of my animated circles flicker?

With trident I created a (seemingly) simple animation. A number of circles are moving from bottom to top and back again with a sine interpolation:
The animation itself seems to work, but there is one or two frames where all my spheres flicker all to the topmost location.
Why does it flicker? Who is invoking the setY method with seemingly wrong values?
I've made a testclass to reproduce the behavior. You need radiance-trident 3.0 to make it work:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JDialog;
import javax.swing.JPanel;
import org.pushingpixels.trident.api.Timeline;
import org.pushingpixels.trident.api.Timeline.RepeatBehavior;
import org.pushingpixels.trident.api.ease.TimelineEase;
import org.pushingpixels.trident.api.swing.SwingRepaintTimeline;
public class MovingSpheresTest extends JDialog {
private final double sphereRadius = 3d;
private final double sphereCount = 12d;
private final double helixHeight = 100d;
private final double size = 200d;
private final double animationSpeed = 0.5d;
private List<CenteredSphere> spheres;
private SwingRepaintTimeline repaintTimeline;
private final JPanel contentPanel = new JPanel() {
protected void paintComponent(Graphics g) {
super.paintComponent(g);
paintFrame((Graphics2D) g);
}
private void paintFrame(Graphics2D g) {
Graphics2D create = (Graphics2D) g.create();
try {
create.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
create.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
create.setColor(Color.BLACK);
for (CenteredSphere centeredSphere : spheres) {
create.fill(centeredSphere);
}
} finally {
create.dispose();
}
}
};
/**
* Launch the application.
*/
public static void main(String[] args) {
try {
MovingSpheresTest dialog = new MovingSpheresTest();
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
dialog.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Create the dialog.
*/
public MovingSpheresTest() {
setBounds(100, 100, 450, 300);
getContentPane().setLayout(new BorderLayout());
contentPanel.setLayout(new FlowLayout());
getContentPane().add(contentPanel, BorderLayout.CENTER);
installSpheres();
installRepaintTimeline();
}
private void installSpheres() {
double helixRadius = helixHeight / 2;
double effectiveWidth = size - (2 * sphereRadius);
double sphereDistance = effectiveWidth / (sphereCount - 1);
double sphereCenterX = sphereRadius;
double sphereCenterY = size / 2d;
double sphereCenterYInitial = sphereCenterY - helixRadius;
spheres = new ArrayList<>();
for (int sphereIndex = 0; sphereIndex < sphereCount; sphereIndex++) {
CenteredSphere sphere = new CenteredSphere(sphereCenterX, sphereRadius);
spheres.add(sphere);
sphereCenterX += sphereDistance;
Timeline.builder()
.addPropertyToInterpolate(Timeline.<Double>property("y").on(sphere).from(sphereCenterYInitial)
.to(sphereCenterY + helixRadius))
.setEase(new FullSine((float) (sphereIndex * 2 * Math.PI / sphereCount)))
.setDuration((long) (animationSpeed * 3000d)).playLoop(RepeatBehavior.LOOP);
}
}
private class FullSine implements TimelineEase {
private float horizontalOffset;
private FullSine(float horizontalOffset) {
this.horizontalOffset = horizontalOffset;
}
#Override
public float map(float durationFraction) {
return ((float) Math.sin(durationFraction * Math.PI * 2f + horizontalOffset) + 1f) / 2f;
}
}
private void installRepaintTimeline() {
repaintTimeline = SwingRepaintTimeline.repaintBuilder(contentPanel).build();
repaintTimeline.playLoop(RepeatBehavior.LOOP);
}
public class CenteredSphere extends Ellipse2D.Double {
private double sphereCenterX;
private double sphereRadius;
public CenteredSphere(double sphereCenterX, double sphereRadius) {
this.sphereCenterX = sphereCenterX;
this.sphereRadius = sphereRadius;
}
public void setY(double y) {
setFrameFromCenter(sphereCenterX, y, sphereCenterX + sphereRadius, y + sphereRadius);
}
}
}
As noted here and fixed here,
This is interesting. It's because of the underlying assumption that the TimelineEase always maps the [0.0-1.0] interval without "warping" the end points. In this particular case, during each animation loop, the custom FullSine is used to remap that interval based on the sphere offset, but during the loop reset, the "end" points are not mapped.

Drawing a Line with MouseAdapter Not Showing Java

Found this sample code which should produce a drawn line after clicking, but does not show anything or works. Assume all import statements are correct, code gives no errors and I have no idea why it would not work. The line color is red while the background is white so it should show clearly if it works. The mouse listener seems to be correct as well. Any reason why this code will not work?
public class PathPanel extends JPanel {
/**
* The panel width.
*/
public static final int WIDTH = 400;
/**
* The panel height.
*/
public static final int HEIGHT = 400;
/**
* The background color of the panel.
*/
public static final Color BACKGROUND_COLOR = Color.WHITE;
/**
* The color to paint with.
*/
public static final Color FOREGROUND_COLOR = Color.RED;
/**
* The line width.
*/
public static final int LINE_WIDTH = 8;
// Instance Fields
/**
*
*/
private static final long serialVersionUID = -3644129903653409515L;
/**
* The path being created.
*/
private final Path2D myPath;
// OR you could use Path2D.Double instead of GeneralPath
// Constructor
/**
* Constructs a new general path panel.
*/
public PathPanel() {
super();
myPath = new GeneralPath();
myPath.setWindingRule(GeneralPath.WIND_EVEN_ODD);
//myPath = new Path2D.Double();
setPreferredSize(new Dimension(WIDTH, HEIGHT));
setBackground(BACKGROUND_COLOR);
addMouseListener(new MyMouseListener());
}
/**
* Paints the current path.
*
* #param theGraphics The graphics context to use for painting.
*/
#Override
public void paintComponent(final Graphics theGraphics) {
super.paintComponent(theGraphics);
final Graphics2D g2d = (Graphics2D) theGraphics;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setPaint(FOREGROUND_COLOR);
g2d.setStroke(new BasicStroke(LINE_WIDTH));
g2d.draw(myPath);
}
// Main Method
/**
* Creates and displays a GeneralPathPanel.
*
* #param theArgs Command line arguments (ignored).
*/
public static void main(final String... theArgs) {
final PathPanel panel = new PathPanel();
final JFrame frame = new JFrame("GeneralPathPanel Demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
// Inner Class
/**
* Listens for mouse clicks, to draw on our panel.
*/
private class MyMouseListener extends MouseAdapter {
/**
* Handles a click event.
*
* #param theEvent The event.
*/
#Override
public void mouseClicked(final MouseEvent theEvent) {
if (myPath.getCurrentPoint() == null) {
myPath.moveTo(theEvent.getX(), theEvent.getY());
} else if (theEvent.getClickCount() == 2) {
myPath.closePath();
} else {
myPath.lineTo(theEvent.getX(), theEvent.getY());
}
repaint();
}
}
}
The Sample Code you posted works fine for me. Have you tried to add a System.out.println() in the mouseClicked(final MouseEvent theEvent) Method to check if it is actually called? If it is not called, you could try to change it to mouseReleased.
The imports I used:
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.GeneralPath;
import java.awt.geom.Path2D;

Stop JComponent from repainting when state changes

I'm sure, this is something like a super stupid standard problem, but I spent hours searching and trying to get this fixed, but it just won't work... I just can't find my mistake here...
I'm trying to build a simple program that prints something on a JComponent. The paintComponent()-Method refers to some variables and I want the JComponent ONLY to repaint, if i say so! But it always repaints whenever i change the variables...
Heres the code of my 2 classes:
import java.awt.Dimension;
import javax.swing.*;
public class SimplePaint extends JFrame {
/**
*
*/
private static final long serialVersionUID = 1L;
private PaintingCanvas pc;
public SimplePaint() {
super("SimplePaint");
this.pc = new PaintingCanvas();
this.pc.setPreferredSize(new Dimension(800, 600));
this.add(pc);
this.setResizable(false);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.pack();
this.setVisible(true);
this.setLocationRelativeTo(null);
}
public static void main(String[] args) {
SimplePaint sp = new SimplePaint();
sp.pc.setxStart(50);
sp.pc.setyStart(60);
sp.pc.setxEnd(140);
sp.pc.setyEnd(300);
}
}
and
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.*;
public class PaintingCanvas extends JComponent {
/**
*
*/
private static final long serialVersionUID = 1L;
private int xStart, yStart;
private int xEnd, yEnd;
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.fillRect(xStart, yStart, xEnd, yEnd);
}
/**
* #param xStart the xStart to set
*/
public void setxStart(int xStart) {
this.xStart = xStart;
}
/**
* #param yStart the yStart to set
*/
public void setyStart(int yStart) {
this.yStart = yStart;
}
/**
* #param xEnd the xEnd to set
*/
public void setxEnd(int xEnd) {
this.xEnd = xEnd;
}
/**
* #param yEnd the yEnd to set
*/
public void setyEnd(int yEnd) {
this.yEnd = yEnd;
}
}
What it displays: The Canvas with a rectangle (50, 60, 140, 300)...
What is should display: The blank canvas, and if i then put sp.pc.repaint() or something like that in the main method, it should repaint and therefore show the rectangle...
You cannot make any assumption on when and how many times paintComponent will be called. When making the frame visible, it wille be invoked. If you maximize your frame also, etc... There are many situations when paintComponent will be invoked.
The solution is pretty easy:
Add a flag (boolean drawRectangle = false;) on your PaintingCanvas class
In paintComponent check the value of the flag and draw (or not) the rectangle accordingly
When you want the rectangle to appear, toggle the value of the flag

advice needed on JPanel - Drawing Area & listeners

below is the code i currently have for the drawing class, iv been looking for documents which teach me how to create 3 shapes on screen.
i have a jframe, with menus to select the shape (seen below) ect.
if (clickedMenu.getText().equals("Square")){
value = pane.returnslider();
shape = new ASquare(value);
so my question is: how to i edit the below class to create a 2D square that appears on my Jframe which changes size depending on the slider value?
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package assignment;
import java.awt.Graphics;
/**
*
* #author Steven
*/
public class MyDrawing extends javax.swing.JPanel {
/**
* Creates new form MyDrawing
*/
#Override public void paintComponent(Graphics g) {
super.paintComponent(g); // paints background
}
}
some code from my jpanel that would maybe help you in answering my question:
public class MyChangeAction implements ChangeListener {
//complete code here
#Override
public void stateChanged(ChangeEvent ce) {
double value = slider.getValue();
String str = Double.toString(value);
sliderLabel.setText(str);
DecimalFormat df = new DecimalFormat("0.0");
boundary_length.setText("" + df.format(MyFrame.shape.computeBoundaryLength(value)));
area.setText("" + df.format(MyFrame.shape.computeArea(value)));
}
} // end class
public double returnslider() {
return slider.getValue();
}
my square class:
package assignment;
import java.awt.event.ActionListener;
/**
*
* #author b00560806
*/
public class ASquare extends MyShape {
double value;
public ASquare(double value) {
this.value = value;
}
#Override
public double computeBoundaryLength(double Length)
{
thelength=(4*Length);
return thelength;
}
#Override
public double computeArea(double Length)
{
thearea=(Length*Length);
return thearea;
}
}
Try this:
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g); // paints background
Graphics2D g2 = (Graphics2D)g;
g2.setStroke(new Stroke((int)returnslider));
// rest of drawing.
}

Java AWT/Swing "contains" and "intersects" methods not working correctly

I'm attempting to teach myself some Java AWT and simple graphics but have had difficulty with using the contains and intersects method.
The problem is that it seems to detect the collision several pixels up from where the mouse is clicked and the actual shape.
GameDemo.java
package uk.co.mhayward.games.sandbox;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class GameDemo extends JFrame {
GamePanel gamePanel = new GamePanel();
public static void main(String[] args) {
new GameDemo();
}
public GameDemo() {
super("click me");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.add(gamePanel);
this.setSize(200, 200);
this.setVisible(true);
this.addMouseListener(new MouseListener() {
public void mouseClicked(MouseEvent e) {}
public void mousePressed(MouseEvent e) {
System.out.println(e.getPoint().toString());
if (gamePanel.shape.contains(e.getPoint())) {
System.out.println("IN");
} else {
System.out.println("out");
}
}
public void mouseReleased(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
});
}
public class GamePanel extends JPanel {
Shape shape = new RegularPolygon(100, 100, 100, 6, 0);
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setStroke(new BasicStroke(1));
g2d.setPaint(Color.WHITE);
g2d.fill(shape);
g2d.setPaint(Color.BLACK);
g2d.draw(shape);
}
}
public static class RegularPolygon extends Polygon {
private static final long serialVersionUID = 8828151557263250246L;
/**
* #param x
* #param y
* #param r
* #param vertexCount
*/
public RegularPolygon(int x, int y, int r, int vertexCount) {
this(x, y, r, vertexCount, 0);
}
/**
* #param x
* #param y
* #param r
* #param vertexCount
* #param startAngle
* 360deg = PI
*/
public RegularPolygon(int x, int y, int r, int vertexCount, double startAngle) {
super(getXCoordinates(x, y, r, vertexCount, startAngle),
getYCoordinates(x, y, r, vertexCount, startAngle),
vertexCount);
}
protected static int[] getXCoordinates(int x, int y, int r, int vertexCount, double startAngle) {
int res[] = new int[vertexCount];
double addAngle = 2 * Math.PI / vertexCount;
double angle = startAngle;
for (int i = 0; i < vertexCount; i++) {
res[i] = (int) Math.round(r * Math.cos(angle)) + x;
angle += addAngle;
}
return res;
}
protected static int[] getYCoordinates(int x, int y, int r, int vertexCount, double startAngle) {
int res[] = new int[vertexCount];
double addAngle = 2 * Math.PI / vertexCount;
double angle = startAngle;
for (int i = 0; i < vertexCount; i++) {
res[i] = (int) Math.round(r * Math.sin(angle)) + y;
angle += addAngle;
}
return res;
}
}
}
EDITS
04/Jan/12 - changed Override paint(g) to paintComponent(g) - still doesn't detect collision properly.
05/Jan/12 - created a SSCCE to more easily demonstrate the problem.
For reference, this short example examines a transformed Polygon using the contains() method. The result seems correct to the nearest pixel. You might compare it to your result.
Listen on the panel, rather than the JFrame. The offset you are seeing is from the titlebar.
you have override paintComponent(Graphics g) for Swing JComponents instead of method valid for AWT paint(Graphics g), more in tutorials 2D Graphics and Performing Custom Painting
you have override paintComponent(Graphics g) for Swing JComponents instead of method valid for AWT paint(Graphics g)

Categories