001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2014, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 * ------------------------- 028 * CategoryStepRenderer.java 029 * ------------------------- 030 * 031 * (C) Copyright 2004-2014, 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 052package org.jfree.chart.renderer.category; 053 054import java.awt.Graphics2D; 055import java.awt.Paint; 056import java.awt.Shape; 057import java.awt.geom.Line2D; 058import java.awt.geom.Rectangle2D; 059import java.io.Serializable; 060 061import org.jfree.chart.LegendItem; 062import org.jfree.chart.axis.CategoryAxis; 063import org.jfree.chart.axis.ValueAxis; 064import org.jfree.chart.entity.EntityCollection; 065import org.jfree.chart.event.RendererChangeEvent; 066import org.jfree.chart.plot.CategoryPlot; 067import org.jfree.chart.plot.PlotOrientation; 068import org.jfree.chart.plot.PlotRenderingInfo; 069import org.jfree.chart.renderer.xy.XYStepRenderer; 070import org.jfree.data.category.CategoryDataset; 071import 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 */ 082public 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 @Override 169 public LegendItem getLegendItem(int datasetIndex, int series) { 170 171 CategoryPlot p = getPlot(); 172 if (p == null) { 173 return null; 174 } 175 176 // check that a legend item needs to be displayed... 177 if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) { 178 return null; 179 } 180 181 CategoryDataset dataset = p.getDataset(datasetIndex); 182 String label = getLegendItemLabelGenerator().generateLabel(dataset, 183 series); 184 String description = label; 185 String toolTipText = null; 186 if (getLegendItemToolTipGenerator() != null) { 187 toolTipText = getLegendItemToolTipGenerator().generateLabel( 188 dataset, series); 189 } 190 String urlText = null; 191 if (getLegendItemURLGenerator() != null) { 192 urlText = getLegendItemURLGenerator().generateLabel(dataset, 193 series); 194 } 195 Shape shape = lookupLegendShape(series); 196 Paint paint = lookupSeriesPaint(series); 197 198 LegendItem item = new LegendItem(label, description, toolTipText, 199 urlText, shape, paint); 200 item.setLabelFont(lookupLegendTextFont(series)); 201 Paint labelPaint = lookupLegendTextPaint(series); 202 if (labelPaint != null) { 203 item.setLabelPaint(labelPaint); 204 } 205 item.setSeriesKey(dataset.getRowKey(series)); 206 item.setSeriesIndex(series); 207 item.setDataset(dataset); 208 item.setDatasetIndex(datasetIndex); 209 return item; 210 } 211 212 /** 213 * Creates a new state instance. This method is called from 214 * {@link #initialise(Graphics2D, Rectangle2D, CategoryPlot, int, 215 * PlotRenderingInfo)}, and we override it to ensure that the state 216 * contains a working Line2D instance. 217 * 218 * @param info the plot rendering info (<code>null</code> is permitted). 219 * 220 * @return A new state instance. 221 */ 222 @Override 223 protected CategoryItemRendererState createState(PlotRenderingInfo info) { 224 return new State(info); 225 } 226 227 /** 228 * Draws a line taking into account the specified orientation. 229 * <p> 230 * In version 1.0.5, the signature of this method was changed by the 231 * addition of the 'state' parameter. This is an incompatible change, but 232 * is considered a low risk because it is unlikely that anyone has 233 * subclassed this renderer. If this *does* cause trouble for you, please 234 * report it as a bug. 235 * 236 * @param g2 the graphics device. 237 * @param state the renderer state. 238 * @param orientation the plot orientation. 239 * @param x0 the x-coordinate for the start of the line. 240 * @param y0 the y-coordinate for the start of the line. 241 * @param x1 the x-coordinate for the end of the line. 242 * @param y1 the y-coordinate for the end of the line. 243 */ 244 protected void drawLine(Graphics2D g2, State state, 245 PlotOrientation orientation, double x0, double y0, double x1, 246 double y1) { 247 248 if (orientation == PlotOrientation.VERTICAL) { 249 state.line.setLine(x0, y0, x1, y1); 250 g2.draw(state.line); 251 } 252 else if (orientation == PlotOrientation.HORIZONTAL) { 253 state.line.setLine(y0, x0, y1, x1); // switch x and y 254 g2.draw(state.line); 255 } 256 257 } 258 259 /** 260 * Draw a single data item. 261 * 262 * @param g2 the graphics device. 263 * @param state the renderer state. 264 * @param dataArea the area in which the data is drawn. 265 * @param plot the plot. 266 * @param domainAxis the domain axis. 267 * @param rangeAxis the range axis. 268 * @param dataset the dataset. 269 * @param row the row index (zero-based). 270 * @param column the column index (zero-based). 271 * @param pass the pass index. 272 */ 273 @Override 274 public void drawItem(Graphics2D g2, CategoryItemRendererState state, 275 Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis, 276 ValueAxis rangeAxis, CategoryDataset dataset, int row, 277 int column, int pass) { 278 279 // do nothing if item is not visible 280 if (!getItemVisible(row, column)) { 281 return; 282 } 283 284 Number value = dataset.getValue(row, column); 285 if (value == null) { 286 return; 287 } 288 PlotOrientation orientation = plot.getOrientation(); 289 290 // current data point... 291 double x1s = domainAxis.getCategoryStart(column, getColumnCount(), 292 dataArea, plot.getDomainAxisEdge()); 293 double x1 = domainAxis.getCategoryMiddle(column, getColumnCount(), 294 dataArea, plot.getDomainAxisEdge()); 295 double x1e = 2 * x1 - x1s; // or: x1s + 2*(x1-x1s) 296 double y1 = rangeAxis.valueToJava2D(value.doubleValue(), dataArea, 297 plot.getRangeAxisEdge()); 298 g2.setPaint(getItemPaint(row, column)); 299 g2.setStroke(getItemStroke(row, column)); 300 301 if (column != 0) { 302 Number previousValue = dataset.getValue(row, column - 1); 303 if (previousValue != null) { 304 // previous data point... 305 double previous = previousValue.doubleValue(); 306 double x0s = domainAxis.getCategoryStart(column - 1, 307 getColumnCount(), dataArea, plot.getDomainAxisEdge()); 308 double x0 = domainAxis.getCategoryMiddle(column - 1, 309 getColumnCount(), dataArea, plot.getDomainAxisEdge()); 310 double x0e = 2 * x0 - x0s; // or: x0s + 2*(x0-x0s) 311 double y0 = rangeAxis.valueToJava2D(previous, dataArea, 312 plot.getRangeAxisEdge()); 313 if (getStagger()) { 314 int xStagger = row * STAGGER_WIDTH; 315 if (xStagger > (x1s - x0e)) { 316 xStagger = (int) (x1s - x0e); 317 } 318 x1s = x0e + xStagger; 319 } 320 drawLine(g2, (State) state, orientation, x0e, y0, x1s, y0); 321 // extend x0's flat bar 322 323 drawLine(g2, (State) state, orientation, x1s, y0, x1s, y1); 324 // upright bar 325 } 326 } 327 drawLine(g2, (State) state, orientation, x1s, y1, x1e, y1); 328 // x1's flat bar 329 330 // draw the item labels if there are any... 331 if (isItemLabelVisible(row, column)) { 332 drawItemLabel(g2, orientation, dataset, row, column, x1, y1, 333 (value.doubleValue() < 0.0)); 334 } 335 336 // add an item entity, if this information is being collected 337 EntityCollection entities = state.getEntityCollection(); 338 if (entities != null) { 339 Rectangle2D hotspot = new Rectangle2D.Double(); 340 if (orientation == PlotOrientation.VERTICAL) { 341 hotspot.setRect(x1s, y1, x1e - x1s, 4.0); 342 } 343 else { 344 hotspot.setRect(y1 - 2.0, x1s, 4.0, x1e - x1s); 345 } 346 addItemEntity(entities, dataset, row, column, hotspot); 347 } 348 349 } 350 351 /** 352 * Tests this renderer for equality with an arbitrary object. 353 * 354 * @param obj the object (<code>null</code> permitted). 355 * 356 * @return A boolean. 357 */ 358 @Override 359 public boolean equals(Object obj) { 360 if (obj == this) { 361 return true; 362 } 363 if (!(obj instanceof CategoryStepRenderer)) { 364 return false; 365 } 366 CategoryStepRenderer that = (CategoryStepRenderer) obj; 367 if (this.stagger != that.stagger) { 368 return false; 369 } 370 return super.equals(obj); 371 } 372 373}