The renderer / series spacing Conundrum (solved - with code)

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
Alfred63
Posts: 22
Joined: Mon Jun 21, 2004 9:38 pm

The renderer / series spacing Conundrum (solved - with code)

Post by Alfred63 » Thu Jun 24, 2004 5:00 pm

Take a look at this :

Image

When I place a mouse over a bar it highlights.

Right now I'm overloading paint() at the JPanel level (the chart is a component of the panel). This is a very ugly way to do this as there are artifacts when the tool tip help overlaps my paint.

So I've been on a Holy Grail quest to find something within the existing API that will allow me to change bar colors...

Finally, I figured out that I could put each bar into a series and then change a given bar's color by changing the series color.... Problem is, when I have 60+ bars with each having a distinct series the bars look like this:

Image


As you can see, the bars are about 1 pixel in width. Changing the size of the chart doesn’t matter.

I'm hoping I won't have to extend and create my own renderer class but it looks like that is the case.

Unless of course someone out there has an idea or two on how to change the margins between series or a better way to chart the above.

Any ideas? :)
Last edited by Alfred63 on Fri Jun 25, 2004 4:02 pm, edited 1 time in total.

richard_atkinson
Posts: 115
Joined: Fri Mar 14, 2003 3:13 pm
Location: London, England
Contact:

Changing bar colors within a series

Post by richard_atkinson » Fri Jun 25, 2004 12:37 pm

Take a look at BarChartDemo3. It may help you in solving your problem.

Regards,
Richard...

Alfred63
Posts: 22
Joined: Mon Jun 21, 2004 9:38 pm

Post by Alfred63 » Fri Jun 25, 2004 1:59 pm

Thanks.

I "just" solved the problem. I'll take a look at demo3 to see if it will add more insight to the problem.

Ok as it turns out the real issue is that I'm creating a two dimensional array that has values as such:

Code: Select all

            for (int i=0, j=xLowerLimit; j <= xUpperLimit; i++, j++)
            {
                dataset.addValue (data[i], String.valueOf (j), String.valueOf (j));
            }
I'm assuming this results in a dataset having something to the effect of:

dataset["0", "0"] assigned to a specific value
dataset["1", "1"] assigned to a specific value
...

If you have 60 bars you end up with 60 categories and 60 series...

The BarRenderer code assumes that it has to make the bars small enough to handle 60 series with each having 60 bars. In my case I actually have 60 series with 1 category (bar) each. The existing code doesn't handle this (apparently) special case!

I thought about making my own renderer class but as it turns out I only need to change two methods: calculateBarWidth() and calculateBarW0() found within the BarRenderer class. For anyone that isn't aware of the following little trick :), it is a time saver which only works if you have access to the original code...

Copy the methods from the original source and place them in your class, place them right under the BarRenderer (or in this case BarRenderer3D) object creation. Here is an example of my modified methods using the trick of overloading an existing method without extending the whole class:

Code: Select all

        BarRenderer3D renderer = new BarRenderer3D()
        {
            protected void calculateBarWidth(CategoryPlot plot, 
                                             Rectangle2D dataArea, 
                                             int rendererIndex,
                                             CategoryItemRendererState state) 
            {

                CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
                CategoryDataset dataset = plot.getDataset(rendererIndex);
                if (dataset != null) 
                {
                    int rows = dataset.getRowCount();
                    double space = 0.0;
                    PlotOrientation orientation = plot.getOrientation();
                    if (orientation == PlotOrientation.HORIZONTAL) 
                    {
                        space = dataArea.getHeight();
                    }
                    else if (orientation == PlotOrientation.VERTICAL) 
                    {
                        space = dataArea.getWidth();
                    }
                    double maxWidth = space * getMaxBarWidth();
                    double categoryMargin = 0.0;
                    double currentItemMargin = 0.0;
                    if (rows > 1) 
                    {
                        currentItemMargin = getItemMargin();
                    }
                    double used = space * (1 - domainAxis.getLowerMargin() - domainAxis.getUpperMargin()
                                             - categoryMargin - currentItemMargin);
                    if (rows > 0) 
                    {
                        state.setBarWidth(Math.min(used / rows, maxWidth));
                    }
                    else 
                    {
                        state.setBarWidth(Math.min(used, maxWidth));
                    }
                }
            }
            
            protected double calculateBarW0(CategoryPlot plot, 
                                            PlotOrientation orientation, 
                                            Rectangle2D dataArea,
                                            CategoryAxis domainAxis,
                                            CategoryItemRendererState state,
                                            int row,
                                            int column) 
            {
                // calculate bar width...
                double space = 0.0;
                if (orientation == PlotOrientation.HORIZONTAL) 
                {
                    space = dataArea.getHeight();
                }
                else 
                {
                    space = dataArea.getWidth();
                }
                double barW0 = domainAxis.getCategoryStart(
                    column, getColumnCount(), dataArea, plot.getDomainAxisEdge()
                );
                
                barW0 = domainAxis.getCategoryMiddle(
                    column, getColumnCount(), dataArea, plot.getDomainAxisEdge()
                ) - state.getBarWidth() / 2.0;
                
                return barW0;
            }
        };
        
If you compare this to the original code you will notice that I've removed reference and calculation based on the column count for the first method and as well for most of the second.

Here is the code that changes the color of the bar :

Code: Select all

    private int oldseries;
    private Paint oldpaint = null;

    public void chartMouseMoved (ChartMouseEvent event)
    {
        ChartEntity ce = chartPanel.getEntityForPoint (event.getTrigger ().getX (), event.getTrigger ().getY ());
        
        if ((ce != null) && 
            (ce.getClass () == CategoryItemEntity.class))
        {
            CategoryItemEntity cie = (CategoryItemEntity) ce;
            int series = cie.getSeries ();
            if (oldseries != series)
            {
                if (oldpaint != null)
                {
                    event.getChart ().getCategoryPlot ().getRenderer ().setSeriesPaint (oldseries, oldpaint);
                }
                oldpaint = event.getChart ().getCategoryPlot ().getRenderer ().getSeriesPaint (series);
                event.getChart ().getCategoryPlot ().getRenderer ().setSeriesPaint (series, Color.RED);
                oldseries = series;
            }
        }
    }
This is a much cleaner process than the old way that I was using, and again this only works because I created a crazy dataset that has unique series and categories entries that are equal (in my case they are (["1","1"], ["2","2"], ["3","3"]...))

Locked