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