[HOWTO] create charts with defined data/plot area

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
paradoxoff
Posts: 1634
Joined: Sat Feb 17, 2007 1:51 pm

[HOWTO] create charts with defined data/plot area

Post by paradoxoff » Wed Nov 26, 2008 11:18 pm

I have recently worked extensively with multi axis charts and wanted to have a way to create a plot with a user-defineable plot area so that the actual plot does not get squeezed when more axes are added. This request seems to come up now and then.
It turned out to be less difficult than expected, at least for some simple cases, since the calculations are more or less already implemented in the current version and needed only some very minor modification to make them run into the other direction. The approach simply consists in reverting the drawing process.
Here is the code for three methods.
The first one has to be placed in the XYPlot class. It receives a Graphics2D and a Rectangle2D as argument and expands the rectangle in-place with the space required for the plot insets, the plot border, and the axis space.
The second one belongs to the JFreeChart class. It also receives a graphics and rectangle parameter. The rectangle is expanded by the chart insets and the space required by the titles. A proper consideration of the space for the titles simply required to iterate over the titles list in reverse order. The third method is just a helper for the second. If a given rectangle is expanded consecutively with these two methods, it can be used as rectangle parameter to draw a JFreeChart which will then have the original rectangle as data area.
At present, the code only cares about the rectangle size, i. e. the rectangle is only expanded to the left and bottom but it should be possible to also care about the position. This could be a way to not only give different plots of different charts the same size but also to align them in a certain way in a suitable container.
Here is the code. I would be happy to know if somebody else has also worked on this and has an altenative/better solution.
The same approach should also work for CategoryPlots but I have not yet checked that.
XYPlot method

Code: Select all


	public void expandDataToPlotArea(Graphics2D g2, Rectangle2D dataArea) {
        AxisSpace domainSpace = new AxisSpace();
        AxisSpace rangeSpace = new AxisSpace();
        domainSpace = calculateDomainAxisSpace(g2, dataArea, null);
        rangeSpace = calculateRangeAxisSpace(g2, dataArea, null);
        double leftRightOffset;
        double topBottomOffset;
        if(orientation.equals(PlotOrientation.VERTICAL)){
  	    	leftRightOffset = rangeSpace.getLeft()+rangeSpace.getRight();
        	 topBottomOffset = domainSpace.getTop()+domainSpace.getBottom();
        }
        else{
  	    	leftRightOffset = domainSpace.getLeft()+domainSpace.getRight();
        	 topBottomOffset = rangeSpace.getTop()+rangeSpace.getBottom();
        }
        if(isOutlineVisible()){
        	leftRightOffset += 2;
        	topBottomOffset += 2;
        } 
        leftRightOffset = leftRightOffset + getInsets().getLeft() + getInsets().getRight();
        topBottomOffset = topBottomOffset + getInsets().getTop() + getInsets().getBottom();
        dataArea.setRect(dataArea.getX(),dataArea.getY(),dataArea.getWidth()+leftRightOffset,dataArea.getHeight()+topBottomOffset);
        //return plotArea;
    }
JFreeChart methods

Code: Select all

	public void expandPlotToChartArea(Graphics2D g2,Rectangle2D plotArea){
	    for(int ti = subtitles.size()-1 ; ti>=0 ; ti--){
	        Title currentTitle = (Title)(subtitles.get(ti));
	        if(currentTitle.isVisible()){
	        	expandWithTitleArea(g2,plotArea,currentTitle);	
	        }
	    } 
        if (this.title != null && this.title.isVisible()) {
        	expandWithTitleArea(g2,plotArea,this.title);	
        }
		plotArea.setRect(plotArea.getX(),plotArea.getY(),plotArea.getWidth()+padding.getLeft()+padding.getRight(),plotArea.getHeight()+padding.getTop()+padding.getBottom());
	}

