001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2020, 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 * GradientBarPainter.java 029 * ----------------------- 030 * (C) Copyright 2008-2016, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 */ 036 037package org.jfree.chart.renderer.category; 038 039import java.awt.Color; 040import java.awt.GradientPaint; 041import java.awt.Graphics2D; 042import java.awt.Paint; 043import java.awt.Stroke; 044import java.awt.geom.Rectangle2D; 045import java.awt.geom.RectangularShape; 046import java.io.Serializable; 047 048import org.jfree.chart.HashUtils; 049import org.jfree.chart.ui.RectangleEdge; 050 051/** 052 * An implementation of the {@link BarPainter} interface that uses several 053 * gradient fills to enrich the appearance of the bars. 054 * 055 * @since 1.0.11 056 */ 057public class GradientBarPainter implements BarPainter, Serializable { 058 059 /** The division point between the first and second gradient regions. */ 060 private double g1; 061 062 /** The division point between the second and third gradient regions. */ 063 private double g2; 064 065 /** The division point between the third and fourth gradient regions. */ 066 private double g3; 067 068 /** 069 * Creates a new instance. 070 */ 071 public GradientBarPainter() { 072 this(0.10, 0.20, 0.80); 073 } 074 075 /** 076 * Creates a new instance. 077 * 078 * @param g1 percentage value defining the line between regions 1 and 2. 079 * @param g2 percentage value defining the line between regions 2 and 3. 080 * @param g3 percentage value defining the line between regions 3 and 4. 081 */ 082 public GradientBarPainter(double g1, double g2, double g3) { 083 this.g1 = g1; 084 this.g2 = g2; 085 this.g3 = g3; 086 } 087 088 /** 089 * Paints a single bar instance. 090 * 091 * @param g2 the graphics target. 092 * @param renderer the renderer. 093 * @param row the row index. 094 * @param column the column index. 095 * @param bar the bar 096 * @param base indicates which side of the rectangle is the base of the 097 * bar. 098 */ 099 @Override 100 public void paintBar(Graphics2D g2, BarRenderer renderer, int row, 101 int column, RectangularShape bar, RectangleEdge base) { 102 103 Paint itemPaint = renderer.getItemPaint(row, column); 104 105 Color c0, c1; 106 if (itemPaint instanceof Color) { 107 c0 = (Color) itemPaint; 108 c1 = c0.brighter(); 109 } 110 else if (itemPaint instanceof GradientPaint) { 111 GradientPaint gp = (GradientPaint) itemPaint; 112 c0 = gp.getColor1(); 113 c1 = gp.getColor2(); 114 } 115 else { 116 c0 = Color.BLUE; 117 c1 = Color.BLUE.brighter(); 118 } 119 120 // as a special case, if the bar colour has alpha == 0, we draw 121 // nothing. 122 if (c0.getAlpha() == 0) { 123 return; 124 } 125 126 if (base == RectangleEdge.TOP || base == RectangleEdge.BOTTOM) { 127 Rectangle2D[] regions = splitVerticalBar(bar, this.g1, this.g2, 128 this.g3); 129 GradientPaint gp = new GradientPaint((float) regions[0].getMinX(), 130 0.0f, c0, (float) regions[0].getMaxX(), 0.0f, Color.WHITE); 131 g2.setPaint(gp); 132 g2.fill(regions[0]); 133 134 gp = new GradientPaint((float) regions[1].getMinX(), 0.0f, 135 Color.WHITE, (float) regions[1].getMaxX(), 0.0f, c0); 136 g2.setPaint(gp); 137 g2.fill(regions[1]); 138 139 gp = new GradientPaint((float) regions[2].getMinX(), 0.0f, c0, 140 (float) regions[2].getMaxX(), 0.0f, c1); 141 g2.setPaint(gp); 142 g2.fill(regions[2]); 143 144 gp = new GradientPaint((float) regions[3].getMinX(), 0.0f, c1, 145 (float) regions[3].getMaxX(), 0.0f, c0); 146 g2.setPaint(gp); 147 g2.fill(regions[3]); 148 } 149 else if (base == RectangleEdge.LEFT || base == RectangleEdge.RIGHT) { 150 Rectangle2D[] regions = splitHorizontalBar(bar, this.g1, this.g2, 151 this.g3); 152 GradientPaint gp = new GradientPaint(0.0f, 153 (float) regions[0].getMinY(), c0, 0.0f, 154 (float) regions[0].getMaxY(), Color.WHITE); 155 g2.setPaint(gp); 156 g2.fill(regions[0]); 157 158 gp = new GradientPaint(0.0f, (float) regions[1].getMinY(), 159 Color.WHITE, 0.0f, (float) regions[1].getMaxY(), c0); 160 g2.setPaint(gp); 161 g2.fill(regions[1]); 162 163 gp = new GradientPaint(0.0f, (float) regions[2].getMinY(), c0, 164 0.0f, (float) regions[2].getMaxY(), c1); 165 g2.setPaint(gp); 166 g2.fill(regions[2]); 167 168 gp = new GradientPaint(0.0f, (float) regions[3].getMinY(), c1, 169 0.0f, (float) regions[3].getMaxY(), c0); 170 g2.setPaint(gp); 171 g2.fill(regions[3]); 172 173 } 174 175 // draw the outline... 176 if (renderer.isDrawBarOutline() 177 /*&& state.getBarWidth() > renderer.BAR_OUTLINE_WIDTH_THRESHOLD*/) { 178 Stroke stroke = renderer.getItemOutlineStroke(row, column); 179 Paint paint = renderer.getItemOutlinePaint(row, column); 180 if (stroke != null && paint != null) { 181 g2.setStroke(stroke); 182 g2.setPaint(paint); 183 g2.draw(bar); 184 } 185 } 186 187 } 188 189 /** 190 * Paints a single bar instance. 191 * 192 * @param g2 the graphics target. 193 * @param renderer the renderer. 194 * @param row the row index. 195 * @param column the column index. 196 * @param bar the bar 197 * @param base indicates which side of the rectangle is the base of the 198 * bar. 199 * @param pegShadow peg the shadow to the base of the bar? 200 */ 201 @Override 202 public void paintBarShadow(Graphics2D g2, BarRenderer renderer, int row, 203 int column, RectangularShape bar, RectangleEdge base, 204 boolean pegShadow) { 205 206 // handle a special case - if the bar colour has alpha == 0, it is 207 // invisible so we shouldn't draw any shadow 208 Paint itemPaint = renderer.getItemPaint(row, column); 209 if (itemPaint instanceof Color) { 210 Color c = (Color) itemPaint; 211 if (c.getAlpha() == 0) { 212 return; 213 } 214 } 215 216 RectangularShape shadow = createShadow(bar, renderer.getShadowXOffset(), 217 renderer.getShadowYOffset(), base, pegShadow); 218 g2.setPaint(renderer.getShadowPaint()); 219 g2.fill(shadow); 220 221 } 222 223 /** 224 * Creates a shadow for the bar. 225 * 226 * @param bar the bar shape. 227 * @param xOffset the x-offset for the shadow. 228 * @param yOffset the y-offset for the shadow. 229 * @param base the edge that is the base of the bar. 230 * @param pegShadow peg the shadow to the base? 231 * 232 * @return A rectangle for the shadow. 233 */ 234 private Rectangle2D createShadow(RectangularShape bar, double xOffset, 235 double yOffset, RectangleEdge base, boolean pegShadow) { 236 double x0 = bar.getMinX(); 237 double x1 = bar.getMaxX(); 238 double y0 = bar.getMinY(); 239 double y1 = bar.getMaxY(); 240 if (base == RectangleEdge.TOP) { 241 x0 += xOffset; 242 x1 += xOffset; 243 if (!pegShadow) { 244 y0 += yOffset; 245 } 246 y1 += yOffset; 247 } 248 else if (base == RectangleEdge.BOTTOM) { 249 x0 += xOffset; 250 x1 += xOffset; 251 y0 += yOffset; 252 if (!pegShadow) { 253 y1 += yOffset; 254 } 255 } 256 else if (base == RectangleEdge.LEFT) { 257 if (!pegShadow) { 258 x0 += xOffset; 259 } 260 x1 += xOffset; 261 y0 += yOffset; 262 y1 += yOffset; 263 } 264 else if (base == RectangleEdge.RIGHT) { 265 x0 += xOffset; 266 if (!pegShadow) { 267 x1 += xOffset; 268 } 269 y0 += yOffset; 270 y1 += yOffset; 271 } 272 return new Rectangle2D.Double(x0, y0, (x1 - x0), (y1 - y0)); 273 } 274 275 /** 276 * Splits a bar into subregions (elsewhere, these subregions will have 277 * different gradients applied to them). 278 * 279 * @param bar the bar shape. 280 * @param a the first division. 281 * @param b the second division. 282 * @param c the third division. 283 * 284 * @return An array containing four subregions. 285 */ 286 private Rectangle2D[] splitVerticalBar(RectangularShape bar, double a, 287 double b, double c) { 288 Rectangle2D[] result = new Rectangle2D[4]; 289 double x0 = bar.getMinX(); 290 double x1 = Math.rint(x0 + (bar.getWidth() * a)); 291 double x2 = Math.rint(x0 + (bar.getWidth() * b)); 292 double x3 = Math.rint(x0 + (bar.getWidth() * c)); 293 result[0] = new Rectangle2D.Double(bar.getMinX(), bar.getMinY(), 294 x1 - x0, bar.getHeight()); 295 result[1] = new Rectangle2D.Double(x1, bar.getMinY(), x2 - x1, 296 bar.getHeight()); 297 result[2] = new Rectangle2D.Double(x2, bar.getMinY(), x3 - x2, 298 bar.getHeight()); 299 result[3] = new Rectangle2D.Double(x3, bar.getMinY(), 300 bar.getMaxX() - x3, bar.getHeight()); 301 return result; 302 } 303 304 /** 305 * Splits a bar into subregions (elsewhere, these subregions will have 306 * different gradients applied to them). 307 * 308 * @param bar the bar shape. 309 * @param a the first division. 310 * @param b the second division. 311 * @param c the third division. 312 * 313 * @return An array containing four subregions. 314 */ 315 private Rectangle2D[] splitHorizontalBar(RectangularShape bar, double a, 316 double b, double c) { 317 Rectangle2D[] result = new Rectangle2D[4]; 318 double y0 = bar.getMinY(); 319 double y1 = Math.rint(y0 + (bar.getHeight() * a)); 320 double y2 = Math.rint(y0 + (bar.getHeight() * b)); 321 double y3 = Math.rint(y0 + (bar.getHeight() * c)); 322 result[0] = new Rectangle2D.Double(bar.getMinX(), bar.getMinY(), 323 bar.getWidth(), y1 - y0); 324 result[1] = new Rectangle2D.Double(bar.getMinX(), y1, bar.getWidth(), 325 y2 - y1); 326 result[2] = new Rectangle2D.Double(bar.getMinX(), y2, bar.getWidth(), 327 y3 - y2); 328 result[3] = new Rectangle2D.Double(bar.getMinX(), y3, bar.getWidth(), 329 bar.getMaxY() - y3); 330 return result; 331 } 332 333 /** 334 * Tests this instance for equality with an arbitrary object. 335 * 336 * @param obj the obj ({@code null} permitted). 337 * 338 * @return A boolean. 339 */ 340 @Override 341 public boolean equals(Object obj) { 342 if (obj == this) { 343 return true; 344 } 345 if (!(obj instanceof GradientBarPainter)) { 346 return false; 347 } 348 GradientBarPainter that = (GradientBarPainter) obj; 349 if (this.g1 != that.g1) { 350 return false; 351 } 352 if (this.g2 != that.g2) { 353 return false; 354 } 355 if (this.g3 != that.g3) { 356 return false; 357 } 358 return true; 359 } 360 361 /** 362 * Returns a hash code for this instance. 363 * 364 * @return A hash code. 365 */ 366 @Override 367 public int hashCode() { 368 int hash = 37; 369 hash = HashUtils.hashCode(hash, this.g1); 370 hash = HashUtils.hashCode(hash, this.g2); 371 hash = HashUtils.hashCode(hash, this.g3); 372 return hash; 373 } 374 375}