/*
* Lokomo OneCMDB - An Open Source Software for Configuration
* Management of Datacenter Resources
*
* Copyright (C) 2006 Lokomo Systems AB
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* Lokomo Systems AB can be contacted via e-mail: info@lokomo.com or via
* paper mail: Lokomo Systems AB, Sv�rdv�gen 27, SE-182 33
* Danderyd, Sweden.
*
*/
package org.onecmdb.web.graphs;
import java.awt.BasicStroke;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RectangularShape;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import javax.swing.JComponent;
import javax.swing.JFrame;
import org.onecmdb.core.internal.model.ItemId;
import org.onecmdb.web.Area;
import org.onecmdb.web.ImageMap;
import prefuse.Constants;
import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.ItemAction;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.DataColorAction;
import prefuse.action.assignment.FontAction;
import prefuse.action.assignment.StrokeAction;
import prefuse.action.layout.Layout;
import prefuse.action.layout.graph.*;
import prefuse.activity.Activity;
import prefuse.activity.ActivityAdapter;
import prefuse.data.Graph;
import prefuse.data.Schema;
import prefuse.data.io.DataIOException;
import prefuse.data.io.GraphMLReader;
import prefuse.data.tuple.TupleSet;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.EdgeRenderer;
import prefuse.render.LabelRenderer;
import prefuse.render.Renderer;
import prefuse.util.ColorLib;
import prefuse.util.GraphicsLib;
import prefuse.util.PrefuseLib;
import prefuse.util.display.ItemBoundsListener;
import prefuse.visual.DecoratorItem;
import prefuse.visual.EdgeItem;
import prefuse.visual.VisualItem;
import prefuse.visual.VisualTable;
import prefuse.visual.expression.InGroupPredicate;
public class PrefuseRenderer implements GraphRenderer {
public static final String GRAPH = "graph";
public static final String NODES = "graph.nodes";
public static final String EDGES = "graph.edges";
public static final String EDGE_DECORATORS = "edgeDeco";
private static final Schema DECORATOR_SCHEMA = PrefuseLib.getVisualItemSchema();
static {
DECORATOR_SCHEMA.setDefault(VisualItem.INTERACTIVE, false);
DECORATOR_SCHEMA.setDefault(VisualItem.TEXTCOLOR, ColorLib.gray(128));
}
/* (non-Javadoc)
* @see org.onecmdb.web.graphs.GraphRenderer#render(java.io.InputStream, java.io.OutputStream)
*/
public void render(final InputStream graphML,
final OutputStream output, final String format,
final ImageMap imagemap) throws IOException {
final Graph g;
try {
g = new GraphMLReader().readGraph(graphML);
} catch (DataIOException e1) {
e1.printStackTrace();
throw new IOException("Cannot parse input:" + e1.getMessage());
}
// add the graph to the visualization as the data group "graph"
// nodes and edges are accessible as "graph.nodes" and "graph.edges"
Visualization vis = new Visualization();
vis.add(GRAPH, g);
// draw the "label" label for NodeItems
final Renderer nodeR; {
LabelRenderer r = new LabelRenderer("label");
r.setRoundedCorner(4, 4); // round the corners
r.setVerticalPadding(6);
r.setHorizontalPadding(6);
nodeR = r;
}
final Renderer edgeR = new EdgeRenderer(Constants.EDGE_TYPE_CURVE,
Constants.EDGE_ARROW_FORWARD);
// create a new default renderer factory
// return our name label renderer as the default for all non-EdgeItems
// includes straight line edges for EdgeItems by default
DefaultRendererFactory drf = new DefaultRendererFactory(nodeR, edgeR);
drf.add(new InGroupPredicate(EDGE_DECORATORS), new LabelRenderer("label") );
vis.setRendererFactory(drf);
// adding decorators, one for the edges
vis.addDecorators(EDGE_DECORATORS, EDGES, DECORATOR_SCHEMA);
int[] palette = ColorLib.getGrayscalePalette(256);
palette[0] = ColorLib.rgb(0xFF,0xA5,0x00);
// map nominal data values to colors using our provided palette
DataColorAction fill = new DataColorAction("graph.nodes", "distance",
Constants.NOMINAL, VisualItem.FILLCOLOR, palette);
ItemAction outline = new StrokeAction("graph.nodes", new BasicStroke(2));
ItemAction outlineColor = new ColorAction("graph.nodes",
VisualItem.STROKECOLOR, ColorLib.gray(248));
ColorAction text = new ColorAction("graph.nodes",
VisualItem.TEXTCOLOR, ColorLib.gray(255));
ColorAction edges = new ColorAction("graph.edges",
EdgeItem.STROKECOLOR, ColorLib.gray(100));
ColorAction arrows = new ColorAction("graph.edges",
EdgeItem.FILLCOLOR, ColorLib.gray(100));
//FontAction font = new FontAction("graph.nodes", new Font("Verdana", 0, 8));
// create an action list containing all color assignments
ActionList color = new ActionList();
color.add(outline);
color.add(outlineColor);
color.add(fill);
color.add(text);
color.add(edges);
color.add(arrows);
//color.add(font);
// create an action list with an animated layout
// the INFINITY parameter tells the action list to run indefinitely
ActionList layout = new ActionList(vis);
Layout alg = new FruchtermanReingoldLayout("graph", (int) (250 /* + Math.sqrt( (double) g.getNodeCount() + 1) */) );
layout.add(alg);
layout.add(new LabelLayout2(EDGE_DECORATORS));
//layout.add(new RepaintAction());
// add the actions to the visualization
vis.putAction("color", color);
vis.putAction("layout", layout);
// create a new Display that pull from our Visualization
Display display = new Display(vis);
// display.setOpaque(false);
// display.setBackground(ColorLib.getColor(255, 255, 255, 255));
int w = Math.min(g.getNodeCount() * 120 + 80, 720);
int h = Math.min(g.getNodeCount() * 60 + 40, 500);
display.setSize(w, h); // set display size
// used to make sure all actions have run before trying to gnerate
// a picture.
final CountDownLatch mutex = new CountDownLatch(2);
class MutexData {
boolean doneLayout = false;
};
ActivityAdapter listener = new ActivityAdapter() {
@Override
public void activityScheduled(Activity a) {
super.activityScheduled(a);
}
@Override
public void activityStarted(Activity a) {
super.activityStarted(a);
}
@Override
public void activityFinished(Activity a) {
super.activityFinished(a);
mutex.countDown();
}
};
layout.addActivityListener(listener);
color.addActivityListener(listener);
{
vis.run("color");
vis.run("layout");
try {
mutex.await();
} catch (InterruptedException e) { }
}
layout.removeActivityListener(listener);
color.removeActivityListener(listener);
listener = null;
display.saveImage(output, format, 1);
{ // TODO: use a separate thread for this (to save some cycles)
VisualTable nodes = (VisualTable) display.getVisualization().getGroup("graph.nodes");
Object cookie = imagemap.startFilling();
for (int r=0,rows=nodes.getRowCount(); r<rows; r++) {
Rectangle2D rect = nodes.getBounds(r);
Rectangle b = rect.getBounds();
String s = nodes.getString(r, "label");
ItemId ciid = new ItemId(nodes.getString(r, "id"));
imagemap.addArea(new Area(ciid, b));
}
imagemap.stopFilling(cookie);
display.setVisualization(null);
g.dispose();
display = null;
}
}
/** displays a graph using swing */
public void display(JComponent display) {
// create a new window to hold the visualization
JFrame frame = new JFrame("prefuse example");
// ensure application exits when window is closed
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(display);
frame.pack(); // layout components in window
frame.setVisible(true); // show the window
// display.addControlListener(new DragControl()); // drag items around
// display.addControlListener(new PanControl()); // pan with background left-drag
// display.addControlListener(new ZoomControl()); // zoom with vertical right-drag
}
/**
* Set label positions. Labels are assumed to be DecoratorItem instances,
* decorating their respective nodes. The layout simply gets the bounds
* of the decorated node and assigns the label coordinates to the center
* of those bounds.
*/
class LabelLayout2 extends Layout {
public LabelLayout2(String group) {
super(group);
}
public void run(double frac) {
Iterator iter = m_vis.items(m_group);
while ( iter.hasNext() ) {
DecoratorItem item = (DecoratorItem)iter.next();
VisualItem node = item.getDecoratedItem();
Rectangle2D bounds = node.getBounds();
setX(item, null, bounds.getCenterX());
setY(item, null, bounds.getCenterY());
}
}
} // end of inner class LabelLayout
}