Code: Select all

	private void expandWithTitleArea(Graphics2D g2,Rectangle2D rect, Title title){

		Rectangle2D titleArea = new Rectangle2D.Double();
		RectangleEdge position = title.getPosition();
        double ww = rect.getWidth();
        double hh = rect.getHeight();
		double width = rect.getWidth();
		double height = rect.getHeight();
        RectangleConstraint constraint = new RectangleConstraint(ww,
                new Range(0.0, ww), LengthConstraintType.RANGE, hh,
                new Range(0.0, hh), LengthConstraintType.RANGE);

        if (position == RectangleEdge.TOP) {
            Size2D size = title.arrange(g2, constraint);
            titleArea = createAlignedRectangle2D(size, rect,
                    title.getHorizontalAlignment(), VerticalAlignment.TOP);
            height += size.height;
        }
        else if (position == RectangleEdge.BOTTOM) {
            Size2D size = title.arrange(g2, constraint);
            titleArea = createAlignedRectangle2D(size, rect,
                    title.getHorizontalAlignment(), VerticalAlignment.BOTTOM);
            height += size.height;
        }
        else if (position == RectangleEdge.RIGHT) {
            Size2D size = title.arrange(g2, constraint);
            titleArea = createAlignedRectangle2D(size, rect,
                    HorizontalAlignment.RIGHT, title.getVerticalAlignment());
            width += size.width;
        }

        else if (position == RectangleEdge.LEFT) {
            Size2D size = title.arrange(g2, constraint);
            titleArea = createAlignedRectangle2D(size, rect,
                    HorizontalAlignment.LEFT, title.getVerticalAlignment());
            width += size.width;
        }
        rect.setRect(rect.getX(), rect.getY(), width, height);
	}
And some demo code

Code: Select all

import java.awt.geom.Rectangle2D;
import java.awt.geom.Rectangle2D.Double;
import java.awt.Font;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.io.File;
import javax.swing.JFrame;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.xy.DefaultXYDataset;
import org.jfree.ui.RectangleEdge;
public class QuadraticPlotDemo {
	public static void main(String[] args) {
		double[][] values = new double[][]{{1,2,3,4,5},{10,15,8,37,23}};
		DefaultXYDataset set = new DefaultXYDataset();
		set.addSeries("Values",values);		
		JFreeChart chart = ChartFactory.createScatterPlot(
			"Quadratic Demo","x","y",set,PlotOrientation.VERTICAL,true, true, false);
		//chart.getLegend().setPosition(RectangleEdge.LEFT);
		TextTitle big = new TextTitle("Long title in a big font",new Font("SansSerif",1,36));
		big.setPosition(RectangleEdge.RIGHT);
		chart.addSubtitle(big);
		TextTitle small = new TextTitle("An even longer title than the first one but with a smaller font",new Font("SansSerif",1,24));
		small.setPosition(RectangleEdge.LEFT);
		chart.addSubtitle(small);
		//chart.getXYPlot().setOrientation(PlotOrientation.HORIZONTAL);
		BufferedImage image = new BufferedImage(400,800,BufferedImage.TYPE_4BYTE_ABGR);
		Graphics2D g2 = image.createGraphics();
		Rectangle2D rect = 	new Rectangle2D.Double(0,0,600,600.0);
		chart.getXYPlot().expandDataToPlotArea(g2,rect);
		chart.expandPlotToChartArea(g2,rect);
		Rectangle2D drawer = new Rectangle2D.Double(0,0,rect.getWidth()-rect.getX(),rect.getHeight()-rect.getY()); 
		System.out.println(rect.toString());	
		try{
			ChartUtilities.saveChartAsPNG(new File("export.png"),chart,(int)rect.getWidth(),(int)rect.getHeight());		
		}
		catch(Exception e){
			e.printStackTrace();
		}	
	}
}
Last edited by paradoxoff on Tue Jan 06, 2009 2:28 pm, edited 1 time in total.

paradoxoff
Posts: 1634
Joined: Sat Feb 17, 2007 1:51 pm

Post by paradoxoff » Tue Jan 06, 2009 2:22 pm

The approach above works only for "static charts". If you want to modify the chart (after it has been created with the "best" chart area), by e.g. adding/removing axes or titles, the nice and well defined plot/data area will be messed up. We thus need to introduce some kind of "preferred size calculation", similar to what is used in a JPanel that contains various JComponents of various size.
Prompted by the request for "set proportions between domain and range axes" and kalle´s comments in another thread, here are some thoughts:
Add the following fields to the Plot class, first two with public setters/getters, the last one with a public getter and no setter:

