To Those Who Use JFreeChart for Dynamic Plotting/Large Sets

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
Carl Manaster
Posts: 35
Joined: Tue Mar 28, 2006 1:10 am
Location: La Jolla
Contact:

Performance impact vs. java.util.Collections.binarySearch?

Post by Carl Manaster » Tue Sep 05, 2006 4:35 pm

Hi, Skunk,
skunk wrote: 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.
I wouldn't have thought that enough comparisons are conducted in the course of a binary search to make any significant difference. Did the heap thrashing notably affect performance, as compared with your ultimate implementation?

Peace,
--Carl

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

Post by knc » Tue Sep 05, 2006 9:51 pm

Hello

I'm having some trouble getting the JFreeChart Fast classes to compile, specifically the FastChartPanel class. FastChartPanel includes several classes that aren't defined:

XYSeriesInfo
PlotPanel
Timetag
MainWindow

Were these classes accidentally excluded or can you make these available too?

Thanks,
knc

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

Post by knc » Tue Sep 05, 2006 10:42 pm

Hi again,

Also, the ChartPanel.java file that was included doesn't look correct. What I am seeing in ChartPanel.java is a collection of all the classes that were included in the JFreeChartFAST.zip file. FastChartPanel calls super(chart, buffering, false) - there is currently no ChartPanel constructor with three parameters and I'm assuming that the JFreeChartFAST.zip file needs to include an updated ChartPanel.java file. ??

Thanks,
knc

atongo
Posts: 10
Joined: Sun Jul 23, 2006 9:53 pm

Post by atongo » Fri Sep 08, 2006 8:38 pm

I am having the same problem too..I think there are a lot of missing classes and errors in the code. If it is working for someone do let us know..

regards,

atongo

atongo
Posts: 10
Joined: Sun Jul 23, 2006 9:53 pm

why these errors

Post by atongo » Sun Sep 10, 2006 12:06 am

I tried to experiment with the "FAST classes" but Im getting some errors: This is a part of my chart code:

private FastXYSeries hsf3_hse;
private FastXYSeries hsp;
private FastXYSeries hsp_hsf;
private FastXYSeries all_hsp;
/**
* create a jfreechart object
*/
JFreeChart chart;
// create a renderer for the chart
StandardXYItemRendererFast renderer;
/**
* create a dataset to collect the series
* objects using XYSeriesCollection class
*/
FastXYDataset dataset;



/**
* create constructor for the class
* which will initialize the various objects
* and their features
*/
public Chart(){
super(new BorderLayout());

this.hsf3_hse = new FastXYSeries("HSF3:HSE");
this.hsp = new FastXYSeries("HSP");
this.hsp_hsf = new FastXYSeries("HSP:HSF");
this.all_hsp = new FastXYSeries("Total HSP");

// initialize the dataset
dataset = new FastXYDataset();
// add dataset

dataset.addSeries(this.hsf3_hse);
dataset.addSeries(this.hsp);
dataset.addSeries(this.hsp_hsf);
dataset.addSeries(this.all_hsp);
// set text for the X (Time) axis of the chart
DateAxis domain = new DateAxis("Time");
//domain.setTickUnit(new DateTickUnit(DateTickUnit.HOUR, 1));
// set text for the Y (Population) axis
NumberAxis range = new NumberAxis("Population of Reacting Molecules");
/**
* create an XYPlot object to contain the
* dataset and the axes parameters
*/
FastXYPlot plot = new FastXYPlot();
// add the dataset to the plot
plot.setDataset(dataset);
// set the domain axis
plot.setDomainAxis(domain);
// set the range axis
plot.setRangeAxis(range);
// set the background color for the plot
plot.setBackgroundPaint(Color.LIGHT_GRAY);
// inset space around the axis
plot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0));
// set color for domaingridlines
plot.setDomainGridlinePaint(Color.white);
// set color for rangegridlines
plot.setRangeGridlinePaint(Color.white);
// create a number format to customize the range axis
NumberFormat nf = NumberFormat.getInstance();
// creae a simple date format to customize the domain
SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");
//initialize the renderer
renderer = new StandardXYItemRendererFast();
// customize the a tooltip for the renderer
renderer
.setToolTipGenerator(new StandardXYToolTipGenerator("{0}: {2}@{1}", df,
nf));
// customise the colors for the series
renderer.setSeriesPaint(0, Color.red);
renderer.setSeriesPaint(1, Color.green);
renderer.setSeriesPaint(2, Color.blue);
renderer.setSeriesPaint(3, Color.yellow);
renderer.setSeriesPaint(4, Color.cyan);
renderer.setSeriesPaint(5, Color.magenta);
renderer.setSeriesPaint(6, Color.orange);
renderer.setSeriesPaint(7, Color.pink);
// dont show the base shape of the plot
renderer.setBaseShapesVisible(false);

