/* * (C) Copyright IBM Corp. 2009 * * LICENSE: Eclipse Public License v1.0 * http://www.eclipse.org/legal/epl-v10.html */ package com.ibm.gaiandb.apps.dashboard; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Map.Entry; import java.awt.Color; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Insets; import javax.swing.JPanel; public class StatsChart extends JPanel { // Use PROPRIETARY notice if class contains a main() method, otherwise use COPYRIGHT notice. public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2009"; private static final long serialVersionUID = -5062918008888904844L; private static final int AXIS_MARGIN = 5; private static final Color BACKGROUND_COLOR = new Color(0xFFFFFF); private static final Color AXIS_COLOR = new Color(0xAAAAAA); private static final Color LINE_COLOR = new Color(0x000000); public final class Stat implements Comparable<Stat> { public final long timestamp; public final int value; public Stat(int timestamp_s, int value) { this((long)timestamp_s * 1000, value); } public Stat(long timestamp_ms, int value) { this.timestamp = timestamp_ms; this.value = value; } public boolean equals(Object o) { if (this == o) { return true; } if (o == null) { return false; } if (!(o instanceof Stat)) { return false; } Stat other = (Stat)o; if (timestamp != other.timestamp) { return false; } return true; } public int hashCode() { return (int)(timestamp ^ (timestamp >>> 32)); } public int compareTo(Stat o) { if (null == o) { throw new NullPointerException(); } if (timestamp < o.timestamp) { return -1; } else if (timestamp > o.timestamp) { return 1; } return 0; } public String toString() { return "( " + timestamp + ", " + value + " )"; } } public class ValueRange { public final int min; public final int max; public ValueRange(int min, int max) { if (min < max) { this.min = min; this.max = max; } else if (min > max) { throw new IllegalArgumentException("Min cannot be greater than max."); } else { throw new IllegalArgumentException("Min cannot be equal to max."); } } } private String title; public String getTitle() { return title; } public void setTitle(String title) { if (null == title) { this.title = ""; } else { this.title = title; } } private long duration; public long getDuration() { return duration; } public void setDuration(int duration) { setDuration((long)duration * 1000); } public void setDuration(long duration) { if (duration <= 0) { throw new IllegalArgumentException(String.valueOf(duration)); } this.duration = duration; } private ValueRange valueRange; public ValueRange getValueRange() { return valueRange; } public void setValueRange(ValueRange valueRange) { this.valueRange = valueRange; } public void setValueRange(int min, int max) { if (min != max) { setValueRange(new ValueRange(min, max)); } else { setValueRange(null); } } private Insets insets = new Insets(0, 0, 0, 0); public Insets getInsets() { return insets; } public void setInsets(Insets insets) { this.insets = insets; } public void setInsets(int size) { this.insets = new Insets(size, size, size, size); } public void setInsets(int top, int left, int bottom, int right) { this.insets = new Insets(top, left, bottom, right); } private Map<String, List<Stat>> stats = new HashMap<String, List<Stat>>(); private Long lastUpdateTime = 0L; private Map<String, Color> nodeColors = null; public StatsChart(int duration) { super(); setDuration(duration); } public StatsChart(long duration) { super(); setDuration(duration); } public StatsChart(int duration, Map<String, Color> nodeColors) { this(duration); this.nodeColors = nodeColors; } public StatsChart(long duration, Map<String, Color> nodeColors) { this(duration); this.nodeColors = nodeColors; } public boolean addStat(String node, int timestamp_s, int value) { return addStat(node, new Stat(timestamp_s, value)); } public boolean addStat(String node, long timestamp_ms, int value) { return addStat(node, new Stat(timestamp_ms, value)); } public boolean addStat(String node, Stat s) { synchronized (lastUpdateTime) { List<Stat> nodeStats = stats.get(node); if (null == nodeStats) { nodeStats = new LinkedList<Stat>(); stats.put(node, nodeStats); } // Insert in order. ListIterator<Stat> iterator = nodeStats.listIterator(nodeStats.size()); while (iterator.hasPrevious()) { int compare = iterator.previous().compareTo(s); if (compare == 0) { return false; } else if (compare >= 0) { // Whoa, too far. iterator.next(); break; } } iterator.add(s); lastUpdateTime = System.currentTimeMillis(); lastUpdateTime -= lastUpdateTime % 1000; } return true; } public void removeOldStats() { synchronized (lastUpdateTime) { for (List<Stat> nodeStats : stats.values()) { ListIterator<Stat> iterator = nodeStats.listIterator(); while (iterator.hasNext()) { if (lastUpdateTime - iterator.next().timestamp > duration) { iterator.remove(); } } } } } public int statCount() { int total = 0; for (List<Stat> nodeStats : stats.values()) { total += nodeStats.size(); } return total; } public void paintComponent(Graphics g) { removeOldStats(); super.paintComponent(g); synchronized (lastUpdateTime) { Color originalColor = g.getColor(); // Wipe it out. g.setColor(BACKGROUND_COLOR); g.fillRect(0, 0, getWidth(), getHeight()); // Calculate the bounds. int left = insets.left; int top = insets.top; int width = getWidth() - insets.left - insets.right; int height = getHeight() - insets.top - insets.bottom; // Get the minimum and maximum values. int min; int max; if (null != valueRange) { min = valueRange.min; max = valueRange.max; } else { min = Integer.MAX_VALUE; max = Integer.MIN_VALUE; for (List<Stat> nodeStats : stats.values()) { for (Stat stat : nodeStats) { if (stat.value < min) { min = stat.value; } if (stat.value > max) { max = stat.value; } } } } // Draw the axes. FontMetrics fontMetrics = g.getFontMetrics(); int fontHeight = (int)fontMetrics.getLineMetrics("0123456789", g).getAscent(); String minValue = String.valueOf(min); String maxValue = String.valueOf(max); // Figure out the widths of each value so we can right-align them. int minValueWidth = (int)fontMetrics.getStringBounds(minValue, 0, minValue.length(), g).getWidth(); int maxValueWidth = (int)fontMetrics.getStringBounds(maxValue, 0, maxValue.length(), g).getWidth(); int axisWidth = (int)fontMetrics.getStringBounds("00000", g).getWidth() + AXIS_MARGIN; g.setColor(AXIS_COLOR); if (stats.size() > 0) { g.drawString(maxValue, left + axisWidth - AXIS_MARGIN - maxValueWidth, top + fontHeight); g.drawString(minValue, left + axisWidth - AXIS_MARGIN - minValueWidth, top + height); } g.drawLine( left + axisWidth, top, left + axisWidth, top + height); g.drawLine( left + axisWidth, top + height - fontHeight / 2, left + width, top + height - fontHeight / 2); left += axisWidth; width -= axisWidth; top += fontHeight / 2; height -= fontHeight; // Draw the lines. g.setColor(LINE_COLOR); for (Entry<String, List<Stat>> entry : stats.entrySet()) { String node = entry.getKey(); if (null == nodeColors || nodeColors.containsKey(node)) { if (null != nodeColors) { g.setColor(nodeColors.get(node)); } List<Stat> nodeStats = entry.getValue(); boolean first = true; int lastX = 0; int lastY = 0; for (Stat stat : nodeStats) { int x = (int)((stat.timestamp - lastUpdateTime + duration) * width / duration) + left; int y; if (min == max) { y = height / 2 + top; } else { y = height - ((stat.value - min) * height / (max - min)) + top; } if (first) { g.drawLine(x, y, x, y); first = false; } else { g.drawLine(lastX, lastY, x, y); } lastX = x; lastY = y; } } } g.setColor(originalColor); } } }