/*
* (C) Copyright IBM Corp. 2009
*
* LICENSE: Eclipse Public License v1.0
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.ibm.gaiandb.draw;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.awt.Insets;
import java.awt.Shape;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import javax.swing.BorderFactory;
import prefuse.Constants;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.assignment.ColorAction;
import prefuse.action.layout.AxisLabelLayout;
import prefuse.action.layout.AxisLayout;
import prefuse.data.expression.parser.ExpressionParser;
import prefuse.data.query.RangeQueryBinding;
import prefuse.render.AbstractShapeRenderer;
import prefuse.render.AxisRenderer;
import prefuse.render.Renderer;
import prefuse.render.RendererFactory;
import prefuse.util.collections.IntIterator;
import prefuse.visual.VisualItem;
import prefuse.visual.expression.VisiblePredicate;
public class TimeChart extends Chart {
// Use PROPRIETARY notice if class contains a main() method, otherwise use COPYRIGHT notice.
public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2009";
public enum ExtrapolationStyle {
FIRST,
AVERAGE,
LAST;
public static ExtrapolationStyle parse(String name) {
try {
return valueOf(name.toUpperCase());
}
catch (IllegalArgumentException e) {
return null;
}
}
}
private class Row implements Cloneable {
public String node;
public int value;
public long received;
public Row(String node, int value, long received) {
this.node = node;
this.value = value;
this.received = received;
}
public Row(int row) {
this.node = data.getString(row, NODE_NAME);
this.value = data.getInt(row, Y_AXIS);
this.received = data.getLong(row, X_AXIS);
}
public Row clone() {
return new Row(node, value, received);
}
public int addToTable() {
int rowId = data.addRow();
data.setLong(rowId, X_AXIS, received);
data.setInt(rowId, Y_AXIS, value);
data.setString(rowId, NODE_NAME, node);
return rowId;
}
}
private static final long serialVersionUID = -8625439444277732390L;
public static final String X_AXIS = "RECEIVED";
public static final String Y_AXIS = "VALUE";
public static final String NODE_NAME = "NODE";
private static final int axisWidth = 20;
private static final int axisHeight = 10;
private AxisLayout xAxis;
private AxisLayout yAxis;
private Rectangle2D chartBounds = new Rectangle2D.Double();
private Rectangle2D xAxisBounds = new Rectangle2D.Double();
private Rectangle2D yAxisBounds = new Rectangle2D.Double();
private RangeQueryBinding xAxisRange;
private RangeQueryBinding yAxisRange;
private int minValue = 0;
public int getMinValue() { return minValue; }
public void setMinValue(int minValue) { this.minValue = minValue; }
private int maxValue = 0;
public int getMaxValue() { return maxValue; }
public void setMaxValue(int maxValue) { this.maxValue = maxValue; }
private PreparedStatement previousNodesStatement = null;
public PreparedStatement getPreviousNodesStatement() {
return previousNodesStatement;
}
public void setPreviousNodesStatement(PreparedStatement previousNodesStatement) {
this.previousNodesStatement = previousNodesStatement;
}
private ExtrapolationStyle extrapolation = ExtrapolationStyle.AVERAGE;
public ExtrapolationStyle getExtrapolation() { return extrapolation; }
public void setExtrapolation(ExtrapolationStyle extrapolation) {
this.extrapolation = extrapolation;
}
public void setExtrapolation(String extrapolation) {
ExtrapolationStyle eS = ExtrapolationStyle.parse(extrapolation);
if (null != eS) {
this.extrapolation = eS;
}
}
private HashMap<String, HashSet<VisualItem>> chartPoints =
new HashMap<String, HashSet<VisualItem>>();
public TimeChart(PreparedStatement statement, Object... params) throws SQLException {
super(statement, params);
keys.add(X_AXIS);
keys.add(NODE_NAME);
setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
}
protected void afterUpdate(Object[] params) throws SQLException {
if (ExtrapolationStyle.AVERAGE == extrapolation) {
return;
}
if (null == previousNodesStatement) {
return;
}
TableUtil.bindToPreparedStatement(previousNodesStatement, params);
TableUtil.addResultSetToTable(previousNodesStatement, data, handler, keys);
if (0 == data.getRowCount()) {
return;
}
Hashtable<String, Row> nodeLastRow = new Hashtable<String, Row>();
List<Row> newRows = new ArrayList<Row>();
IntIterator i = data.rowsSortedBy(X_AXIS, true);
Row row = new Row(i.nextInt());
nodeLastRow.put(row.node, row);
while (i.hasNext()) {
Row nextRow = new Row(i.nextInt());
addMissingRows(newRows, row, nextRow);
row = nextRow;
nodeLastRow.put(row.node, row);
}
if (ExtrapolationStyle.FIRST == extrapolation) {
for (String node : nodeLastRow.keySet()) {
Row secondToLast = nodeLastRow.get(node);
Row last = secondToLast.clone();
last.received = System.currentTimeMillis() / 1000;
addMissingRows(newRows, secondToLast, last);
}
}
for (Row newRow : newRows) {
newRow.addToTable();
}
}
private void addMissingRows(List<Row> rows, Row start, Row end) {
long now = System.currentTimeMillis() / 1000;
long startTime = Math.max(start.received, now - 60) + 1;
long endTime = Math.min(end.received, now) - 1;
String node;
int value;
if (ExtrapolationStyle.FIRST == extrapolation) {
node = start.node;
value = start.value;
}
else if (ExtrapolationStyle.LAST == extrapolation) {
node = end.node;
value = end.value;
}
else {
return;
}
for (long i = startTime; i <= endTime; i++) {
rows.add(new Row(node, value, i));
}
}
protected void prepareVis() {
vt = m_vis.addTable(GROUP, data);
m_vis.setRendererFactory(new RendererFactory() {
private Renderer yAxisRenderer = new AxisRenderer(Constants.LEFT, Constants.TOP);
private Renderer lineRenderer = new AbstractShapeRenderer() {
// Return a line from the last point to this point.
protected Shape getRawShape(VisualItem item) {
// Get the list of points on the same node.
String node = item.getString(NODE_NAME);
HashSet<VisualItem> nodeItems;
synchronized (chartPoints) {
nodeItems = chartPoints.get(node);
if (null == nodeItems) {
nodeItems = new HashSet<VisualItem>();
chartPoints.put(node, nodeItems);
}
}
// Find the latest point prior to this one.
VisualItem lastItem = null;
for (VisualItem currentItem : nodeItems) {
if ((null == lastItem ||
lastItem.getLong(X_AXIS) < currentItem.getLong(X_AXIS)) &&
currentItem.getLong(X_AXIS) < item.getLong(X_AXIS)) {
lastItem = currentItem;
}
}
nodeItems.add(item);
if (null != lastItem) {
return new Line2D.Double(
lastItem.getX(), lastItem.getY(),
item.getX(), item.getY()
);
}
else {
return new Line2D.Double(
item.getX(), item.getY(),
item.getX(), item.getY()
);
}
}
};
public Renderer getRenderer(VisualItem item) {
return
item.isInGroup("yAxis") ? yAxisRenderer :
lineRenderer;
}
});
// Define the axis regions.
xAxis = new AxisLayout(
GROUP, X_AXIS, Constants.X_AXIS, VisiblePredicate.TRUE);
yAxis = new AxisLayout(
GROUP, Y_AXIS, Constants.Y_AXIS, VisiblePredicate.TRUE);
xAxis.setLayoutBounds(chartBounds);
yAxis.setLayoutBounds(chartBounds);
xAxisRange = new RangeQueryBinding(data, X_AXIS);
xAxis.setRangeModel(xAxisRange.getModel());
AxisLabelLayout yAxisLabel = new AxisLabelLayout(
"yAxis", yAxis, yAxisBounds);
ActionList axes = new ActionList();
axes.add(xAxis);
axes.add(yAxis);
axes.add(yAxisLabel);
m_vis.putAction("axes", axes);
if (null == colorAction) {
colorAction = new ColorAction(GROUP, VisualItem.STROKECOLOR);
}
ActionList draw = new ActionList();
draw.add(colorAction);
draw.add(new RepaintAction());
m_vis.putAction("draw", draw);
// Reset the object boundaries whenever the window is resized.
addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
updateVisualization();
}
});
updateVis();
}
protected void updateVis() {
long latestTime = System.currentTimeMillis() / 1000;
if (data.getRowCount() > 0) {
// Update the axes to show the latest {maxDuration} seconds.
xAxisRange.getNumberModel().setValueRange(
latestTime - maxDuration, latestTime,
latestTime - maxDuration, latestTime);
}
// Remove old data from the table.
synchronized (data) {
String expression = latestTime + " - [" + X_AXIS + "] > " + maxDuration;
data.remove(ExpressionParser.predicate(expression));
}
// Remove old data from the list of nodes.
synchronized (chartPoints) {
for (String node : chartPoints.keySet()) {
HashSet<VisualItem> nodeItems = chartPoints.get(node);
Iterator<VisualItem> i = nodeItems.iterator();
while (i.hasNext()) {
VisualItem item = i.next();
try {
item.getInt(X_AXIS);
}
catch (IllegalStateException e) {
i.remove();
}
}
}
}
if (data.getRowCount() > 0) {
yAxisRange = new RangeQueryBinding(data, Y_AXIS);
yAxis.setRangeModel(yAxisRange.getModel());
// Set up the y axis min and max values.
if (minValue < maxValue) {
yAxisRange.getNumberModel().setValueRange(
minValue, maxValue, minValue, maxValue);
}
else {
yAxisRange.getNumberModel().setMinValue(minValue);
}
}
// Update the axis and chart boundaries.
Insets insets = getInsets();
int width = getWidth();
int height = getHeight();
int insetsWidth = insets.left + insets.right;
int insetsHeight = insets.top + insets.bottom;
chartBounds.setRect(
insets.left + axisWidth,
insets.top,
width - insetsWidth - axisWidth,
height - insetsHeight - axisHeight);
xAxisBounds.setRect(
insets.left + axisWidth,
height - insets.bottom - axisHeight,
width - insetsWidth - axisWidth,
axisHeight);
yAxisBounds.setRect(
insets.left,
insets.top,
width - insetsWidth,
height - insetsHeight - axisHeight);
m_vis.run("axes");
m_vis.run("draw");
}
}