New Approach to show LegendTitle

Discussion about JFreeChart related to stockmarket charts.
Locked
Celso
Posts: 10
Joined: Mon Feb 16, 2009 6:39 pm

New Approach to show LegendTitle

Post by Celso » Sun Mar 01, 2009 7:33 pm

I have created a class named FinStockXYPlot that extends XYPlot and has a member of type LegendTitle, so it can be drawn in any position relative to XYPlot (namely TOP, BOTTOM, LEFT, RIGHT), according to a parameter passed in a call to the method drawTitle.
I think this is better than the default behavior of JFreeChart, which is creating the Legends and put them all together on screen.
Take a look at the picture.

http://picasaweb.google.com.br/ricardo. ... 5334564226

Code: Select all

package br.com.crmsoft.finstock.chart.plot;

import static br.com.crmsoft.finstock.Constantes.FONT_LEGEND_TITLE;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.AxisSpace;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.block.BlockParams;
import org.jfree.chart.block.LengthConstraintType;
import org.jfree.chart.block.LineBorder;
import org.jfree.chart.block.RectangleConstraint;
import org.jfree.chart.plot.CombinedDomainXYPlot;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.plot.PlotState;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.chart.title.Title;
import org.jfree.data.Range;
import org.jfree.data.xy.XYDataset;
import org.jfree.ui.HorizontalAlignment;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RectangleInsets;
import org.jfree.ui.Size2D;
import org.jfree.ui.VerticalAlignment;

/**
 * <p>Classe que herda todas as caracteristicas do XYPlot e permite que seja incluído uma legenda para cada Plot, ao invés
 * de uma para o {@link JFreeChart}. Com esta melhoria é possível utilizar com o {@link CombinedDomainXYPlot} e ter a separação
 * dos plots pela legenda.</p>
 * 
 * @author Celso Ricardo - CRMSoft Projetos e Consultoria
 * @version $Id$
 */
public class FinStockXYPlot extends XYPlot {

	private static final long serialVersionUID = 826606319967789395L;
	
	/**
	 * The chart innerSubtitles (zero, one or many). This field should never be <code>null</code>.
	 */
	protected LegendTitle legendTitle;
	
	protected boolean legendTitleVisible = true;

	public FinStockXYPlot() {
		super();
	}

	public FinStockXYPlot(XYDataset dataset, ValueAxis domainAxis, ValueAxis rangeAxis, XYItemRenderer renderer) {
		this(dataset, domainAxis, rangeAxis, renderer, true);
	}
	
	public FinStockXYPlot(XYDataset dataset, ValueAxis domainAxis, ValueAxis rangeAxis, XYItemRenderer renderer, boolean legendTitleVisible) {
		super(dataset, domainAxis, rangeAxis, renderer);
		
		this.legendTitleVisible = legendTitleVisible;
		
		this.legendTitle = new LegendTitle(this);
		this.legendTitle.setItemFont(FONT_LEGEND_TITLE);
		this.legendTitle.setHorizontalAlignment(HorizontalAlignment.LEFT);
		this.legendTitle.setPosition(RectangleEdge.TOP);
		this.legendTitle.setMargin(new RectangleInsets(1.0, 0, 1.0, 0));
		this.legendTitle.setFrame(new LineBorder(Color.GRAY, new BasicStroke(1.0f), new RectangleInsets()));
		this.legendTitle.setBackgroundPaint(Color.white);
		this.setLegendTitle(this.legendTitle);
	}

	public void setLegendTitle(LegendTitle legendTitle) {
		this.legendTitle = legendTitle;
	}
	
	public boolean isLegendTitleVisible() {
		return legendTitleVisible;
	}

	public void setLegendTitleVisible(boolean legendTitleVisible) {
		this.legendTitleVisible = legendTitleVisible;
		this.fireChangeEvent();
	}