// ask the plot to recognise the renderer
plot.setRenderer(renderer);
/**
* create a dynamic axis so that the range can expand with
* the data
*/
domain.setAutoRange(true);
// set lower margin for domain axis
domain.setLowerMargin(0.0);
// set upper margin for the domain axis
domain.setUpperMargin(0.0);
// show the label for the domain ticks
domain.setTickLabelsVisible(true);

// show only integer values for the range axis
range.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
// create a jfreechart without a title
chart =
new JFreeChart("", JFreeChart.DEFAULT_TITLE_FONT, plot, true);
/**
* create a chartpanel and add the generated chart to it
*/

ChartPanel chartPanel = new ChartPanel(chart);
// set the display tooltip
chartPanel.setDisplayToolTips(true);
// customize the legend of the chart
LegendTitle l = chart.getLegend();
l.setItemFont(new Font("SansSerif", Font.PLAIN, 10));


add(chartPanel, BorderLayout.CENTER);
// place the checkboxes to the south of the chart

}

/**
* add an observation to the "hsf3:hse" series
* @param population
* @param time
*/
public void addHsf3_Hse_Observation(long x, int y){
hsf3_hse.add(x, y);
}
/**
* add an observation to the "hsp" series
* @param population
* @param time
*/
public void addHsp_Observation(long x, int y){
hsp.add(x, y);
}
/**
* add an observation to the "hsp:hsf" series
* @param population
* @param time
*/
public void addHsp_Hsf_Observation(long x, int y){
hsp_hsf.add(x, y);
}
/**
* add an observation to the "total hsp" series
* @param population
* @param time
*/
public void addAll_Hsp_Observation(long x, int y){
all_hsp.add(x, y,true);
}

This chart recieved data from the engine and it's suppposed to plot the
chart dynamically as the data is received..

This is the error messages I get;

at java.awt.EventDispatchThread.run(Unknown Source)
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.RangeCheck(Unknown Source)
at java.util.ArrayList.get(Unknown Source)
at org.jfree.chart.plot.FastXYPlot.render(FastXYPlot.java:258)
at org.jfree.chart.plot.XYPlot.draw(XYPlot.java:2219)
at org.jfree.chart.plot.FastXYPlot.draw(FastXYPlot.java:107)
at org.jfree.chart.JFreeChart.draw(JFreeChart.java:1133)
at org.jfree.chart.ChartPanel.paintComponent(ChartPanel.java:1275)
at javax.swing.JComponent.paint(Unknown Source)
at javax.swing.JComponent.paintWithOffscreenBuffer(Unknown Source)
at javax.swing.JComponent.paintDoubleBuffered(Unknown Source)
at javax.swing.JComponent._paintImmediately(Unknown Source)
at javax.swing.JComponent.paintImmediately(Unknown Source)
at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source)
at javax.swing.SystemEventQueueUtilities$ComponentWorkRequest.run(Unknown Source)
at java.awt.event.InvocationEvent.dispatch(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source)

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

Post by Mercer » Tue Sep 12, 2006 4:56 pm

Ahh crap. Sorry guys, I COMPLETELY forgot that things like TimeTag are intrinsic code for us here. I'm so used to using them, I just assumed EVERYONE had them. Timetags are a way to store time in a specific format used by us. Conversion between Median Julian Time and Milliseconds-since-some-date kind of stuff. Sorry about that.

But I will reiterate. These classes are NOT intended to be just thrown in and used. You do have to know a fair bit about the package to make these work with your stuff. There is a TON of stuff that I'll go so far as to say have NOTHING to do with JFreeChart, they only apply to our specific application. The classes are just submitted to give you an idea of how to implement the efficiency customizations that we created or gathered from others. Let me assure you that the original FAST classes we got didn't work for us at all either :).

The classes are more of a code-wise way to explain the points presented in the original post. You are, of course, more than welcome to fiddle/modify/delete the code within the stuff I sent, but don't expect it to work for you in a carte-blanche kind of way.

Heheh, that's not my department :P

Good Luck.

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

Post by Mercer » Tue Sep 12, 2006 5:02 pm

Brian Johnson wrote: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
This one's easy to answer... so i will.

