/*
* Copyright 2009-2011 Collaborative Research Centre SFB 632
*
* Copyright 2013
* Ubiquitous Knowledge Processing (UKP) Lab
* Technische Universität Darmstadt
* (Change: DefaultLabeler.extractAnnotation() returns null instead of "--" if no annotation found)
*
* 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 annis.frontend.servlets.visualizers.tree;
import annis.frontend.servlets.MatchedNodeColors;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.imageio.ImageIO;
import annis.frontend.servlets.visualizers.Visualizer;
import annis.frontend.servlets.visualizers.tree.backends.staticimg.AbstractImageGraphicsItem;
import annis.frontend.servlets.visualizers.tree.backends.staticimg.Java2dBackend;
import annis.model.AnnisNode;
import annis.model.Annotation;
import annis.model.Edge;
import annis.service.ifaces.AnnisResult;
import edu.uci.ics.jung.graph.DirectedGraph;
public class TigerTreeVisualizer extends Visualizer {
private static final int SIDE_MARGIN = 20;
private static final int TOP_MARGIN = 40;
private static final int TREE_DISTANCE = 40;
private final Java2dBackend backend;
private final DefaultLabeler labeler;
private final DefaultStyler styler;
private final AnnisGraphTools graphtools;
public class DefaultStyler implements TreeElementStyler {
private final BasicStroke DEFAULT_PEN_STYLE = new BasicStroke(1);
public static final int LABEL_PADDING = 2;
public static final int HEIGHT_STEP = 40;
public static final int TOKEN_SPACING = 15;
public static final int VEDGE_OVERLAP_THRESHOLD = 20;
private final Java2dBackend backend;
public DefaultStyler(Java2dBackend backend_) {
backend = backend_;
}
@Override
public int getLabelPadding() {
return LABEL_PADDING;
}
@Override
public GraphicsBackend.Font getFont(AnnisNode n) {
if (n.isToken()) {
return backend.getFont(Font.SANS_SERIF, 12, java.awt.Font.PLAIN);
} else {
return backend.getFont(Font.SANS_SERIF, 12, java.awt.Font.PLAIN);
}
}
@Override
public GraphicsBackend.Font getFont(Edge e) {
return backend.getFont(Font.SANS_SERIF, 10, java.awt.Font.PLAIN);
}
@Override
public Shape getShape(AnnisNode n) {
if (isQueryMatch(n))
{
// get CSS color name
String backColorName = getMarkableMap().get(""+n.getId());
Color backColor = Color.RED;
try
{
backColor = MatchedNodeColors.valueOf(backColorName).getColor();
}
catch(IllegalArgumentException ex)
{}
if (n.isToken()) {
return new Shape.Rectangle(Color.WHITE, backColor, DEFAULT_PEN_STYLE, getLabelPadding());
} else
{
return new Shape.Ellipse(Color.WHITE, backColor, DEFAULT_PEN_STYLE, getLabelPadding());
}
} else {
if (n.isToken()) {
return new Shape.Invisible(getLabelPadding());
} else {
return new Shape.Ellipse(Color.BLACK, Color.WHITE, DEFAULT_PEN_STYLE, getLabelPadding());
}
}
}
private boolean isQueryMatch(AnnisNode n) {
return getMarkableExactMap().containsKey(Long.toString(n.getId()));
}
@Override
public Shape getShape(Edge e) {
if (AnnisGraphTools.hasEdgeSubtype(e, AnnisGraphTools.SECEDGE_SUBTYPE)) {
return new Shape.Rectangle(getEdgeColor(e), Color.WHITE, DEFAULT_PEN_STYLE, getLabelPadding());
} else {
return new Shape.Rectangle(new Color(0.4f, 0.4f, 0.4f), Color.WHITE, DEFAULT_PEN_STYLE, getLabelPadding());
}
}
@Override
public Color getTextBrush(AnnisNode n) {
if (isQueryMatch(n)) {
return Color.WHITE;
} else {
return Color.BLACK;
}
}
@Override
public Color getTextBrush(Edge n) {
return Color.BLACK;
}
@Override
public int getHeightStep() {
return HEIGHT_STEP;
}
@Override
public Color getEdgeColor(Edge e) {
if (AnnisGraphTools.hasEdgeSubtype(e, AnnisGraphTools.SECEDGE_SUBTYPE)) {
return new Color(0.5f, 0.5f, 0.8f, 0.7f);
} else {
return new Color(0.3f, 0.3f, 0.3f);
}
}
@Override
public int getTokenSpacing() {
return TOKEN_SPACING;
}
@Override
public int getVEdgeOverlapThreshold() {
return VEDGE_OVERLAP_THRESHOLD;
}
@Override
public Stroke getStroke(Edge e) {
if (AnnisGraphTools.hasEdgeSubtype(e, AnnisGraphTools.SECEDGE_SUBTYPE)) {
return new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10, new float[] {2, 2}, 0);
} else {
return new BasicStroke(2);
}
}
}
private class DefaultLabeler implements TreeElementLabeler {
@Override
public String getLabel(AnnisNode n) {
if (n.isToken()) {
String spannedText = n.getSpannedText();
if(spannedText == null || "".equals(spannedText)) {
spannedText = " ";
}
return spannedText;
} else {
return extractAnnotation(n.getNodeAnnotations(),
getMappings().getProperty("node_anno_ns", getNamespace()),
getMappings().getProperty("node_key", "cat"));
}
}
@Override
public String getLabel(Edge e) {
return extractAnnotation(e.getAnnotations(),
getMappings().getProperty("edge_anno_ns", getNamespace()),
getMappings().getProperty("edge_key", "func"));
}
private String extractAnnotation(Set<Annotation> annotations, String namespace, String featureName) {
for (Annotation a: annotations) {
if (a.getNamespace().equals(namespace) && a.getName().equals(featureName)) {
return a.getValue();
}
}
return null;
}
}
public TigerTreeVisualizer() {
backend = new Java2dBackend();
labeler = new DefaultLabeler();
styler = new DefaultStyler(backend);
graphtools = new AnnisGraphTools();
}
@Override
public void writeOutput(OutputStream outstream) {
AnnisResult result = getResult();
List<AbstractImageGraphicsItem> layouts = new LinkedList<AbstractImageGraphicsItem>();
double width = 0;
double maxheight = 0;
for (DirectedGraph<AnnisNode, Edge> g: graphtools.getSyntaxGraphs(result.getGraph(),
getMappings().getProperty("node_ns", getNamespace()))) {
ConstituentLayouter<AbstractImageGraphicsItem> cl = new ConstituentLayouter<AbstractImageGraphicsItem>(
g, backend, labeler, styler);
AbstractImageGraphicsItem item = cl.createLayout(
new LayoutOptions(VerticalOrientation.TOP_ROOT, AnnisGraphTools.detectLayoutDirection(result.getGraph())));
Rectangle2D treeSize = item.getBounds();
maxheight = Math.max(maxheight, treeSize.getHeight());
width += treeSize.getWidth();
layouts.add(item);
}
BufferedImage image = new BufferedImage(
(int)(width + (layouts.size() - 1) * TREE_DISTANCE + 2 * SIDE_MARGIN),
(int)(maxheight + 2 * TOP_MARGIN), BufferedImage.TYPE_INT_ARGB);
Graphics2D canvas = createCanvas(image);
double xOffset = SIDE_MARGIN;
for (AbstractImageGraphicsItem item: layouts) {
AffineTransform t = canvas.getTransform();
Rectangle2D bounds = item.getBounds();
canvas.translate(xOffset, TOP_MARGIN + maxheight - bounds.getHeight());
renderTree(item, canvas);
xOffset += bounds.getWidth() + TREE_DISTANCE;
canvas.setTransform(t);
}
try {
ImageIO.write(image, "png", outstream);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void renderTree(AbstractImageGraphicsItem item, Graphics2D canvas) {
List<AbstractImageGraphicsItem> allItems = new ArrayList<AbstractImageGraphicsItem>();
item.getAllChildren(allItems);
Collections.sort(allItems, new Comparator<AbstractImageGraphicsItem>() {
@Override
public int compare(AbstractImageGraphicsItem o1,
AbstractImageGraphicsItem o2) {
return o1.getZValue() - o2.getZValue();
}
});
for (AbstractImageGraphicsItem c: allItems) {
c.draw(canvas);
}
}
private Graphics2D createCanvas(BufferedImage image) {
Graphics2D canvas = (Graphics2D)image.getGraphics();
canvas.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
canvas.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
canvas.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
return canvas;
}
@Override
public String getContentType() {
return "image/png";
}
@Override
public String getCharacterEncoding() {
return "ISO-8859-1";
}
}