	@Override
	public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, PlotState parentState, PlotRenderingInfo info) {
		
		// Desenhando legenda
		AxisSpace space = new AxisSpace();
        space = calculateRangeAxisSpace(g2, area, space);
        
		if (this.isLegendTitleVisible() && this.legendTitle != null) {
			if (this.legendTitle.isVisible()) {
				this.drawTitle(this.legendTitle, g2, area, space);
			}
		}
		
		// Desenhando o plot
		super.draw(g2, area, anchor, parentState, info);
	}
	
	/**
	 * Draws a title. The title should be drawn at the top, bottom, left or right of the specified area, and the area should be
	 * updated to reflect the amount of space used by the title.
	 * 
	 * @param t
	 *            the title (<code>null</code> not permitted).
	 * @param g2
	 *            the graphics device (<code>null</code> not permitted).
	 * @param area
	 *            the chart area, excluding any existing titles (<code>null</code> not permitted).
	 * @param space 
	 */
	protected void drawTitle(Title t, Graphics2D g2, Rectangle2D area, AxisSpace space) {

		if (t == null) {
			throw new IllegalArgumentException("Null 't' argument.");
		}

		if (area == null) {
			throw new IllegalArgumentException("Null 'area' argument.");
		}
		Rectangle2D titleArea = new Rectangle2D.Double();
		RectangleEdge position = t.getPosition();
		double ww = area.getWidth();
		if (ww <= 0.0) {
			return;
		}
		double hh = area.getHeight();
		if (hh <= 0.0) {
			return;
		}
		RectangleConstraint constraint = new RectangleConstraint(ww, new Range(0.0, ww), LengthConstraintType.RANGE, hh,
				new Range(0.0, hh), LengthConstraintType.RANGE);
		BlockParams p = new BlockParams();
		if (position == RectangleEdge.TOP) {
			Size2D size = t.arrange(g2, constraint);
			titleArea = createAlignedRectangle2D(size, area, space, t.getHorizontalAlignment(), VerticalAlignment.TOP);
			t.draw(g2, titleArea, p);
			area.setRect(area.getX(), Math.min(area.getY() + size.height, area.getMaxY()), area.getWidth(), Math.max(area
					.getHeight()
					- size.height, 0));
		} else if (position == RectangleEdge.BOTTOM) {
			Size2D size = t.arrange(g2, constraint);
			titleArea = createAlignedRectangle2D(size, area, space, t.getHorizontalAlignment(), VerticalAlignment.BOTTOM);
			t.draw(g2, titleArea, p);
			area.setRect(area.getX(), area.getY(), area.getWidth(), area.getHeight() - size.height);
		} else if (position == RectangleEdge.RIGHT) {
			Size2D size = t.arrange(g2, constraint);
			titleArea = createAlignedRectangle2D(size, area, space, HorizontalAlignment.RIGHT, t.getVerticalAlignment());
			t.draw(g2, titleArea, p);
			area.setRect(area.getX(), area.getY(), area.getWidth() - size.width, area.getHeight());
		}

		else if (position == RectangleEdge.LEFT) {
			Size2D size = t.arrange(g2, constraint);
			titleArea = createAlignedRectangle2D(size, area, space, HorizontalAlignment.LEFT, t.getVerticalAlignment());
			t.draw(g2, titleArea, p);
			area.setRect(area.getX() + size.width, area.getY(), area.getWidth() - size.width, area.getHeight());
		} else {
			throw new RuntimeException("Unrecognised title position.");
		}
	}

	/**
	 * Creates a rectangle that is aligned to the frame.
	 * 
	 * @param dimensions
	 *            the dimensions for the rectangle.
	 * @param frame
	 *            the frame to align to.
	 * @param space 
	 * @param hAlign
	 *            the horizontal alignment.
	 * @param vAlign
	 *            the vertical alignment.
	 * 
	 * @return A rectangle.
	 */
	private Rectangle2D createAlignedRectangle2D(Size2D dimensions, Rectangle2D frame, AxisSpace space, HorizontalAlignment hAlign,
			VerticalAlignment vAlign) {
		double x = Double.NaN;
		double y = Double.NaN;
		if (hAlign == HorizontalAlignment.LEFT) {
			x = frame.getX();
		} else if (hAlign == HorizontalAlignment.CENTER) {
			x = frame.getCenterX() - (dimensions.width / 2.0);
		} else if (hAlign == HorizontalAlignment.RIGHT) {
			x = frame.getMaxX() - dimensions.width;
		}
		if (vAlign == VerticalAlignment.TOP) {
			y = frame.getY();
		} else if (vAlign == VerticalAlignment.CENTER) {
			y = frame.getCenterY() - (dimensions.height / 2.0);
		} else if (vAlign == VerticalAlignment.BOTTOM) {
			y = frame.getMaxY() - dimensions.height;
		}

		return new Rectangle2D.Double(x + space.getLeft(), y, frame.getWidth() - space.getLeft() - space.getRight(), dimensions.height);
	}
}
Last edited by Celso on Tue Mar 03, 2009 3:56 pm, edited 1 time in total.

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Re: New Approach to show LegendTitle

