001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * ------------------------- 028 * CategoryStepRenderer.java 029 * ------------------------- 030 * 031 * (C) Copyright 2004-2008, by Brian Cole and Contributors. 032 * 033 * Original Author: Brian Cole; 034 * Contributor(s): David Gilbert (for Object Refinery Limited); 035 * 036 * Changes 037 * ------- 038 * 21-Apr-2004 : Version 1, contributed by Brian Cole (DG); 039 * 22-Apr-2004 : Fixed Checkstyle complaints (DG); 040 * 05-Nov-2004 : Modified drawItem() signature (DG); 041 * 08-Mar-2005 : Added equals() method (DG); 042 * ------------- JFREECHART 1.0.x --------------------------------------------- 043 * 30-Nov-2006 : Added checks for series visibility (DG); 044 * 22-Feb-2007 : Use new state object for reusable line, enable chart entities 045 * (for tooltips, URLs), added new getLegendItem() override (DG); 046 * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG); 047 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG); 048 * 049 */ 050 051 package org.jfree.chart.renderer.category; 052 053 import java.awt.Graphics2D; 054 import java.awt.Paint; 055 import java.awt.Shape; 056 import java.awt.geom.Line2D; 057 import java.awt.geom.Rectangle2D; 058 import java.io.Serializable; 059 060 import org.jfree.chart.LegendItem; 061 import org.jfree.chart.axis.CategoryAxis; 062 import org.jfree.chart.axis.ValueAxis; 063 import org.jfree.chart.entity.EntityCollection; 064 import org.jfree.chart.event.RendererChangeEvent; 065 import org.jfree.chart.plot.CategoryPlot; 066 import org.jfree.chart.plot.PlotOrientation; 067 import org.jfree.chart.plot.PlotRenderingInfo; 068 import org.jfree.chart.renderer.xy.XYStepRenderer; 069 import org.jfree.data.category.CategoryDataset; 070 import org.jfree.util.PublicCloneable; 071 072 /** 073 * A "step" renderer similar to {@link XYStepRenderer} but 074 * that can be used with the {@link CategoryPlot} class. 075 */ 076 public class CategoryStepRenderer extends AbstractCategoryItemRenderer 077 implements Cloneable, PublicCloneable, Serializable { 078 079 /** 080 * State information for the renderer. 081 */ 082 protected static class State extends CategoryItemRendererState { 083 084 /** 085 * A working line for re-use to avoid creating large numbers of 086 * objects. 087 */ 088 public Line2D line; 089 090 /** 091 * Creates a new state instance. 092 * 093 * @param info collects plot rendering information (<code>null</code> 094 * permitted). 095 */ 096 public State(PlotRenderingInfo info) { 097 super(info); 098 this.line = new Line2D.Double(); 099 } 100 101 } 102 103 /** For serialization. */ 104 private static final long serialVersionUID = -5121079703118261470L; 105 106 /** The stagger width. */ 107 public static final int STAGGER_WIDTH = 5; // could make this configurable 108 109 /** 110 * A flag that controls whether or not the steps for multiple series are 111 * staggered. 112 */ 113 private boolean stagger = false; 114 115 /** 116 * Creates a new renderer (stagger defaults to <code>false</code>). 117 */ 118 public CategoryStepRenderer() { 119 this(false); 120 } 121 122 /** 123 * Creates a new renderer. 124 * 125 * @param stagger should the horizontal part of the step be staggered by 126 * series? 127 */ 128 public CategoryStepRenderer(boolean stagger) { 129 this.stagger = stagger; 130 } 131 132 /** 133 * Returns the flag that controls whether the series steps are staggered. 134 * 135 * @return A boolean. 136 */ 137 public boolean getStagger() { 138 return this.stagger; 139 } 140 141 /** 142 * Sets the flag that controls whether or not the series steps are 143 * staggered and sends a {@link RendererChangeEvent} to all registered 144 * listeners. 145 * 146 * @param shouldStagger a boolean. 147 */ 148 public void setStagger(boolean shouldStagger) { 149 this.stagger = shouldStagger; 150 fireChangeEvent(); 151 } 152 153 /** 154 * Returns a legend item for a series. 155 * 156 * @param datasetIndex the dataset index (zero-based). 157 * @param series the series index (zero-based). 158 * 159 * @return The legend item. 160 */ 161 public LegendItem getLegendItem(int datasetIndex, int series) { 162 163 CategoryPlot p = getPlot(); 164 if (p == null) { 165 return null; 166 } 167 168 // check that a legend item needs to be displayed... 169 if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) { 170 return null; 171 } 172 173 CategoryDataset dataset = p.getDataset(datasetIndex); 174 String label = getLegendItemLabelGenerator().generateLabel(dataset, 175 series); 176 String description = label; 177 String toolTipText = null; 178 if (getLegendItemToolTipGenerator() != null) { 179 toolTipText = getLegendItemToolTipGenerator().generateLabel( 180 dataset, series); 181 } 182 String urlText = null; 183 if (getLegendItemURLGenerator() != null) { 184 urlText = getLegendItemURLGenerator().generateLabel(dataset, 185 series); 186 } 187 Shape shape = new Rectangle2D.Double(-4.0, -3.0, 8.0, 6.0); 188 Paint paint = lookupSeriesPaint(series); 189 190 LegendItem item = new LegendItem(label, description, toolTipText, 191 urlText, shape, paint); 192 item.setSeriesKey(dataset.getRowKey(series)); 193 item.setSeriesIndex(series); 194 item.setDataset(dataset); 195 item.setDatasetIndex(datasetIndex); 196 return item; 197 } 198 199 /** 200 * Creates a new state instance. This method is called from 201 * {@link #initialise(Graphics2D, Rectangle2D, CategoryPlot, int, 202 * PlotRenderingInfo)}, and we override it to ensure that the state 203 * contains a working Line2D instance. 204 * 205 * @param info the plot rendering info (<code>null</code> is permitted). 206 * 207 * @return A new state instance. 208 */ 209 protected CategoryItemRendererState createState(PlotRenderingInfo info) { 210 return new State(info); 211 } 212 213 /** 214 * Draws a line taking into account the specified orientation. 215 * <p> 216 * In version 1.0.5, the signature of this method was changed by the 217 * addition of the 'state' parameter. This is an incompatible change, but 218 * is considered a low risk because it is unlikely that anyone has 219 * subclassed this renderer. If this *does* cause trouble for you, please 220 * report it as a bug. 221 * 222 * @param g2 the graphics device. 223 * @param state the renderer state. 224 * @param orientation the plot orientation. 225 * @param x0 the x-coordinate for the start of the line. 226 * @param y0 the y-coordinate for the start of the line. 227 * @param x1 the x-coordinate for the end of the line. 228 * @param y1 the y-coordinate for the end of the line. 229 */ 230 protected void drawLine(Graphics2D g2, State state, 231 PlotOrientation orientation, double x0, double y0, double x1, 232 double y1) { 233 234 if (orientation == PlotOrientation.VERTICAL) { 235 state.line.setLine(x0, y0, x1, y1); 236 g2.draw(state.line); 237 } 238 else if (orientation == PlotOrientation.HORIZONTAL) { 239 state.line.setLine(y0, x0, y1, x1); // switch x and y 240 g2.draw(state.line); 241 } 242 243 } 244 245 /** 246 * Draw a single data item. 247 * 248 * @param g2 the graphics device. 249 * @param state the renderer state. 250 * @param dataArea the area in which the data is drawn. 251 * @param plot the plot. 252 * @param domainAxis the domain axis. 253 * @param rangeAxis the range axis. 254 * @param dataset the dataset. 255 * @param row the row index (zero-based). 256 * @param column the column index (zero-based). 257 * @param pass the pass index. 258 */ 259 public void drawItem(Graphics2D g2, 260 CategoryItemRendererState state, 261 Rectangle2D dataArea, 262 CategoryPlot plot, 263 CategoryAxis domainAxis, 264 ValueAxis rangeAxis, 265 CategoryDataset dataset, 266 int row, 267 int column, 268 int pass) { 269 270 // do nothing if item is not visible 271 if (!getItemVisible(row, column)) { 272 return; 273 } 274 275 Number value = dataset.getValue(row, column); 276 if (value == null) { 277 return; 278 } 279 PlotOrientation orientation = plot.getOrientation(); 280 281 // current data point... 282 double x1s = domainAxis.getCategoryStart(column, getColumnCount(), 283 dataArea, plot.getDomainAxisEdge()); 284 double x1 = domainAxis.getCategoryMiddle(column, getColumnCount(), 285 dataArea, plot.getDomainAxisEdge()); 286 double x1e = 2 * x1 - x1s; // or: x1s + 2*(x1-x1s) 287 double y1 = rangeAxis.valueToJava2D(value.doubleValue(), dataArea, 288 plot.getRangeAxisEdge()); 289 g2.setPaint(getItemPaint(row, column)); 290 g2.setStroke(getItemStroke(row, column)); 291 292 if (column != 0) { 293 Number previousValue = dataset.getValue(row, column - 1); 294 if (previousValue != null) { 295 // previous data point... 296 double previous = previousValue.doubleValue(); 297 double x0s = domainAxis.getCategoryStart(column - 1, 298 getColumnCount(), dataArea, plot.getDomainAxisEdge()); 299 double x0 = domainAxis.getCategoryMiddle(column - 1, 300 getColumnCount(), dataArea, plot.getDomainAxisEdge()); 301 double x0e = 2 * x0 - x0s; // or: x0s + 2*(x0-x0s) 302 double y0 = rangeAxis.valueToJava2D(previous, dataArea, 303 plot.getRangeAxisEdge()); 304 if (getStagger()) { 305 int xStagger = row * STAGGER_WIDTH; 306 if (xStagger > (x1s - x0e)) { 307 xStagger = (int) (x1s - x0e); 308 } 309 x1s = x0e + xStagger; 310 } 311 drawLine(g2, (State) state, orientation, x0e, y0, x1s, y0); 312 // extend x0's flat bar 313 314 drawLine(g2, (State) state, orientation, x1s, y0, x1s, y1); 315 // upright bar 316 } 317 } 318 drawLine(g2, (State) state, orientation, x1s, y1, x1e, y1); 319 // x1's flat bar 320 321 // draw the item labels if there are any... 322 if (isItemLabelVisible(row, column)) { 323 drawItemLabel(g2, orientation, dataset, row, column, x1, y1, 324 (value.doubleValue() < 0.0)); 325 } 326 327 // add an item entity, if this information is being collected 328 EntityCollection entities = state.getEntityCollection(); 329 if (entities != null) { 330 Rectangle2D hotspot = new Rectangle2D.Double(); 331 if (orientation == PlotOrientation.VERTICAL) { 332 hotspot.setRect(x1s, y1, x1e - x1s, 4.0); 333 } 334 else { 335 hotspot.setRect(y1 - 2.0, x1s, 4.0, x1e - x1s); 336 } 337 addItemEntity(entities, dataset, row, column, hotspot); 338 } 339 340 } 341 342 /** 343 * Tests this renderer for equality with an arbitrary object. 344 * 345 * @param obj the object (<code>null</code> permitted). 346 * 347 * @return A boolean. 348 */ 349 public boolean equals(Object obj) { 350 if (obj == this) { 351 return true; 352 } 353 if (!(obj instanceof CategoryStepRenderer)) { 354 return false; 355 } 356 CategoryStepRenderer that = (CategoryStepRenderer) obj; 357 if (this.stagger != that.stagger) { 358 return false; 359 } 360 return super.equals(obj); 361 } 362 363 }