To Those Who Use JFreeChart for Dynamic Plotting/Large Sets

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
Mercer
Posts: 11
Joined: Mon Jun 05, 2006 6:55 pm

To Those Who Use JFreeChart for Dynamic Plotting/Large Sets

Post by Mercer » Fri Aug 25, 2006 7:53 pm

An Open Letter to Those Who Use JFreeChart for Dynamic Plotting and Large Datasets

also titled

What We Have Learned!

Introduction:

The following long post is directed to all those people (just like us) who use these forums to try and figure out JFreeChart. Let me rephrase: ...to try and figure out how to use JFreeChart for something OTHER than it was originally intended. JFreeChart as a package for static plotting is excellent, and because it is, is the package we elected to use in our application. An application that unfortunately, is anything BUT static.
This is intended to provide some help and answers to the several posts that we all see from day to day asking how JFreeChart can be used dynamically and for large sets (upwards of a hundred thousand points, or even upwards of a million). This post may not provide anything new to many, it may just be a rehash of several older posts that we have all seen over the last year or so, in which case all it will serve to do is localize them in one place. But the licensing agreement says we should share the changes we make to the package doesn't it? We're just taking it a little more literally than most.
Finally, a very LARGE amount of credit for all of these changes and 'things we've learned' go to other previous posters. Most notably, the FAST classes were originally conceived and written by Lindsay Pender, acquired from the JFreeChart Forums posted Tue Jan 25, 2005 titled "Another dynamic data solution". Also credit goes to Gumshoe (and others in the thread) in the post Mon Mar 14, 2005 titled “High Performance Apps” for the invaluable '2-pixel' check.

References:
We include our various FAST classes, taken from Lindsay Pender and updated to work with version 1.0 (along with massive changes for our own project), and the few renderers we needed modified for efficiency. PLEASE NOTE: Not all the changes in there will apply to your specific needs! Pick and choose which changes you like and take 'em and play with 'em. We have commented as much as time has permitted, but as we're moving on to other projects the comments may be a less comprehensive than we might like.

Background:
Just so you all know where we're coming from with this, our application is a Swing based GUI that has to dynamically chart (in realtime) an unknown number of series that contain an unknown amount of data for, you guessed it, an unknown amount of time. Right away you can see where things like memory efficiency and speed began to concern us. It is quite possible for our users to acquire literally millions of points in an 'initial load' situation, and then sit back and watch for a couple of DAYS as more and more points come in. At any point they can pause the retrieval and scroll through the past data, they can zoom, print and do any of the lovely things JFreeChart allows, with some few exceptions (for example we disabled tool-tips, just TOO much memory required, more on this later).

Problems and Our Solutions:

1)When loading or adding data, the time required to add each successive point gets longer and longer!

This problem is caused by the events being fired by each 'add' call, called notification. Whenever JFreeChart accepts a new point there a few things it needs to know about it, and to figure out these things, it looks back at EVERY SINGLE point in the series so far. So for the first 1000 or so points, not a problem. Get up to more than that, and it gets a little unwieldy. To add the 999,234 point it goes back and loops linearly through the previous 999,233! However there IS a solution already present in JFreeChart. If you look at the add() method there is one that has a boolean flag at the end of it. This turns off the notification for that particular add(). What we did was simply load our 999,233 points with notification off, then turn it on for the last one. Presto, all the axis's are correct and it only has to loop through the previous points once. From then on, we just check the values manually against the axis. If one comes in out of range, we just fireSeriesChanged() and we're set.

2)Drawing my graph takes too long! Minutes and minutes! (or more)

This is just the way the renderes currently are. Our benefactor David Gilbert never claimed they were Corvettes, more like reliable Civics, so here are some things you can do to make them faster. You can check out the changes we made to our renderers to see what I'm talking about. Still not Corvettes, but maybe something in the line of a lowered Subaru WRX with racing stripes.
The big one we added here was Gumshoe's '2-pixel' check. This means that if, because of the scale of a plot, several datapoints translate with valueToJava2D() to exactly the same PIXEL point, don't bother drawing more than one of them. This works EXTREMELY well, and it works over zooming and at every scale.
The other changes in the renderer are intended for the appendedDraw stuff, which we'll get to a bit later.