Post by david.gilbert » Tue Mar 03, 2009 11:12 am

I think this is a good idea, but I think it ought to be generalised to handle any Title, not just a LegendTitle (even though LegendTitle is probably the most important case).
David Gilbert
JFreeChart Project Leader

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

Celso
Posts: 10
Joined: Mon Feb 16, 2009 6:39 pm

Re: New Approach to show LegendTitle

Post by Celso » Tue Mar 03, 2009 5:30 pm

Hi David,

thank you for the answer.
I changed to make it more generalised.

Code: Select all

package br.com.crmsoft.finstock.chart.plot;

import static br.com.crmsoft.finstock.Constantes.FONT_LEGEND_TITLE;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.AxisSpace;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.block.BlockParams;
import org.jfree.chart.block.LengthConstraintType;
import org.jfree.chart.block.LineBorder;
import org.jfree.chart.block.RectangleConstraint;
import org.jfree.chart.plot.CombinedDomainXYPlot;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.plot.PlotState;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.chart.title.Title;
import org.jfree.data.Range;
import org.jfree.data.xy.XYDataset;
import org.jfree.ui.HorizontalAlignment;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RectangleInsets;
import org.jfree.ui.Size2D;
import org.jfree.ui.VerticalAlignment;

/**
 * <p>Classe que herda todas as caracteristicas do XYPlot e permite que seja incluído uma legenda para cada Plot, ao invés
 * de uma para o {@link JFreeChart}. Com esta melhoria é possível utilizar com o {@link CombinedDomainXYPlot} e ter a separação
 * dos plots pela legenda.</p>
 * 
 * @author Celso Ricardo - CRMSoft Projetos e Consultoria
 * @version $Id: FinStockXYPlot.java,v 1.2 2009/03/01 19:13:49 celso Exp $
 */
public class FinStockXYPlot extends XYPlot {

	private static final long serialVersionUID = 826606319967789395L;
	
	/**
	 * The chart {@link Title}
	 */
	protected Title title;

	protected boolean titleVisible;
	
	public FinStockXYPlot() {
		super();
	}

	public FinStockXYPlot(XYDataset dataset, ValueAxis domainAxis, ValueAxis rangeAxis, XYItemRenderer renderer) {
		this(dataset, domainAxis, rangeAxis, renderer, true);
	}
	
	public FinStockXYPlot(XYDataset dataset, ValueAxis domainAxis, ValueAxis rangeAxis, XYItemRenderer renderer, boolean createLegendTitle) {
		super(dataset, domainAxis, rangeAxis, renderer);
		
		if (createLegendTitle) {
			LegendTitle legendTitle = new LegendTitle(this);
			legendTitle.setItemFont(FONT_LEGEND_TITLE);
			legendTitle.setHorizontalAlignment(HorizontalAlignment.LEFT);
			legendTitle.setPosition(RectangleEdge.TOP);
			legendTitle.setMargin(new RectangleInsets(1.0, 0, 1.0, 0));
			legendTitle.setFrame(new LineBorder(Color.GRAY, new BasicStroke(1.0f), new RectangleInsets()));
			legendTitle.setBackgroundPaint(Color.white);
			this.setTitle(legendTitle);
			
			this.titleVisible = true;
		}
	}

