added 'Hardcoded' Legend Items (labels & colors)

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
juan

added 'Hardcoded' Legend Items (labels & colors)

Post by juan » Tue Oct 29, 2002 11:20 pm

I have created a subclass of Legend (based on StandardLegend.java)
This class will allow you to add your own labels and colors for your Legend independent of the Series you use.

There is a new parameter when you instantiate JFChartLegendUtil which is a boolean value. true = you wish to add personalized (hardcoded) legend items.

A new method was also added: addLegendItem(String str, Paint paint)
you add whichever number of legend items you wish to add.

to use it:
------------

//set customized legend
JFChartLegendUtil legend = new JFChartLegendUtil( chart, true );

// Add label and color
legend.addLegendItem("Mexico", Color.white);
legend.addLegendItem("USA", new Color(170,255,170));
legend.addLegendItem("Canada", new Color(255,255,170));

// associate customized legend to the chart
chart.setLegend( legend );

Here is the code of JFChartLegendUtil:
------------------------------------------------
package com.viacore.consoleUI.chart;

/* ======================================
* JFreeChart : a free Java chart library
* ======================================
*
* Project Info: http://www.object-refinery.com/jfreechart/index.html
* Project Lead: David Gilbert (david.gilbert@object-refinery.com);
*
* (C) Copyright 2000-2002, by Simba Management Limited and Contributors.
*
* This library is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation;
* either version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* ---------------------------------------------------------
* (JFChartLegendUtil.java) based on StandardLegend.java
* ---------------------------------------------------------
* (C) Copyright 2000-2002, by Simba Management Limited and Contributors.
*
* Original Author: David Gilbert (for Simba Management Limited);
* Contributor(s): Andrzej Porebski;
*
* $Id: StandardLegend.java,v 1.5 2002/10/17 16:26:26 mungady Exp $
*
* Changes
* --------------------------
* 24-Oct-2002 : Modified and adapted by Juan Garcia-Garcia to enable hardcoded Event Summary Legend
*/

import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Font;
import java.awt.Paint;
import java.awt.Color;
import java.awt.Stroke;
import java.awt.BasicStroke;
import java.awt.FontMetrics;
import java.awt.font.LineMetrics;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import com.jrefinery.chart.event.LegendChangeEvent;
import com.jrefinery.chart.*;

import java.util.*;