Code: Select all

Size2D fixedDataAreaSize

Code: Select all

boolean useFixedDataAreaSize

Code: Select all

Size2D preferredPlotAreaSize
The preferredPlotAreaSize will need to be updated in case of
1. an AxisChangeEvent or
2. if the plot insets are changed and
3. possibly on more occasions but 1. and 2. are all that come to my mind
but only if useFixedDataAreaSize is true.
The preferredPlotAreaSize can be calculated in a similar way as described above via a method like expandDataToPlotArea.

Add the following fields to the JFreeChart class, first two with public setters/getters, the last one with a public getter and no setter:

Code: Select all

Size2D fixedPlotAreaSize

Code: Select all

boolean useFixedPlotAreaSize

Code: Select all

Size2D preferredChartAreaSize
The preferredChartAreaSize will have to be changed/recalculated if
1. a PlotChangeEvent is triggered.
2. a TitleChangeEvent is triggered.
3. the chart padding is changed.
4. possibly on more occasions
but only if useFixedPlotAreaSize is true.
The preferredChartAreaSize can be calculated with a method like expandPlotToChartArea above. THe plot area that is expanded with the space for the chart insets and the titles can either be
1. the fixedPlotAreaSize (if plot.getUseFixedDataAreaSize() is false) or
2. the return value from plot.getPreferredPlotAreaSize() in case plot.getUseFixedDataAreaSize() is true.
An object that wants to draw a JFreeChart could check whether the chart uses a fixedPlotArea size and thus has a preferredChartAreaSize and construct the rectangle for the JFreeChart.draw method based on this preferredChartAreaSize.

Since we can´t access the Graphics2D instance that is required to calculate a chart area size based on a plot area size and to calculate a plot area size based on a data area size from within a [Plot/Title/Axis]ChangeEvent or when the padding/insets of the chart/plot are changing, we cannot directly update the sizes in case of the various events. But wen can set flags that indicate whether the values need to be updated in case the JFreeChart needs to be drawn.

The drawing process could thus look like this:
1. It is checked whether JfreeChart.getUseFixedPlotAreaSize() is true.
2. If so, the preferredChartAreaSize is requested. The getter is given the Graphics2D instance on which the chart should be drawn.
3. In the getPreferredChartAreaSize(Graphics2D g2) method of the JFreeChart class, it is checked whether a private flag chartSizeValid is true. Plot- and TitleChangeEvents (among others) will reset this flag to false. If the flag is true, the "old" and still valid size is returned.
4. If chartSizeValid is false, a new size is calculated. If plot.getUseFixedDataAreaSize() returns false, a user defined plot area size is used as starting point to calculate the new chart area size. The new chart area size is stored to be available for the next draw request and returned for the current draw request. The chartSizeValid flag is set to true.
5. If plot.getUseFixedDataArea() returns true, the preferredPlotAreaSize is requested from within JFreeChart.getPreferredChartAreaSize(Graphics2D g2).
6. In the getPreferredPlotAreaSize(Graphics2D g2) method of the Plot class it is checked whether a flag plotSizeValid is true. AxisChangeEvents and other occasions will set this flag to false. If plotSizeValid is true, the old and still valid plot area size is returned. If plotSizeValid is false, then a new plot area size is calculated (by expanding the fixedDataAreaSize) with the provided Graphics2D, stored internally and returned. The plotSizeValid flag is set to true.
7. Based on the returned preferredPlotAreaSize, a new preferredChartAreaSize is calculated by expanding increasing the preferredPlotAreaSize, stored internally and returned. chartSizeValid is set to true.
8. The returnd preferredChartAreaSize is used to create a Rectangle that is used as parameter in the JFreeChart.draw-method.

Does that approach look valid? The only thing that I can think of at present is what could happen if subsequent draw requests of an unchanged chart (i.e. all plot/ChartSizeValid flags are true) use two different Graphics2D implementations. But a public method revalidate(Graphics2D g2) that leads to a recalculation of the chart area size regardless of the chartSizeValid flag could probably solve these issues (if any).
Feedback/comments are welcome.

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 06, 2009 11:04 pm

