001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-present, by David Gilbert 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-present, by Brian Cole and Contributors. 032 * 033 * Original Author: Brian Cole; 034 * Contributor(s): David Gilbert; 035 * 036 */ 037 038package org.jfree.chart.renderer.category; 039 040import java.awt.Graphics2D; 041import java.awt.Paint; 042import java.awt.Shape; 043import java.awt.geom.Line2D; 044import java.awt.geom.Rectangle2D; 045import java.io.Serializable; 046 047import org.jfree.chart.LegendItem; 048import org.jfree.chart.axis.CategoryAxis; 049import org.jfree.chart.axis.ValueAxis; 050import org.jfree.chart.entity.EntityCollection; 051import org.jfree.chart.event.RendererChangeEvent; 052import org.jfree.chart.plot.CategoryPlot; 053import org.jfree.chart.plot.PlotOrientation; 054import org.jfree.chart.plot.PlotRenderingInfo; 055import org.jfree.chart.renderer.xy.XYStepRenderer; 056import org.jfree.chart.util.PublicCloneable; 057import org.jfree.data.category.CategoryDataset; 058 059/** 060 * A "step" renderer similar to {@link XYStepRenderer} but 061 * that can be used with the {@link CategoryPlot} class. The example shown 062 * here is generated by the {@code CategoryStepChartDemo1.java} program 063 * included in the JFreeChart Demo Collection: 064 * <br><br> 065 * <img src="doc-files/CategoryStepRendererSample.png" 066 * alt="CategoryStepRendererSample.png"> 067 */ 068public class CategoryStepRenderer extends AbstractCategoryItemRenderer 069 implements Cloneable, PublicCloneable, Serializable { 070 071 /** 072 * State information for the renderer. 073 */ 074 protected static class State extends CategoryItemRendererState { 075 076 /** 077 * A working line for re-use to avoid creating large numbers of 078 * objects. 079 */ 080 public Line2D line; 081 082 /** 083 * Creates a new state instance. 084 * 085 * @param info collects plot rendering information ({@code null} 086 * permitted). 087 */ 088 public State(PlotRenderingInfo info) { 089 super(info); 090 this.line = new Line2D.Double(); 091 } 092 093 } 094 095 /** For serialization. */ 096 private static final long serialVersionUID = -5121079703118261470L; 097 098 /** The stagger width. */ 099 public static final int STAGGER_WIDTH = 5; // could make this configurable 100 101 /** 102 * A flag that controls whether or not the steps for multiple series are 103 * staggered. 104 */ 105 private boolean stagger = false; 106 107 /** 108 * Creates a new renderer (stagger defaults to {@code false}). 109 */ 110 public CategoryStepRenderer() { 111 this(false); 112 } 113 114 /** 115 * Creates a new renderer. 116 * 117 * @param stagger should the horizontal part of the step be staggered by 118 * series? 119 */ 120 public CategoryStepRenderer(boolean stagger) { 121 this.stagger = stagger; 122 setDefaultLegendShape(new Rectangle2D.Double(-4.0, -3.0, 8.0, 6.0)); 123 } 124 125 /** 126 * Returns the flag that controls whether the series steps are staggered. 127 * 128 * @return A boolean. 129 */ 130 public boolean getStagger() { 131 return this.stagger; 132 } 133 134 /** 135 * Sets the flag that controls whether or not the series steps are 136 * staggered and sends a {@link RendererChangeEvent} to all registered 137 * listeners. 138 * 139 * @param shouldStagger a boolean. 140 */ 141 public void setStagger(boolean shouldStagger) { 142 this.stagger = shouldStagger; 143 fireChangeEvent(); 144 } 145 146 /** 147 * Returns a legend item for a series. 148 * 149 * @param datasetIndex the dataset index (zero-based). 150 * @param series the series index (zero-based). 151 * 152 * @return The legend item. 153 */ 154 @Override 155 public LegendItem getLegendItem(int datasetIndex, int series) { 156 157 CategoryPlot p = getPlot(); 158 if (p == null) { 159 return null; 160 } 161 162 // check that a legend item needs to be displayed... 163 if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) { 164 return null; 165 } 166 167 CategoryDataset dataset = p.getDataset(datasetIndex); 168 String label = getLegendItemLabelGenerator().generateLabel(dataset, 169 series); 170 String description = label; 171 String toolTipText = null; 172 if (getLegendItemToolTipGenerator() != null) { 173 toolTipText = getLegendItemToolTipGenerator().generateLabel( 174 dataset, series); 175 } 176 String urlText = null; 177 if (getLegendItemURLGenerator() != null) { 178 urlText = getLegendItemURLGenerator().generateLabel(dataset, 179 series); 180 } 181 Shape shape = lookupLegendShape(series); 182 Paint paint = lookupSeriesPaint(series); 183 184 LegendItem item = new LegendItem(label, description, toolTipText, 185 urlText, shape, paint); 186 item.setLabelFont(lookupLegendTextFont(series)); 187 Paint labelPaint = lookupLegendTextPaint(series); 188 if (labelPaint != null) { 189 item.setLabelPaint(labelPaint); 190 } 191 item.setSeriesKey(dataset.getRowKey(series)); 192 item.setSeriesIndex(series); 193 item.setDataset(dataset); 194 item.setDatasetIndex(datasetIndex); 195 return item; 196 } 197 198 /** 199 * Creates a new state instance. This method is called from 200 * {@link #initialise(Graphics2D, Rectangle2D, CategoryPlot, int, 201 * PlotRenderingInfo)}, and we override it to ensure that the state 202 * contains a working Line2D instance. 203 * 204 * @param info the plot rendering info ({@code null} is permitted). 205 * 206 * @return A new state instance. 207 */ 208 @Override 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 @Override 260 public void drawItem(Graphics2D g2, CategoryItemRendererState state, 261 Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis, 262 ValueAxis rangeAxis, CategoryDataset dataset, int row, 263 int column, int pass) { 264 265 // do nothing if item is not visible 266 if (!getItemVisible(row, column)) { 267 return; 268 } 269 270 Number value = dataset.getValue(row, column); 271 if (value == null) { 272 return; 273 } 274 PlotOrientation orientation = plot.getOrientation(); 275 276 // current data point... 277 double x1s = domainAxis.getCategoryStart(column, getColumnCount(), 278 dataArea, plot.getDomainAxisEdge()); 279 double x1 = domainAxis.getCategoryMiddle(column, getColumnCount(), 280 dataArea, plot.getDomainAxisEdge()); 281 double x1e = 2 * x1 - x1s; // or: x1s + 2*(x1-x1s) 282 double y1 = rangeAxis.valueToJava2D(value.doubleValue(), dataArea, 283 plot.getRangeAxisEdge()); 284 g2.setPaint(getItemPaint(row, column)); 285 g2.setStroke(getItemStroke(row, column)); 286 287 if (column != 0) { 288 Number previousValue = dataset.getValue(row, column - 1); 289 if (previousValue != null) { 290 // previous data point... 291 double previous = previousValue.doubleValue(); 292 double x0s = domainAxis.getCategoryStart(column - 1, 293 getColumnCount(), dataArea, plot.getDomainAxisEdge()); 294 double x0 = domainAxis.getCategoryMiddle(column - 1, 295 getColumnCount(), dataArea, plot.getDomainAxisEdge()); 296 double x0e = 2 * x0 - x0s; // or: x0s + 2*(x0-x0s) 297 double y0 = rangeAxis.valueToJava2D(previous, dataArea, 298 plot.getRangeAxisEdge()); 299 if (getStagger()) { 300 int xStagger = row * STAGGER_WIDTH; 301 if (xStagger > (x1s - x0e)) { 302 xStagger = (int) (x1s - x0e); 303 } 304 x1s = x0e + xStagger; 305 } 306 drawLine(g2, (State) state, orientation, x0e, y0, x1s, y0); 307 // extend x0's flat bar 308 309 drawLine(g2, (State) state, orientation, x1s, y0, x1s, y1); 310 // upright bar 311 } 312 } 313 drawLine(g2, (State) state, orientation, x1s, y1, x1e, y1); 314 // x1's flat bar 315 316 // draw the item labels if there are any... 317 if (isItemLabelVisible(row, column)) { 318 drawItemLabel(g2, orientation, dataset, row, column, x1, y1, 319 (value.doubleValue() < 0.0)); 320 } 321 322 // add an item entity, if this information is being collected 323 EntityCollection entities = state.getEntityCollection(); 324 if (entities != null) { 325 Rectangle2D hotspot = new Rectangle2D.Double(); 326 if (orientation == PlotOrientation.VERTICAL) { 327 hotspot.setRect(x1s, y1, x1e - x1s, 4.0); 328 } 329 else { 330 hotspot.setRect(y1 - 2.0, x1s, 4.0, x1e - x1s); 331 } 332 addItemEntity(entities, dataset, row, column, hotspot); 333 } 334 335 } 336 337 /** 338 * Tests this renderer for equality with an arbitrary object. 339 * 340 * @param obj the object ({@code null} permitted). 341 * 342 * @return A boolean. 343 */ 344 @Override 345 public boolean equals(Object obj) { 346 if (obj == this) { 347 return true; 348 } 349 if (!(obj instanceof CategoryStepRenderer)) { 350 return false; 351 } 352 CategoryStepRenderer that = (CategoryStepRenderer) obj; 353 if (this.stagger != that.stagger) { 354 return false; 355 } 356 return super.equals(obj); 357 } 358 359}