/**
* A chart legend shows customized labels and does not rely on the existance of 'Series'
* that are plotted in a chart.
*/
public class JFChartLegendUtil extends Legend
{

/** Default font. */
public static final Font DEFAULT_FONT = new Font("SansSerif", Font.PLAIN, 10);

/** The pen/brush used to draw the outline of the legend. */
private Stroke outlineStroke;

/** The color used to draw the outline of the legend. */
private Paint outlinePaint;

/** The color used to draw the background of the legend. */
private Paint backgroundPaint;

/** The blank space inside the legend box. */
private Spacer innerGap;

/** The font used to display the legend item names. */
private Font itemFont;

/** The color used to display the legend item names. */
private Paint itemPaint;

/** The stroke used to outline key boxes. */
private Stroke keyBoxOutlineStroke = new BasicStroke(0.5f);

/** The paint used to outline key boxes. */
//private Paint keyBoxOutlinePaint = Color.lightGray;
private Paint keyBoxOutlinePaint = Color.gray;

/**. */
private boolean hcLegend = false;

/** Legend Item collection used for hard coded Legend Items */
LegendItemCollection legendItems = null;


/**
* Constructs a new legend with default settings.
*
* @param chart the chart that the legend belongs to.
*/
public JFChartLegendUtil(JFreeChart chart) {

this(chart,
3,
new Spacer(Spacer.ABSOLUTE, 2, 2, 2, 2),
Color.white, new BasicStroke(), Color.gray,
DEFAULT_FONT, Color.black);

}

/**
* Constructs a new legend with default settings and check for hardcoded values
*
* @param chart the chart that the legend belongs to.
*/
public JFChartLegendUtil(JFreeChart chart, boolean hcLegend) {

this(chart,
3,
new Spacer(Spacer.ABSOLUTE, 2, 2, 2, 2),
Color.white, new BasicStroke(), Color.gray,
DEFAULT_FONT, Color.black);

if ( hcLegend )
{
this.hcLegend = hcLegend;
legendItems = new LegendItemCollection();
}
}



/**
* Constructs a new legend.
*
* @param chart the chart that the legend belongs to.
* @param outerGap the gap around the outside of the legend.
* @param innerGap the gap inside the legend.
* @param backgroundPaint the background color.
* @param outlineStroke the pen/brush used to draw the outline.
* @param outlinePaint the color used to draw the outline.
* @param itemFont the font used to draw the legend items.
* @param itemPaint the color used to draw the legend items.
*/
public JFChartLegendUtil(JFreeChart chart,
int outerGap, Spacer innerGap,
Paint backgroundPaint,
Stroke outlineStroke, Paint outlinePaint,
Font itemFont, Paint itemPaint) {

super(chart, outerGap);
this.innerGap = innerGap;
this.backgroundPaint = backgroundPaint;
this.outlineStroke = outlineStroke;
this.outlinePaint = outlinePaint;
this.itemFont = itemFont;
this.itemPaint = itemPaint;

}

/**
* Returns the background color for the legend.
*
* @return the background color.
*/
public Paint getBackgroundPaint() {
return this.backgroundPaint;
}

/**
* Sets the background color of the legend.
* <P>
* Registered listeners are notified that the legend has changed.
*
* @param paint the new background color.
*/
public void setBackgroundPaint(Paint paint) {
this.backgroundPaint = paint;
notifyListeners(new LegendChangeEvent(this));
}

/**
* Returns the outline pen/brush.
*
* @return the outline pen/brush.
*/
public Stroke getOutlineStroke() {
return this.outlineStroke;
}

/**
* Sets the outline pen/brush.
* <P>
* Registered listeners are notified that the legend has changed.
*
* @param stroke the new outline pen/brush.
*/
public void setOutlineStroke(Stroke stroke) {
this.outlineStroke = stroke;
notifyListeners(new LegendChangeEvent(this));
}

/**
* Returns the outline color.
*
* @return the outline color.
*/
public Paint getOutlinePaint() {
return this.outlinePaint;
}

/**
* Sets the outline color.
* <P>
* Registered listeners are notified that the legend has changed.
*
* @param paint the new outline color.
*/
public void setOutlinePaint(Paint paint) {
this.outlinePaint = paint;
notifyListeners(new LegendChangeEvent(this));
}

/**
* Returns the series label font.
*
* @return the series label font.
*/
public Font getItemFont() {
return this.itemFont;
}

/**
* Sets the series label font.
* <P>
* Registered listeners are notified that the legend has changed.
*
* @param font the new series label font.
*/
public void setItemFont(Font font) {
this.itemFont = font;
notifyListeners(new LegendChangeEvent(this));
}

/**
* Returns the series label color.
*
* @return the series label color.
*/
public Paint getItemPaint() {
return this.itemPaint;
}

/**
* Sets the series label color.
* <P>
* Registered listeners are notified that the legend has changed.
*
* @param paint the new series label color.
*/
public void setItemPaint(Paint paint) {
this.itemPaint = paint;
notifyListeners(new LegendChangeEvent(this));
}

/**
* Returns the stroke used to outline key boxes.
*
* @return the stroke.
*/
public Stroke getKeyBoxOutlineStroke() {
return this.keyBoxOutlineStroke;
}

/**
* Sets the stroke used to outline key boxes.
* <P>
* Registered listeners are notified of the change.
*
* @param stroke the stroke.
*/
public void setKeyBoxOutlineStroke(Stroke stroke) {
this.keyBoxOutlineStroke = stroke;
notifyListeners(new LegendChangeEvent(this));
}

/**
* Returns the paint used to outline key boxes.
*
* @return the paint.
*/
public Paint getKeyBoxOutlinePaint() {
return this.keyBoxOutlinePaint;
}

/**
* Sets the paint used to outline key boxes.
* <P>
* Registered listeners are notified of the change.
*
* @param paint the paint.
*/
public void setKeyBoxOutlinePaint(Paint paint) {
this.keyBoxOutlinePaint = paint;
notifyListeners(new LegendChangeEvent(this));
}

/**
* Draws the legend on a Java 2D graphics device (such as the screen or a printer).
*
* @param g2 the graphics device.
* @param available the area within which the legend, and afterwards the plot, should be
* drawn.
*
* @return the area used by the legend.
*/
public Rectangle2D draw(Graphics2D g2, Rectangle2D available) {

return draw(g2, available, (getAnchor() & HORIZONTAL) != 0, (getAnchor() & INVERTED) != 0);

}

/**
* Draws the legend.
*
* @param g2 the graphics device.
* @param available the area available for drawing the chart.
* @param horizontal a flag indicating whether the legend items are laid out horizontally.
* @param inverted ???
*
* @return the remaining available drawing area.
*/
protected Rectangle2D draw(Graphics2D g2, Rectangle2D available, boolean horizontal, boolean inverted )
{

// not hardcoded values - default
if ( !hcLegend )
{
legendItems = getChart().getPlot().getLegendItems();
}

if ((legendItems != null) && (legendItems.getItemCount() > 0))
{

Rectangle2D legendArea = new Rectangle2D.Double();

// the translation point for the origin of the drawing system
Point2D translation = new Point2D.Double();

// Create buffer for individual rectangles within the legend
DrawableLegendItem[] items = new DrawableLegendItem[legendItems.getItemCount()];
g2.setFont(itemFont);

// Compute individual rectangles in the legend, translation point as well
// as the bounding box for the legend.
if (horizontal)
{
double xstart = available.getX() + getOuterGap();
double xlimit = available.getX() + available.getWidth() - 2 * getOuterGap() - 1;
double maxRowWidth = 0;
double xoffset = 0;
double rowHeight = 0;
double totalHeight = 0;
boolean startingNewRow = true;

for (int i = 0; i < legendItems.getItemCount(); i++)
{
items = createDrawableLegendItem(g2, legendItems.get(i),
xoffset, totalHeight);
if ((!startingNewRow)
&& (items.getX() + items.getWidth() + xstart > xlimit))
{

maxRowWidth = Math.max(maxRowWidth, xoffset);
xoffset = 0;
totalHeight += rowHeight;
i--;
startingNewRow = true;

}
else
{
rowHeight = Math.max(rowHeight, items.getHeight());
xoffset += items.getWidth();
startingNewRow = false;
}
}

maxRowWidth = Math.max(maxRowWidth, xoffset);
totalHeight += rowHeight;

// Create the bounding box
legendArea = new Rectangle2D.Double(0, 0, maxRowWidth, totalHeight);

// The yloc point is the variable part of the translation point
// for horizontal legends. xloc is constant.
double yloc = (inverted)
? available.getY() + available.getHeight() - totalHeight - getOuterGap()
: available.getY() + getOuterGap();
double xloc = available.getX() + available.getWidth() / 2 - maxRowWidth / 2;

// Create the translation point
translation = new Point2D.Double(xloc, yloc);
}
else
{ // vertical...
double totalHeight = 0;
double maxWidth = 0;
g2.setFont(itemFont);
for (int i = 0; i < items.length; i++)
{
items = createDrawableLegendItem(g2, legendItems.get(i),
0, totalHeight);
totalHeight += items.getHeight();
maxWidth = Math.max(maxWidth, items.getWidth());
}

// Create the bounding box
legendArea = new Rectangle2D.Float(0, 0, (float) maxWidth, (float) totalHeight);

// The xloc point is the variable part of the translation point
// for vertical legends. yloc is constant.
double xloc = (inverted)
? available.getMaxX() - maxWidth - getOuterGap()
: available.getX() + getOuterGap();
double yloc = available.getY() + (available.getHeight() / 2) - (totalHeight / 2);

// Create the translation point
translation = new Point2D.Double(xloc, yloc);
}

// Move the origin of the drawing to the appropriate location
g2.translate(translation.getX(), translation.getY());

// Draw the legend's bounding box
g2.setPaint(backgroundPaint);
g2.fill(legendArea);
g2.setPaint(outlinePaint);
g2.setStroke(outlineStroke);
g2.draw(legendArea);

// Draw individual series elements
for (int i = 0; i < items.length; i++)
{
g2.setPaint(items.getItem().getPaint());
Shape keyBox = items.getMarker();
g2.fill(keyBox);
if (getOutlineKeyBoxes())
{
g2.setPaint(this.keyBoxOutlinePaint);
g2.setStroke(this.keyBoxOutlineStroke);
g2.draw(keyBox);
}
g2.setPaint(this.itemPaint);
g2.setFont(this.itemFont);
g2.drawString(items[i].getItem().getLabel(),
(float) items[i].getLabelPosition().getX(),
(float) items[i].getLabelPosition().getY());
}

// translate the origin back to what it was prior to drawing the legend
g2.translate(-translation.getX(), -translation.getY());

if (horizontal)
{
// The remaining drawing area bounding box will have the same
// x origin, width and height independent of the anchor's
// location. The variable is the y coordinate. If the anchor is
// SOUTH, the y coordinate is simply the original y coordinate
// of the available area. If it is NORTH, we adjust original y
// by the total height of the legend and the initial gap.
double yy = available.getY();
double yloc = (inverted) ? yy
: yy + legendArea.getHeight() + getOuterGap();

// return the remaining available drawing area
return new Rectangle2D.Double(available.getX(), yloc, available.getWidth(),
available.getHeight() - legendArea.getHeight() - 2 * getOuterGap());
}
else
{
// The remaining drawing area bounding box will have the same
// y origin, width and height independent of the anchor's
// location. The variable is the x coordinate. If the anchor is
// EAST, the x coordinate is simply the original x coordinate
// of the available area. If it is WEST, we adjust original x
// by the total width of the legend and the initial gap.
double xloc = (inverted) ? available.getX()
: available.getX()
+ legendArea.getWidth() + 2 * getOuterGap();

// return the remaining available drawing area
return new Rectangle2D.Double(xloc, available.getY(),
available.getWidth() - legendArea.getWidth() - 2 * getOuterGap(),
available.getHeight());
}
}
else
{
return available;
}
}

/**
* Returns a box that will be positioned next to the name of the specified
* series within the legend area. The box will be square and 65% of the
* height of a line.
*
* @param series the index of the series.
* @param seriesCount number of series.
* @param textHeight the height of one line of text.
* @param innerLegendArea the upper left corner of the inner legend.
*
* @return a box.
*/
private Rectangle2D getLegendBox(int series, int seriesCount,
float textHeight, Rectangle2D innerLegendArea) {

int innerGap = 2; // added to make this compile
float boxHeightAndWidth = textHeight * 0.70f;
float xx = (float) innerLegendArea.getX() + innerGap + 0.15f * textHeight;
float yy = (float) innerLegendArea.getY() + innerGap + (series + 0.15f) * textHeight;
return new Rectangle2D.Float(xx, yy, boxHeightAndWidth, boxHeightAndWidth);

}

/**
* Returns a rectangle surrounding a individual entry in the legend.
* <P>
* The marker box for each entry will be positioned next to the name of the
* specified series within the legend area. The marker box will be square
* and 70% of the height of current font.
*
* @param graphics the graphics context (supplies font metrics etc.).
* @param legendItem the legend item.
* @param x the upper left x coordinate for the bounding box.
* @param y the upper left y coordinate for the bounding box.
*
* @return a DrawableLegendItem encapsulating all necessary info for drawing.
*/
private DrawableLegendItem createDrawableLegendItem(Graphics2D graphics,
LegendItem legendItem,
double x, double y) {

int innerGap = 2;
FontMetrics fm = graphics.getFontMetrics();
LineMetrics lm = fm.getLineMetrics(legendItem.getLabel(), graphics);
float textAscent = lm.getAscent();
float lineHeight = textAscent + lm.getDescent() + lm.getLeading();

DrawableLegendItem item = new DrawableLegendItem(legendItem);

float xloc = (float) (x + innerGap + 1.15f * lineHeight);
float yloc = (float) (y + innerGap + 0.15f * lineHeight + textAscent);

item.setLabelPosition(new Point2D.Float(xloc, yloc));

float boxDim = lineHeight * 0.70f;
xloc = (float) (x + innerGap + 0.15f * lineHeight);
yloc = (float) (y + innerGap + 0.15f * lineHeight);

item.setMarker(new Rectangle2D.Float(xloc, yloc, boxDim, boxDim));

float width = (float) (item.getLabelPosition().getX() - x
+ fm.getStringBounds(legendItem.getLabel(), graphics).getWidth()
+ 0.5 * textAscent);

float height = (float) (2 * innerGap + lineHeight);
item.setBounds(x, y, width, height);
return item;

}

/**
* Allows to add legendItems and Colors to the Legend
*/
public void addLegendItem(String label, Paint paint)
{
LegendItem li = new LegendItem(label,"", null, paint, null, null, null);
legendItems.add(li);
}
}

