package com.onaro.sanscreen.client.view.common.renderers; import java.awt.Color; import java.awt.Component; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Rectangle; import java.text.NumberFormat; import javax.swing.JTable; import com.onaro.sanscreen.client.prefs.GeneralPreferenceManager; import com.onaro.sanscreen.client.view.tabular.value.NumberValue; import com.onaro.sanscreen.client.view.tabular.value.ValueDecoration; public class PercentValueRenderer extends NumberValueRenderer { public static final boolean SHOW_BAR = true; /** * Indicates if the given value will be rendered as zero. * This is done for "tiny" values to mask rounding errors. * * @param value to be tested * @return {@code true} if the value will be rendered as 0, {@code false} otherwise */ public static boolean isConsideredZero (double value) { return (value < TINY_VALUE && value > -TINY_VALUE); } public PercentValueRenderer() { this (true, 0); } public PercentValueRenderer (int numFractionDigits) { this (true, numFractionDigits); } public PercentValueRenderer (boolean doShowBar) { this (doShowBar, 0); } public PercentValueRenderer (boolean doShowBar, int numFractionDigits) { super (NumberFormat.getPercentInstance(), doShowBar? CENTER : RIGHT); if (numFractionDigits > 0) { setMinimumFractionDigits (numFractionDigits); } setBarEnabled (doShowBar); } /** * Both update and get bar state. * Note enabling bars doesn't necessarily show them. They are not shown * even if enabled if they are turned off in SANscreen preferences. * @param doEnable set true to enable bars, false to disable and hide bars * @return {@code true} if bars will be shown (enabled and turned on in preferences) otherwise {@code false} */ public boolean setBarEnabled (boolean doEnable) { // Bar is shown only if enabled on this object and in SANscreen settings. isBarEnabled = doEnable; doEnable = doEnable && GeneralPreferenceManager.getInstance().isPercentBarsEnabled(); if (doShowBar != doEnable) { doShowBar = doEnable; setHorizontalAlignment (doShowBar? CENTER : RIGHT); setRatio (null); // So the previous bar is not drawn before a value is set. } return doShowBar; } public boolean isBarEnabled() { // Check SANscreen settings again before returning state. return isBarEnabled && setBarEnabled (true); } @Override public Component getTableCellRendererComponent (JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (isBarEnabled()) { setRatio (value); this.isSelected = isSelected; // Change NumberValue to Number so SimpleTableCellRenderer doesn't adjust colors. We adjust colors below. if (value instanceof NumberValue) { value = ((NumberValue)value).getNumberObject(); } } return super.getTableCellRendererComponent (table, value, isSelected, hasFocus, row, column); } @Override protected void paintComponent (Graphics g) { Graphics2D graphics = (Graphics2D)g; Color selectedColor = null; if (doShowBar && ratio > 0.0) { // Compute bar extent, if any. if (updateBarBounds (getWidth())) { // Don't bother with bar if it's completely outside clip bounds. if (graphics.hitClip (barBounds.x, barBounds.y, barBounds.width, barBounds.height)) { // Override standard painting of selection background, so it doesn't cover bar. if (isSelected) { selectedColor = getBackground(); setBackground (TRANSPARENT); } // Set bar color. Color prevColor = graphics.getColor(); if (isSelected) { graphics.setColor (selectedColor); } else { graphics.setColor (getBarColor()); } // Don't bother filling bar if it's too small to see fill. boolean doFill = (barBounds.width > 2 && barBounds.height > 2); // If selected, draw selection background. if (isSelected) { graphics.getClipBounds (clipBounds); graphics.fill (clipBounds); // If selected, fill bar only if a color has been encoded in the cell value. if (valueColor == null) { doFill = false; } else { graphics.setColor (valueColor); } } // Fill bar if necessary. if (doFill) { graphics.fillRect (barBounds.x + 1, barBounds.y + 1, barBounds.width - 2, barBounds.height - 2); } /* * Draw edges of bar. * Use sunken bar for positive values, raised bar for negative values. This is * so bar shadow edge isn't under cell value, which would reduce legibility. */ graphics.draw3DRect (barBounds.x, barBounds.y, barBounds.width, barBounds.height, isNegative); graphics.setColor (prevColor); // Change color of the font if the value color is red or dark. // TODO: Remove this when colors are changed on server side or color is specified in client. if (valueColor != null && valueColor.getGreen() < 128 && valueColor.getBlue() < 128) { setForeground (Color.white); } // Move origin up by 1 pixel so text sits in bar. graphics.translate (0, -1); } } } // Paint usual cell contents. super.paintComponent (graphics); } @Override protected void paintBorder (Graphics graphics) { // If we translated origin to move up text, we have to move it back down for border. if (doShowBar) { graphics.translate (0, 1); } super.paintBorder (graphics); } /** * Determine ratio of bar width to cell width according to cell value. * Negative values have the same ratio as positive values but are * rendered against the opposite edge of the cell. * @param value cell value */ protected void setRatio (Object value) { ratio = 0.0; isNegative = false; if (value == null) { return; } // Check if a color has been encoded in the value. if (value instanceof ValueDecoration){ ValueDecoration decoration = (ValueDecoration)value; valueColor = decoration.getBackground(); } // Convert NumberValue objects to Number objects. if (value instanceof NumberValue) { value = ((NumberValue)value).getNumberObject(); } if (value instanceof Double) { Double doubleValue = (Double)value; if (! doubleValue.isNaN()) { ratio = doubleValue.isInfinite()? Double.MAX_VALUE : doubleValue.doubleValue(); } } else if (value instanceof Float) { Float floatValue = (Float)value; if (! floatValue.isNaN()) { ratio = floatValue.isInfinite()? Double.MAX_VALUE : floatValue.doubleValue(); } } else if (value instanceof Number) { Number number = (Number)value; ratio = number.doubleValue(); } // Handle negative ratios and ratios very close to zero. if (ratio < TINY_VALUE) { if (ratio < -TINY_VALUE) { isNegative = true; ratio = -ratio; } else { ratio = 0.0; } } } /** * Calculate bar bounds (outside extent). * @param cellWidth width of cell to be rendered * @return indicates if bar width > 0 */ protected boolean updateBarBounds (int cellWidth) { // Bar width is a percentage of cell width. int barWidth = ratio >= 1.0? cellWidth : (int)(cellWidth * ratio); if (barWidth <= 0) { return false; } // Because bar right edge is near-white, we make it a bit wider if less than full. if (barWidth < cellWidth) { barBounds.width = barWidth + 1; } else { barBounds.width = barWidth; } barBounds.height = getHeight() - 4; // Draw bar from left of cell if positive percentage, otherwise from right. if (isNegative) { barBounds.x = cellWidth - barBounds.width; } else { barBounds.x = 0; } barBounds.y = 2; return true; } /** * Get bar color according to ratio. * Use darker bar if ratio is high (> 100%). * * @return bar color */ protected Color getBarColor() { // Decide color based on ratio unless a color has been encoded in the cell value. if (valueColor == null) { Color barColor; if (ratio < 1.1) { barColor = BAR_COLOR_NORMAL; } else if (ratio < 10.1) { barColor = BAR_COLOR_BIG; } else if (ratio < 100.1) { barColor = BAR_COLOR_BIGGER; } else { barColor = BAR_COLOR_HUGE; } return barColor; } else { return valueColor; } } public static final double TINY_VALUE = 1.0e-5; public static final Color TRANSPARENT = new Color (0, 0, 0, 0); public static final Color BAR_COLOR_NORMAL = new Color (224, 240, 255); // Pale blue. public static final Color BAR_COLOR_BIG = new Color (224, 224, 255); // Very light indigo. public static final Color BAR_COLOR_BIGGER = new Color (224, 224, 240); // Light indigo. public static final Color BAR_COLOR_HUGE = new Color (208, 224, 240); // Light blue. private static Rectangle clipBounds = new Rectangle(); private static Rectangle barBounds = new Rectangle(); private static final long serialVersionUID = 3L; private boolean isBarEnabled = true; // Setting for this instance. private boolean doShowBar = true; // Combination of isBarEnabled and SANscreen preference. private boolean isSelected = false; private boolean isNegative = false; private Color valueColor = null; // Indicates color-coding for value, if any. private double ratio = 0.0; }