StackedVerticalBarXYRenderer

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

StackedVerticalBarXYRenderer

Post by Don Mitchell » Wed Jan 29, 2003 1:16 am

Below is a crude renderer for Stacked Vertical Bars using the XYDataset. I'm calling it "crude" because of two things:

1. The render needs to keep a history of previous item values. There's no way for the render to know that it is done being used so these values remain cached until the references to the render are garbage collected. This may not be a big deal, but it would be nice if there was a way to have them removed since they are no longer needed.

2. I can not adjust the range axis maximum. The default axis range that is chosen is based on the dataset and not the stacked values so it is always too small. I reset the range outside of this render. It would be nice if this could get accomplished in the same manner that it gets done for CategoryPlots. I think it would also make the XY and Category APIs more similar. One alternative would be to set the max in the initialise method in this render, but I chose not to.

This is in response to the following thread in this phorum:

http://www.object-refinery.com/phorum-3 ... 740&t=6704

Does anyone know if there's a better place to post code like this?

Don

/* =======================================
* JFreeChart : a Java Chart Class Library
* =======================================
*
* Project Info: http://www.object-refinery.com/jfreechart/index.html
* Project Lead: David Gilbert (david.gilbert@object-refinery.com);
*
* 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.
*
*/

//package foo;

import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import com.jrefinery.chart.AbstractXYItemRenderer;
import com.jrefinery.chart.ChartRenderingInfo;
import com.jrefinery.chart.CrosshairInfo;
import com.jrefinery.chart.ValueAxis;
import com.jrefinery.chart.XYItemRenderer;
import com.jrefinery.chart.XYPlot;
import com.jrefinery.chart.entity.EntityCollection;
import com.jrefinery.chart.entity.XYItemEntity;
import com.jrefinery.chart.tooltips.XYToolTipGenerator;
import com.jrefinery.chart.tooltips.StandardXYToolTipGenerator;
import com.jrefinery.data.XYDataset;
import com.jrefinery.data.IntervalXYDataset;
import com.jrefinery.data.Range;

