BarRenderer3D wrong overlapping ... bug?

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
Tom
Posts: 32
Joined: Mon Dec 11, 2006 2:50 pm

BarRenderer3D wrong overlapping ... bug?

Post by Tom » Thu Feb 22, 2007 3:51 pm

Hi,

sorry if I#Ve discovered a bug again ;-)

In my barchart3D the bars from different categories do not overlap correctly.

If you have a close look, you can see that the bars of the last series (red) overlap the bars from the first of the following category (violet).

Image

Overlapping of series in one category works correctly. I tried to get this working with plot.setColumnRenderingOrder() both ASCENDING and DESCENDING... without effect. Is this a bug? Or is therer anything else i can set to get this?

Thanks a lot,

Tom
[/img]

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 » Thu Feb 22, 2007 4:13 pm

You should set both the rowRenderingOrder and the columnRenderingOrder for the CategoryPlot---for the chart you've shown, both should be set to SortOrder.ASCENDING (the defaults).
David Gilbert
JFreeChart Project Leader

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

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

Post by Tom » Fri Feb 23, 2007 3:51 pm

Thanks for the reply, I'll try this on Monday, but I'm skeptic that it works, because you say that these orders are the defaults, it should drawn correctly due to the fact that i did not change the rendering order in my code.

I'll tell you on Monday...

you are doing a great job, go on

Tom

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

Post by Tom » Mon Feb 26, 2007 9:23 am

Ok this didn't work.

Unfortunately setting the SortOrders both to ASCENDING or DESCENDING doesn't have ANY effect on my chart.

Is there something I forgot?

I cannot use a chart thats looking like this.

Greets,

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 » Mon Feb 26, 2007 10:46 am

OK - can you post a small(ish) self-contained demo that reproduces the problem? And tell me the version of JFreeChart you are running.
David Gilbert
JFreeChart Project Leader

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

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

Post by Tom » Mon Feb 26, 2007 1:24 pm

Sure, but this will a take a bit, I'll try to get this done today...

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

Post by Tom » Mon Feb 26, 2007 4:42 pm

Ok , here it comes...

First, I think you'll need some more information on what I've done. The chart which I posted above is a barchart3D with multiple axes using multiple datasets. To get the bars plottet side by side, the datasets are filled with "dummy" series in the way that every xth dataset has its "real" values in the xth series, the other series in that dataset are null. The second dataset for example would look like this: series1 : nullvalues; series2:data; seriesn:nullvalues; and so on.

For our Test, i created a barchart with 2 datasets and 2 NumberAxes. The first dataset has its data in the first series (the second is the "dummy") .

The second dataset has its values in the second series, here, the first one is null;

basicly this is the same as in the MultipleAxisBarDemo

The rest should be clear by looking at the code. I tried to keep the structure of my "real world" chart, so don't blame me if something could be coded easier for this example :-)

There are two classes. The first one ist BarChart3D, this is the whole issue.

The second one is ChartPrinter, a class that just instantiates the BarChart, and then prints it to a file named "chart.png" on C:\\temp\.

Puh, long story...

So here comes the code:

Code: Select all

import java.awt.Color;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis3D;
import org.jfree.chart.axis.CategoryLabelPositions;
import org.jfree.chart.axis.NumberAxis3D;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.DatasetRenderingOrder;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.BarRenderer3D;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.ui.*;
import org.jfree.util.SortOrder;

public class BarChart3D extends JFreeChart
{
	private CategoryPlot plot = null;
	private DefaultCategoryDataset dataset1 = null;
	private DefaultCategoryDataset dataset2 = null;
	private CategoryAxis3D xAxis = null;
	private NumberAxis3D yAxis1 = null;
	private NumberAxis3D yAxis2 = null;
	private BarRenderer3D renderer1 = null;
	private BarRenderer3D renderer2 = null;
	
	public BarChart3D(String title)
	{
	    super(title, new CategoryPlot());
	    createChart();
	}

	/**
	 *  creates the chart
	 */
	public void createChart()
	{   
			initChart();
			
			fillChart();

			//X-Achse erzeugen 
			createXAxis();

			//Y-Achsen erzeugen
			createYAxis();
			
			//Legende erzeugen
			//createLegend();
	}
	 
		/**
		* creates and fills the datasets
		*/
		public void fillChart() 
		{	
			String[] categories = 	{ "A", "B", "C","D","E","F",
									  "G", "H", "I","J", "K", "L", "M",
									  "N", "O", "P", "Q", "R", "S", "T"
									};
			
			double[] data = { 8.8, 9.7, 11.6, 5.0, 15.5,10,
							  19.5,12, 23.8, 25,  29.8, 
							  35.6, 45.8, 70.7, 98.6, 100.0, 100.0, 100.0, 
   							  100.0, 100.0 };
					
		    double[] data2 = { 10.2, 11.5, 13.4, 5, 17.5,6,
							   21.5, 7, 25.8, 10,  31.8, 
		                       37.6, 47.8, 73.7, 100.0, 100.0, 100.0, 
							   100.0, 100.0, 100.0 };
			   
			//create Datasets
			 dataset1 = new DefaultCategoryDataset();
			 for (int i=0; i < categories.length; i++)
			 {
				 dataset1.addValue(data[i], "series1", categories[i]);
				 //to get the bars plottet side by side, we add a "dummy" series
			     dataset1.addValue(null, "series2", categories[i]);
			 }
			 
		    dataset2 = new DefaultCategoryDataset();
		    for (int i=0; i < categories.length; i++)
		    {	
				// same as above but the other way round
		        dataset2.addValue(null, "series1", categories[i]);
		        dataset2.addValue(data2[i], "series2", categories[i]);
		    }
		}
		