What I mean is when you add() points/items to a series, there is an option to add(something,something, FALSE). uh, there may be too many or too few 'somethings' in there. The point is, when you add() an item in with the last field being FALSE, you tell JFreeChart to not worry about the point being added. It won't draw, it won't check that the ranges are correct, it'll just add it to the underlying series and forget about it. If you ahve a LARGE number of points/items to add, say 1000, add 999 with FALSE, then add the 1000 one with TRUE. This will tell JFreeChart to send notification that a new point was added, and it will resize and redraw everything correctly.

The problem (from an efficiency point of view) is that JFreeChart tryies to do the resize and redraw with EVERY point added if you don't tell it otherwise. This can reall bog things down, especially when added a hundred thousand points.

Hope this helps.

mkivinie
Posts: 51
Joined: Wed Jul 06, 2005 8:35 am

Post by mkivinie » Thu Sep 14, 2006 6:53 am

skunk wrote: 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.
Thanks skunk! Your optimizations work like a charm for my application as well.

A note to users concerned about performance:
JFreeChart's standard way of handling tooltip generation is not very good for realtime applications. When a datapoint is added to the dataset and you have tooltip generation ON, then tooltips are generated anew for all datapoints in the set. And it happens again when you add again a new datapoint.
If/when your dataset is growing this of course causes another bottleneck.

Thanks to skunk's optimization tooltip generation now happens only for the visible datapoints which at least in my case is a lot less than the whole set.

David, if you ever read this: would it be possible to have JFreeChart generate a tooltip only when required and not compute them all beforehand? Maybe there is a reason for some static chart requirements (web image map or something) that is behind this current design?

And no, I have not yet played with any custom mouse event handlers and/or tooltip generators... maybe later if I have time.

develop
Posts: 296
Joined: Wed Mar 23, 2005 10:01 pm

Post by develop » Thu Sep 21, 2006 9:33 pm

are there any chances to come these fast classes integrated in Jfreechart itself?

thanks

nonlinear5
Posts: 6
Joined: Wed Dec 27, 2006 1:03 am

Post by nonlinear5 » Wed Dec 27, 2006 1:19 am

skunk wrote: Here are the changes that I needed to make to implement this optimization. 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.
Thanks, skunk, your code works well. The performance improvement is especially noticeable when zoomed in. However, when zooming out, it still takes a long time, because all points become visible.

I made a few more changes on top of your changes and now my 250,000 data points dataset renders in under 2 seconds, no matter which portion of the plot is displayed.

Another advantage of using it is that it's a single class change (as opposed to a whole set of classes as proposed in the original post), so it will make it very easy to integrate to your app. It doesn't require any changes to renderers, either.

The idea is the same as in the original post: if an overlapping of pixels is detected, it's simply not rendered.

For those who try to use this code, let me know if you find any problem.

Code: Select all

package com.jsystemtrader.chart;

import java.awt.*;
import java.awt.geom.*;

import org.jfree.chart.axis.*;
import org.jfree.chart.plot.*;
import org.jfree.chart.renderer.xy.*;
import org.jfree.data.general.*;
import org.jfree.data.xy.*;
import java.util.*;

public class FastXYPlot extends XYPlot {
    private final Map<Integer, Long> lastRenderedX = new HashMap<Integer, Long> ();

    public FastXYPlot() {
        this(null, null, null, null);
    }

    public FastXYPlot(XYDataset dataset, ValueAxis domainAxis, ValueAxis rangeAxis, XYItemRenderer renderer) {
        super(dataset, domainAxis, rangeAxis, renderer);
    }


    public void renderFast(ValueAxis xAxis, ValueAxis yAxis, XYDataset dataset, int series, XYItemRenderer renderer,
                           XYItemRendererState state, int pass, Graphics2D g2, Rectangle2D dataArea,
                           PlotRenderingInfo info, CrosshairState crosshairState) {

        int firstItem = findFirstItemIndex(xAxis.getLowerBound(), dataset, series);
        int lastItem = findLastItemIndex(xAxis.getUpperBound(), dataset, series);

        for (int item = firstItem; item < lastItem; item++) {

            double x = dataset.getXValue(series, item);
            double xx = xAxis.valueToJava2D(x, dataArea, getDomainAxisEdge());

            long lastRendered = lastRenderedX.containsKey(series) ? lastRenderedX.get(series) : 0;
            long rounded = (long) ( (xx * 100) / 10);
            if (rounded != lastRendered) {
                lastRenderedX.put(series, rounded);

                renderer.drawItem(g2, state, dataArea, info, this, xAxis, yAxis, dataset, series, item, crosshairState,
                                  pass);
            }
        }

    }


