001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2007, 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-2007, 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, 078 Serializable { 079 080 /** 081 * State information for the renderer. 082 */ 083 protected static class State extends CategoryItemRendererState { 084 085 /** 086 * A working line for re-use to avoid creating large numbers of 087 * objects. 088 */ 089 public Line2D line; 090 091 /** 092 * Creates a new state instance. 093 * 094 * @param info collects plot rendering information (<code>null</code> 095 * permitted). 096 */ 097 public State(PlotRenderingInfo info) { 098 super(info); 099 this.line = new Line2D.Double(); 100 } 101 102 } 103 104 /** For serialization. */ 105 private static final long serialVersionUID = -5121079703118261470L; 106 107 /** The stagger width. */ 108 public static final int STAGGER_WIDTH = 5; // could make this configurable 109 110 /** 111 * A flag that controls whether or not the steps for multiple series are 112 * staggered. 113 */ 114 private boolean stagger = false; 115 116 /** 117 * Creates a new renderer (stagger defaults to <code>false</code>). 118 */ 119 public CategoryStepRenderer() { 120 this(false); 121 } 122 123 /** 124 * Creates a new renderer. 125 * 126 * @param stagger should the horizontal part of the step be staggered by 127 * series? 128 */ 129 public CategoryStepRenderer(boolean stagger) { 130 this.stagger = stagger; 131 } 132 133 /** 134 * Returns the flag that controls whether the series steps are staggered. 135 * 136 * @return A boolean. 137 */ 138 public boolean getStagger() { 139 return this.stagger; 140 } 141 142 /** 143 * Sets the flag that controls whether or not the series steps are 144 * staggered and sends a {@link RendererChangeEvent} to all registered 145 * listeners. 146 * 147 * @param shouldStagger a boolean. 148 */ 149 public void setStagger(boolean shouldStagger) { 150 this.stagger = shouldStagger; 151 fireChangeEvent(); 152 } 153 154 /** 155 * Returns a legend item for a series. 156 * 157 * @param datasetIndex the dataset index (zero-based). 158 * @param series the series index (zero-based). 159 * 160 * @return The legend item. 161 */ 162 public LegendItem getLegendItem(int datasetIndex, int series) { 163 164 CategoryPlot p = getPlot(); 165 if (p == null) { 166 return null; 167 } 168 169 // check that a legend item needs to be displayed... 170 if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) { 171 return null; 172 } 173 174 CategoryDataset dataset = p.getDataset(datasetIndex); 175 String label = getLegendItemLabelGenerator().generateLabel(dataset, 176 series); 177 String description = label; 178 String toolTipText = null; 179 if (getLegendItemToolTipGenerator() != null) { 180 toolTipText = getLegendItemToolTipGenerator().generateLabel( 181 dataset, series); 182 } 183 String urlText = null; 184 if (getLegendItemURLGenerator() != null) { 185 urlText = getLegendItemURLGenerator().generateLabel(dataset, 186 series); 187 } 188 Shape shape = new Rectangle2D.Double(-4.0, -3.0, 8.0, 6.0); 189 Paint paint = lookupSeriesPaint(series); 190 191 LegendItem item = new LegendItem(label, description, toolTipText, 192 urlText, shape, paint); 193 item.setSeriesKey(dataset.getRowKey(series)); 194 item.setSeriesIndex(series); 195 item.setDataset(dataset); 196 item.setDatasetIndex(datasetIndex); 197 return item; 198 } 199 200 /** 201 * Creates a new state instance. This method is called from 202 * {@link #initialise(Graphics2D, Rectangle2D, CategoryPlot, int, 203 * PlotRenderingInfo)}, and we override it to ensure that the state 204 * contains a working Line2D instance. 205 * 206 * @param info the plot rendering info (<code>null</code> is permitted). 207 * 208 * @return A new state instance. 209 */ 210 protected CategoryItemRendererState createState(PlotRenderingInfo info) { 211 return new State(info); 212 } 213 214 /** 215 * Draws a line taking into account the specified orientation. 216 * <p> 217 * In version 1.0.5, the signature of this method was changed by the 218 * addition of the 'state' parameter. This is an incompatible change, but 219 * is considered a low risk because it is unlikely that anyone has 220 * subclassed this renderer. If this *does* cause trouble for you, please 221 * report it as a bug. 222 * 223 * @param g2 the graphics device. 224 * @param state the renderer state. 225 * @param orientation the plot orientation. 226 * @param x0 the x-coordinate for the start of the line. 227 * @param y0 the y-coordinate for the start of the line. 228 * @param x1 the x-coordinate for the end of the line. 229 * @param y1 the y-coordinate for the end of the line. 230 */ 231 protected void drawLine(Graphics2D g2, State state, 232 PlotOrientation orientation, double x0, double y0, double x1, 233 double y1) { 234 235 if (orientation == PlotOrientation.VERTICAL) { 236 state.line.setLine(x0, y0, x1, y1); 237 g2.draw(state.line); 238 } 239 else if (orientation == PlotOrientation.HORIZONTAL) { 240 state.line.setLine(y0, x0, y1, x1); // switch x and y 241 g2.draw(state.line); 242 } 243 244 } 245 246 /** 247 * Draw a single data item. 248 * 249 * @param g2 the graphics device. 250 * @param state the renderer state. 251 * @param dataArea the area in which the data is drawn. 252 * @param plot the plot. 253 * @param domainAxis the domain axis. 254 * @param rangeAxis the range axis. 255 * @param dataset the dataset. 256 * @param row the row index (zero-based). 257 * @param column the column index (zero-based). 258 * @param pass the pass index. 259 */ 260 public void drawItem(Graphics2D g2, 261 CategoryItemRendererState state, 262 Rectangle2D dataArea, 263 CategoryPlot plot, 264 CategoryAxis domainAxis, 265 ValueAxis rangeAxis, 266 CategoryDataset dataset, 267 int row, 268 int column, 269 int pass) { 270 271 // do nothing if item is not visible 272 if (!getItemVisible(row, column)) { 273 return; 274 } 275 276 Number value = dataset.getValue(row, column); 277 if (value == null) { 278 return; 279 } 280 PlotOrientation orientation = plot.getOrientation(); 281 282 // current data point... 283 double x1s = domainAxis.getCategoryStart(column, getColumnCount(), 284 dataArea, plot.getDomainAxisEdge()); 285 double x1 = domainAxis.getCategoryMiddle(column, getColumnCount(), 286 dataArea, plot.getDomainAxisEdge()); 287 double x1e = 2 * x1 - x1s; // or: x1s + 2*(x1-x1s) 288 double y1 = rangeAxis.valueToJava2D(value.doubleValue(), dataArea, 289 plot.getRangeAxisEdge()); 290 g2.setPaint(getItemPaint(row, column)); 291 g2.setStroke(getItemStroke(row, column)); 292 293 if (column != 0) { 294 Number previousValue = dataset.getValue(row, column - 1); 295 if (previousValue != null) { 296 // previous data point... 297 double previous = previousValue.doubleValue(); 298 double x0s = domainAxis.getCategoryStart(column - 1, 299 getColumnCount(), dataArea, plot.getDomainAxisEdge()); 300 double x0 = domainAxis.getCategoryMiddle(column - 1, 301 getColumnCount(), dataArea, plot.getDomainAxisEdge()); 302 double x0e = 2 * x0 - x0s; // or: x0s + 2*(x0-x0s) 303 double y0 = rangeAxis.valueToJava2D(previous, dataArea, 304 plot.getRangeAxisEdge()); 305 if (getStagger()) { 306 int xStagger = row * STAGGER_WIDTH; 307 if (xStagger > (x1s - x0e)) { 308 xStagger = (int) (x1s - x0e); 309 } 310 x1s = x0e + xStagger; 311 } 312 drawLine(g2, (State) state, orientation, x0e, y0, x1s, y0); 313 // extend x0's flat bar 314 315 drawLine(g2, (State) state, orientation, x1s, y0, x1s, y1); 316 // upright bar 317 } 318 } 319 drawLine(g2, (State) state, orientation, x1s, y1, x1e, y1); 320 // x1's flat bar 321 322 // draw the item labels if there are any... 323 if (isItemLabelVisible(row, column)) { 324 drawItemLabel(g2, orientation, dataset, row, column, x1, y1, 325 (value.doubleValue() < 0.0)); 326 } 327 328 // add an item entity, if this information is being collected 329 EntityCollection entities = state.getEntityCollection(); 330 if (entities != null) { 331 Rectangle2D hotspot = new Rectangle2D.Double(); 332 if (orientation == PlotOrientation.VERTICAL) { 333 hotspot.setRect(x1s, y1, x1e - x1s, 4.0); 334 } 335 else { 336 hotspot.setRect(y1 - 2.0, x1s, 4.0, x1e - x1s); 337 } 338 addItemEntity(entities, dataset, row, column, hotspot); 339 } 340 341 } 342 343 /** 344 * Tests this renderer for equality with an arbitrary object. 345 * 346 * @param obj the object (<code>null</code> permitted). 347 * 348 * @return A boolean. 349 */ 350 public boolean equals(Object obj) { 351 if (obj == this) { 352 return true; 353 } 354 if (!(obj instanceof CategoryStepRenderer)) { 355 return false; 356 } 357 CategoryStepRenderer that = (CategoryStepRenderer) obj; 358 if (this.stagger != that.stagger) { 359 return false; 360 } 361 return super.equals(obj); 362 } 363 364 }