My code plots 5000 points of time series data in a panel that is 581 pixels wide by default, but this width changes when the user resizes the window. My code also plots several rectangular markers that each identify a local maximum/peak in this same space.
I need to enable the user to right click on any of the rectangular-peak-markers so that the user can manually delete any false peak. The problem is that my code is reporting different x-coordinates than expected when the user right-clicks on a peak-marker. I suspect that the reason may have to do with rounding error in converting from 581 x-pixels back to 5000 data indices. But I am not certain of the reason.
Can anyone suggest a solution that enables my users to manually select one of the above-described peak markers by right-clicking on it?
I am enclosing relevant sections of the code below. My actual code is very, very long, and too complicated to post. But the relevant portions below should be enough for someone to see the logic of my approach, and to then suggest a more effective approach.
The code that declares the class in question is:
class SineDraw extends JPanel implements MouseMotionListener, MouseListener {
// lots of code, including the two segments excerpted below
}
This segment of code overloads the paintComponent of the JPanel so that my data is plotted:
// declare some variables
ArrayList<Double> PeakList = new ArrayList<Double>() // this ArrayList is populated by an extraneous process
visiblePoints = 5000
hstep = getWidth()/visiblePoints //=581/5000 by default, but will change when user resizes window
int numPeaks = PeakList.size();
// scale (y-coordinate) data relative to height of panel
pts = new double[visiblePoints]
for (int i = 0; i < pts.length-1; i++){pts[i]=//data vertical scaled to fill panel;}
// plot the 5000 time-series-data-points within the 581 pixels in x-axis
for (int i = 1; i < visiblePoints; i++) {
int x1 = (int) ((i - 1) * hstep);
int x2 = (int) (i * hstep);
int y1 = (int)pts[i - 1];
int y2 = (int)pts[i];
g2.drawLine(x1, y1, x2, y2);
}
// plot a rectangle for each of the local peaks
for(int m=0;m<=(numPeaks-1);m++){
if(i==(int)(PeakList.get(m)){
int currentVal = (int)pts[(int)(PeakList.get(m)];
g2.drawRect((int)(PeakList.get(m), currentVal, 6, 6);
}
}
This section of code is for handling the right-clicking of the mouse:
public void mousePressed(MouseEvent e){
// check to see if right mouse button was clicked
boolean jones = (e.getModifiers()&InputEvent.BUTTON3_MASK)==InputEvent.BUTTON3_MASK;
if(jones==true){
// test the value returned as x-coordinate when user right-clicks (code always underestimates x-coordinate of local peaks by this test)
double ReverseHstep = visiblePoints/getWidth();
int getX_ConvertedTo_i = (int) (e.getX()*ReverseHstep);
System.out.println("getX_ConvertedTo_i is: "+getX_ConvertedTo_i );
// check to see if peaklist contains a value within the x-coordinates of the user-selected-rectangle
if(PeakList.contains((double)(e.getX()-3))
||PeakList.contains((double)(e.getX()-2))
||PeakList.contains((double)(e.getX()-1))
||PeakList.contains((double)(e.getX()))
||PeakList.contains((double)(e.getX()+1))
||PeakList.contains((double)(e.getX()+2))
||PeakList.contains((double)(e.getX()+3))
){
// handling code will go here, but for now it is a print test that never succeeds because x-coordinate is always underestimated
System.out.println("You just selected a peak!");
}
}
repaint();
}
I suggest you create objects (in this case Rectangles) for each thing you want to be clickable. Here is an over-simplified example of how you can make something you draw clickable. The key thing to take away from this is the mouseClicked method which will display a dialog only if the mouse clicked within the rectangle.
One tricky point is that I wasn't able to figure out how to make the rectangle filled in with color without drawing another rectangle over it. I'll leave that one for you ;-)
public class Canvas extends JPanel implements MouseListener{
private Rectangle rect = new Rectangle(100,100);
public Canvas(){
this.addMouseListener(this);
rect.setSize(100, 100);
}
#Override
public void paintComponent(Graphics g){
g.setClip(rect);
g.setColor(Color.RED);
g.fillRect(0, 0, 100, 100);
}
#Override
public void mouseClicked(MouseEvent e){
if(rect.contains(e.getPoint())){
JOptionPane.showConfirmDialog(this, "Click!");
}
}
// The rest of the MouseListener methods have been cut out
public static void main(String[] a){
JFrame frame = new JFrame("Canvas Thingy");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBounds(0, 0, 300, 300);
frame.add(new Canvas());
frame.setVisible(true);
}
}
Related
I have a 2D polygon that only has lines that are perfectly horizontal or perfectly verticle. There are 2 other posts relating to this. However, one is merging multiple rectangles at once into a polygon, and the other is written in javascript.
I would like to take a rectangle of any size and add it to an existing 2D Polygon. Some examples are below.
In the diagram, each box represents 1 pixel. The navy boxes are the points that data exists for and the red and green boxes show how these points are connected. The points of both the red polygon and the green rectangle are held in a linked list (the order of points is what determines what point connects to what). Red and Green boxes don't exist in memory, they're used here only to show connections.
A point is defined as having an x and y coordinate.
How would I go about merging the green rectangle into the red polygon?
I really appreciate any help given thanks!
Edit: Here is some code that draws these shapes:
public class Core
{
private static Polygon polygon = new Polygon();
private static Rectangle rect = new Rectangle();
public static void main(String args[])
{
new Core();
}
public Core()
{
polygon = new Polygon(new int[] {0,12,12,24,24,32,32,12,12,5,5,0},new int[] {0,0,4,4,-3,-3,13,13,15,15,8,8},12);
rect = new Rectangle(18,14,7,6);
Draw2D drawing = new Draw2D();
drawing.setVisible(true);
}
public class Draw2D extends JFrame
{
private static final long serialVersionUID = -1033542824627245527L;
public Draw2D()
{
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new BorderLayout());
add(new DrawPane(), BorderLayout.CENTER);
pack();
}
}
class DrawPane extends JComponent
{
private static final long serialVersionUID = 5457330297413941626L;
public DrawPane() {
setPreferredSize(new Dimension(1280,720));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setBackground(Color.black);
g2.clearRect(0,0,1280,720);
g2.translate(getWidth() / 2, getHeight() / 2);
g2.setColor(Color.white);
g2.setStroke(new BasicStroke(0.001f));
g2.draw(polygon);
g2.draw(rect);
}
}
}
Edit 2:
A partial solution that I have is to inflate the rectangle by pushing each side out by 1 pixel/box, then checking if the entire side is inside one of the red polygon lines. By inside I mean collinear. A green rectangle side is sitting inside one of the red polygon sides.
If it does sit inside then leave the points there, if not move them back to where they originally were. Repeat this clockwise until all 4 sides have ha their points moved or not. Duplicate point objects that reference the same point as a point object that already exists will be deleted so that only 1 point object references 1 point.
This is only part of a solution as this method doesn't work when a rectangle is added on the edge of a polygon (shown in the first image, 2nd and 3rd examples from the top down).
You can simplify your problem by separating it in different parts. So the issue is based on lines.
Horizontal case
1.
R1------R2
xxxxxx (overlaps)
G1------------G2
2.
R1----------R2
xxxxxxx (overlaps)
G1------------G2
3.
R1---------------R2
no overlapping
G1-----G2
...
distance: abs(R_y1 - G_y1)
range of overlap: x1 = max(R_x1,G_x1), x2 = min(R_x2,G_x2),
if (distance <= 1 && x1-x2 > 0) {
// both lines are neighbours and has a valid overlapping range
}
Vertical case
Analogous to horizontal
As your boxes are stored in linked lists, you iterate both boxes and calculate distance and range of overlap.
List<Line> greenBox; // {Line((28,30),(60,30)) , Line((60,30),(60,50)), ...
List<Line> redBox; // ...
for (Line r : redBox) {
for (Line g : greenBox) {
if (distance(r,g) <= 1 && rangeOfOverlap(r,g) > 0) {
// both lines matches and can be merged over the range of overlaps
merge(r,g);
}
}
}
This is just an example to inspire you. You'll need to check if the picked green and red line are both horizontal or both vertical.
The complexity of the algorhythm would be around at m^n (without merging) where n is the number of green lines and m is the number of red lines.
lol
I have a libgdx application that contains a class Button. The constructor of Button takes three arguements: Filename of graphics, position, and game (the latter being used for callbacks of various sorts).
The button scales itself based on the graphics provided, thus setting its width and height based on the properties of the graphics.
The main class, Game, when a click is detected compares the coordinates of the click up against the coordinates of the button combined with its width and height.
Now, the main issue is that there is a little bit of a horizontal offset between the button and the click coordinates, so the effect is that the graphics show up a few pixels to the right of the clickable area. I cannot for the life of me figure out the source of this discrepancy, so I would greatly appreciate some fresh eyes to see where I'm going wrong here.
Button, constructor and polling-method for clickable area.
public Rectangle getClickArea() {
return new Rectangle(pos.x - (img.getWidth() / 2), pos.y + (img.getHeight() / 2), w, h);
}
public Button(String assetfile, int x, int y, Game game) {
this.game = game;
img = new Pixmap(new FileHandle(assetfile));
pos = new Vector2(x, y);
this.w = img.getWidth();
this.h = img.getHeight();
}
A relevant snippet from InputHandler. It listens for input and passes on the event. Please note that the vertical click position is subtracted from the vertical size of the screen, as vertical 0 is opposite in InputHandler:
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
tracker.click(screenX, Settings.windowSize_Y - screenY);
return false;
}
ClickTracker (referenced as tracker in the above snippet), the Class that does the actual comparison between clicks and clickables:
public void click(int x, int y) {
Vector2 clickPos = new Vector2(x, y);
for (Tickable c : world.getPaintables())
{
if (!(c instanceof Unit))
continue;
if (((Clickable)c).getClickArea().contains(clickPos)) {
System.out.println("Clicked on unit");
}
}
for (Clickable c : clickables)
{
if (c.getClickArea().contains(clickPos)) {
c.clicked(x, y);
}
}
In short: The vertical alignment works as intended, but the horizontal is slightly off. The button graphics appear maybe around 10-20 pixels to the right of the clickable area.
I'll gladly post more info or code if needed, but I believe I have the relevant parts covered.
Edit:
As Maciej Dziuban requested, here's the snipped that draws the UI elements. batch is a SpriteBatch as provided by libgdx:
for (Paintable p : ui) {
batch.draw(new Texture(p.getImg()), p.getImgPos().x, p.getImgPos().y);
}
the getImgPos() is an interface method implemented by all drawable items:
public Vector2 getImgPos() {
return new Vector2(pos.x - (getImg().getWidth() / 2), pos.y);
}
It's worth noting that half of the horizontal image size is subtracted from the X pos, as X pos refers to the bottom center.
You have inconsistency in your position transformations:
Your clickArea's corner is pos translated by [-width/2, height/2] vector.
Your drawArea's corner is pos translated by [-width/2, 0] vector
They clearly should be the same, so if you want your pos to represent bottom-center of your entity (as you've explicitly stated) you have to change your getClickArea() method to, so it matches getImgPos().
public Rectangle getClickArea() {
return new Rectangle(pos.x - (img.getWidth() / 2), pos.y, w, h);
}
Side note: as Tenfour04 noticed, you create new texture each frame and this is huge memory leak. You should make it a field initialized in constructor or even a static variable given some buttons share the texture. Don't forget to call dispose() on resources. For more powerful asset management check out this article (note it may be an overkill in small projects).
I got a bit of a problem which I am completely baffled about. Not even sure where to start to debug the problem.
I have a JFrame which promotes the user to enter a number, lets say the number was 4. I sent this number to my paint component to create a vertical line of 4 x 50. SO the line should be 200 in length. Which it does.
But if I minimise and reopen the window, that 4 seems to be multiplied again by 50 giving 200. Then that again is multiplied by 50 making the line 10,000.
I placed my paint component code in hope that it would help as the issue must be there, but if more code is needed I'll be happy to post.
class mainPanel extends JPanel
{
int processes;
public mainPanel(int x) //the value x is passed from another class, this was the number the user chooses...i.e 4
{
processes = x;
setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 5));
}
public Dimension getPreferredSize() {
return new Dimension (1000, 1000);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
int storedProcesses = processes;
// Draw Title
g.setFont(new Font("TimesRoman", Font.PLAIN, 28));
g.drawString("We place the title here",380,50);
processes = processes * 50;
g.drawLine(100,100,100, processes+100); //Vertical (down) line
//labels for vertical line
g.setFont(new Font("TimesRoman", Font.PLAIN, 20));
int y = 125;
for (int i=1; i<=storedProcesses; i++) //this loop keeps repeating and getting larger for some unknown reason...then its repainting my JFrame
{
g.drawString(String.valueOf(i), 70,y);
y=y+50;
System.out.println("Loop" + storedProcesses); //used for debugging
}
g.drawLine(100,processes+100,1000,processes+100); //Horizontal (across) line
}
}
I've attached some screen shots to illustrate what's going on with 1 process as an example.
Before I minimise the window (this is fine):
This is after I minimise and re maximise the window
You can't control when Swing invokes the paintComponent() method. Therefore you should NEVER change a property of your class in the paintComponent() method. All variables that you manipulate in the paintComponent() method should be local variables.
processes = processes * 50;
Your current code is modifying the "processes" variable. Don't do this.
There are many questions of the converse, inserting a JTextPane into a JPanel. This is not my question. I need to be able to insert a custom JPanel (with drag and drop, drag, and mouse click listeners) into a JTextPane, which is then put into a JScrollPane, and finally put into a JFrame for displaying. The reason is because I need to have an image with support for resizing by dragging it within a chat client, which is itself primarily text.
Conveniently enough, there is a relevant method in JTextPane: insertComponent(Component c), but whenever I use it, my components end up being squished to exactly one line of text worth of space (even though they report having a larger size). This is perfect for plain buttons, but if I need anything larger, I'm out of luck. I can insert images by themselves just fine, with ImageIcons, but images wrapped inside a JPanel don't work at all (plus I can't add any listeners to ImageIcons, since they're not GUI elements; overriding one isn't an option).
Whenever a user drags an image into the chat client, this bit of code inserts the custom JPanel:
private void sendImage(BufferedImage im, int cl) throws IOException {
if(output == null) return;
//Send the image itself over to your friend
byte[] toSend = toBytes(im, cl);
sendString(nickname.hashCode() + "image"); //Header for image
output.writeInt(toSend.length); //Tells how many bytes to read.
output.write(toSend);
//Let the user know that the image was sent
float linmb = (float)(toSend.length / 1048576.0); //Size of file sent
addText("\n" + nickname + " sent an image! (" + linmb + " MB)\n", Color.RED.darker());
//Show the image itself
DraggerPanel d = new DraggerPanel(im, true);
text.insertComponent(d);
d.repaint();
//Spacer
addText("\n");
}
This is the source for DraggerPanel, the custom JPanel that holds an image:
public class DraggerPanel extends JPanel {
private BufferedImage image; //The image we're drawing
private Point startingPoint = null; //Starting point for resizing
private boolean first = true; //Is this the first drag?
private boolean lockedDrag; //If true, then lock x and y to be proportionally dragged.
public DraggerPanel(BufferedImage image, boolean lockedDrag) {
super();
this.image = image;
this.lockedDrag = lockedDrag;
//The listener for dragging events.
addMouseMotionListener(new MouseMotionListener() {
private int inWidth = 0, inHeight = 0; //Initial height and width values
private double ratio = 0; //Ratio of height to width for locked drag.
public void mouseDragged(MouseEvent m) {
if (first) { //If we're first, record initial position.
startingPoint = m.getPoint();
first = false;
inWidth = getWidth();
inHeight = getHeight();
ratio = (double)inHeight / inWidth;
} else { //Otherwise, change the size of the window.
if (!lockedDrag) {
int w = (int)startingPoint.getX() - m.getX();
int h = (int)startingPoint.getY() - m.getY();
setSize(Math.abs(inWidth - w), Math.abs(inHeight - h));
} else {
int w = (int)startingPoint.getX() - m.getX();
int h = (int)((double)ratio * w);
setSize(Math.abs(inWidth - w), Math.abs(inHeight - h));
}
}
repaint();
}
public void mouseMoved(MouseEvent m){
}
});
//Lets us know when you're not dragging anymore.
addMouseListener(new MouseAdapter(){public void mouseReleased(MouseEvent m){first = true;}});
//Set appropriate size.
if(image != null) setSize(image.getWidth(), image.getHeight());
else setSize(200,200);
//We're live, baby.
setVisible(true);
}
public void paint(Graphics g) {
if (image == null) super.paint(g);
else g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
}
}
Update 1: I followed #camickr 's advice, and updated the DraggerPanel to use setPreferredSize instead of setSize, as well as overrode paintComponent() instead of paint(). Now, the image has the proper height, but is stretched to the width of the JTextPane (which seems like what it was doing before). Furthermore, resizing doesn't seem to matter- the image doesn't change its size at all. Mouse events are definitely going through, but not affecting the size. It seems as though the original problem isn't fully resolved, since the JPanel's size isn't what I need it to be, and the solution to that will also lead to a solution to the resizing issue.
Update 2: I did it! I finally did it. To the future time travelers who have this issue, I basically yelled at the JTextPane by not only using setSize() in my overridden JPanel, but also setPreferredSize() and setMaximumSize(). The preferred one works well with height, and the maximum sets the width (God knows why). Thanks for your tips, #camickr!
my components end up being squished to exactly one line of text worth of space (even though they report having a larger size).
I would guess the size is not important.
I would think you need to override the getPreferredSize() method of your DraggerPanel to return the preferred size of the panel so the text pane can display the panel.
Also, custom painting is done by overriding the paintComponent(...) method NOT the paint() method.
Very simple thing I'm trying to do here. I would like to have 2 images on top of one another. When i use my mouse event dragged and clicked on the top image, the area of the top level image selected will fade and make the lower image visible.
The way I see it, there are 2 ways I can do this:
I can make the top image Transparent over time (within the selected area)
or
I can delete the pixels individually in a spray can style fashion. Think the spray can tool from MS paint back in the day.
Heres some very basic code that i started which just lays the images on top of eachother
PImage sand;
PImage fossil;
void setup()
{
size(400,400);
background(255,255,0);
frameRate(30);
fossil = loadImage("foss.jpg");
sand = loadImage("sand.jpeg");
}
void draw()
{
image(fossil, 0, 0, width,height);
image(sand, 0, 0, width,height);
smooth();
if (mousePressed) {
fill(0);
tint(255,127); //the opacity function
} else {
fill(255);
}
}
So has anyone any comments on these 2 ways of creating opacity or perhaps there an easier way I've overlooked?
Perhaps I wasn't clear in my Spec as the 2 comments below are asking for clarification.
In its simplest terms, I have 2 images on top of each other. I would like to be able to make some modification to the top level image which would make the bottom image visible. However I need to make this modification to only part of the top level image.
I would like to know which is the better option. To make part of the top image become transparent using tint() or to delete the pixels from the top layer.
Then I will proceed with that approach. Any indication as to how to do it is also appreciated.
I hope this clears up any confusion.
If you simply want to crossfade between images, it can be with tint() as you code suggest. You were in fact quite close:
PImage sand;
PImage fossil;
void setup()
{
size(400, 400);
fossil = loadImage("CellNoise.jpg");
sand = loadImage("CellVoronoi.jpg");
}
void draw()
{
//tint from 255 to 0 for the top image
tint(255,map(mouseX,0,width,255,0));
image(fossil, 0, 0, width, height);
//tint from 0 to 255 for the bottom image - 'cross fade'
tint(255,map(mouseX,0,width,0,255));
image(sand, 0, 0, width, height);
}
For the "spray can style " erosion you can simply copy pixels from a source image into the destination image. It's up to you how you loop through pixels (how many, what order, etc.) to get the "spray" like effect you want, but here's a basic example of how to use the copy() function:
PImage sand,fossil;
int side = 40;//size of square 'brush'
void setup()
{
size(400, 400);
fossil = loadImage("CellNoise.jpg");
sand = loadImage("CellVoronoi.jpg");
}
void draw()
{
image(fossil, 0, 0, 400, 400);
if(mousePressed) {
for(int y = 0 ; y < side ; y++){
for(int x = 0; x < side; x++){
//copy pixel from 'bottom' image to the top one
//map sketch dimensions to sand/fossil an dimensions to copy from/to right coords
int srcX = (int)map(mouseX+x,0,width+side,0,sand.width);
int srcY = (int)map(mouseY+y,0,height+side,0,sand.height);
int dstX = (int)map(mouseX+x,0,width+side,0,fossil.width);
int dstY = (int)map(mouseY+y,0,height+side,0,fossil.height);
fossil.set(dstX, dstY, sand.get(srcX,srcY));
}
}
}
}
Note what I am simply looping to copy a square (40x40 in my case), but you can find other fun ways to loop and get different effects.
Have fun!