/* * NOTE: This copyright does *not* cover user programs that use HQ * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2004, 2005, 2006], Hyperic, Inc. * This file is part of HQ. * * HQ is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. This program is distributed * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ package org.hyperic.image.chart; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.util.Collection; import java.util.Iterator; import org.hyperic.util.data.IDisplayDataPoint; import org.hyperic.util.data.IEventPoint; import org.hyperic.util.units.FormattedNumber; import org.hyperic.util.units.UnitsConstants; import org.hyperic.util.units.UnitsFormat; public class VerticalChart extends Chart { protected static final Color DEFAULT_COLOR = new Color(0x00, 0x00, 0xFF); protected static final Color GOOD_COLOR // Green = new Color(0x48, 0xB3, 0x68); protected static final Color DANGER_COLOR // Red = new Color(0xD5, 0x3E, 0x3E); protected static final Color UNKNOWN_COLOR // Grey = new Color(0x00, 0x00, 0xCC); private Rectangle m_rect; private long m_timeScale; private int m_cumulativeTrend = Trend.TREND_NONE; public VerticalChart() { super(); init(); } public int getCumulativeTrend() { return m_cumulativeTrend; } public void setCumulativeTrend(int trend) { if(trend < Trend.TREND_NONE || trend > Trend.TREND_UP) throw new IllegalArgumentException( "Argument must be a Cumulative type."); m_cumulativeTrend = trend; } protected VerticalChart(int width, int height) { super(width, height); init(); } protected VerticalChart(int charts) { super(charts); init(); } protected VerticalChart(int width, int height,int charts) { super(width, height, charts); init(); } protected void init() { this.showAverage = true; this.showValueLines = true; this.showLow = true; this.showPeak = true; } protected Collection initData(Collection coll) { if( this.m_fmtType == UnitsConstants.UNIT_PERCENTAGE && (this.m_dLowValue >= 0 && this.m_dPeakValue <= 1) ) { this.floor = 0; this.ceiling = 1; } return coll; } protected Point adjustBorders(Point pt) { if(pt != null) { // Adjust to add the left and top margins to put in the interior rectangle pt.x += m_rect.x; pt.y += m_rect.y; } return pt; } protected Rectangle adjustRectangle(Graphics2D g, Rectangle rect) { int cDataPts = this.getDataPoints().size(); int spread = this.getUnitSpread(g, rect); rect.width = (spread * (cDataPts - 1)) + (this.valueIndent * 2) + this.lineWidth; this.m_rect = rect; return rect; } protected Rectangle getInteriorRectangle(ChartGraphics g) { return m_rect; } protected String[] getXLabels() { DataPointCollection coll = this.getDataPoints(); int collSize = coll.size(); String[] result = new String[collSize]; for (int i = 0; i < collSize; i++) { IDisplayDataPoint dp = (IDisplayDataPoint) coll.get(i); result[i] = ScaleFormatter.formatTime(dp.getTimestamp(), this.m_timeScale, collSize); } return result; } protected int[] getXPoints(ChartGraphics g, Rectangle rect) { DataPointCollection coll = this.getDataPoints(); int collSize = coll.size(); int[] res = new int[collSize]; int spread = this.getUnitSpread(g.graphics, this.getInteriorRectangle(g)); int xHorzMarks = rect.x + this.valueIndent; for (int i = 0, x = xHorzMarks; i < collSize; i++, x += spread) res[i] = x; return res; } private int getUnitSpread(Graphics2D g, Rectangle rect) { int cDataPts = this.getDataPoints().size(); int iSpread = rect.width - (this.valueIndent * 2); return (cDataPts > 1) ? (iSpread / (cDataPts - 1)) : iSpread; } protected int getYLabelWidth(Graphics2D g) { FormattedNumber[] fmtValueLabels = UnitsFormat.formatSame(m_adRangeMarks, m_fmtType, m_fmtScale); int width; int maxWidth = 0; int cnt = fmtValueLabels.length; for(int i = 1;i < cnt;i++) { width = this.m_metricsLabel.stringWidth( fmtValueLabels[i].toString() ); if(width > maxWidth) maxWidth = width; } return maxWidth; } protected Rectangle draw(ChartGraphics g) { /////////////////////////////// // Paint the chart background Rectangle rect = super.draw(g); if(this.hasData() == false) return rect; /////////////////////////////////////// // Paint the chart exterior and lines Graphics2D graph = g.graphics; // Calculate points double dScale = this.scale(rect.height); int lineWidth = this.lineWidth; int x2 = rect.x + rect.width; int y2 = rect.y + rect.height; int yAvgLine = y2 - (int)Math.round( (this.getAverageValue() - this.m_floor) * dScale); int yLowLine = y2 - (int)Math.round( (this.getLowValue() - this.m_floor) * dScale); int yBaseLine = y2 - (int)Math.round( (this.baseline - this.m_floor) * dScale); int yPeakLine = y2 - (int)Math.round( (this.getPeakValue() - this.m_floor) * dScale); int yHighBottom = 0; if(Double.isNaN(this.highRange) == false) yHighBottom = Math.min(y2 - lineWidth, y2 - (int)Math.round( (this.highRange - this.m_floor) * dScale)); yHighBottom = Math.max(yHighBottom, rect.y); int yLowTop = 0; if(Double.isNaN(this.lowRange) == false) yLowTop = Math.max(rect.y + lineWidth, y2 - (int)Math.round( (this.lowRange - this.m_floor) * dScale)); yLowTop = Math.min(yLowTop, y2); int xAvgLabel = x2 - m_metricsLabel.stringWidth(Chart.AVG) - 3; int yAvgLabel = yAvgLine - 3; Rectangle avgLabel = new Rectangle(); if(this.showAverage) avgLabel.setRect(xAvgLabel, yAvgLabel, m_metricsLabel.stringWidth(Chart.AVG), m_metricsLabel.getHeight()); int xBaselineLabel = x2 - m_metricsLabel.stringWidth(Chart.BASELINE) - 4; int yBaselineLabel = yBaseLine - 3; Rectangle baselineLabel = new Rectangle(); if(this.showBaseline) baselineLabel.setRect(xBaselineLabel, yBaselineLabel, m_metricsLabel.stringWidth(Chart.BASELINE), m_metricsLabel.getHeight()); int xLowLabel = x2 - m_metricsLabel.stringWidth(Chart.LOW) - 4; int yLowLabel = yLowLine - 3; Rectangle lowLabel = new Rectangle(); if(this.showLow) lowLabel.setRect(xLowLabel, yLowLabel, m_metricsLabel.stringWidth(Chart.LOW), m_metricsLabel.getHeight()); int xPeakLabel = x2 - m_metricsLabel.stringWidth(Chart.PEAK) - 4; int yPeakLabel = yPeakLine - 3; Rectangle peakLabel = new Rectangle(); if(this.showPeak) peakLabel.setRect(xPeakLabel, yPeakLabel, m_metricsLabel.stringWidth(Chart.PEAK), m_metricsLabel.getHeight()); if(this.showTopLegend) g.drawYLegendString(this.getValueLegend()); ////////////////////////////////////////////////////////// // Draw the value (Y) axis cross lines and labels FormattedNumber[] fmtValueLabels = UnitsFormat.formatSame(m_adRangeMarks, m_fmtType, m_fmtScale); String[] labels = new String[this.m_adRangeMarks.length]; int[] lines = new int[this.m_adRangeMarks.length]; for(int i = 0;i < lines.length;i ++) { lines[i] = rect.y + (int)Math.round( (this.m_adRangeMarks[i] - this.m_floor) * dScale ); labels[i] = fmtValueLabels[i].toString(); } g.drawXLines(lines, labels, true); ////////////////////////////////////////////////////////// // Draw the high range and low range boolean bHighLow = false; int cxGuide = lineWidth * 3; int xGuide = rect.x - cxGuide; if(this.showHighRange && Double.isNaN(this.highRange) == false && (yHighBottom > (rect.y + lineWidth))) { graph.setColor(this.highRangeColor); graph.fillRect(rect.x + lineWidth, rect.y + lineWidth, rect.width - lineWidth, yHighBottom - rect.y); graph.setColor(DANGER_COLOR); graph.fillRect(xGuide, rect.y + lineWidth, cxGuide, yHighBottom - rect.y); bHighLow = true; } if(this.showLowRange && Double.isNaN(this.lowRange) == false && (yLowTop < (y2 - lineWidth))) { graph.setColor(this.lowRangeColor); graph.fillRect(rect.x + lineWidth, yLowTop, rect.width - lineWidth, y2 - yLowTop); graph.setColor(DANGER_COLOR); graph.fillRect(xGuide, yLowTop, cxGuide, y2 - yLowTop); bHighLow = true; } if(bHighLow) { if(this.showHighRange == false) yHighBottom = rect.y + lineWidth; else { yHighBottom ++; if(this.showLowRange == false) yLowTop = y2; } graph.setColor(GOOD_COLOR); graph.fillRect(xGuide, yHighBottom, cxGuide, yLowTop - yHighBottom); } ////////////////////////////////////////////////////////// // Draw the unit (X) axis tick marks and labels lines = this.getXPoints(g, rect); g.drawYLines(lines, this.getXLabels(), false, xLabelsSkip); ////////////////////////////////////////////////////////// // Draw Events if (this.showEvents) { if(this.getDataSetCount() == 1) { EventPointCollection collEvts = this.getEventPoints(); if(collEvts.size() > 0){ int[] evtDataPts = getDataPointEventIndexes(0); g.graphics.setColor(DEFAULT_COLOR); for(int i = 0;i < evtDataPts.length;i ++) { if(evtDataPts[i] == -1) continue; IEventPoint evt = (IEventPoint)collEvts.get(i); g.drawEvent(evt.getEventID(), lines[ evtDataPts[i] ], y2 + ChartGraphics.HALF_EVENT_HEIGHT + this.lineWidth); } } } } //////////////////////////////////////////////////////////// // Draw the Bottom Legend if(this.showBottomLegend) g.drawXLegendString(this.getUnitLegend()); ////////////////////////////////////////////////////////// // Draw the Peak, Avg and Low Lines graph.setFont(this.font); int xLast = 0; if(this.showLow) { graph.setColor(this.lowLineColor); graph.drawLine(this.xVertMarks, yLowLine, this.x2VertMarks, yLowLine); graph.drawString(Chart.LOW, xLowLabel, yLowLabel); xLast = xLowLabel; } if(this.showAverage) { if(avgLabel.intersects(lowLabel)) xAvgLabel = xLast - this.m_metricsLabel.stringWidth(Chart.AVG) - this.m_metricsLabel.charWidth('W'); graph.setColor(this.averageLineColor); graph.drawLine(xVertMarks, yAvgLine, x2VertMarks, yAvgLine); graph.drawString(Chart.AVG, xAvgLabel, yAvgLabel); xLast = Math.min(xLast, xAvgLabel); } if(this.showPeak) { if(peakLabel.intersects(lowLabel) || peakLabel.intersects(avgLabel)) xPeakLabel = xLast - this.m_metricsLabel.stringWidth(Chart.PEAK) - this.m_metricsLabel.charWidth('W'); graph.setColor(this.peakLineColor); graph.drawLine(xVertMarks, yPeakLine, x2VertMarks, yPeakLine); graph.drawString(Chart.PEAK, xPeakLabel, yPeakLabel); xLast = Math.min(xLast, xPeakLabel); } if(this.showBaseline && yBaseLine > rect.y && yBaseLine < y2) { if(baselineLabel.intersects(lowLabel) || baselineLabel.intersects(avgLabel) || baselineLabel.intersects(peakLabel)) xBaselineLabel = xLast - this.m_metricsLabel.stringWidth(Chart.BASELINE) - this.m_metricsLabel.charWidth('W'); graph.setColor(this.baselineColor); graph.drawLine(xVertMarks, yBaseLine, x2VertMarks, yBaseLine); graph.drawString(Chart.BASELINE, xBaselineLabel, yBaselineLabel); } /////////////////////////////////////////////////////////// // Paint the chart interior if(this.showValues) this.paint(g, rect); return rect; } protected void paint(ChartGraphics graph, Rectangle rect) { // Subclasses will take care of the painting } protected int[] getDataPointEventIndexes(int dataSetNumber) { DataPointCollection datapts = this.getDataPoints(dataSetNumber); EventPointCollection collEvts = this.getEventPoints(dataSetNumber); Iterator iterEvts = collEvts.iterator(); int[] tmp = new int[collEvts.size()]; int cActual = 0; for(int i = 0;iterEvts.hasNext();i++) { IEventPoint evt = (IEventPoint)iterEvts.next(); int index = this.findDataPointIndex(evt.getTimestamp(), datapts); // if(index == -1) // continue; tmp[i] = index; cActual ++; } /* if(cActual == 0) return null; */ // Compact the array and return it int[] res = new int[cActual]; for(int i = 0;i < res.length;i++) res[i] = tmp[i]; return res; } protected Point getDataPoint(Rectangle rect, int datapoint) { return this.getDataPoint(rect, datapoint, this.getDataPoints()); } protected Point getDataPoint(Rectangle rect, int datapoint, DataPointCollection coll) { Point ptResult = super.getDataPoint(rect.height, rect.width, datapoint, coll); if (ptResult != null) this.adjustBorders(ptResult); return ptResult; } protected void setTimeScale(long scale) { this.m_timeScale = scale; } protected int findDataPointIndex(long timestamp, DataPointCollection coll) { int collSize = coll.size(); if(collSize == 0) return -1; long first = ((IDisplayDataPoint)coll.get(0)).getTimestamp(); if(collSize == 1) return ( (first == timestamp) ? 0 : -1 ); long second = ((IDisplayDataPoint)coll.get(1)).getTimestamp(); long interval = second - first; long prev = first - interval; int index; for (index = 0; index < collSize; index++) { IDisplayDataPoint datapt = (IDisplayDataPoint)coll.get(index); // Break if we find what we're looking for if(timestamp > prev && timestamp <= datapt.getTimestamp()) break; // Prepare for the next loop prev = datapt.getTimestamp(); } // Return a index for an actual point return ( (index == collSize) ? -1 : index ); } }