Thanks for your detailed posts.

I can follow the approach you've outlined in your first post - you define the size of the rectangle that you want the data to be plotted in, then work backwards to determine (a) the size of the rectangle needed for the XYPlot to include the axes plus the fixed size data area, and then (b) the size of the rectangle needed for the JFreeChart instance to include all the titles, legend etc plus the fixed size plot area. That's a useful option, and one that we should get incorporated into a future release.

A got a little lost in the second post with the purpose of the "preferredSize" fields. Is it just to cache the calculated rectangles to avoid (if it is possible) recalculating everything when something in the chart is updated? Or was there some other purpose that I missed?

I think the fact that the chart size can grow or shrink is something that would be difficult to handle within a GUI (e.g. Swing) environment (bearing in mind that the preferredSize() is not always granted by the layout manager...what size would the chart take in that case?).

But this does remind me of some work I did for a client a few years ago to create a version of JFreeChart where the layouting was separate from the rendering...this permitted the implementation of the existing fixed (constrained) overall chart size layout, plus an unconstrained layout where the chart took its "preferred" size (you generally have to specify the data area, as you are doing in your approach, otherwise the preferred overall size is indeterminate), plus some other combinations such as fixed width (but unconstrained height) and vice versa. It was an interesting approach that had some great advantages (particularly for static charts), but unfortunately enough quirks in the implementation that I didn't feel confident that it could be merged into the main JFreeChart release without a lot more work. And we didn't have the resources for it.

But in the long run, maybe that is the best solution for this...to separate out the layouting and rendering parts and make them independent (as much as that is possible).
David Gilbert
JFreeChart Project Leader

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

kalle
Posts: 36
Joined: Wed Dec 31, 2008 4:17 pm

Post by kalle » Wed Jan 07, 2009 1:46 am

Personally I'd recommend taking java-way all the way here. Using layoutmanagers to layout everything should make life easier. These managers and components can then define which one axes or data area give ground.

One thing that I've been looking for is shorter axes like 100k, 1m, 1.5m and so forth. This might pave way for that too.

Also, it takes away a lot of pains concerning different legend locations and such. If I wish to layout my legend where the sun doesn't shine, I'd be welcome to do so. Particularly easy would be overlaylayout over the data area... ;)

paradoxoff
Posts: 1634
Joined: Sat Feb 17, 2007 1:51 pm

Post by paradoxoff » Wed Jan 07, 2009 9:46 pm

Hi David,
thanks for your response.
david.gilbert wrote: I got a little lost in the second post with the purpose of the "preferredSize" fields. Is it just to cache the calculated rectangles to avoid (if it is possible) recalculating everything when something in the chart is updated? Or was there some other purpose that I missed?
You are right, the preferredSize fields just exist to avoid a recalculation of the preferredSize if such a recalculation is not required.
And the fixedSize fields just replace the rectangles (or better their width and height) that were used as parameters for the expandXXX methods explaind in the first post. I thought it would be better to store the rectangles/sizes inside the JFreeChart and Plot classes instead of e. g. in a ChartPanel.
david.gilbert wrote: I think the fact that the chart size can grow or shrink is something that would be difficult to handle within a GUI (e.g. Swing) environment (bearing in mind that the preferredSize() is not always granted by the layout manager...what size would the chart take in that case?).
The object that wants to draw the chart (e. g. a ChartPanel or generally the "context provider") can decide whether it wants to honor the preferred chart size or not. It can even decide to not ask the JFreeChart at all! It is just an option! If the preferred chart size can not be granted for whatever reason (might it be layout constraint of a swing gui or, if we think about the creation of images, crossing of a given maximum image size threshold) it should be up to the context provider to find a solution, e.g. determine the aspect ratio of the plot or data area and shrink those areas until the preferred size is reached, or transfer the maximum possible size to the chart and let the chart return a new preferredSize that takes the given constraints into account.
In order to keep the swing layout managers from interfering, I have written a ChartComponent that derives from JComponent (my ChartComponent is 99 % the ChartPanel. I just cut out the scaling code that might produce a skewed JFreeChart if the available ChartPanel size is outside the minx/max range) and place that in a JScrollPane that is located in a JTabbedPane. When the chart is made visible, I get the size of the chart (which is at present stored in a wrapper object, but I would like to store it in the JFreeChart directly), use that to set the preferred size of the ChartComponent and the preferred size of the JScrollPane´s viewport. In that way the chart (or precisely, the ChartComponent) has always the same size. In case the space on the JTabbedPane is smaller or bigger than the size of the ChartComponent, I either get scroll bars or a dull grey border around the chart but either case is fine for me.
kalle wrote: Using layoutmanagers to layout everything should make life easier.
Well lets see . :D .. Until I discovered Miglayout, doing the layout code was a biiig pain for me. I am not sure what is the best approach: have a "chart object" with a "layout manager" and add to that "chart object" the data area, the axes, legends, titles asf at specific positions using some layout manager constraints (the next step would probably be a GUI builder for a JFreeChart) or store the position information in the title etc. itself. I am happy with the way how a JFreeChart is constructed at present. I just want to have a way to control the data area size!
kalle wrote: Particularly easy would be overlaylayout over the data area... :wink:
Agreed :wink: . One step towards the omnipotent null layout of the swing world. I have seen people been beaten up in java forums for using it.