Dave Gilbert

Re: added 'Hardcoded' Legend Items (labels & colors)

Post by Dave Gilbert » Wed Oct 30, 2002 10:10 am

Hi Juan,

Thankyou very much for sharing your code!

Regards,

DG

juan

Re: added 'Hardcoded' Legend Items (labels & colors)

Post by juan » Wed Oct 30, 2002 8:20 pm

I am glad to do so...
JFreeChart is great. Better than other Charting packages that are not free.

Jc

ev
Posts: 11
Joined: Fri Jan 28, 2005 10:39 am
Location: Stuttgart (DE)

"Hard" Legends - JFChartLegendUtil does not compil

Post by ev » Tue Mar 08, 2005 2:51 pm

JFChartLegendUtil uses currently not existing Legend.outlineKeyBoxes() .
JFChartLegendUtil would be usefull for me - if compiles.
Questions:
- What Legend.outlineKeyBoxes() did ?
- What could be a replacement for it ?
- Are there other possibilities to have "hard" legends ?
Thanks !
ev.

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 Mar 08, 2005 3:24 pm

Take a look at the setFixedLegendItems() method in the CategoryPlot and XYPlot classes.
David Gilbert
JFreeChart Project Leader

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

ev
Posts: 11
Joined: Fri Jan 28, 2005 10:39 am
Location: Stuttgart (DE)

!!!, are multililine legends possible as well ?

Post by ev » Wed Mar 09, 2005 9:38 am

Thank you David !
I really enjoy JFreeChart.
Some questions more :
- Are multiline legends possible ?
- It looks StandardLegend.java have to be rewritten for this ? Isn't it ?

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 Mar 09, 2005 12:15 pm

I've written a new LegendTitle class that will be included in the next release. It allows for more flexible layout of the legend items. Perhaps it will be able to do what you want.

I have one more fix I want to make then the next release will be posted.
David Gilbert
JFreeChart Project Leader

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

Locked