	/**
	 * Makes all the initial settings 
	 */
	public void initChart()
	{   
		//we won't need a Legend
		this.removeLegend();
		
		plot = (CategoryPlot) this.getPlot();

		//-------------------------   Renderer   -----------------------------//
		renderer1 = new BarRenderer3D();
	    renderer2 = new BarRenderer3D();
		
		//--------------------------   Plot   -------------------------------//
		plot.setOrientation(PlotOrientation.VERTICAL);

		double offset = 0.0;
		plot.setAxisOffset(new RectangleInsets(offset, offset, offset, offset));
		
		plot.setDomainGridlinesVisible(true);
		plot.setDomainGridlinePaint(Color.darkGray);
		plot.setRangeGridlinePaint(Color.darkGray); 
		
		
		//try to get the right overlapping
		plot.setDatasetRenderingOrder(DatasetRenderingOrder.FORWARD);
		plot.setColumnRenderingOrder(SortOrder.ASCENDING);
		plot.setRowRenderingOrder(SortOrder.ASCENDING);
				
	}
		
	/** 
	 * creates the xAxis
	 */
	public void createXAxis()
	{   
		xAxis = new CategoryAxis3D("xAxis");
		xAxis.setMaximumCategoryLabelLines(5);
		xAxis.setCategoryLabelPositions(CategoryLabelPositions.DOWN_90);
		xAxis.setLowerMargin(0.0);
		xAxis.setUpperMargin(0.0);
		plot.setDomainAxis(xAxis);

	}
		
	/**
	 * creates the yAxis.
	 */
	public void createYAxis()
	{
		yAxis1 = new NumberAxis3D("yAxis1"); 
	    yAxis2 = new NumberAxis3D("yAxis2");
			
		yAxis1.setAutoRange(true);
	    yAxis2.setAutoRange(true);

		plot.setRangeAxis(0, yAxis1);
	    plot.setRangeAxis(1, yAxis2);
		plot.setDataset(0, dataset1);
	    plot.setDataset(1, dataset2);
		plot.mapDatasetToRangeAxis(0, 0);
	    plot.mapDatasetToRangeAxis(1, 1);
		plot.setRenderer(0, renderer1);
	    plot.setRenderer(1, renderer2);
		

	}
}
and the ChartPrinter...

Code: Select all


import java.awt.Color;

import java.io.File;

import java.io.FileOutputStream;


import org.jfree.chart.ChartUtilities;

/**
 * This class produces an image named "Chart.png".
 * 
 * @author th
 * @date 09/07/2007
 * 
 * @version 1.0
 */
public class ChartPrinter
{
	public ChartPrinter()
	{
	}
	
	public static void main(String[] args) 
	{

	    File file = new File("C:\\temp\\chart.png");
	    FileOutputStream fos;
		
	    

	    BarChart3D chart = new BarChart3D("Test");
		chart.setBackgroundPaint(new Color(255,255,255));
		chart.setBorderPaint(new Color(255,255,255,255));
		
	    
	    try
	    {
	        fos = new FileOutputStream(file);
	        ChartUtilities.writeChartAsPNG(fos, chart, 600, 400);
	    }
	    catch (Exception e)
	    {
	        System.out.println(e.getMessage());
	    }
	}
}
At the end, the result looks like this:

Image

Thanks for reading this long post,

Tom

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

Post by Tom » Mon Feb 26, 2007 4:45 pm

I'm using JfreeChart 1.0.4..

Puh, nearly forgot that :-)

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 » Mon Feb 26, 2007 6:23 pm

OK, I don't think the 3D effect is going to work in this case, no matter what order you draw the items within each dataset (nor, for that matter, the order in which you process the datasets).

Is there a reason you need to use multiple datasets?
David Gilbert
JFreeChart Project Leader

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

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

Post by Tom » Mon Feb 26, 2007 11:20 pm

Yes,

I have to use multiple datasets because the different series do not have the same dimensions. This may sound like it doesn't make sense, but in my case there are some special cases in which it does. This chart would be used to display chemical data, and in some cases it is useful to compare data in, for example ppm with data in mol/l. That is why I cannot use one dataset alone.

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

Post by Tom » Mon Feb 26, 2007 11:31 pm

Just thought about that...

i could use only one dataset if it was possible to map one series in it to its own axis... This would fix several issues, i'm thinking about the problem in this post...

http://www.jfree.org/phpBB2/viewtopic.php?t=19984

Is it possible to do that? Haven't tried this yet...

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 Feb 27, 2007 6:13 pm

