tags:

views:

26

answers:

2

I'm using drawLine() and friends to paint a graph onto a JPanel. There are tens of thousands of points to graph, so it takes 3-5 seconds. I want to have a viewport, like the blue one illustrated below, over the graph.

graph

I would like this viewport to be updatable, as I have a MouseMotionListener on the JPanel that the graph is on. The problem is, if I redraw the viewport (the square) as it is now, (using drawRect()), I have to redraw the entire graph, which isn't desirable.

I've read some about GlassPanes, which may be useful for solving this problem, but they evidently are a part of JFrames, and not JPanels. (I have this panel along with other components inside of a JFrame already.)

What would be a nice way to handle this, staying in Swing and being efficient?

For what it's worth, I'm actually coding in Clojure, but that shouldn't change anything here.

Thank you very much!

Isaac

+1  A: 

From the screenshot you posted, it shouldn't be taking 3-5 seconds to paint. You shouldn't have to use layers to accomplish what you're talking about. I have an application with as much data as you describe, but a more complicated visualization. It also has the same sort of viewport feature you discuss. It has no problem painting, and most times paints in 3-5 milliseconds.

Make sure that the data is stored in a way that the paint thread can access it quickly. The paint thread should spend all its time painting, and if it has to search through data to find what it needs, then it's wasting its time.

Collect timing information inside the paint method and write it to the console. Track down how much time each part of the method takes and figure out which line(s) of code are causing the delay. Figure out how to get rid of this delay. The paint method should only take 3-5 milliseconds to run. You're off by a factor of 1,000 - something can be optimized.

Make sure you're only painting what actually will be visible on the screen. Don't waste time painting things that will appear off-screen. Also make sure you're not painting really long lines that are only partly visible on the screen. (Like, if you zoom in a hell of a lot, and even the smallest line is now huge.) The whole thing has to be painted, whether or not it's visible.

I'd be surprised, based on the screenshot, that you have to use any sort of layering to accomplish this. 3-5 seconds is way too long for even one paint cycle.

Erick Robertson
The blue box seen in the screenshot above is for the user to drag around: it should/will send the coordinates it has to the graph above it, along with the width of the viewport, in seconds (to be implemented later), so the graph above can repaint to be as big as necessary. (http://cl.ly/1rDa for a picture of the entire setup, which is what takes 3-5 seconds to paint!) Thanks!
Isaac Hodes
For instance, with the viewport as shown, the data might look like this (http://cl.ly/1qhc) in the upper, main, graph. It's the bottom graph that I basically want to be a static image that I can draw and redraw the viewpoint over. The thing is, I have to graph it one in the beginning, so I didn't want to sav it as an image and then paint the image right away, again.
Isaac Hodes
This looks so much like the application I'm working on - it's almost scary. I offer the same functionality with the overview on the bottom and the viewport highlighting what appears on the top. I allow people to zoom out far enough to display as much data as you have shown. I don't have any speed issues. So check your data retrieval routines in the paint routine. Make sure your paint thread isn't spending a bunch of time getting the data it needs to display. Optimize that paint method!
Erick Robertson
Can you summarize the data for the bottom graph so you don't have to paint every point on it? Even if you're only going to paint it once, you shouldn't have it take 3-5 seconds to paint. Every time you resize the window it's going to have to repaint, and that's ridiculous.
Erick Robertson
I will definitely do that–I just hacked it up this morning, so it's got some rough edges for sure, but there's *got* to be a better way to draw a little box over a JPanel than redrawing the entire JPanel and `drawRect()`ing on top of that for every time someone wants to move!
Isaac Hodes
On second thought, you may have more data - zoomed in I can see there's a lot more than I originally thought. It should still only be a factor of 4 or 5, not 1000. Even if it was 20ms it would be quite manageable, and I think that's a reasonable target. Can you post the code from your paint method?
Erick Robertson
You can use a JLayeredPane, or you can get the JXLayer library. I chose not to use either because I still want to have some additional effects which may trigger a redraw anyways, even in those classes. For example, I paint a semi-transparent rectangle inside the viewport box. It looks nice and fancy that way. I might be wrong about forcing a redraw anyways - it's quite possible that the design can be further optimized by using JXLayer or a JLayeredPane. But it's fast enough that I don't need it.
Erick Robertson
There's a ton to clean up here, and I'm not even implementing `repaint()` yet, so just explicitly call `xy-plot` when I need to graph it (this is the proof of concept! I'll make it better, I promise!). The code is at the link, but it's in Clojure–the gist of it should come through, though, it's written kinda imperatively. http://ideone.com/G4PM2
Isaac Hodes
I don't override repaint()... you're not planning to do that, are you? Typically, you don't want to. Also, I'm curious as to why you're using Clojure. I assume it's not for graphical reasons?
Erick Robertson
I'm using Clojure because it's fun, I'm more comfortable with it since it's been a while since I'd used Java, and I started the project doing lots of data processing, which is nice to do with a functional language. It's also, generally, shorter output. And er, `paintComponent()`, not `repaint()`.
Isaac Hodes
Isaac Hodes
I'm not sure. I'm using custom decorations with custom controls for resizing, so I had to enforce the minimum size manually. I don't know if you can even do this on frames, normally.
Erick Robertson
+2  A: 

My approach to this sort of problem was to consider the volatility of the data. Unless the chart is ticking along in real time, the only thing that will change from paint to paint is the position of the blue box. One way to minimize the paint time is to not repeat work you have already performed. For example:

protected void paintComponent(Graphics g) {
    // paint chart
    if (dirty || buffer == null) {
        buffer = new BufferedImage(getWidth(), getHeight(),
                                   BufferedImage.TYPE_ING_ARGB);
        // Paint the chart onto the buffered image
        dirty = false;
    }
    b.drawImage(0, 0, buffer);
    // Draw blue box
    ...
}

The very first call to paintComponent initializes the buffer and does the heavy lifting of painting the graph. Thereafter, the buffer is just painted. If you need to handle the case where the display is redrawn to show a different part of the data, you just need to invalidate the buffer.

public void invalidateBuffer() {
    dirty = true;
    repaint();
}
Devon_C_Miller
Interesting–I'll be trying that out, thank you! I didn't even consider using a BufferedImage. That'll be interesting to try out in Clojure.
Isaac Hodes