/**
* A renderer that draws stacked vertical bars on an XY plot (requires
* an IntervalXYDataset). Each data series may have different data, but the
* intention and best look is when all series have the same X values. The
* range axis maximum is not updated, so anyone using this class will need
* to recalculate their range axis maximum and set it explicitly.
* <p>
* XXX dsm - This class was posted to the JFreeChart Phorum and hopefully
* will be incorporated into version 0.9.5.
*
* @author Don Mitchell
*/
public class StackedXYBarRenderer extends AbstractXYItemRenderer
implements XYItemRenderer {

/** Percentage margin (to reduce the width of bars). */
private double margin;

/** A data value of zero translated to a Java2D value. */
private double translatedRangeZero;

/**
* Holds all of the start points that get rendered and the current
* top value of the stacked bar.
*/
private HashMap stackedPoints;


/**
* The default constructor.
*/
public StackedXYBarRenderer() {
this(0.0, new StandardXYToolTipGenerator());
}

/**
* Constructs a new renderer.
*
* @param margin the percentage amount to trim from the width of each bar.
*/
public StackedXYBarRenderer(double margin) {
this(margin, new StandardXYToolTipGenerator());
}

/**
* Constructs a new renderer.
*
* @param margin the percentage amount to trim from the width of each bar.
* @param toolTipGenerator the tool tip generator (null permitted).
*/
public StackedXYBarRenderer(double margin,
XYToolTipGenerator toolTipGenerator) {

super(toolTipGenerator);
this.margin = margin;

}

/**
* Sets the percentage amount by which the bars are trimmed.
* <P>
* Fires a property change event.
*
* @param margin the new margin.
*/
public void setMargin(double margin) {

Double old = new Double(this.margin);
this.margin = margin;
this.firePropertyChanged("StackedXYBarRenderer.margin",
old,
new Double(margin));

}

/**
* Initialises the renderer. Here we calculate the Java2D y-coordinate
* for zero, since all the bars have their bases fixed at zero.
*
* @param g2 The graphics device.
* @param dataArea The area inside the axes.
* @param plot The plot.
* @param data The data.
* @param info An optional info collection object to return data
* back to the caller.
*/
public void initialise(Graphics2D g2, Rectangle2D dataArea, XYPlot plot,
XYDataset data, ChartRenderingInfo info) {

super.initialise(g2, dataArea, plot, data, info);
ValueAxis rangeAxis = plot.getRangeAxis();
this.translatedRangeZero = rangeAxis.translateValueToJava2D(0.0,
dataArea);

this.stackedPoints = new HashMap();
}

/**
* Draws the visual representation of a single data item. A history
* is kept of previous values so that new values can be stacked upon them.
*
* @param g2 The graphics device.
* @param dataArea The area within which the plot is being drawn.
* @param info Collects information about the drawing.
* @param plot The plot (can be used to obtain standard color
* information etc).
* @param domainAxis The domain axis.
* @param rangeAxis The range axis.
* @param data The dataset.
* @param series The series index.
* @param item The item index.
* @param crosshairInfo Collects information about crosshairs.
*/
public void drawItem(Graphics2D g2,
Rectangle2D dataArea,
ChartRenderingInfo info,
XYPlot plot,
ValueAxis domainAxis,
ValueAxis rangeAxis,
XYDataset data,
int series,
int item,
CrosshairInfo crosshairInfo) {

IntervalXYDataset intervalData = (IntervalXYDataset) data;

Paint seriesPaint = plot.getSeriesPaint(series);
Paint seriesOutlinePaint = plot.getSeriesOutlinePaint(series);

double valueNumber = intervalData.getYValue(series, item).doubleValue();

if (valueNumber < 0.0) {
// can't stack negative number, looks weird
// XXX throw an exception instead or make an option?
valueNumber = 0.0;
}

Number startXNumber = intervalData.getStartXValue(series, item);

Number endXNumber = intervalData.getEndXValue(series, item);

Number stackTop = (Number)stackedPoints.get(startXNumber);

if (stackTop == null) {
stackTop = new Double(0.0);
}

double translatedStackTop = rangeAxis.translateValueToJava2D(
(stackTop.doubleValue() + valueNumber),
dataArea);

double translatedStartX = domainAxis.translateValueToJava2D(
startXNumber.doubleValue(), dataArea);

double translatedEndX = domainAxis.translateValueToJava2D(
endXNumber.doubleValue(), dataArea);

double translatedValue = rangeAxis.translateValueToJava2D(
valueNumber, dataArea);

double translatedWidth =
Math.max(1, translatedEndX - translatedStartX);

double translatedHeight =
Math.abs(translatedValue - translatedRangeZero);

if (margin > 0.0) {
double cut = translatedWidth * margin;
translatedWidth = translatedWidth - cut;
translatedStartX = translatedStartX + cut / 2;
}

Rectangle2D bar
= new Rectangle2D.Double(
translatedStartX,
translatedStackTop,
translatedWidth,
translatedHeight);

if (stackTop != null) {
stackTop = new Double(stackTop.doubleValue() + valueNumber);
}

stackedPoints.put(startXNumber, stackTop);

g2.setPaint(seriesPaint);
g2.fill(bar);
if ((translatedEndX - translatedStartX) > 3) {
g2.setStroke(plot.getSeriesOutlineStroke(series));
g2.setPaint(seriesOutlinePaint);
g2.draw(bar);
}

// add an entity for the item...
if (info != null) {
EntityCollection entities = info.getEntityCollection();
if (entities != null) {
String tip = "";
if (getToolTipGenerator() != null) {
tip = getToolTipGenerator().generateToolTip(
data, series, item);
}
String url = null;
if (getURLGenerator() != null) {
url = getURLGenerator().generateURL(data, series, item);
}
XYItemEntity entity = new XYItemEntity(
bar, tip, url, series, item);
entities.addEntity(entity);
}
}
}
}

rahulb

Re: StackedVerticalBarXYRenderer

Post by rahulb » Wed Jan 29, 2003 9:43 pm

hi Don,
I was actually working on my own solution, but having a dumb bug I couldn't figure out, so thanks for the code. I was trying to do without having the stack outside of the drawItem(...) method. So I copied drawItem from VerticalXYBarRenderer and was doing something like this:

double stackTop = 0.0;
for (int i=0;i<series;i++){
try{
Number itemNumber = intervalData.getYValue(i,item);
stackTop+=itemNumber.doubleValue();
} catch (IndexOutOfBoundsException iobe){}
}
double translatedStackTop = rangeAxis.translateValueToJava2D(
stackTop+valueNumber.doubleValue(),dataArea);

and then drawing the bar with a translatedStackTop as the startY coordinate. however, it kept on drawing all of them too low on the axis, and I couldn't determine why.

Anyway, your solution works for me, so I'll just use it - I just added a little hack in another part of my code to fix the vertical axis. thanks.

Locked