/* * Copyright 2008 the original author or authors. * Copyright 2005 Sun Microsystems, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.rioproject.watch; import java.awt.*; import java.rmi.RemoteException; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.ArrayList; /** * Default attributes for viewing a collection of Calculables */ public class DefaultCalculableView implements CalculableViewable { final static protected int TOP_AXIS_MARGIN = 20; final static protected int BOTTOM_AXIS_MARGIN = 20; final static protected int LEFT_AXIS_MARGIN = 50; final static protected int RIGHT_AXIS_MARGIN = 35; final static protected int TOP_GRAPH_MARGIN = 40; final static protected int BOTTOM_GRAPH_MARGIN = 21; final static protected int LEFT_GRAPH_MARGIN = 51; final static protected int RIGHT_GRAPH_MARGIN = 45; final static protected int VERTICAL_GRAPH_OFFSET = 10; final static protected int TOTAL_H_GRAPH_MARGIN = LEFT_GRAPH_MARGIN + RIGHT_GRAPH_MARGIN; final static protected int TOTAL_V_GRAPH_MARGIN = TOP_GRAPH_MARGIN + BOTTOM_GRAPH_MARGIN; final static protected int VERTICAL__MIN_GRID_HEIGHT = 15; final static protected int HORIZONTAL__MIN_GRID_WIDTH = 40; final static protected Color devColor = new Color(204,0,204); protected static final Color avgColor = new Color(68,187,121); final public static DecimalFormat defaultFormater = new DecimalFormat("#0.0"); public final static Font defaultFont = new Font("Dialog", Font.PLAIN, 10); protected static final CalculableDisplayAttributes defaultDisplayAttributes = new CalculableDisplayAttributes( "Value", new FontDescriptor("Dialog", java.awt.Font.PLAIN, 10), 0, (NumberFormat)new DecimalFormat("#,##0.###"), "Events", new FontDescriptor("Dialog", java.awt.Font.PLAIN, 10), 0, (NumberFormat)new DecimalFormat("#,##0.###"), "Measurement", new FontDescriptor("Dialog", java.awt.Font.BOLD, 12), Calculable.class); protected WatchDataSource watchDataSource; protected Accumulator accum; protected Calculable[] data = null; protected double highThreshold = Double.NaN; protected double lowThreshold = Double.NaN; protected double startValue = 0; protected double stopValue = 0; protected double min = 0; protected double max = 0; protected double mean = 0; protected double stdDev = 0; protected String yLegend = ""; protected Font yLegendFont = defaultFont; protected double yScale = 0; protected NumberFormat yFormat = defaultFormater; protected String xLegend = ""; protected Font xLegendFont = defaultFont; protected double xScale = 0; protected NumberFormat xFormat = defaultFormater; protected String title = ""; protected Font titleFont = defaultFont; protected CalculableDisplayAttributes calcDisplayAttrs; protected int currentSize = 0; protected double drawWidth = 0.0; protected double rangeSize = 0.0; protected double drawHeight = 0.0; protected double topLineValue = 0.0; protected double bottomLineValue = 0.0; protected double valuesRange = 0.0; protected double vStep = 0.0; protected double vStepMultiplier = 1; protected double nbrHGrids = 0.0; protected double maxY = 0.0; protected double minY = 0.0; private final java.util.List<PlottedCalculable> plotted = new ArrayList<PlottedCalculable>(); /** * Creates new DefaultCalculableView */ public DefaultCalculableView() { this(defaultDisplayAttributes); } /** * Creates new DefaultCalculableView * * @param calcDisplayAttrs The Calculable Display Attributes used to * format the graph */ public DefaultCalculableView(CalculableDisplayAttributes calcDisplayAttrs) { this.calcDisplayAttrs = calcDisplayAttrs; } /** * Sets the Calculable Display Attributes used to format the graph * * @param calcDisplayAttrs The Calculable Display Attributes used to * format the graph */ public void setCalculableDisplayAttributes(CalculableDisplayAttributes calcDisplayAttrs) { this.calcDisplayAttrs = calcDisplayAttrs; } /** * Sets the watch data source for this view * * @param watchDataSource The WatchDataSource */ public void setWatchDataSource(WatchDataSource watchDataSource) { resetValues(); this.watchDataSource = watchDataSource; initValues(); } /** * Reset the graph values to default values */ protected void resetValues() { watchDataSource = null; data = null; highThreshold = Double.NaN; lowThreshold = Double.NaN; startValue = 0; stopValue = 0; min = 0; max = 0; mean = 0; stdDev = 0; yLegend = ""; yLegendFont = defaultFont; yScale = 0; yFormat = defaultFormater; xLegend = ""; xLegendFont = defaultFont; xScale = 0; xFormat = defaultFormater; title = ""; titleFont = defaultFont; currentSize = 0; plotted.clear(); } /** * Initialize the graph values from the WatchDataSource */ protected void initValues() { try { currentSize = watchDataSource.getCurrentSize(); ThresholdValues tvalues = watchDataSource.getThresholdValues(); highThreshold = tvalues.getHighThreshold(); lowThreshold = tvalues.getLowThreshold(); accum = new Accumulator(watchDataSource); accum.init(); data = accum.getCalcs(); //if(data == null || data.length == 0) { // return; //} yLegend = calcDisplayAttrs.getYLegend(); FontDescriptor fd = calcDisplayAttrs.getYLegendFont(); yLegendFont = new Font(fd.getName(), fd.getStyle(), fd.getSize()); yScale = Math.pow(10, calcDisplayAttrs.getYScale()); yFormat = calcDisplayAttrs.getYFormat(); xLegend = calcDisplayAttrs.getXLegend(); fd = calcDisplayAttrs.getXLegendFont(); xLegendFont = new Font(fd.getName(), fd.getStyle(), fd.getSize()); xScale = Math.pow(10, calcDisplayAttrs.getXScale()); xFormat = calcDisplayAttrs.getXFormat(); title = calcDisplayAttrs.getTitle(); fd = calcDisplayAttrs.getTitleFont(); titleFont = new Font(fd.getName(), fd.getStyle(), fd.getSize()); //plot the records min = accum.min() / yScale; max = accum.max() / yScale; mean = accum.mean() / yScale; //average stdDev = accum.standardDeviation() / yScale; if(!Double.isNaN(highThreshold)) highThreshold /= yScale; if(!Double.isNaN(lowThreshold)) lowThreshold /= yScale; startValue = 0; stopValue = data.length - 1; } catch(RemoteException ex) { ex.printStackTrace(); } } /** * Paints the view to the graphics context * * @param g The Graphics context */ public void paint(Graphics g, Dimension size) { try { g.setColor(new Color(232, 232, 232)); g.fillRect(0, 0, size.width, size.height); g.setColor(Color.black); // draw axis g.drawLine(LEFT_AXIS_MARGIN, TOP_AXIS_MARGIN, LEFT_AXIS_MARGIN, size.height - BOTTOM_AXIS_MARGIN); g.drawLine(LEFT_AXIS_MARGIN, size.height - BOTTOM_AXIS_MARGIN, size.width - RIGHT_AXIS_MARGIN, size.height - BOTTOM_AXIS_MARGIN); // draw axis labels g.setFont(yLegendFont); g.drawString(yLegend, LEFT_AXIS_MARGIN - 5, 15); g.setFont(xLegendFont); g.drawString(xLegend, size.width - 30, size.height - 15); g.setFont(titleFont); g.drawString(title, LEFT_GRAPH_MARGIN + 320, 20); if(data == null || data.length == 0) return; // draw textual info g.setFont(defaultFont); g.drawString("min=" + yFormat.format(min), LEFT_GRAPH_MARGIN + 50, 10); g.drawString("max=" + yFormat.format(max), LEFT_GRAPH_MARGIN + 50, 20); g.drawString("stdDev=" + yFormat.format(stdDev), LEFT_GRAPH_MARGIN + 130, 10); g.drawString("mean=" + yFormat.format(mean), LEFT_GRAPH_MARGIN + 130, 20); if(!Double.isNaN(highThreshold)) { g.drawString("highThreshold=" + yFormat.format(highThreshold), LEFT_GRAPH_MARGIN + 210, 10); } if(!Double.isNaN(lowThreshold)) { g.drawString("lowThreshold=" + yFormat.format(lowThreshold), LEFT_GRAPH_MARGIN + 210, 20); } // calculate key values drawWidth = size.width - TOTAL_H_GRAPH_MARGIN; drawHeight = size.height - TOTAL_V_GRAPH_MARGIN - VERTICAL_GRAPH_OFFSET; topLineValue = getTopLineValue(); bottomLineValue = getBottomLineValue(); if (bottomLineValue == topLineValue) { // zero range detected. We need to select some non-zero range if (bottomLineValue == 0) { // if the range is [0,0], set it to [0,1] topLineValue = 1; } else { // if the range is [x,x], set it to [0,x] bottomLineValue = 0; } } valuesRange = topLineValue - bottomLineValue; vStep = drawHeight / valuesRange; vStepMultiplier = 1; while ((vStep * vStepMultiplier) < VERTICAL__MIN_GRID_HEIGHT) vStepMultiplier++; nbrHGrids = valuesRange / vStepMultiplier + 1; maxY = drawHeight - vStep * valuesRange + TOP_GRAPH_MARGIN; minY = drawHeight + TOP_GRAPH_MARGIN; double startValue = this.startValue; double stopValue = this.stopValue; if (stopValue == startValue) { stopValue++; } rangeSize = stopValue - startValue; // draw vertical grid double divider = 1; while (drawWidth / (rangeSize / divider) < HORIZONTAL__MIN_GRID_WIDTH) divider++; double nbrVLabels = rangeSize / divider; double xLabelsStep = drawWidth / nbrVLabels; double xLabelValIncrement = rangeSize / nbrVLabels; g.setColor(Color.lightGray); for(double i = 1; i <= nbrVLabels; i++) { int x = (int) (LEFT_AXIS_MARGIN + i * xLabelsStep); g.drawLine(x, TOP_AXIS_MARGIN + 10, x, size.height - BOTTOM_GRAPH_MARGIN); } // draw x-axis labels g.setFont(xLegendFont); g.setColor(Color.black); for(double i = 0, lValue = /* startRange */startValue; i <= nbrVLabels; i++, lValue += xLabelValIncrement) { g.drawString(String.valueOf((int)lValue + 1), (int)(45 + i * xLabelsStep), (size.height - 10)); } // draw horizontal grid and y-axis labels g.setFont(yLegendFont); FontMetrics fm = g.getFontMetrics(); double vGridStart = ((int) (topLineValue / vStepMultiplier)) * vStepMultiplier; double lValue = vGridStart; for(int i = 0; i < nbrHGrids; i++, lValue -= vStepMultiplier) { double y = vGridStart - i * vStepMultiplier; int pY = (int) (minY - vStep * (y - bottomLineValue)); if (pY >= size.height - BOTTOM_AXIS_MARGIN) { // nbrHGrids is not always calculated correctly, // that's why we need this check break; } // line g.setColor(Color.lightGray); g.drawLine(LEFT_GRAPH_MARGIN, pY, size.width - RIGHT_AXIS_MARGIN, pY); // label g.setColor(Color.black); String fmtVal = yFormat.format(lValue); g.drawString(fmtVal, 48 - fm.stringWidth(fmtVal), pY + 3); } // draw highThreshold if(!Double.isNaN(highThreshold)) { g.setColor(Color.red); int pY = (int) (minY - vStep * (highThreshold - bottomLineValue)); g.drawLine(LEFT_GRAPH_MARGIN, pY, size.width - RIGHT_AXIS_MARGIN, pY); g.drawLine(LEFT_GRAPH_MARGIN, pY + 1, size.width - RIGHT_AXIS_MARGIN, pY + 1); } // draw lowThreshold if(!Double.isNaN(lowThreshold)) { g.setColor(Color.orange); int pY = (int) (minY - vStep * (lowThreshold - bottomLineValue)); g.drawLine(LEFT_GRAPH_MARGIN, pY, size.width - RIGHT_AXIS_MARGIN, pY); g.drawLine(LEFT_GRAPH_MARGIN, pY + 1, size.width - RIGHT_AXIS_MARGIN, pY + 1); } // draw mean if (stdDev > 0) { g.setColor(avgColor); int pY = (int) (minY - vStep * (mean - bottomLineValue)); g.drawLine(LEFT_GRAPH_MARGIN, pY, size.width - RIGHT_AXIS_MARGIN, pY); g.drawString("mean", size.width - 30, pY + 2); } // draw deviations if (!Double.isNaN(stdDev) && stdDev > 0) { int uY = (int) (minY - vStep * ((mean + stdDev) - bottomLineValue)); int dY = (int) (minY - vStep * ((mean - stdDev) - bottomLineValue)); g.setColor(devColor); for(int x = LEFT_GRAPH_MARGIN, len = size.width - RIGHT_AXIS_MARGIN; x < len; x += 10) { g.drawLine(x, uY, x + 5, uY); g.drawLine(x, dY, x + 5, dY); } g.drawString("dev", size.width - 30, uY + 2); g.drawString("dev", size.width - 30, dY + 2); } // draw graph of Calculable values double hStep = drawWidth / rangeSize; double idxJump = 1; if(hStep == 0) { hStep = 1; idxJump = rangeSize / drawWidth; } double dataIdx =0; g.setColor(Color.blue); synchronized(plotted) { plotted.clear(); } Point lastPoint = null; Calculable lastCalc = null; // if their is only one point - draw it in a special way int len = getLength(); if (len == 1 && dataIdx < len) { lastCalc = data[(int)dataIdx]; double value = lastCalc.getValue(); double y = value / yScale; int pY = (int) (minY - vStep * (y - bottomLineValue)); lastPoint = new Point(LEFT_GRAPH_MARGIN - 2, pY - 1); g.fillOval(LEFT_GRAPH_MARGIN - 2, pY - 1, 3, 3); } // draw segments for(double pX = LEFT_GRAPH_MARGIN; dataIdx < len; dataIdx += idxJump, pX += hStep) { Calculable calc = data[(int)dataIdx]; double y = calc.getValue() / yScale; double pY = minY - vStep * (y - bottomLineValue); int index = (int)(dataIdx + idxJump); if(index >= len) break; if(pX >= (size.width - RIGHT_GRAPH_MARGIN)) break; Calculable nextCalc = data[index]; double nextY = nextCalc.getValue() / yScale; double pNextY = minY - vStep * (nextY - bottomLineValue); int startX = (int)pX; int startY = (int)(pY > minY ? minY : pY); synchronized(plotted) { plotted.add(new PlottedCalculable(new Point(startX, startY), calc)); } int endX = (int)(pX + hStep); int endY = (int)(pNextY > minY ? minY : pNextY); g.drawLine(startX, startY, endX, endY); lastCalc = nextCalc; lastPoint = new Point(endX, endY); } if(lastPoint!=null && lastCalc!=null) { synchronized(plotted) { plotted.add(new PlottedCalculable(lastPoint, lastCalc)); } } } catch(Exception e) { e.printStackTrace(); } } public PlottedCalculable getCalcForPoint(Point point) { PlottedCalculable calc = null; synchronized(plotted) { for(PlottedCalculable p : plotted) { if(p.getPoint().getX()==point.getX()) { calc = p; break; } } } return calc; } /** * @return Get the length property */ protected int getLength() { return (data == null ? 0 : data.length); } /** * @return The value for the top Y axis */ protected double getTopLineValue() { double topLineValue = mean + 3 * stdDev; topLineValue = Math.max(topLineValue, max); if(!Double.isNaN(highThreshold)) { topLineValue = Math.max(topLineValue, highThreshold); } return topLineValue; } /** * @return The value for the bottom Y axis */ protected double getBottomLineValue() { double bottomLineValue = mean - 3 * stdDev; bottomLineValue = Math.min(bottomLineValue, min); if(!Double.isNaN(lowThreshold)) { bottomLineValue = Math.min(bottomLineValue, lowThreshold); } return bottomLineValue; } }