No, whole datasets get mapped to specific axes.

The only thing I can suggest here is to drop the 3D effect (which doesn't add any information anyway) and use a plain 2D bar chart.
David Gilbert
JFreeChart Project Leader

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

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

Post by Tom » Tue Feb 27, 2007 7:45 pm

yeah seems so... its just eyecandy, but it would have been nice to have that effect, this renderer looks real good.

I haven't really understood why this doesn't work. What is the Problem, maybe we can patch it? Maybe you can put it on your List for 1.05 ? ;-)

Can you tell me an approach to fix that, maybe we'll (me and my workmate)
have a look at it.

Anyway, thanks for your reply,

Tom

TheBear
Posts: 21
Joined: Mon Feb 20, 2006 3:16 pm

Post by TheBear » Wed Feb 28, 2007 9:35 am

Hi Tom, hi David,
I just had a look at the sourcecode. In fact, without changing the CategoryPlot this feature is impossible to realize. But it should be possible to solve by implementing a new DatasetRenderingOrder that mixes up Datasets and Colums in the rendering order so that the order is as follows:

Column 1, Dataset 1, Series 1 to n
Column 1, Dataset 2, Series 1 to n
Column 1, Dataset 3, Series 1 to n
Column 2, Dataset 1, Series 1 to n
Column 2, Dataset 2, Series 1 to n
Column 2, Dataset 3, Series 1 to n
...

This would lead to the correct visual effect. I hacked a small example how it could be realized in CategoryPlot. It overloads the render() method and adds a new DatasetRenderingOrder to the draw-method.

Code: Select all

	// Draw-Method
	//------------
	// [...]
	
	// now render data items...
	boolean foundData = false;

	// set up the alpha-transparency...
	Composite originalComposite = g2.getComposite();
	g2.setComposite(AlphaComposite.getInstance(
		AlphaComposite.SRC_OVER, getForegroundAlpha()));

	DatasetRenderingOrder order = getDatasetRenderingOrder();
	if (order == DatasetRenderingOrder.FORWARD) {
	    for (int i = 0; i < this.datasets.size(); i++) {
		foundData = render(g2, dataArea, i, state) || foundData;
	    }
	}
	else if (order == DatasetRenderingOrder.REVERSE) {  // DatasetRenderingOrder.REVERSE
	    for (int i = this.datasets.size() - 1; i >= 0; i--) {
		foundData = render(g2, dataArea, i, state) || foundData;   
	    }
	}
	else { // !new! DatasetRenderingOrder.MIX_COLS_AND_DATASETS

	// find out the maximum number of columns in one dataset
		int maxColumns = 0;
		for (int i = 0; i < this.datasets.size(); i++) {
			CategoryDataset dataset = (CategoryPlot)datasets.get(i);
			int dsColumns = dataset.getColumnCount();
			if (dsColumns > maxColumns) {
					maxColumns = dsColumns;
				}
			}

			for (int i = 0; i < maxColumns; i++) {
				for (int j = 0; j < this.datasets.size(); j++) {
					foundData = render(g2, dataArea, j, i, state) || foundData;
				}
			}
	}
	
	// [...]
	
	/**
	 * This renders all series of a dataset (datasetIndex) at a specific 
	 * column (columnIndex)
	 */
	public boolean render(Graphics2D g2, Rectangle2D dataArea, int datasetIndex, 
						  int columnIndex, PlotRenderingInfo info) {
		
		boolean foundData = false;
		CategoryDataset currentDataset = getDataset(index);
		CategoryItemRenderer renderer = getRenderer(index);
		CategoryAxis domainAxis = getDomainAxisForDataset(index);
		ValueAxis rangeAxis = getRangeAxisForDataset(index);
		int columnCount = currentDataset.getColumnCount();
		boolean hasData = !DatasetUtilities.isEmptyOrNull(currentDataset);
		if (hasData && renderer != null && columnIndex < columnCount) {

			foundData = true;
			CategoryItemRendererState state = renderer.initialise(
				g2, dataArea, this, index, info
			);
			int rowCount = currentDataset.getRowCount();
			int passCount = renderer.getPassCount();
			for (int pass = 0; pass < passCount; pass++) {            
				if (this.rowRenderingOrder == SortOrder.ASCENDING) {
					for (int row = 0; row < rowCount; row++) {
						renderer.drawItem(g2, state, dataArea, this, 
								domainAxis, rangeAxis, currentDataset, 
								row, columnIndex, pass);
					}
				}
			}
			// ------------------------------------------------------------
			// perhaps implement some more cases dependent on the SortOrder
			// ------------------------------------------------------------
		}
						
						
		return foundData;
	}
Of course it is difficult for me to see possible side-effects of this solution, also since it is not tested yet. But I think it should work about like this.

What do you think about it David?

Regards

Björn

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 » Thu Mar 01, 2007 6:00 pm

I think it would work, but it's a layer of complexity to work around another problem (that the 3D effect is just a hack). I wouldn't want to incorporate this into the main source tree.
David Gilbert
JFreeChart Project Leader

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

Locked