3)My dataset is enormous, and I'm running out of memory!

Well, this is a problem to some extent, because obviously the more points you store, the more space you need right? However there are some things you can do to make this as efficient a data structure as possible.
Firstly, the TimeSeries defined by JFreeChart are very convenient, but not particlarly efficient. We started with TimeSeries, but then dropped them in favour of FixedMilliseconds, then dropped THAT in favour of FASTXYSeries. We just store our own primitive double values (the most memory efficient) then stole the sorting algorithm out of the TimeSeries add() and made it work for us. While Java and objects are great and easy and all that, a great use of space they don't make.
Also, the Entity Collection! That's the collection of little rectangles that the renderer creates that stores a reference to where on the screen each datapoint has been drawn. If you're using tool-tips, then you need this to figure out which point your mouse is hovering over. If memory is a concern DONT USE THIS! It's... well... massive. I think we even went so far as to completely erase the code from our FAST renderers, to be sure it didn't get turned on even by accident. If you have 100,000 points, that's 100,000 Rectangle2D objects and their associated index references. Not cheap.
Double–buffering can also be a memory hog but you may choose to keep it (as we did) because the improvements in performance outweight the losses in memory. Actually if you choose to use the FAST classes, I don't think we ever got them working without double-buffering because we decided early on we wanted it, and never tried to make single buffering work.
Speaking of FAST classes that brings us to...

4)If I dynamically add points to a huge dataset it takes forever to draw!