RichardWest
Posts: 844
Joined: Fri Oct 13, 2006 9:29 pm
Location: Sunnyvale, CA

Post by RichardWest » Wed Jan 07, 2009 10:15 pm

paradoxoff wrote:Well lets see . :D .. Until I discovered Miglayout, doing the layout code was a biiig pain for me. I am not sure what is the best approach: have a "chart object" with a "layout manager" and add to that "chart object" the data area, the axes, legends, titles asf at specific positions using some layout manager constraints (the next step would probably be a GUI builder for a JFreeChart) or store the position information in the title etc. itself. I am happy with the way how a JFreeChart is constructed at present. I just want to have a way to control the data area size!
MigLayout is a great library. It certainly takes the headache out of complex layouts. If I am not mistaken, MigLayout is seriously being considered for inclusion in a future revision of Java.

I agree that having a layout manager control how a chart is drawn could get overly complex. However, a layout manager could provide some simple controls over the plot, title, and legend placement. Using BorderLayout (for example), you could specify you want the chart in the center, title at 'north', and the legend at 'east'.
Richard West
Design Engineer II
Advanced Micro Devices
Sunnyvale, CA

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 » Wed Jan 07, 2009 10:16 pm

kalle wrote:One thing that I've been looking for is shorter axes like 100k, 1m, 1.5m and so forth. This might pave way for that too.
This could be done now, just by implementing a custom NumberFormat class and populating a set of "standard" tick units with appropriate formats.
kalle wrote:Also, it takes away a lot of pains concerning different legend locations and such. If I wish to layout my legend where the sun doesn't shine, I'd be welcome to do so. Particularly easy would be overlaylayout over the data area... ;)
The XYTitleAnnotation is one approach to putting a legend (actually any Title) over the data area. But it isn't necessary to feel constrained by using the annotations framework for this either...you could subclass XYPlot (or CategoryPlot) and near the end of the draw() method (about where drawAnnotations() is called) insert whatever code you want to draw the legend wherever you want inside the data area.
David Gilbert
JFreeChart Project Leader

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

kalle
Posts: 36
Joined: Wed Dec 31, 2008 4:17 pm

Post by kalle » Thu Jan 08, 2009 6:33 am

I'm not saying jfc should use gridbaglayout, far from it.

LayoutManager2 interface is pretty simple to implement, and it allows to create ChartLayout or what ever is required, but separation of layout/renderer would *allow* the use of other *standard* methods for layouting.

It's not to say that things would be much different after such change, mainly I'd say that edges for axes might be changed and definitely renderer would no longer be responsible for rendering that 'component'.

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 Jan 08, 2009 8:54 am

Oh, it would be a bad idea to use LayoutManager2 because that's hard-coded to use AWT Component and Container objects and trying to retrofit JFreeChart classes into that hierarchy would just make a mess that you wouldn't recognise as a chart library.
David Gilbert
JFreeChart Project Leader

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

kalle
Posts: 36
Joined: Wed Dec 31, 2008 4:17 pm

