/** * Copyright (c) 2009, iPlant Collaborative, Texas Advanced Computing Center This software is licensed * under the CC-GNU GPL version 2.0 or later. License: http://creativecommons.org/licenses/GPL/2.0/ */ package org.iplantc.phyloviewer.client.tree.viewer.render.canvas; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.iplantc.phyloviewer.client.tree.viewer.canvas.Canvas; import org.iplantc.phyloviewer.shared.math.Box2D; import org.iplantc.phyloviewer.shared.math.Matrix33; import org.iplantc.phyloviewer.shared.math.Vector2; import org.iplantc.phyloviewer.shared.render.Defaults; import org.iplantc.phyloviewer.shared.render.Graphics; import org.iplantc.phyloviewer.shared.render.style.IBranchStyle; import org.iplantc.phyloviewer.shared.render.style.IGlyphStyle; import org.iplantc.phyloviewer.shared.render.style.ILabelStyle; import org.iplantc.phyloviewer.shared.render.style.INodeStyle; import org.iplantc.phyloviewer.shared.scene.Text; import com.google.gwt.core.client.JavaScriptException; import com.google.gwt.user.client.ui.Widget; public class CanvasGraphics extends Graphics { private static Logger rootLogger = Logger.getLogger(""); private static final double DEGREES_270 = 3 * Math.PI / 2; private static final double DEGREES_90 = Math.PI / 2; private Canvas canvas = null; private List<Box2D> drawnTextExtents = new ArrayList<Box2D>(); private double pointSize = Defaults.POINT_SIZE; String textColor = "black"; public CanvasGraphics(Canvas canvas) { this.canvas = canvas; } public Widget getWidget() { return canvas; } /** * Clear the canvas. */ @Override public void clear() { drawnTextExtents.clear(); canvas.clear(); } public void clearDrawnTextExtents() { drawnTextExtents.clear(); } /** * Draw a point at given position. */ @Override public void drawPoint(Vector2 position) { Vector2 p = objectToScreenMatrix.transform(position); canvas.beginPath(); canvas.arc(p.getX(), p.getY(), pointSize / 2.0, 0, Math.PI * 2, true); canvas.closePath(); canvas.fill(); } @Override public void drawLineStrip(Vector2[] vertices) { if(vertices.length < 2) { return; } canvas.beginPath(); Vector2 vector = objectToScreenMatrix.transform(vertices[0]); canvas.moveTo(vector.getX(), vector.getY()); for(int i = 1;i < vertices.length;++i) { vector = objectToScreenMatrix.transform(vertices[i]); canvas.lineTo(vector.getX(), vector.getY()); } canvas.stroke(); } @Override public void drawText(Vector2 position, Vector2 offset, String text, double angle) { try { if(text == null || text.equals("")) { return; } Vector2 p = objectToScreenMatrix.transform(position); Vector2 startingPosition = new Vector2(p.getX() + offset.getX(), p.getY() + offset.getY()); // TODO: Get the text height from the canvas. float height = 10; double width = canvas.measureText(text); // Make a bounding box of the text. Box2D bbox = createBoundingBox(startingPosition, height, width, angle); // If this bounding box will intersect any text that we have already drawn, don't draw. // If this brute force search proves to be too slow, perhaps a quad tree search would be // better. for(Box2D box : drawnTextExtents) { if(box.intersects(bbox)) { return; } } canvas.save(); canvas.translate(startingPosition.getX(), startingPosition.getY()); canvas.rotate(angle); if(angle > DEGREES_90 && angle < DEGREES_270) { // flip labels on the left side of the circle so they are right-side-up canvas.translate(width, -height); canvas.rotate(Math.PI); } canvas.setFillStyle(textColor); canvas.fillText(text, 0.0, 0.0); drawnTextExtents.add(bbox); canvas.restore(); } catch(JavaScriptException e) { rootLogger.log(Level.WARNING, "An exception was caught in Canvas.drawText: " + e.getMessage()); } } @Override public Box2D calculateBoundingBox(Text text) { Vector2 position = text.getPosition(); Vector2 offset = text.getPixelOffset(); String textValue = text.getText(); double angle = text.getAngle(); Vector2 p = objectToScreenMatrix.transform(position); Vector2 startingPosition = new Vector2(p.getX() + offset.getX(), p.getY() + offset.getY()); // TODO: Get the text height from the canvas. float height = 10; double width = canvas.measureText(textValue); // Make a bounding box of the text. Box2D bbox = createBoundingBox(startingPosition, height, width, angle); Matrix33 IM = objectToScreenMatrix.inverse(); bbox = IM.transform(bbox); return bbox; } private Box2D createBoundingBox(Vector2 startingPosition, float height, double width, double angle) { // This bounding box does a much better job of enclosing text, but it culls too much in circular // layout. // We probably need an object oriented bounding box instead of a axis aligned bounding box. /* * Vector2 min = new Vector2 ( startingPosition.getX(), startingPosition.getY() ); Vector2 max = * new Vector2 ( startingPosition.getX() + width, startingPosition.getY() ); * * Vector2 rotatePoint = startingPosition; Vector2 minRotated = * ((min.subtract(rotatePoint)).rotate(angle)).add(rotatePoint); Vector2 maxRotated = * ((max.subtract(rotatePoint)).rotate(angle)).add(rotatePoint); * * Box2D bbox = new Box2D(); bbox.expandBy(minRotated); bbox.expandBy(maxRotated); * bbox.expandBy(height); return bbox; */ Vector2 min = new Vector2(startingPosition.getX(), startingPosition.getY() - (height / 2)); Vector2 max = new Vector2(startingPosition.getX() + width, startingPosition.getY() + (height / 2)); return new Box2D(min, max); } @Override public void drawPolygon(Vector2 vertices[]) { if(vertices.length < 3) { return; } canvas.beginPath(); Vector2 vector = objectToScreenMatrix.transform(vertices[0]); canvas.moveTo(vector.getX(), vector.getY()); for(int i = 1;i < vertices.length;++i) { vector = objectToScreenMatrix.transform(vertices[i]); canvas.lineTo(vector.getX(), vector.getY()); } canvas.closePath(); canvas.fill(); canvas.stroke(); } @Override public void drawWedge(Vector2 center, Vector2 peak, double radius, double startAngle, double endAngle) { canvas.save(); center = objectToScreenMatrix.transform(center); peak = objectToScreenMatrix.transform(peak); radius = radius * objectToScreenMatrix.getScaleY(); canvas.beginPath(); canvas.moveTo(peak.getX(), peak.getY()); canvas.arc(center.getX(), center.getY(), radius, startAngle, endAngle, false); canvas.closePath(); canvas.fill(); canvas.stroke(); canvas.restore(); } @Override public void drawArc(Vector2 center, double radius, double startAngle, double endAngle) { // note: I don't think Canvas can draw elliptical arcs, so xzoom and yzoom are assumed to be the // same. Alternatively, the canvas transform could be manipulated here instead of the arc // parameters, or the arcs can be approximated with bezier curves. center = objectToScreenMatrix.transform(center); radius = radius * objectToScreenMatrix.getScaleY(); canvas.beginPath(); canvas.arc(center.getX(), center.getY(), radius, startAngle, endAngle, false); canvas.stroke(); } @Override public void setStyle(IBranchStyle style) { try { if(style != null) { if(style.getStrokeColor() != null) { canvas.setStrokeStyle(style.getStrokeColor()); } if(!Double.isNaN(style.getLineWidth())) { canvas.setLineWidth(style.getLineWidth()); } } } catch(Exception e) { canvas.setStrokeStyle(Defaults.LINE_COLOR); canvas.setLineWidth(1.0); } } @Override public void setStyle(IGlyphStyle style) { try { if(style != null) { if(style.getFillColor() != null) { canvas.setFillStyle(style.getFillColor()); } if(style.getStrokeColor() != null) { canvas.setStrokeStyle(style.getStrokeColor()); } if(!Double.isNaN(style.getLineWidth())) { canvas.setLineWidth(style.getLineWidth()); } } } catch(Exception e) { canvas.setFillStyle(Defaults.TRIANGLE_FILL_COLOR); canvas.setStrokeStyle(Defaults.TRIANGLE_OUTLINE_COLOR); canvas.setLineWidth(1.0); } } @Override public void setStyle(ILabelStyle style) { try { if(style != null) { if(style.getColor() != null) { textColor = style.getColor(); } } } catch(Exception e) { canvas.setFillStyle(Defaults.TEXT_COLOR); } } @Override public void setStyle(INodeStyle style) { try { if(style != null) { if(style.getColor() != null) { canvas.setFillStyle(style.getColor()); canvas.setStrokeStyle(style.getColor()); } if(!Double.isNaN(style.getPointSize())) { this.pointSize = style.getPointSize(); } } } catch(Exception e) { canvas.setFillStyle(Defaults.LINE_COLOR); canvas.setStrokeStyle(Defaults.LINE_COLOR); canvas.setLineWidth(1.0); } } }