Ahh, HERE is the crux of the JFreeChart dynamic problem. For every call to repaint() forces a COMPLETE redraw! This is not JFreeChart's fault by any means, think of the problem from it's perspective. If I have a graph all nicely drawn and static, it can just sit there looking pretty right? If a new point gets added... well then just redraw the whole damn thing, speed isn't an issue. Makes sense...
Until you get a bunch of people, like us, who demand that it add AT LEAST ONE NEW POINT EVERY SINGLE SECOND! Which means it tries to redraw itself every single second with the new information, but the full redraw takes upwards of 5 seconds, which means there's 5 seconds of new points to waiting to be added, which then takes 6 seconds to draw, which means theres 6 seconds of new points... you get the idea.
Here's where the FAST classes come in. Essentially 2 routes of drawing have been created. One is the normal JfreeChart repaint() (as defined in paintComponent() ) which does exactly what JFreeChart normally does. It wipes out the background, it draw fresh axis's (axises? Dunno), new grids, new lines etc. etc. That's known as the long route. The FAST classes add the second dataAppended route (this is where credit goes to Lindsay Pender). This route moves from ChartPanel down to the JFreeChart, all the way down to the XYPlot, only taking the bare minimum of calculations it needs from each step (things like dataArea, plotInfo and the like) and
passes to the renderer ONLY the points that have not been drawn since the last time the repaint() was called. Great! We now have a short route that doesn't perform a full draw with every repaint(). It only has to draw a few lines connecting a few dots directly on the graphic object of the chartPanel, something that can easily be accomplished in under a second with relatively little CPU usage.
Lindsay's original concept fired a dataAppendedEvent() every time a new point was added. This didn't work for us, because every second we could get 50+ some odd points, and 50 events flying around brought efficiency right back to where we started. So our trigger for appendedDraw is a manual method call, only made after all the new data is collected and stored. However, the dataAppendedEvent() code is still there, feel free to use it if you wish.
Look at the various FAST classes at the url below, and peruse them for yourself. Again, these changes are specific to OUR application in the same way the originals we got from Lindsay Pender were for hers (his? Sorry Lindsay, we don't know). For example, those original classes were for only XYPlots. Every one of our plots is a CombinedDomainXYPlot, so we had to create a FAST class for those. There's a few other changes, many of which are to make them work with the 1.0 release.

Results:
So what did we get out of all this? Well, we can load about 5 million points of time based data, over multiple double-buffered chartPanels using only about 150megs of RAM (give or take), which will refresh every second with newly gathered information using an average of < 20% CPU resources. Now if the graph has to refresh, or shift left (because the new points are plotting at the right edge of the graph we need more room), then we're still talking about a full redraw which can take 10 or 20 (or more) seconds with 100% CPU , but hey you can't fix everything...

Further Efficiency Suggestions!

The Renderers:
Another thing you can do (which we didn't, due to time constraints) is modify the renderers to receive the whole dataset at once, instead of being called for each point. This would streamline things like setPaint() and series.getShape() and the like. Calling 'em once, instead of a hundred thousand times is admittedly not a HUGE savings according to profiling, but every little bit helps.
You could also decide which points are within the current domain and only play with those, though David Gilbert has already said he's thinking of a way to do that.
Profiling also tells us that the NUMBER ONE use of time and processing in the renderers are calls to valueToJava2D(). If the renderer could have access to the whole dataset at a time, you could calculate the current point with valueToJava2D() and then store the results of that calculation for use with the next point, cutting the number of those expensive calls (and the time required to make them) in HALF.

PaintComponent:
The big problem with the speed of JFreeChart for dynamic work is packing all that drawing code into the paintComponent() method itself. This means that, unmodified, every single call to repaint() has to physically redraw all that stuff every time. While repaint calls do coalesce, there can be dozens that happen beneath the surface, for unknown reasons, which really bog things down. In theory, all paintComponent() should do is provide a bufferedImage or somesuch and some AffineTransform stretching. This means that any dirty regions or the like could be flashed up from memory almost instantly. Indeed, this is somewhat done by having the refreshBuffer flag, which only provides the last updated bufferedImage if the flag is false, just like I'm suggesting. Unfortunately anytime we want to update the chart, we have to call repaint again with the flag true, which takes up multiple seconds of processing time, and because repaint() calls are in the main event loop, this crushes anything else trying to get through during that time.
Perhaps all draw() calls could modify some maintained background image, and paintComponent() provides the latest version of that image(). Yes, I can see the problems that would have to be overcome, like what if the image was requested while a draw() was taking place, but the basic theory seems to make sense. At least it would keep the GUI's responsive. If anyone manages something like this please post the code.

Closing:
So if you're reading this, you've hopefully read this exhaustively long post right from the beginning, and if you're REALLY lucky, you've found something useful within.
We would definitely like to thank David Gilbert and the JFreeChart team for keeping us from having to re-invent the wheel with this graphing package. Also much thanks to all the people who post on this forum, especialy those who take the time to actually answer those posts.

Cheers!

The FAST classes and other modified classes can be found at the following url:

Uhh yeah now here's the thing. We don't have anywhere to post this 78k or so zip file... our company is behind so many firewalls it's just impractical. Anyone able to volunteer to take this and make it accessible for a while?


One last note, there are possible undocumented changes in the orginal JfreeChart classes of some variables from 'private' to 'protected' status. Don't worry, your compiler will tell you where they are...

Brian Johnson
Posts: 8
Joined: Mon Aug 28, 2006 8:21 am

Post by Brian Johnson » Mon Aug 28, 2006 4:40 pm

hi there your post was most informative and explains the problems that I am having trying to plot my results, Please Help me, this thing is vital and any point in the right direction would be greatly appreciated
Thank you
Brian

Crusy
Posts: 9
Joined: Mon Aug 28, 2006 6:22 pm

Re: To Those Who Use JFreeChart for Dynamic Plotting/Large S

Post by Crusy » Mon Aug 28, 2006 6:36 pm

Mercer wrote:The FAST classes and other modified classes can be found at the following url:

Uhh yeah now here's the thing. We don't have anywhere to post this 78k or so zip file... our company is behind so many firewalls it's just impractical. Anyone able to volunteer to take this and make it accessible for a while?
Hi there,
I'm working on a chart with "a lot" of TimeSeries, each of them containing ~8100 values - so I'm very interested in the changes you made. I have no experience with offers like this, but I could place the files on my webspace. At least "for a while", because I have not unlimited traffic (20 GB/month). Do you think, this could help?
C.

PS: "axes" :wink:

Mercer
Posts: 11
Joined: Mon Jun 05, 2006 6:55 pm

Post by Mercer » Mon Aug 28, 2006 7:14 pm

The zipped file of all the fast classes and the renderes and stuff is something like 80k or so, so I can't IMAGINE that theres so much interest that a limit like that would come into play.

If you can provide me with an email address, I'll just send the file off to you and you can then post the url of where to find it. That would be great!

As those of you who have read this can guess, it kind of takes looking at the classes themselves to figure out what the hell I'm actually talking about, heheheh.

Brian Johnson
Posts: 8
Joined: Mon Aug 28, 2006 8:21 am

Post by Brian Johnson » Mon Aug 28, 2006 7:44 pm

I could also place it on our space for quite some time, would be more than happy to offer our space to help others.

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Post by david.gilbert » Tue Aug 29, 2006 4:05 pm

Thanks for the feedback, I'm sure this will help a lot of other developers. Feel free to post your code into the Patch Manager at SourceForge, then people can download it directly from there:

http://sourceforge.net/tracker/?group_i ... tid=315494
Mercer wrote:2)Drawing my graph takes too long! Minutes and minutes! (or more)

This is just the way the renderes currently are. Our benefactor David Gilbert never claimed they were Corvettes, more like reliable Civics, so here are some things you can do to make them faster. You can check out the changes we made to our renderers to see what I'm talking about. Still not Corvettes, but maybe something in the line of a lowered Subaru WRX with racing stripes.
The big one we added here was Gumshoe's '2-pixel' check. This means that if, because of the scale of a plot, several datapoints translate with valueToJava2D() to exactly the same PIXEL point, don't bother drawing more than one of them. This works EXTREMELY well, and it works over zooming and at every scale.
The other changes in the renderer are intended for the appendedDraw stuff, which we'll get to a bit later.
This kind of "resolution dependent" optimisation is something I'd like to incorporate into the renderer, but I need to provide some mechanism that ensures that it only gets applied when drawing to the screen, and not other devices that support much higher resolution (for example, most vector based output formats). Ideas and suggestions are welcome...maybe it's something as simple as defining a flag and maximum resolution in the renderer.
Mercer wrote:3)My dataset is enormous, and I'm running out of memory!