	public void setTitle(Title title) {
		this.title = title;
		this.fireChangeEvent();
	}
	
	public boolean isTitleVisible() {
		return this.titleVisible;
	}

	public void setTitleVisible(boolean titleVisible) {
		this.titleVisible = titleVisible;
		this.fireChangeEvent();
	}

	@Override
	public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, PlotState parentState, PlotRenderingInfo info) {
		
		// Desenhando legenda
		AxisSpace space = new AxisSpace();
        space = calculateRangeAxisSpace(g2, area, space);
        
		if (this.isTitleVisible() && this.title != null) {
			if (this.title.isVisible()) {
				this.drawTitle(this.title, g2, area, space);
			}
		}
		
		// Desenhando o plot
		super.draw(g2, area, anchor, parentState, info);
	}
	
	/**
	 * Draws a title. The title should be drawn at the top, bottom, left or right of the specified area, and the area should be
	 * updated to reflect the amount of space used by the title.
	 * 
	 * @param t
	 *            the title (<code>null</code> not permitted).
	 * @param g2
	 *            the graphics device (<code>null</code> not permitted).
	 * @param area
	 *            the chart area, excluding any existing titles (<code>null</code> not permitted).
	 * @param space 
	 */
	protected void drawTitle(Title t, Graphics2D g2, Rectangle2D area, AxisSpace space) {

		if (t == null) {
			throw new IllegalArgumentException("Null 't' argument.");
		}

		if (area == null) {
			throw new IllegalArgumentException("Null 'area' argument.");
		}
		Rectangle2D titleArea = new Rectangle2D.Double();
		RectangleEdge position = t.getPosition();
		double ww = area.getWidth();
		if (ww <= 0.0) {
			return;
		}
		double hh = area.getHeight();
		if (hh <= 0.0) {
			return;
		}
		RectangleConstraint constraint = new RectangleConstraint(ww, new Range(0.0, ww), LengthConstraintType.RANGE, hh,
				new Range(0.0, hh), LengthConstraintType.RANGE);
		BlockParams p = new BlockParams();
		if (position == RectangleEdge.TOP) {
			Size2D size = t.arrange(g2, constraint);
			titleArea = createAlignedRectangle2D(size, area, space, t.getHorizontalAlignment(), VerticalAlignment.TOP);
			t.draw(g2, titleArea, p);
			area.setRect(area.getX(), Math.min(area.getY() + size.height, area.getMaxY()), area.getWidth(), Math.max(area
					.getHeight()
					- size.height, 0));
		} else if (position == RectangleEdge.BOTTOM) {
			Size2D size = t.arrange(g2, constraint);
			titleArea = createAlignedRectangle2D(size, area, space, t.getHorizontalAlignment(), VerticalAlignment.BOTTOM);
			t.draw(g2, titleArea, p);
			area.setRect(area.getX(), area.getY(), area.getWidth(), area.getHeight() - size.height);
		} else if (position == RectangleEdge.RIGHT) {
			Size2D size = t.arrange(g2, constraint);
			titleArea = createAlignedRectangle2D(size, area, space, HorizontalAlignment.RIGHT, t.getVerticalAlignment());
			t.draw(g2, titleArea, p);
			area.setRect(area.getX(), area.getY(), area.getWidth() - size.width, area.getHeight());
		}

		else if (position == RectangleEdge.LEFT) {
			Size2D size = t.arrange(g2, constraint);
			titleArea = createAlignedRectangle2D(size, area, space, HorizontalAlignment.LEFT, t.getVerticalAlignment());
			t.draw(g2, titleArea, p);
			area.setRect(area.getX() + size.width, area.getY(), area.getWidth() - size.width, area.getHeight());
		} else {
			throw new RuntimeException("Unrecognised title position.");
		}
	}

	/**
	 * Creates a rectangle that is aligned to the frame.
	 * 
	 * @param dimensions
	 *            the dimensions for the rectangle.
	 * @param frame
	 *            the frame to align to.
	 * @param space 
	 * @param hAlign
	 *            the horizontal alignment.
	 * @param vAlign
	 *            the vertical alignment.
	 * 
	 * @return A rectangle.
	 */
	private Rectangle2D createAlignedRectangle2D(Size2D dimensions, Rectangle2D frame, AxisSpace space, HorizontalAlignment hAlign,
			VerticalAlignment vAlign) {
		double x = Double.NaN;
		double y = Double.NaN;
		if (hAlign == HorizontalAlignment.LEFT) {
			x = frame.getX();
		} else if (hAlign == HorizontalAlignment.CENTER) {
			x = frame.getCenterX() - (dimensions.width / 2.0);
		} else if (hAlign == HorizontalAlignment.RIGHT) {
			x = frame.getMaxX() - dimensions.width;
		}
		if (vAlign == VerticalAlignment.TOP) {
			y = frame.getY();
		} else if (vAlign == VerticalAlignment.CENTER) {
			y = frame.getCenterY() - (dimensions.height / 2.0);
		} else if (vAlign == VerticalAlignment.BOTTOM) {
			y = frame.getMaxY() - dimensions.height;
		}

		return new Rectangle2D.Double(x + space.getLeft(), y, frame.getWidth() - space.getLeft() - space.getRight(), dimensions.height);
	}
}

