/**
* Copyright 2015-2017 Linagora, Université Joseph Fourier, Floralis
*
* The present code is developed in the scope of the joint LINAGORA -
* Université Joseph Fourier - Floralis research program and is designated
* as a "Result" pursuant to the terms and conditions of the LINAGORA
* - Université Joseph Fourier - Floralis research program. Each copyright
* holder of Results enumerated here above fully & independently holds complete
* ownership of the complete Intellectual Property rights applicable to the whole
* of said Results, and may freely exploit it in any manner which does not infringe
* the moral rights of the other copyright holders.
*
* 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 net.roboconf.doc.generator.internal;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import org.apache.commons.collections15.Transformer;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.visualization.VisualizationImageServer;
import edu.uci.ics.jung.visualization.decorators.AbstractEdgeShapeTransformer;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
import edu.uci.ics.jung.visualization.renderers.DefaultVertexLabelRenderer;
import edu.uci.ics.jung.visualization.renderers.Renderer.VertexLabel.Position;
import net.roboconf.core.model.beans.AbstractType;
import net.roboconf.core.model.beans.Component;
import net.roboconf.core.utils.Utils;
import net.roboconf.doc.generator.DocConstants;
/**
* @author Vincent Zurczak - Linagora
*/
public final class GraphUtils {
public static final int SHAPE_HEIGHT = 80;
/**
* Private empty constructor.
*/
private GraphUtils() {
// nothing
}
/**
* Computes the width of a shape for a given component or facet.
* @param type a type
* @return the width it should take once displayed as a graph vertex
*/
public static int computeShapeWidth( AbstractType type ) {
Font font = GraphUtils.getDefaultFont();
BufferedImage img = new BufferedImage( 1, 1, BufferedImage.TYPE_INT_ARGB );
FontMetrics fm = img.getGraphics().getFontMetrics( font );
int width = fm.stringWidth( type.getName());
width = Math.max( width, 80 ) + 20;
return width;
}
/**
* @return the default font to use in graph diagrams
*/
public static Font getDefaultFont() {
return new Font( "Helvetica Neue", Font.BOLD, 15 );
}
/**
* Writes a graph as a PNG image.
* @param outputFile the output file
* @param selectedComponent the component to highlight
* @param layout the layout
* @param graph the graph
* @param edgeShapeTransformer the transformer for edge shapes (straight line, curved line, etc)
* @throws IOException if something went wrong
*/
public static void writeGraph(
File outputFile,
Component selectedComponent,
Layout<AbstractType,String> layout,
Graph<AbstractType,String> graph ,
AbstractEdgeShapeTransformer<AbstractType,String> edgeShapeTransformer,
Map<String,String> options )
throws IOException {
VisualizationImageServer<AbstractType,String> vis =
new VisualizationImageServer<AbstractType,String>( layout, layout.getSize());
vis.setBackground( Color.WHITE );
vis.getRenderContext().setEdgeLabelTransformer( new NoStringLabeller ());
vis.getRenderContext().setEdgeShapeTransformer( edgeShapeTransformer );
vis.getRenderContext().setVertexLabelTransformer( new ToStringLabeller<AbstractType> ());
vis.getRenderContext().setVertexShapeTransformer( new VertexShape());
vis.getRenderContext().setVertexFontTransformer( new VertexFont());
Color defaultBgColor = decode( options.get( DocConstants.OPTION_IMG_BACKGROUND_COLOR ), DocConstants.DEFAULT_BACKGROUND_COLOR );
Color highlightBgcolor = decode( options.get( DocConstants.OPTION_IMG_HIGHLIGHT_BG_COLOR ), DocConstants.DEFAULT_HIGHLIGHT_BG_COLOR );
vis.getRenderContext().setVertexFillPaintTransformer( new VertexColor( selectedComponent, defaultBgColor, highlightBgcolor ));
Color defaultFgColor = decode( options.get( DocConstants.OPTION_IMG_FOREGROUND_COLOR ), DocConstants.DEFAULT_FOREGROUND_COLOR );
vis.getRenderContext().setVertexLabelRenderer( new MyVertexLabelRenderer( selectedComponent, defaultFgColor ));
vis.getRenderer().getVertexLabelRenderer().setPosition( Position.CNTR );
BufferedImage image = (BufferedImage) vis.getImage(
new Point2D.Double(
layout.getSize().getWidth() / 2,
layout.getSize().getHeight() / 2),
new Dimension( layout.getSize()));
ImageIO.write( image, "png", outputFile );
}
/**
* Decodes an hexadecimal color and resolves it as an AWT color.
* @param value the value to decode
* @param defaultValue the default value to use if value is invalid
* @return a color (not null)
*/
static Color decode( String value, String defaultValue ) {
Color result;
try {
result = Color.decode( value );
} catch( NumberFormatException e ) {
Logger logger = Logger.getLogger( GraphUtils.class.getName());
logger.severe( "The specified color " + value + " could not be parsed. Back to default value: " + defaultValue );
Utils.logException( logger, e );
result = Color.decode( defaultValue );
}
return result;
}
/**
* @author Vincent Zurczak - Linagora
*/
static class VertexColor implements Transformer<AbstractType,Paint> {
private final Component selectedComponent;
private final Color defaultBgColor, highlightBgcolor;
public VertexColor( Component selectedComponent, Color defaultBgColor, Color highlightBgcolor ) {
this.selectedComponent = selectedComponent;
this.defaultBgColor = defaultBgColor;
this.highlightBgcolor = highlightBgcolor;
}
@Override
public Paint transform( AbstractType type ) {
return type.equals( this.selectedComponent ) ? this.highlightBgcolor : this.defaultBgColor;
}
}
/**
* @author Vincent Zurczak - Linagora
*/
static class VertexFont implements Transformer<AbstractType,Font> {
@Override
public Font transform( AbstractType type ) {
return GraphUtils.getDefaultFont();
}
}
/**
* @author Vincent Zurczak - Linagora
*/
static class MyVertexLabelRenderer extends DefaultVertexLabelRenderer {
private static final long serialVersionUID = -7669532897039301417L;
private final Component selectedComponent;
public MyVertexLabelRenderer( Component selectedComponent, Color defaultFgColor ) {
super( defaultFgColor );
this.selectedComponent = selectedComponent;
}
@Override
public <T> java.awt.Component getVertexLabelRendererComponent(
JComponent vv, Object value, Font font, boolean isSelected,
T vertex ) {
super.getVertexLabelRendererComponent( vv, value, font, isSelected, vertex );
if( ! this.selectedComponent.equals( vertex ))
super.setForeground( this.pickedVertexLabelColor );
return this;
}
}
/**
* @author Vincent Zurczak - Linagora
*/
static class NoStringLabeller implements Transformer<String,String> {
@Override
public String transform( String v ) {
return "";
}
}
/**
* @author Vincent Zurczak - Linagora
*/
static class VertexShape implements Transformer<AbstractType,Shape> {
@Override
public Shape transform( AbstractType type ) {
int width = computeShapeWidth( type );
return new Ellipse2D.Double( -width / 2.0, -SHAPE_HEIGHT / 2.0, width, SHAPE_HEIGHT );
}
}
}