SVGwritter.java
package com.github.celldynamics.quimp.utils.graphics.svg;
import java.awt.Rectangle;
import java.io.IOException;
import java.io.PrintWriter;
import com.github.celldynamics.quimp.QColor;
import com.github.celldynamics.quimp.geom.ExtendedVector2d;
import ij.IJ;
/**
* Plot primitives on SVG image.
*
* <p>Written svg object must be close by /svg tag manually.
*
* @author p.baniukiewicz
*
*/
public abstract class SVGwritter {
/**
* Generate typical header.
*
* @param osw file to write
* @param d bounding box
* @throws IOException on file error
*/
public static void writeHeader(PrintWriter osw, Rectangle d) throws IOException {
Rectangle d1 = new Rectangle(d);
d1.grow(1, 1);
osw.write("<?xml version=\"1.0\" standalone=\"no\"?>\n");
osw.write("<svg width=\"15cm\" height=\"15cm\" viewBox=\"" + d1.x + " " + d1.y + " " + d1.width
+ " " + d1.height + "\" "
+ "xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns=\"http://www.w3.org/2000/svg\">\n");
osw.write("\n");
osw.write("<rect x=\"" + d.getX() + "\" y=\"" + d.getY() + "\" width=\"" + d.getWidth()
+ "\" height=\"" + d.getHeight() + "\" "
+ "style=\"fill:rgb(100.0%,100.0%,100.0%);stroke-width:0.01;"
+ "stroke:rgb(0.0%,0.0%,0.0%)\"/>\n\n");
}
/**
* Write to file svg definition of frawn object.
*
* @param osw file to write
* @throws IOException on file error
*/
public abstract void draw(PrintWriter osw) throws IOException;
/**
* Represent circle on SVG image.
*
* @author p.baniukiewicz
*
*/
public static class Qcircle extends SVGwritter {
/**
* x coordinate of center.
*/
public double x1;
/**
* y coordinate of center.
*/
public double y1;
/**
* The radius.
*/
public double radius;
/**
* The colour. If null object has no filling
*/
public QColor colour;
/**
* The strokecolour.
*/
public QColor strokecolour;
/**
* The thickness.
*/
public double thickness;
/**
* Instantiates a new circle.
*
* @param x1 the x 1
* @param y1 the y 1
* @param radius the radius
*/
public Qcircle(double x1, double y1, double radius) {
this.x1 = x1;
this.y1 = y1;
this.radius = radius;
colour = new QColor(0, 0, 0);
strokecolour = colour;
thickness = 0.0;
}
/*
* (non-Javadoc)
*
* @see SVGwritter#draw(java.io.OutputStreamWriter)
*/
@Override
public void draw(PrintWriter osw) throws IOException {
String col; // fill colour
if (colour == null) {
col = "none";
} else {
col = colour.getColorSVG();
}
//!>
osw.write("<circle cx=\"" + x1 + "\"" + " cy=\"" + y1 + "\"" + " r=\"" + radius + "\""
+ " fill=\"" + col + "\"" + " stroke=\"" + strokecolour.getColorSVG() + "\""
+ " stroke-width=\"" + thickness + "\"/>\n");
//!<
}
}
/**
* Represent line on SVG image.
*
* @author p.baniukiewicz
*
*/
public static class Qline extends SVGwritter {
/**
* x coordinate of first point of section.
*/
public double x1;
/**
* y coordinate of first point of section.
*/
public double y1;
/**
* x coordinate of last point of section.
*/
public double x2;
/**
* y coordinate of last point of section.
*/
public double y2;
/**
* The thickness.
*/
public double thickness;
/**
* The colour.
*/
public QColor colour;
/**
* Instantiates a new qline.
*
* @param xx1 the xx 1
* @param yy1 the yy 1
* @param xx2 the xx 2
* @param yy2 the yy 2
*/
public Qline(double xx1, double yy1, double xx2, double yy2) {
x1 = xx1;
x2 = xx2;
y1 = yy1;
y2 = yy2;
thickness = 1;
colour = new QColor(0, 0, 0);
}
/**
* Length.
*
* @return the double
*/
public double length() {
return Math.sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)));
}
/*
* (non-Javadoc)
*
* @see
* com.github.celldynamics.quimp.utils.graphics.svg.SVGwritter#draw(java.io.OutputStreamWriter)
*/
@Override
public void draw(PrintWriter osw) throws IOException {
osw.write("<line x1=\"" + x1 + "\" y1=\"" + y1 + "\" x2=\"" + x2 + "\" y2=\"" + y2 + "\" ");
osw.write("style=\"stroke:" + colour.getColorSVG() + ";stroke-width:" + thickness + "\"/>\n");
}
}
/**
* Represent text on SVG image.
*
* @author p.baniukiewicz
*
*/
public static class Qtext extends SVGwritter {
/**
* The text.
*/
public String text;
/**
* The size.
*/
public double size;
/**
* The colour.
*/
public QColor colour;
/**
* The font.
*/
public String font;
/**
* Position of text.
*/
public ExtendedVector2d pos;
/**
* If letters overlap, set it to half of font size.
*/
public double letterSpacing = 0;
/**
* Text anchor. Possible values: start, middle, end.
*/
public String textAnchor = "start";
/**
* Instantiates a new qtext.
*
* @param t Text
* @param s Size
* @param f Font name
* @param pos position
*/
public Qtext(String t, double s, String f, ExtendedVector2d pos) {
text = t;
size = s;
font = f;
this.pos = pos;
colour = new QColor(0, 0, 0);
}
/**
* Length.
*
* @return the int
*/
public int length() {
return text.length();
}
/*
* (non-Javadoc)
*
* @see
* com.github.celldynamics.quimp.utils.graphics.svg.SVGwritter#draw(java.io.OutputStreamWriter)
*/
@Override
public void draw(PrintWriter osw) throws IOException {
osw.write("\n<text x=\"" + pos.getX() + "\" y=\"" + pos.getY() + "\" "
+ "style=\"font-family: " + font + ";font-size: " + size + ";fill: "
+ colour.getColorSVG() + ";letter-spacing: " + letterSpacing + ";text-anchor: "
+ textAnchor + "\">" + text + "</text>");
}
}
/**
* Create axes in polar coordinates.
*
* @author p.baniukiewicz
*
*/
public static class QPolarAxes extends SVGwritter {
private Rectangle rect;
/**
* The colour.
*/
public QColor colour;
/**
* The thickness.
*/
public double thickness;
/**
* Show angle labels.
*/
public boolean angleLabel = false;
/**
* Font size. If one gets overlap letters, play with fontSize and {@link Qtext#letterSpacing}
*/
public double fontSize = 0.2;
/**
* Labels for radius. Must contain numofIntCircles entries only.
*
* @see #numofIntCircles
*/
public double[] radiusLabels = null;
/**
* Number of circles in polar plot.
*
* @see #radiusLabels
*/
public int numofIntCircles = 5;
/**
* Create polar axes.
*
* @param rect boundaries
*/
public QPolarAxes(Rectangle rect) {
this.rect = rect;
colour = new QColor(0.5, 0.5, 0.5);
thickness = 0.01;
}
/*
* (non-Javadoc)
*
* @see
* com.github.celldynamics.quimp.utils.graphics.svg.SVGwritter#draw(java.io.OutputStreamWriter)
*/
@Override
public void draw(PrintWriter osw) throws IOException {
// plot parameters
double x0 = rect.getLocation().getX() + rect.getWidth() / 2;
double y0 = rect.getLocation().getY() + rect.getHeight() / 2;
double radius = rect.getHeight() / 2;
// main circle
Qcircle qcmain = new Qcircle(x0, y0, radius);
qcmain.colour = new QColor(1, 1, 1);
qcmain.strokecolour = colour;
qcmain.thickness = thickness;
qcmain.draw(osw);
// lines
for (int a = 0; a < 360; a += 20) {
double xend = radius * Math.cos(Math.toRadians(a)) + x0;
double yend = radius * Math.sin(Math.toRadians(a)) + y0;
Qline ql = new Qline(x0, y0, xend, yend);
ql.thickness = thickness;
ql.colour = colour;
ql.draw(osw);
// labels on angles
if (angleLabel) {
Qtext qt = new Qtext(Integer.toString(a), fontSize, "New Roman",
new ExtendedVector2d(xend, yend));
// qt.letterSpacing = fontSize / 2; // may not be important for fontSize>0.6 (?)
if (a < 90 || a > 270) {
qt.textAnchor = "start";
} else {
qt.textAnchor = "end";
}
qt.draw(osw);
}
}
// circles
double d = radius / (numofIntCircles + 1);
double gridrad = d;
int i = 0;
do {
Qcircle g1 = new Qcircle(x0, y0, gridrad);
g1.colour = null;
g1.strokecolour = colour;
g1.thickness = thickness;
g1.draw(osw);
// rlabel
if (radiusLabels != null && i < numofIntCircles) {
String st = IJ.d2s(radiusLabels[i], 2, 4);
double x = gridrad * Math.cos(Math.toRadians(160)) + x0;
double y = gridrad * Math.sin(Math.toRadians(160)) + y0;
Qtext qt = new Qtext(st, fontSize - 0.04, "New Roman", new ExtendedVector2d(x, y));
// qt.letterSpacing = (fontSize - 0.04) / 2;
qt.textAnchor = "middle";
qt.draw(osw);
}
gridrad += d;
} while (++i < numofIntCircles);
// circle red in middle
{
Qcircle qc = new Qcircle(rect.getLocation().getX() + rect.getWidth() / 2,
rect.getLocation().getY() + rect.getHeight() / 2, thickness * 4);
qc.colour = new QColor(1, 0, 0);
qc.draw(osw);
}
// green at 0deg
{
Qcircle qc = new Qcircle(radius * Math.cos(Math.toRadians(0)) + x0,
radius * Math.sin(Math.toRadians(0)) + y0, thickness * 4);
qc.colour = new QColor(0, 1, 0);
qc.draw(osw);
}
// green at 180deg
{
Qcircle qc = new Qcircle(radius * Math.cos(Math.toRadians(180)) + x0,
radius * Math.sin(Math.toRadians(180)) + y0, thickness * 4);
qc.colour = new QColor(0, 0, 1);
qc.draw(osw);
}
}
}
/**
* Plot scale bar on svg.
*
* @author rtyson
*
*/
public static class QScaleBar extends SVGwritter {
private double length;
private String units;
private int value;
/**
* Line thickness.
*/
public double thickness;
/**
* Text colour.
*/
public QColor colour;
private ExtendedVector2d location;
private SVGwritter.Qtext text;
/**
* Constructor.
*
* @param l position of text
* @param u units
* @param v value
* @param s scale
*/
public QScaleBar(ExtendedVector2d l, String u, int v, double s) {
location = l;
units = u;
value = v;
thickness = 1;
colour = new QColor(1, 1, 1);
this.setScale(s);
text = new SVGwritter.Qtext(IJ.d2s(value, 0) + units, 6, "Courier", l);
text.colour = colour;
}
/**
* Set scale.
*
* @param s scale
*/
public void setScale(double s) {
length = value / s;
}
/*
* (non-Javadoc)
*
* @see
* com.github.celldynamics.quimp.utils.graphics.svg.SVGwritter#draw(java.io.OutputStreamWriter)
*/
@Override
public void draw(PrintWriter osw) throws IOException {
SVGwritter.Qline body;
double tickSize = 2 * thickness;
ExtendedVector2d end = new ExtendedVector2d(location.getX(), location.getY());
end.addVec(new ExtendedVector2d(length, 0));
body = new SVGwritter.Qline(location.getX(), location.getY(), end.getX(), end.getY());
body.thickness = thickness;
body.colour = colour;
SVGwritter.Qline ltick;
SVGwritter.Qline rtick;
ltick = new SVGwritter.Qline(location.getX(), location.getY() + tickSize, location.getX(),
location.getY() - tickSize);
rtick = new SVGwritter.Qline(end.getX(), end.getY() + tickSize, end.getX(),
end.getY() - tickSize);
ltick.thickness = thickness;
ltick.colour = colour;
rtick.thickness = thickness;
rtick.colour = colour;
ltick.draw(osw);
rtick.draw(osw);
body.draw(osw);
// centre the text
int textLength = 2 + Integer.toString(value).length();
textLength = textLength * 4;
double textDis = (body.length() - textLength) / 2;
text.pos = new ExtendedVector2d(location.getX() + textDis, location.getY() - 2);
text.draw(osw);
}
}
}