dallimor
Posts: 2
Joined: Sat Jun 06, 2009 8:37 am

Re: New Approach to show LegendTitle

Post by dallimor » Wed Jul 01, 2009 12:35 am

Just a heads up for people who are using this for combined plots.

If you try and put a legend title at the top or bottom of a plot in a combined range plot (or at left/right in combined domain) then the combined axis doesn't plot correctly as it doesn't know about the modified plot area.

I'm trying to do a workaround but its currently a bit circular. I will post if I get anything nice working

vrsarav
Posts: 16
Joined: Mon Nov 09, 2009 4:34 pm
antibot: No, of course not.

Re: New Approach to show LegendTitle

Post by vrsarav » Fri Nov 20, 2009 8:43 pm

Nice approach. I just did try using it. THough not perfect it works fine. At the moment, just adding to Top and Bottom works fine. Left and Right doesn't look that good.
I have one question. When we add LegendTitle with this new approach, I expect mouse click on it to return LegendItemEntity object.
But it doesn't seem to. Regular Legend's moue click gives the corresponding series, i guess.
Is there a way to make this derive as a valid chart entity so that i could cast it to LegendItemEntity?

roonghu
Posts: 3
Joined: Thu Mar 04, 2010 9:36 pm
antibot: No, of course not.

Re: New Approach to show LegendTitle

Post by roonghu » Thu Mar 04, 2010 9:44 pm

I am trying to use JFreeChart and generate a legend frame/box colored outline.
Since the following usage has deprecated,
chart.getLegend().setOutlinePaint(new Color(255,0,0));

I tried to use

plot.getRenderer().setBaseOutlinePaint(Color.red);

However, nothing changed. Can anyone tell me how to do that?
It is really inconvenient that any BlockBorder or BlockFrame in JFreeChart is only having "getPaint()" method, without "setPaint( Paint)" method.

Can it be implemented?

Thanks!

roonghu

rohans
Posts: 4
Joined: Wed Jul 07, 2010 3:07 am
antibot: No, of course not.

Re: New Approach to show LegendTitle

Post by rohans » Wed Jul 07, 2010 3:11 am

Celso- i have a question about your DomainAxis

http://picasaweb.google.com.br/ricardo. ... 0976390434

How were you able to break out the months and years in two separate lines?

Also, is there a way to have the range axis labels inside the graph? like in this example http://www.google.com/finance?q=GOOGLEINDEX_US%3AAUTOBY

thanks

Locked