    public boolean render(Graphics2D g2, Rectangle2D dataArea, int index, PlotRenderingInfo info,
                          CrosshairState crosshairState) {

        boolean foundData = false;
        XYDataset dataset = getDataset(index);
        if (!DatasetUtilities.isEmptyOrNull(dataset)) {
            lastRenderedX.clear();
            foundData = true;
            ValueAxis xAxis = getDomainAxisForDataset(index);
            ValueAxis yAxis = getRangeAxisForDataset(index);
            XYItemRenderer renderer = getRenderer(index);
            if (renderer == null) {
                renderer = getRenderer();
            }

            XYItemRendererState state = renderer.initialise(g2, dataArea, this, dataset, info);
            int passCount = renderer.getPassCount();

            SeriesRenderingOrder seriesOrder = getSeriesRenderingOrder();

            if (seriesOrder == SeriesRenderingOrder.REVERSE) {
                //render series in reverse order
                for (int pass = 0; pass < passCount; pass++) {
                    int seriesCount = dataset.getSeriesCount();
                    for (int series = seriesCount - 1; series >= 0; series--) {
                        renderFast(xAxis, yAxis, dataset, series, renderer, state, pass, g2, dataArea, info,
                                   crosshairState);
                    }
                }
            } else {
                //render series in forward order
                for (int pass = 0; pass < passCount; pass++) {
                    int seriesCount = dataset.getSeriesCount();
                    for (int series = 0; series < seriesCount; series++) {
                        renderFast(xAxis, yAxis, dataset, series, renderer, state, pass, g2, dataArea, info,
                                   crosshairState);
                    }
                }
            }
        }
        return foundData;
    }