Well, this is a problem to some extent, because obviously the more points you store, the more space you need right? However there are some things you can do to make this as efficient a data structure as possible.
Firstly, the TimeSeries defined by JFreeChart are very convenient, but not particlarly efficient. We started with TimeSeries, but then dropped them in favour of FixedMilliseconds, then dropped THAT in favour of FASTXYSeries. We just store our own primitive double values (the most memory efficient) then stole the sorting algorithm out of the TimeSeries add() and made it work for us. While Java and objects are great and easy and all that, a great use of space they don't make.
Also, the Entity Collection! That's the collection of little rectangles that the renderer creates that stores a reference to where on the screen each datapoint has been drawn. If you're using tool-tips, then you need this to figure out which point your mouse is hovering over. If memory is a concern DONT USE THIS! It's... well... massive. I think we even went so far as to completely erase the code from our FAST renderers, to be sure it didn't get turned on even by accident. If you have 100,000 points, that's 100,000 Rectangle2D objects and their associated index references. Not cheap.
JFreeChart 1.0.2 includes two new datasets that are reasonably space efficient, being based on double[] arrays. Maybe I should have done a float[] version for each.

The entity collection is very resource intensive, so don't use it for large datasets. For tooltips, it might be possible to do a more dynamic tooltip construction that doesn't require the use of the entity collection...it is possible to convert the mouse location to data coordinates, so I'm thinking it might be possible to define an alternative tooltip mechanism that creates tooltips on-the-fly after being supplied just the data coordinates. I'll have to experiment with that.
Mercer wrote:The Renderers:
Another thing you can do (which we didn't, due to time constraints) is modify the renderers to receive the whole dataset at once, instead of being called for each point. This would streamline things like setPaint() and series.getShape() and the like. Calling 'em once, instead of a hundred thousand times is admittedly not a HUGE savings according to profiling, but every little bit helps.
You could also decide which points are within the current domain and only play with those, though David Gilbert has already said he's thinking of a way to do that.
Plotting the dataset all at once, rather than iterating through each data item would certainly be more efficient. Originally I went for the "item at a time" approach because it seemed like a nice way to break down the problem of drawing charts. It does have some nice properties (for example, you can change the series rendering order in the plot, rather than having to support it on a per-renderer basis). But if I could go back and make the choice again, I'd spend a bit of time experimenting with a "whole dataset at a time" approach.

Regarding the last optimisation you mentioned, the datasets define a DomainOrder, so for ordered datasets (like the time series) the renderer could start with the first visible data item and end once it reaches the upper bound of the domain axis. This is something that I do want to implement soon. I also want to add a performance measurement framework so I can get some solid information on what works and what doesn't.
Mercer wrote: Uhh yeah now here's the thing. We don't have anywhere to post this 78k or so zip file... our company is behind so many firewalls it's just impractical. Anyone able to volunteer to take this and make it accessible for a while?
As I mentioned at the top, feel free to post the code in the Patch Manager at SourceForge.
David Gilbert
JFreeChart Project Leader

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

knc
Posts: 19
Joined: Mon Aug 07, 2006 11:12 pm

Post by knc » Wed Aug 30, 2006 4:57 pm

Hi

Is the code that was mentioned above available yet? I'm interested in trying it out but I haven't seen it posted on the Sourceforge page.

knc

Crusy
Posts: 9
Joined: Mon Aug 28, 2006 6:22 pm

Post by Crusy » Wed Aug 30, 2006 4:58 pm

Hi,
I sent an address to Mercer via private message, but have no reply by now
C.

Mercer
Posts: 11
Joined: Mon Jun 05, 2006 6:55 pm

Post by Mercer » Wed Aug 30, 2006 7:42 pm

You guys don't have it yet? damn... I sent the file off to BOTH of the two gentlemen who volunteered it.

I'll send it again to both the emails I have been sent privately AND I'll take David up on his offer to post it on SourceForge.

Sorry bout that folks, not sure what happened there. Gimme a few hours.

Oh, possible error. Check your junk email filters for an email from 'usethisnow@yahoo.ca'. We're under various high-level security stuff here (don't ask) so I used a junk email address to send the file. I've resent it just now, but double check that. I'm waiting for a confirmation email from SourceForge, so if the junkaddress doesn't work it'll STILL get posted.