Post by kalle » Thu Jan 08, 2009 11:32 am

I disagree. Or more like I do not agree completely. Component is bad, JComponent is more acceptable. LayoutManagers are able to simplify the process as any swing developer would agree.

As Richard pointed out, using BorderLayout for charting is intuitive. As I stated, using OverlayLayout on data area component to place legend is intuitive.

Creating ones own layoutmanager implementation is not a hard task, albeit a professional approach. Of course the interface used would be LayoutManager, as LayoutManager2 is only a extension of the former.

I do see that changing library like this would be major change, but I totally believe it to be a good change. If separation of layout and rendering is considered, existing layout managers should be possible to use.

I'm thinking that this does more than just said separation. It actually completes the MVC, you have data (complete chart; datasets and axes models), views (components; axes, data areas, titles, legends) and controller (what ever infrastructure it is connected to by the user).

Is there some reason why axis/chart/title/legend could not be a jcomponent? It's just a drawing hierarchy.

paradoxoff
Posts: 1634
Joined: Sat Feb 17, 2007 1:51 pm

Post by paradoxoff » Sat Jan 17, 2009 10:10 pm

I just wonder whether that would't add too much overhead if every axis, legend, annotation, marker... extended JComponent.
Moreover, I am not sure whether it would be necessarily better to add e. g. a title to a specific position in the chart instead of storing the position in the title itself and simply add the title as such to the chart.
I have integrated the necessary methods to maintain a constant data or plot area following the aproach outlined above. It is still a bit messy since it is a mix of the first approach with expansion of rectangles and the second one which is based on preferred sizes but at least seems to work.Using a custom JComponent placed in a JScrollBar for displaying the chart did also behave as expected. If I get things cleaned up I will probably submit a patch.

paradoxoff
Posts: 1634
Joined: Sat Feb 17, 2007 1:51 pm

Post by paradoxoff » Sun Jan 18, 2009 1:32 pm

I have uploaded a patch for 1.0.12.

The public methods for expanding rectangles are gone. The user can only set fixed sizes and flag whether they should be used, the rest is handled in proteced or in private.
The patch also includes changes to ChartPanel: a flag useChartSize that indicates whether the ChartPanel should calculate its size based on the preferred chart area size (scaling is diabled in this case) or whether it should behave as it does now. The former case is handled in an additonal "paintFixedComponent(Graphics2D)" method.
[edit: the "paintFixedComponent" method was apparently a bad idea and killed the zooming. The new patch handles both use cases (preferred chart size is honored or not) in a single paintComponent. The new patch also included an additional flag "useFixedChartAreaSize" to indicate whether the chart should be drawn using a fixed size w/o checking further flags set in the plot ].

If the chart panel is placed in a JScrollPane, things work as expected (at least for the test cases that I have checked), i.e. the scroll pane honors the change in the size of the chart panel if e. g. titles are added to a JFreeChart with a fixed data area and if the chart panel has been set to use the preferred chart size. If the chart panel is placed in a JFrame, the JFrame frequently refuses to resize accordingly (tested with Sun Java 1.5.0_15 on Win2K) though I have tried all kinds of validation, pack and doLayout.:shock: The patch contains a little demo app to show this.

I would be very happy if a proficient Swing programmer could explain or even fix this behaviour! Thanks in advance!

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 20, 2009 10:40 am

Thanks, hopefully I can try this out soon.
David Gilbert
JFreeChart Project Leader

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

sammen
Posts: 15
Joined: Mon Feb 02, 2009 9:55 am

Re: [HOWTO] create charts with defined data/plot area

Post by sammen » Mon Mar 02, 2009 4:38 pm

Hello paradoxoff,

I'm trying to add ternary/triangular diagrams to JFreeChart, and while doing that I discovered the limitation concerning control of axis lengths. I want the triangular diagrams equilateral and in order to achieve that I need to control the axis lengths.

So I found your patch and hope this will be the solution. However I'm a bit confused about what your example application is showing. I don't have a good feeling for how the sizing and positioning of different widgets and JFreeChart components work yet, so please excuse me if I ask trivial questions.