    /**
     *
     * @return int idx of first 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 int 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
    }
}


nonlinear5
Posts: 6
Joined: Wed Dec 27, 2006 1:03 am

Post by nonlinear5 » Thu Dec 28, 2006 6:51 am

Here is a little bit cleaner version (and a more standard solution, too). On my not-so-fast PC, all 250,000 data points render in about 1.5 seconds.

Code: Select all

package com.jsystemtrader.chart;

import java.awt.*;
import java.awt.geom.*;
import java.util.*;

import org.jfree.chart.axis.*;
import org.jfree.chart.plot.*;
import org.jfree.chart.renderer.xy.*;
import org.jfree.data.general.*;
import org.jfree.data.xy.*;

public class FastXYPlot extends XYPlot {

    public FastXYPlot() {
        this(null, null, null, null);
    }

    public FastXYPlot(XYDataset dataset, ValueAxis domainAxis, ValueAxis rangeAxis, XYItemRenderer renderer) {
        super(dataset, domainAxis, rangeAxis, renderer);
    }

    public void renderFast(ValueAxis xAxis, ValueAxis yAxis, XYDataset dataset, int series, XYItemRenderer renderer,
                           XYItemRendererState state, int pass, Graphics2D g2, Rectangle2D dataArea,
                           PlotRenderingInfo info, CrosshairState crosshairState) {

        ArrayList<Double> xValues = new ArrayList<Double> ();
        int count = dataset.getItemCount(series);
        for (int item = 0; item < count; item++) {
            xValues.add(dataset.getXValue(series, item));
        }

        int firstItem = findItemIndex(xValues, xAxis.getLowerBound(), count);
        int lastItem = findItemIndex(xValues, xAxis.getUpperBound(), count);

        long lastRendered = -1;
        for (int item = firstItem; item < lastItem; item++) {

            double x = dataset.getXValue(series, item);
            double xx = xAxis.valueToJava2D(x, dataArea, getDomainAxisEdge());

            long candidateToRender = (long) ( (xx / 10) * 100);
            if (candidateToRender != lastRendered) {
                renderer.drawItem(g2, state, dataArea, info, this, xAxis, yAxis, dataset, series, item, crosshairState,
                                  pass);
                lastRendered = candidateToRender;
            }
        }
    }


    public boolean render(Graphics2D g2, Rectangle2D dataArea, int index, PlotRenderingInfo info,
                          CrosshairState crosshairState) {

        boolean foundData = false;
        XYDataset dataset = getDataset(index);
        if (!DatasetUtilities.isEmptyOrNull(dataset)) {
            foundData = true;
            ValueAxis xAxis = getDomainAxisForDataset(index);
            ValueAxis yAxis = getRangeAxisForDataset(index);
            XYItemRenderer renderer = getRenderer(index);
            if (renderer == null) {
                renderer = getRenderer();
            }

            XYItemRendererState state = renderer.initialise(g2, dataArea, this, dataset, info);
            int passCount = renderer.getPassCount();

            SeriesRenderingOrder seriesOrder = getSeriesRenderingOrder();

            if (seriesOrder == SeriesRenderingOrder.REVERSE) {
                //render series in reverse order
                for (int pass = 0; pass < passCount; pass++) {
                    int seriesCount = dataset.getSeriesCount();
                    for (int series = seriesCount - 1; series >= 0; series--) {
                        renderFast(xAxis, yAxis, dataset, series, renderer, state, pass, g2, dataArea, info,
                                   crosshairState);
                    }
                }
            } else {
                //render series in forward order
                for (int pass = 0; pass < passCount; pass++) {
                    int seriesCount = dataset.getSeriesCount();
                    for (int series = 0; series < seriesCount; series++) {
                        renderFast(xAxis, yAxis, dataset, series, renderer, state, pass, g2, dataArea, info,
                                   crosshairState);
                    }
                }
            }
        }
        return foundData;
    }

    private int findItemIndex(ArrayList<Double> values, double value, int maxCount) {
        int itemIndex = Collections.binarySearch(values, value);
        if (itemIndex < 0) {
            itemIndex = -itemIndex - 1;
        }
        itemIndex = Math.max(0, Math.min(itemIndex, maxCount - 1));
        return itemIndex;
    }
}


Tom
Posts: 32
Joined: Mon Dec 11, 2006 2:50 pm

Post by Tom » Tue Jan 16, 2007 10:42 am

Mercer wrote: What I mean is when you add() points/items to a series, there is an option to add(something,something, FALSE). uh, there may be too many or too few 'somethings' in there. The point is, when you add() an item in with the last field being FALSE, you tell JFreeChart to not worry about the point being added. It won't draw, it won't check that the ranges are correct, it'll just add it to the underlying series and forget about it. If you ahve a LARGE number of points/items to add, say 1000, add 999 with FALSE, then add the 1000 one with TRUE. This will tell JFreeChart to send notification that a new point was added, and it will resize and redraw everything correctly.
Hi Guys,

I'm just testing my charts on performance issues and I'm experiencing the same problem: from the 1000 value on, the addValue()-Method in my DefaultCategoryDataset nearly needs 3 seconds to add a single point!

I tried to do what Mercer said, but I'm not able to find such an add-Method that has such a signature. For my DefaultCategoryDataset I just can find the two addValue()-Methods, and both of them do not have a boolean-flag at the end. So could you please tell me how I have to add points to my Dataset exactly, in case of a CategoryDataset??

Sorry, but I couldnt find out by myself.

Tom

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 Jan 16, 2007 12:17 pm

In general, a CategoryDataset shouldn't be as large as 1000 items. If it is, you probably haven't categorised your data in a way that is going to be easily interpreted by your users.

Perhaps you are (mis)using categories to represent values (numbers or dates) along a scale - in that case, you should try to use an XYDataset.
David Gilbert
JFreeChart Project Leader

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

Clandestino
Posts: 6
Joined: Tue Jul 25, 2006 2:49 pm

Last line not rendered

Post by Clandestino » Tue Feb 06, 2007 3:19 pm

Hi,

Maybe I don't really need FastXYPlot, but I though I'd give it a try anyway. I connect my datapoints with lines, and using the (latest) code posted above, the "last" line is not rendered. I fixed this by changing

Code: Select all

    private int findItemIndex(ArrayList<Double> values, double value, int maxCount) {
        int itemIndex = Collections.binarySearch(values, value);
        if (itemIndex < 0) {
            itemIndex = -itemIndex - 1;
        }
        itemIndex = Math.max(0, Math.min(itemIndex, maxCount-1));
        return itemIndex;
    }
to

Code: Select all

    private int findItemIndex(ArrayList<Double> values, double value, int maxCount) {
        int itemIndex = Collections.binarySearch(values, value);
        if (itemIndex < 0) {
            itemIndex = -itemIndex - 1;
        }
        itemIndex = Math.max(0, Math.min(itemIndex, maxCount));
        return itemIndex;
    }
/ERiK

adrsingle
Posts: 4
Joined: Fri Jul 28, 2006 9:08 pm

Jfreechart - 1.0.3

Post by adrsingle » Thu Feb 08, 2007 8:28 pm

does the last version of Jfreechart has these classes

Locked