001/* =========================================================== 002 * Orson Charts : a 3D chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C)opyright 2013-2022, by David Gilbert. All rights reserved. 006 * 007 * https://github.com/jfree/orson-charts 008 * 009 * This program is free software: you can redistribute it and/or modify 010 * it under the terms of the GNU General Public License as published by 011 * the Free Software Foundation, either version 3 of the License, or 012 * (at your option) any later version. 013 * 014 * This program is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 017 * GNU General Public License for more details. 018 * 019 * You should have received a copy of the GNU General Public License 020 * along with this program. If not, see <http://www.gnu.org/licenses/>. 021 * 022 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 023 * Other names may be trademarks of their respective owners.] 024 * 025 * If you do not wish to be bound by the terms of the GPL, an alternative 026 * commercial license can be purchased. For details, please see visit the 027 * Orson Charts home page: 028 * 029 * http://www.object-refinery.com/orsoncharts/index.html 030 * 031 */ 032 033package org.jfree.chart3d.legend; 034 035import java.awt.Shape; 036import java.util.ArrayList; 037import java.awt.Font; 038import java.awt.Graphics2D; 039import java.awt.Insets; 040import java.awt.BasicStroke; 041import java.awt.Color; 042import java.awt.geom.Dimension2D; 043import java.awt.geom.Rectangle2D; 044import java.util.List; 045import java.util.Map; 046import java.awt.FontMetrics; 047import java.text.DecimalFormat; 048import java.text.NumberFormat; 049 050import org.jfree.chart3d.Orientation; 051import org.jfree.chart3d.data.Range; 052import org.jfree.chart3d.graphics2d.Fit2D; 053import org.jfree.chart3d.graphics2d.TextAnchor; 054import org.jfree.chart3d.internal.Args; 055import org.jfree.chart3d.internal.TextUtils; 056import org.jfree.chart3d.renderer.ColorScale; 057import org.jfree.chart3d.table.AbstractTableElement; 058import org.jfree.chart3d.table.ElementDimension; 059import org.jfree.chart3d.table.TableElement; 060import org.jfree.chart3d.table.TableElementOnDraw; 061import org.jfree.chart3d.table.TableElementVisitor; 062 063/** 064 * A {@link TableElement} that displays a {@link ColorScale}. 065 * <br><br> 066 * NOTE: This class is serializable, but the serialization format is subject 067 * to change in future releases and should not be relied upon for persisting 068 * instances of this class. 069 * 070 * @since 1.1 071 */ 072@SuppressWarnings("serial") 073public class ColorScaleElement extends AbstractTableElement 074 implements TableElement { 075 076 /** The color scale. */ 077 private final ColorScale scale; 078 079 /** The orientation (horizontal or vertical). */ 080 private final Orientation orientation; 081 082 /** The length of the bar. */ 083 private final double barLength; 084 085 /** The width of the bar. */ 086 private final double barWidth; 087 088 /** The gap between the color scale bar and the text labels. */ 089 private final double textOffset; 090 091 /** The font for the text labels. */ 092 private final Font font; 093 094 /** The text color. */ 095 private final Color textColor; 096 097 /** The number formatter. */ 098 private final NumberFormat formatter; 099 100 /** 101 * Creates a new {@code ColorScaleElement} with the specified 102 * attributes. 103 * 104 * @param scale the color scale ({@code null} not permitted). 105 * @param orientation the orientation ({@code null} not permitted). 106 * @param barWidth the bar width (in Java2D units). 107 * @param barLength the bar length (in Java2D units). 108 * @param font the font ({@code null} not permitted). 109 * @param textColor the text color ({@code null} not permitted). 110 * 111 * @since 1.2 112 */ 113 public ColorScaleElement(ColorScale scale, Orientation orientation, 114 double barWidth, double barLength, Font font, Color textColor) { 115 super(); 116 Args.nullNotPermitted(scale, "scale"); 117 Args.nullNotPermitted(orientation, "orientation"); 118 Args.nullNotPermitted(font, "font"); 119 this.scale = scale; 120 this.orientation = orientation; 121 this.barWidth = barWidth; 122 this.barLength = barLength; 123 this.textOffset = 2; 124 this.font = font; 125 this.textColor = textColor; 126 this.formatter = new DecimalFormat("0.00"); 127 } 128 129 /** 130 * Returns the color scale. 131 * 132 * @return The color scale (never {@code null}). 133 */ 134 public ColorScale getColorScale() { 135 return this.scale; 136 } 137 138 /** 139 * Returns the orientation. 140 * 141 * @return The orientation (never {@code null}). 142 */ 143 public Orientation getOrientation() { 144 return this.orientation; 145 } 146 147 /** 148 * Returns the bar width. 149 * 150 * @return The bar width. 151 */ 152 public double getBarWidth() { 153 return this.barWidth; 154 } 155 156 /** 157 * Returns the bar length. 158 * 159 * @return The bar length. 160 */ 161 public double getBarLength() { 162 return this.barLength; 163 } 164 165 /** 166 * Returns the font used to display the labels on the color scale. 167 * 168 * @return The font (never {@code null}). 169 */ 170 public Font getFont() { 171 return this.font; 172 } 173 174 /** 175 * Returns the text color. 176 * 177 * @return The text color (never {@code null}). 178 */ 179 public Color getTextColor() { 180 return this.textColor; 181 } 182 183 /** 184 * Receives a visitor. This is part of a general mechanism to perform 185 * operations on an arbitrary hierarchy of table elements. You will not 186 * normally call this method directly. 187 * 188 * @param visitor the visitor ({@code null} not permitted). 189 * 190 * @since 1.2 191 */ 192 @Override 193 public void receive(TableElementVisitor visitor) { 194 visitor.visit(this); 195 } 196 197 /** 198 * Returns the preferred size for this element. 199 * 200 * @param g2 the graphics target. 201 * @param bounds the available drawing space. 202 * 203 * @return The preferred size (never {@code null}). 204 */ 205 @Override 206 public Dimension2D preferredSize(Graphics2D g2, Rectangle2D bounds) { 207 return preferredSize(g2, bounds, null); 208 } 209 210 /** 211 * Returns the preferred size for this element. 212 * 213 * @param g2 the graphics target. 214 * @param bounds the available drawing space. 215 * @param constraints layout constraints (ignored here). 216 * 217 * @return The preferred size (never {@code null}). 218 */ 219 @Override 220 public Dimension2D preferredSize(Graphics2D g2, Rectangle2D bounds, 221 Map<String, Object> constraints) { 222 g2.setFont(this.font); 223 FontMetrics fm = g2.getFontMetrics(); 224 Range r = this.scale.getRange(); 225 String minStr = this.formatter.format(r.getMin()); 226 String maxStr = this.formatter.format(r.getMax()); 227 Rectangle2D minStrBounds = TextUtils.getTextBounds(minStr, fm); 228 Rectangle2D maxStrBounds = TextUtils.getTextBounds(maxStr, fm); 229 double maxStrWidth = Math.max(minStrBounds.getWidth(), 230 maxStrBounds.getWidth()); 231 Insets insets = getInsets(); 232 double w, h; 233 if (this.orientation == Orientation.HORIZONTAL) { 234 w = Math.min(this.barLength + insets.left + insets.right, 235 bounds.getWidth()); 236 h = Math.min(insets.top + this.barWidth + this.textOffset 237 + minStrBounds.getHeight() + insets.bottom, 238 bounds.getHeight()); 239 } else { 240 w = Math.min(insets.left + this.barWidth + this.textOffset 241 + maxStrWidth + insets.right, bounds.getWidth()); 242 h = Math.min(insets.top + this.barLength + this.textOffset 243 + minStrBounds.getHeight() + insets.bottom, 244 bounds.getHeight()); 245 246 } 247 return new ElementDimension(w, h); 248 } 249 250 @Override 251 public List<Rectangle2D> layoutElements(Graphics2D g2, Rectangle2D bounds, 252 Map<String, Object> constraints) { 253 List<Rectangle2D> result = new ArrayList<>(1); 254 Dimension2D prefDim = preferredSize(g2, bounds); 255 Fit2D fitter = Fit2D.getNoScalingFitter(getRefPoint()); 256 Rectangle2D dest = fitter.fit(prefDim, bounds); 257 result.add(dest); 258 return result; 259 } 260 261 /** 262 * Draws the element within the specified bounds. 263 * 264 * @param g2 the graphics target ({@code null} not permitted). 265 * @param bounds the bounds ({@code null} not permitted). 266 */ 267 @Override 268 public void draw(Graphics2D g2, Rectangle2D bounds) { 269 draw(g2, bounds, null); 270 } 271 272 /** 273 * Draws the element within the specified bounds. 274 * 275 * @param g2 the graphics target ({@code null} not permitted). 276 * @param bounds the bounds ({@code null} not permitted). 277 * @param onDrawHandler receives notification before and after the element 278 * is drawn ({@code null} permitted); 279 * 280 * @since 1.3 281 */ 282 @Override 283 public void draw(Graphics2D g2, Rectangle2D bounds, 284 TableElementOnDraw onDrawHandler) { 285 286 if (onDrawHandler != null) { 287 onDrawHandler.beforeDraw(this, g2, bounds); 288 } 289 290 Shape savedClip = g2.getClip(); 291 g2.clip(bounds); 292 List<Rectangle2D> layoutInfo = layoutElements(g2, bounds, null); 293 Rectangle2D dest = layoutInfo.get(0); 294 if (getBackground() != null) { 295 getBackground().fill(g2, dest); 296 } 297 g2.setFont(this.font); 298 FontMetrics fm = g2.getFontMetrics(); 299 Range r = this.scale.getRange(); 300 String minStr = this.formatter.format(r.getMin()); 301 String maxStr = this.formatter.format(r.getMax()); 302 Rectangle2D minStrBounds = TextUtils.getTextBounds(minStr, fm); 303 Rectangle2D maxStrBounds = TextUtils.getTextBounds(maxStr, fm); 304 Insets insets = getInsets(); 305 if (this.orientation == Orientation.HORIZONTAL) { 306 double x0 = dest.getX() + insets.left 307 + minStrBounds.getWidth() / 2.0; 308 double x1 = dest.getMaxX() - insets.right 309 - maxStrBounds.getWidth() / 2.0; 310 double y0 = dest.getY() + insets.top; 311 double y1 = y0 + this.barWidth; 312 313 drawHorizontalScale(this.scale, g2, new Rectangle2D.Double( 314 (int) x0, (int) y0, (int) (x1 - x0), (int) this.barWidth)); 315 // fill the bar with the color scale 316 g2.setPaint(this.textColor); 317 TextUtils.drawAlignedString(minStr, g2, (float) x0, 318 (float) (y1 + this.textOffset), TextAnchor.TOP_CENTER); 319 TextUtils.drawAlignedString(maxStr, g2, (float) x1, 320 (float) (y1 + this.textOffset), TextAnchor.TOP_CENTER); 321 322 } else { // VERTICAL 323 double maxStrWidth = Math.max(minStrBounds.getWidth(), 324 maxStrBounds.getWidth()); 325 double x1 = dest.getMaxX() - insets.right - maxStrWidth 326 - this.textOffset; 327 double x0 = x1 - this.barWidth; 328 double y0 = dest.getY() + insets.top 329 + maxStrBounds.getHeight() / 2.0; 330 double y1 = y0 + this.barLength; 331 332 drawVerticalScale(this.scale, g2, new Rectangle2D.Double( 333 (int) x0, (int) y0, (int) (x1 - x0), (int) this.barLength)); 334 g2.setPaint(this.textColor); 335 TextUtils.drawAlignedString(minStr, g2, 336 (float) (x1 + this.textOffset), (float) y1, 337 TextAnchor.HALF_ASCENT_LEFT); 338 TextUtils.drawAlignedString(maxStr, g2, 339 (float) (x1 + this.textOffset), (float) y0, 340 TextAnchor.HALF_ASCENT_LEFT); 341 } 342 g2.setClip(savedClip); 343 344 if (onDrawHandler != null) { 345 onDrawHandler.afterDraw(this, g2, bounds); 346 } 347 } 348 349 /** 350 * Draws the color scale horizontally within the specified bounds. 351 * 352 * @param colorScale the color scale. 353 * @param g2 the graphics target. 354 * @param bounds the bounds. 355 */ 356 private void drawHorizontalScale(ColorScale colorScale, Graphics2D g2, 357 Rectangle2D bounds) { 358 g2.setStroke(new BasicStroke(1.0f)); 359 for (int x = (int) bounds.getX(); x < bounds.getMaxX(); x++) { 360 double p = (x - bounds.getX()) / bounds.getWidth(); 361 double value = colorScale.getRange().value(p); 362 g2.setColor(colorScale.valueToColor(value)); 363 g2.drawLine(x, (int) bounds.getMinY(), x, (int) bounds.getMaxY()); 364 } 365 } 366 367 /** 368 * Draws the color scale vertically within the specified bounds. 369 * 370 * @param colorScale the color scale. 371 * @param g2 the graphics target. 372 * @param bounds the bounds. 373 */ 374 private void drawVerticalScale(ColorScale colorScale, Graphics2D g2, 375 Rectangle2D bounds) { 376 g2.setStroke(new BasicStroke(1.0f)); 377 for (int y = (int) bounds.getY(); y < bounds.getMaxY(); y++) { 378 double p = (y - bounds.getY()) / bounds.getHeight(); 379 double value = colorScale.getRange().value(1 - p); 380 g2.setColor(this.scale.valueToColor(value)); 381 g2.drawLine((int) bounds.getX(), y, (int) bounds.getMaxX(), y); 382 } 383 } 384 385 @Override 386 public boolean equals(Object obj) { 387 if (obj == this) { 388 return true; 389 } 390 if (!(obj instanceof ColorScaleElement)) { 391 return false; 392 } 393 ColorScaleElement that = (ColorScaleElement) obj; 394 if (!this.scale.equals(that.scale)) { 395 return false; 396 } 397 if (!this.orientation.equals(that.orientation)) { 398 return false; 399 } 400 if (this.barLength != that.barLength) { 401 return false; 402 } 403 if (this.barWidth != that.barWidth) { 404 return false; 405 } 406 if (!this.font.equals(that.font)) { 407 return false; 408 } 409 return super.equals(obj); 410 } 411}