Separate Legends overlapping

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
GarbageCan
Posts: 9
Joined: Thu Mar 26, 2015 12:45 am
antibot: No, of course not.

Separate Legends overlapping

Post by GarbageCan » Thu Mar 26, 2015 12:53 am

I have a time series chart that uses 2 y axes, and I had wanted to have separate legends for each axes. I found some code either here or on StackOverflow that does it, and for the most part it works fine.

Code: Select all

legend1 = new LegendTitle(plot.getRenderer(0));
legend1.setMargin(new RectangleInsets(2, 2, 2, 2));
legend1.setBorder(new BlockBorder());
legend2 = new LegendTitle(plot.getRenderer(1));
legend2.setMargin(new RectangleInsets(2, 2, 2, 2));
legend2.setBorder(new BlockBorder());
BlockContainer container = new BlockContainer(new BorderArrangement());
container.add(legend1, RectangleEdge.LEFT);
container.add(legend2, RectangleEdge.RIGHT);
container.add(new EmptyBlock(2000, 1));
CompositeTitle legends = new CompositeTitle(container);
legends.setPosition(RectangleEdge.BOTTOM);
chart.addSubtitle(legends);
Although I'm finding if I have enough time series, the left legend gets long enough that it prevents the right legend from showing up at all. Is it possible to set the minimum width on the container components or something to make sure both legends are able to be seen?

Prof.Coutinho
Posts: 1
Joined: Fri Mar 27, 2015 3:52 am
antibot: No, of course not.

Re: Separate Legends overlapping

Post by Prof.Coutinho » Fri Mar 27, 2015 4:08 am

Hi, by the way, do you know how to plot more than one graph on the same chart ? Thank you

GarbageCan
Posts: 9
Joined: Thu Mar 26, 2015 12:45 am
antibot: No, of course not.

Re: Separate Legends overlapping

Post by GarbageCan » Fri Mar 27, 2015 10:02 pm

I don't know, sorry. My separate legends are for multiple y-axes, not multiple plots, and for that it's simple enough to just add things to your plot like they do in the DualAxisDemo http://www.java2s.com/Code/Java/Chart/J ... sDemo2.htm

For multiple plots, I would probably just make multiple ChartPanels, and add them to a frame in however layout seemed appropriate.

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

Re: Separate Legends overlapping

Post by paradoxoff » Sun Mar 29, 2015 9:07 pm

Do I get you right that you have two legends where the legend items are arranged from left to right, and you want to show these two legends side-by-side?
if yes, then I suggest to use a GridArrangement(1,2) instead of a BorderArrangement. The BorderArrangement does not seem to be well suited for complex layouts under constrained conditions.

GarbageCan
Posts: 9
Joined: Thu Mar 26, 2015 12:45 am
antibot: No, of course not.

Re: Separate Legends overlapping

Post by GarbageCan » Tue Mar 31, 2015 6:32 pm

You are correct Paradoxoff. I tried the GridArrangment, but the contents of the legend ends up going past the border of this arrangement. Is there anyway to make it so it would go to a new line rather than just writing on top of the grid box next to it? I think the least messy thing I've been able to do at this point is to use the BorderArrangement and set the legends to the top and bottom. It's not exactly how I wanted it laid out but it will do.

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

Re: Separate Legends overlapping

Post by paradoxoff » Tue Mar 31, 2015 10:02 pm

I think you could achieve your goal by writing your own Arrangement class.
During the rendering of a JFreeChart, the main JFreeChart instance calls the arrange method of each of its Titles, using a RectangleConstraint where the LengthConstraintTypes for both width and height are of type LengthConstraintType.RANGE. If the Title is a CompositeTitle, it will call the arrange method of its container which in turn will call the arrange method of its Arrangement. The same RectangleConstraint will be used during these method calls. All what you would have to is to use a BlockContainer that is having an instance of your special Arrangement (instead of a BorderArrangemt or a GridArrangement).

In the arrange-method of your own arrangement, retrieve the width and height from the supplied RectangleConstraint object. Then, iterate over the blocks in the block container. In your case, you should have two blocks, one for each of the LegendTitles. Distribute the available width between the two Blocks/LegendTitles as needed (50/50, 33/34/33 with an empty block in between, …). Finally, call arrange on each block, using a RectangleConstraint where the LengthConstraintType for both width and height is of type LengthConstraintType.FIXED. You can achieve that by using the constructor RectangleConstraint(double width, double height). If you do that, the Arrangement of the LegendTitles (which should be an instance of FlowArrangement for LegendTitles placed at the top or bottom) should correctly handle the width constraint and wrap the LegendTitle into several rows. Once you have the size requirements for the two Blocks/LegendTitles, you then can combine them, add an eventual empty block between the two LegendTitles, and finally return the combined size.

GarbageCan
Posts: 9
Joined: Thu Mar 26, 2015 12:45 am
antibot: No, of course not.

Re: Separate Legends overlapping