Did you provide an example application to show what is not working (i.e. ChartPanel in a JFrame) or am I doing something wrong? With your patch and as-provided example I get rectangular diagrams, contrary to what the "Quadratic demo" title says. Also, in the code there is a JScrollPane but it doesn't look like it is added to the frame. If I add it to the frame the diagrams are still rectangular. In the output it looks like the sizes are calculated correctly in both cases:

Code: Select all

run:
Plot area java.awt.geom.Rectangle2D$Double[x=8.0,y=30.140625,w=463.0,h=649.375]
Data area java.awt.geom.Rectangle2D$Double[x=67.8984375,y=34.140625,w=399.1015625,h=599.9921875]
Adding axis
Adding text title
Plot area java.awt.geom.Rectangle2D$Double[x=8.0,y=30.140625,w=513.71875,h=649.375]
Data area java.awt.geom.Rectangle2D$Double[x=67.8984375,y=34.140625,w=399.7265625,h=599.9921875]
Adding 2nd text title
Plot area java.awt.geom.Rectangle2D$Double[x=8.0,y=30.140625,w=513.71875,h=649.25]
Data area java.awt.geom.Rectangle2D$Double[x=67.8984375,y=34.140625,w=399.7265625,h=599.8671875]
Adding 3rd text title
Plot area java.awt.geom.Rectangle2D$Double[x=70.375,y=30.140625,w=513.34375,h=649.25]
Data area java.awt.geom.Rectangle2D$Double[x=130.2734375,y=34.140625,w=399.3515625,h=599.8671875]

paradoxoff
Posts: 1634
Joined: Sat Feb 17, 2007 1:51 pm

Re: [HOWTO] create charts with defined data/plot area

Post by paradoxoff » Mon Mar 02, 2009 7:35 pm

Hi sammen,
thanks for trying out the patch!
Sorry for the confusion. The code snippets and the sample program from my first post are outdated. At least, the demo program should result in the generation of a PNG showing a chart with a data area with 600x600 pixels, hence the name "QuadraticPlotDemo".
What you have run seems to be the sample program included in the second version of the patch (at least, the status messages in the command line suggest that. The sample app in my first post does not produce any status messages). In this patch version, the sizes of the chart/plot/data area can be stored permanently in the chart/plot itself, and can be automatically updated to take various constraints into account (e. g. a constant data area). It is then up to the "graphics context provider" (e. g. the ChartPanel) to request these sizes or not.
The sample program of this second version does not produce a chart with a plot having a quadratic data area. In order to change the data area, you will have to change line 37 from

Code: Select all

plot.setFixedDataAreaSize(new Size2D(400,600));
to

Code: Select all

plot.setFixedDataAreaSize(new Size2D(500,500));
or the like.
If the "preferred size" of the chart area (which is calculated based on the fixed data area plus the space for the axis plus the space for the titles plus insets/padding and the like and which will grow if axes and titles are added) will be respected by the ChartPanel is controlled separately. By calling

Code: Select all

chartPanel.setUseChartSize(true);
you tell the ChartPanel to respect this size and to change its preferredSize when the preferred size of the chart is changing (indicated by a ChartChangeEvent), and by calling

Code: Select all

chartPanel.setSize(new Dimension((int)chartSize.getWidth(),(int)chartSize.getHeight()));
and

Code: Select all

chartPanel.setPreferredSize(new Dimension((int)chartSize.getWidth(),(int)chartSize.getHeight()));
you define the initial size of the chart panel (before the chart has been drawn).
Whether the size of the chart panel is respected by the container in which it is placed is a third story.
In the provided demo app, the chartPanel is directly placed in the content pane of a JFrame. For some reason, the JFrame is not reliably resized if the size of the chart panel is changing (so you are right that the sample application as it is shows something that is not working. That could by my fault or a bug in swing :wink: ). If the chart panel is placed in a JScrollPane, the JScrollPane correctly notices changes in the size of the chart panel. The JScrollPane does of course not resize itself, but it adjusts its scroll bars. In order to see that, you will have to change line 51 from

Code: Select all

frame.getContentPane().add(chartPanel);
to

Code: Select all

frame.getContentPane().add(scrollPane);
That should do it.
Just post any further questions/findings. They are very welcome!
best regards
Paradoxoff

Locked