Again, sorry for the delay.

Crusy
Posts: 9
Joined: Mon Aug 28, 2006 6:22 pm

Post by Crusy » Wed Aug 30, 2006 10:47 pm

OK, here the URL:
http://www.crusy.net/files/JFreeChartFAST.zip
have not checked it now, it's to late here :)
Crusy

[edit]
PS: Thanks :-)

Brian Johnson
Posts: 8
Joined: Mon Aug 28, 2006 8:21 am

Post by Brian Johnson » Thu Aug 31, 2006 9:00 am

Hi Mercer Sorry I havnt gotten back to you yet

I am trying to implement your idea into our program. If I am able to get it running the powers that be will allow me to put it up. Unfortunately I am having a bit of an issue to get it to work my main problen is implemeting the FastXYPlot.

Brian Johnson
Posts: 8
Joined: Mon Aug 28, 2006 8:21 am

Post by Brian Johnson » Thu Aug 31, 2006 9:09 am

OK now I am Good and solidly lost

I know that you are very busy and if you have a little patience it would
be great if you could help me get this thing going I've managed to get
the all the Classes to Compile but I am having great difficulty Implementing it. firstly I Use a TimeSeries so naturally I would have to create a fast TimeSeries class. The problem
I am having is the following, here is my code:

1 heading = head;
2 dataSet = dataS;
3 XYBarDataset data = new XYBarDataset(dataSet,100);
4 JFreeChart chart = ChartFactory.createTimeSeriesChart
5 (null,xAxis,yAxis,data,true,true,false);
6 chart.setBackgroundPaint(Color.WHITE);
7 subtitle1 = new TextTitle(heading);
8 subtitle1.setFont(new Font("dialog",0,12));
9 chart.addSubtitle(subtitle1);
10 XYPlot plot = chart.getXYPlot();
11 plot.setBackgroundPaint(new Color(235,235,235));
12 plot.setDomainGridlinePaint(new Color(128,128,128));
13 plot.setDomainGridlinesVisible(true);
14 plot.setRangeGridlinePaint(Color.black);
15 plot.setDomainCrosshairVisible(true);
16 plot.setRangeCrosshairVisible(true);
17 plot.setDomainCrosshairPaint(Color.BLACK);
18 plot.setRangeCrosshairPaint(Color.BLACK);
19 plot.setDomainCrosshairStroke(new BasicStroke(1f));
20 plot.setRangeCrosshairStroke(new BasicStroke(1f));
21 rangeAxisLine = (NumberAxis)plot.getRangeAxis();
22 DateAxis domain = (DateAxis)plot.getDomainAxis();
23 rangeAxisLine.setStandardTickUnits
24 (NumberAxis.createIntegerTickUnits());
25 rangeAxisLine.setAutoRange(true);
26 domain.setAutoRange(true);
27 StandardXYToolTipGenerator tool = new
28 StandardXYToolTipGenerator("{1}, {2}",new SimpleDateFormat("d-MMM-
yyyy"),new DecimalFormat("0.00"));
30 StandardXYItemRenderer ren = new StandardXYItemRenderer
31 (StandardXYItemRenderer.SHAPES_AND_LINES);
32 ren.setToolTipGenerator(tool);
33 ren.setShapesFilled(true);
34 plot.setRenderer(0,ren);
35
36 cPanel = new ChartPanel(chart);
37 setPanel(cPanel);
38 plotType = LINE_GRAPH;

now Since I know the last thing you want to do is teach me how to
program I just have a few short questions
1. in Line 10 when I try to implement the FastXYPlot I get a class cast
exception FastXYPlot plot = (FastXYPlot)chart.getXYPlot();

I just want to tell you my idea of what you have said and your comments
if Im on the right track and if there is anything that I may have
missed. like I said I would have to create a fast TimeSeries class and
then implement the FastXYPlot is there anything else that I have Missed.
I am only intersested to plot at most 360 points on 6 separate graphs in
about a minute therefore 60 points per graph in 10 seconds tops.

Thank you
Brian

Mercer
Posts: 11
Joined: Mon Jun 05, 2006 6:55 pm

Post by Mercer » Thu Aug 31, 2006 1:50 pm

Welll, my first thought is that your ChartFactory doesn't create a FastXYPlot, it creates a regular XYPlot. Cant' cast an object to something lower in the hierarchy, and FastXYPlot extends XYPlot.

Otherwise, why use TimeSeries? A lot of the efficiency was gained in making a very simple streamlined DataItem, and the FastXYSeries works exactly like a TimeSeries (it sorts the data according to the X value which is essentially time), storing as primitve double values, not objects, in order to saves space.

Oh, just reread your post. If you have only 360 points MAX, don't bother with any of this stuff. Just add your points WITHOUT notification untill you hit the last point, then add WITH notification. It doens't take the regular version of JFreeChart 10 secodns to draw 360 points (long as you have notification off). 10 seconds is a couple of hundredthousand points at best. DO use the improved renderers, that will help the drawing speed, but dont bother with the minimal draw if that's your maximum number of points, just isn't worth the headache of trying to make it work.

Hope this helps.

Brian Johnson
Posts: 8
Joined: Mon Aug 28, 2006 8:21 am

Post by Brian Johnson » Fri Sep 01, 2006 10:14 am

Thank you for your reply but I got to say I missed the point when you say WITHOUT notification do you mean calling the TimeSeries.setNotify(false) or am I missing the plot, please can you clarify for me please.
Thanks alot
Brian

skunk
Posts: 1087
Joined: Thu Jun 02, 2005 10:14 pm
Location: Brisbane, Australia

Post by skunk » Mon Sep 04, 2006 2:56 pm

david.gilbert wrote: Regarding the last optimisation you mentioned, the datasets define a DomainOrder, so for ordered datasets (like the time series) the renderer could start with the first visible data item and end once it reaches the upper bound of the domain axis. This is something that I do want to implement soon.
Here are the changes that I needed to make to implement this optimization. Note that you will not see any improvements from this unless you use setRange() on the domainAxis in order to limit the visible range being plotted. I have some custom code that recalculates the range of the domain axis whenever the data changes so that a fixed number of items is always displayed and I have a scrollbar to shift the window over the dataset.

The code fragments and line numbers refer to the 1.0.2 sources
org.jfree.chart.plot.XYPlot

These nested loops are where all the time is spent while plotting, basically for each pass, for each series, for each item -- plot.

Code: Select all

2633                SeriesRenderingOrder seriesOrder = getSeriesRenderingOrder();
2634                if (seriesOrder == SeriesRenderingOrder.REVERSE) {
2635                       //render series in reverse order
2636                    for (int pass = 0; pass < passCount; pass++) {
2637                        int seriesCount = dataset.getSeriesCount();
2638                        for (int series = seriesCount-1; series >= 0 ; series--) {
2639                            int itemCount = dataset.getItemCount(series);
2640                            for (int item = 0; item < itemCount; item++) {
2641                                renderer.drawItem(
2642                                    g2, state, dataArea, info,
2643                                    this, xAxis, yAxis, dataset, series, item,
2644                                    crosshairState, pass
2645                                );
2646                            }
2647                        }
2648                    }
2649                }
2650                else {
2651                       //render series in forward order
2652                    for (int pass = 0; pass < passCount; pass++) {
2653                        int seriesCount = dataset.getSeriesCount();
2654                        for (int series = 0; series < seriesCount; series++) {
2655                            int itemCount = dataset.getItemCount(series);
2656                            for (int item = 0; item < itemCount; item++) {
2657                                renderer.drawItem(
2658                                    g2, state, dataArea, info,
2659                                    this, xAxis, yAxis, dataset, series, item,
2660                                    crosshairState, pass
2661                                );
2662                            }
2663                        }
2664                    }
2665                }
My solution is to replace the identical code at 2639-2640 and 2655-2656 with the following

Code: Select all

int firstitem = findFirstItemIndex(xAxis.getLowerBound(), dataset, series);
int lastitem = findLastItemIndex(xAxis.getUpperBound(), dataset, series);
for (int item = firstitem; item < lastitem; item++) {
Then add these 3 private functions

Code: Select all

// return idx of last item less than xval
private int findFirstItemIndex(double xval, XYDataset ds, int series) {
    if (Double.isNaN(xval))
        return 0;
    int bin = binarySearch(ds, series, xval);
    if (bin < 0)
        bin = -bin - 1;
    return Math.max(0, Math.min(bin, ds.getItemCount(series) - 1));
}
// return idx of first item greater than xval
private int findLastItemIndex(double xval, XYDataset ds, int series) {
    if (Double.isNaN(xval))
        return ds.getItemCount(series);
    int bin = binarySearch(ds, series, xval);
    if (bin < 0)
        bin = -bin - 1;
    if (bin > 0)
        bin++;
    return Math.max(0, Math.min(bin, ds.getItemCount(series)));
}
private int binarySearch(XYDataset ds, int series, double xval) {
    int low = 0;
    int high = ds.getItemCount(series) - 1;

    while (low <= high) {
        int mid = (low + high) >> 1;
        double cmp = ds.getXValue(series, mid) - xval;

        if (cmp < 0)
            low = mid + 1;
        else if (cmp > 0)
            high = mid - 1;
        else
            return mid; // key found
    }
    return -(low + 1);  // key not found
}
I originally implemented this using java.util.Collections.binarySearch(...) but found that the number of Double's being created for each search was thrashing the heap -- the implementation of binarySearch above uses the identical search logic but does not create one Double per compare.

The major effect of this change is that plot time is no longer proportional to the number of items in the dataset, only the number of items visible in the plot. My application displays real time financial info. Before I implemented this change, the application became slower and slower as the day progressed and the number of items in the datasets increased.

Locked