Post by GarbageCan » Tue Mar 31, 2015 10:38 pm

I was looking for similar questions and I found a post of yours in here: http://www.jfree.org/phpBB2/viewtopic.php?t=27287
where you had a solution to setting a fixed width in flowArrangement. And that gave me a good jumping off point for writing my own arrangement.

Very appropriate that your direct advice sends me down the same path. Thanks for your help!

GarbageCan
Posts: 9
Joined: Thu Mar 26, 2015 12:45 am
antibot: No, of course not.

Re: Separate Legends overlapping

Post by GarbageCan » Wed Apr 01, 2015 2:12 am

In case anyone ever wants an arrangement that in the very specific case of having 2 legends, or items placed at either edge of a container, and wanting the widths of those legends to adjust based on which contains more/less, here is some code:

Code: Select all

import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.List;
import org.jfree.chart.block.Block;
import org.jfree.chart.block.BlockContainer;
import org.jfree.chart.block.FlowArrangement;
import org.jfree.chart.block.RectangleConstraint;
import org.jfree.ui.Size2D;

/**
 * A modification of the FlowArrangment class for arranging two legends on the left and right side under the chart
 * @author GarbageCan
 */
public class FlowArrangementVariableWidth extends FlowArrangement {
    private final double horizontalGap = 1.0;
    
    FlowArrangementVariableWidth() {
        super();
    }
    /**
     * Arranges the blocks in the container with a fixed width and no height
     * constraint.
     *
     * @param container  the container.
     * @param constraint  the constraint.
     * @param g2  the graphics device.
     *
     * @return The size.
     */
    @Override
    protected Size2D arrangeFN(BlockContainer container, Graphics2D g2,
                               RectangleConstraint constraint) {

        List blocks = container.getBlocks();
        double width = constraint.getWidth();
        
        Block block1 = (Block) blocks.get(0);
        Block block2 = (Block) blocks.get(1);
        Size2D size1 = block1.arrange(g2, RectangleConstraint.NONE);
        Size2D size2 = block2.arrange(g2, RectangleConstraint.NONE);
        
        double maxHeight;
        
        if(size1.width+size2.width+horizontalGap >= width ) {
            //Determine weighting of 80-20, 33-67, 50-50 and in which way based
            //on the width of each block
            double maxWidth1;
            double maxWidth2;

            if( size1.width >= 4*size2.width ) { // 80-20
                maxWidth1 = (width*0.8d) - horizontalGap;
                maxWidth2 = Math.min((width*0.2d) - horizontalGap, size2.width);
            } else if( size1.width >= 2*size2.width ) { //67-33
                maxWidth1 = (width*0.67d) - horizontalGap;
                maxWidth2 = Math.min((width*0.33d) - horizontalGap, size2.width);            
            } else if( size2.width >= 4*size1.width ) { //20-80
                maxWidth1 = Math.min((width*0.2d) - horizontalGap, size1.width);
                maxWidth2 = (width*0.8d) - horizontalGap;            
            } else if( size2.width >= 2*size1.width ) { //33-67
                maxWidth1 = Math.min((width*0.33d) - horizontalGap, size1.width);
                maxWidth2 = (width*0.67d) - horizontalGap;
            } else { // 50-50
                maxWidth1 = Math.min((width*0.5d) - horizontalGap, size1.width);
                maxWidth2 = Math.min((width*0.5d) - horizontalGap, size2.width);
            }
            
            
            size1 = block1.arrange(g2, RectangleConstraint.NONE.toFixedWidth(maxWidth1));
            size2 = block2.arrange(g2, RectangleConstraint.NONE.toFixedWidth(maxWidth2));

            block1.setBounds(new Rectangle2D.Double(0, 0, size1.width, size1.height));
            block2.setBounds(new Rectangle2D.Double(width-size2.width, 0, size2.width,size2.height));
            maxHeight = Math.max(size1.height, size2.height);
        } else {//everything fits across
            block1.setBounds(new Rectangle2D.Double(0, 0, size1.width, size1.height));
            block2.setBounds(new Rectangle2D.Double(width-size2.width, 0, size2.width, size2.height));
            
            maxHeight = Math.max(size1.height, size2.height);
        }
        
        
        
        return new Size2D(constraint.getWidth(), maxHeight);
    }
    
    /**
     * Changes the arrangeRR so immediately calls arrangeFR to make sure that the legends remain at the edges
     *
     * @param container  the container.
     * @param constraint  the constraint.
     * @param g2  the graphics device.
     *
     * @return The size after the arrangement.
     */
    @Override
    protected Size2D arrangeRR(BlockContainer container, Graphics2D g2, RectangleConstraint constraint) {
        RectangleConstraint c = constraint.toFixedWidth( constraint.getWidthRange().getUpperBound() );
        return arrangeFR(container, g2, c);
    }
    
}
In my case, the contents of each legend could contain from 1-15 data series and so the variableWidth was important.

Locked