View Javadoc
1   package com.github.celldynamics.quimp.utils.graphics.svg;
2   
3   import java.awt.Rectangle;
4   import java.io.IOException;
5   import java.io.PrintWriter;
6   
7   import com.github.celldynamics.quimp.QColor;
8   import com.github.celldynamics.quimp.geom.ExtendedVector2d;
9   
10  import ij.IJ;
11  
12  /**
13   * Plot primitives on SVG image.
14   * 
15   * <p>Written svg object must be close by /svg tag manually.
16   * 
17   * @author p.baniukiewicz
18   *
19   */
20  public abstract class SVGwritter {
21  
22    /**
23     * Generate typical header.
24     * 
25     * @param osw file to write
26     * @param d bounding box
27     * @throws IOException on file error
28     */
29    public static void writeHeader(PrintWriter osw, Rectangle d) throws IOException {
30      Rectangle d1 = new Rectangle(d);
31      d1.grow(1, 1);
32      osw.write("<?xml version=\"1.0\" standalone=\"no\"?>\n");
33      osw.write("<svg width=\"15cm\" height=\"15cm\" viewBox=\"" + d1.x + " " + d1.y + " " + d1.width
34              + " " + d1.height + "\" "
35              + "xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns=\"http://www.w3.org/2000/svg\">\n");
36      osw.write("\n");
37  
38      osw.write("<rect x=\"" + d.getX() + "\" y=\"" + d.getY() + "\" width=\"" + d.getWidth()
39              + "\" height=\"" + d.getHeight() + "\" "
40              + "style=\"fill:rgb(100.0%,100.0%,100.0%);stroke-width:0.01;"
41              + "stroke:rgb(0.0%,0.0%,0.0%)\"/>\n\n");
42    }
43  
44    /**
45     * Write to file svg definition of frawn object.
46     * 
47     * @param osw file to write
48     * @throws IOException on file error
49     */
50    public abstract void draw(PrintWriter osw) throws IOException;
51  
52    /**
53     * Represent circle on SVG image.
54     * 
55     * @author p.baniukiewicz
56     *
57     */
58    public static class Qcircle extends SVGwritter {
59  
60      /**
61       * x coordinate of center.
62       */
63      public double x1;
64      /**
65       * y coordinate of center.
66       */
67      public double y1;
68  
69      /**
70       * The radius.
71       */
72      public double radius;
73  
74      /**
75       * The colour. If null object has no filling
76       */
77      public QColor colour;
78  
79      /**
80       * The strokecolour.
81       */
82      public QColor strokecolour;
83  
84      /**
85       * The thickness.
86       */
87      public double thickness;
88  
89      /**
90       * Instantiates a new circle.
91       *
92       * @param x1 the x 1
93       * @param y1 the y 1
94       * @param radius the radius
95       */
96      public Qcircle(double x1, double y1, double radius) {
97        this.x1 = x1;
98        this.y1 = y1;
99        this.radius = radius;
100       colour = new QColor(0, 0, 0);
101       strokecolour = colour;
102       thickness = 0.0;
103     }
104 
105     /*
106      * (non-Javadoc)
107      * 
108      * @see SVGwritter#draw(java.io.OutputStreamWriter)
109      */
110     @Override
111     public void draw(PrintWriter osw) throws IOException {
112       String col; // fill colour
113       if (colour == null) {
114         col = "none";
115       } else {
116         col = colour.getColorSVG();
117       }
118       //!>
119       osw.write("<circle cx=\"" + x1 + "\"" + " cy=\"" + y1 + "\"" + " r=\"" + radius + "\""
120               + " fill=\"" + col + "\"" + " stroke=\"" + strokecolour.getColorSVG() + "\""
121               + " stroke-width=\"" + thickness + "\"/>\n");
122       //!<
123     }
124   }
125 
126   /**
127    * Represent line on SVG image.
128    * 
129    * @author p.baniukiewicz
130    *
131    */
132   public static class Qline extends SVGwritter {
133 
134     /**
135      * x coordinate of first point of section.
136      */
137     public double x1;
138     /**
139      * y coordinate of first point of section.
140      */
141     public double y1;
142     /**
143      * x coordinate of last point of section.
144      */
145     public double x2;
146     /**
147      * y coordinate of last point of section.
148      */
149     public double y2;
150 
151     /**
152      * The thickness.
153      */
154     public double thickness;
155 
156     /**
157      * The colour.
158      */
159     public QColor colour;
160 
161     /**
162      * Instantiates a new qline.
163      *
164      * @param xx1 the xx 1
165      * @param yy1 the yy 1
166      * @param xx2 the xx 2
167      * @param yy2 the yy 2
168      */
169     public Qline(double xx1, double yy1, double xx2, double yy2) {
170       x1 = xx1;
171       x2 = xx2;
172       y1 = yy1;
173       y2 = yy2;
174 
175       thickness = 1;
176       colour = new QColor(0, 0, 0);
177     }
178 
179     /**
180      * Length.
181      *
182      * @return the double
183      */
184     public double length() {
185       return Math.sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)));
186     }
187 
188     /*
189      * (non-Javadoc)
190      * 
191      * @see
192      * com.github.celldynamics.quimp.utils.graphics.svg.SVGwritter#draw(java.io.OutputStreamWriter)
193      */
194     @Override
195     public void draw(PrintWriter osw) throws IOException {
196       osw.write("<line x1=\"" + x1 + "\" y1=\"" + y1 + "\" x2=\"" + x2 + "\" y2=\"" + y2 + "\" ");
197       osw.write("style=\"stroke:" + colour.getColorSVG() + ";stroke-width:" + thickness + "\"/>\n");
198     }
199   }
200 
201   /**
202    * Represent text on SVG image.
203    * 
204    * @author p.baniukiewicz
205    *
206    */
207   public static class Qtext extends SVGwritter {
208 
209     /**
210      * The text.
211      */
212     public String text;
213 
214     /**
215      * The size.
216      */
217     public double size;
218 
219     /**
220      * The colour.
221      */
222     public QColor colour;
223 
224     /**
225      * The font.
226      */
227     public String font;
228 
229     /**
230      * Position of text.
231      */
232     public ExtendedVector2d pos;
233 
234     /**
235      * If letters overlap, set it to half of font size.
236      */
237     public double letterSpacing = 0;
238 
239     /**
240      * Text anchor. Possible values: start, middle, end.
241      */
242     public String textAnchor = "start";
243 
244     /**
245      * Instantiates a new qtext.
246      *
247      * @param t Text
248      * @param s Size
249      * @param f Font name
250      * @param pos position
251      */
252     public Qtext(String t, double s, String f, ExtendedVector2d pos) {
253       text = t;
254       size = s;
255       font = f;
256       this.pos = pos;
257       colour = new QColor(0, 0, 0);
258     }
259 
260     /**
261      * Length.
262      *
263      * @return the int
264      */
265     public int length() {
266       return text.length();
267     }
268 
269     /*
270      * (non-Javadoc)
271      * 
272      * @see
273      * com.github.celldynamics.quimp.utils.graphics.svg.SVGwritter#draw(java.io.OutputStreamWriter)
274      */
275     @Override
276     public void draw(PrintWriter osw) throws IOException {
277       osw.write("\n<text x=\"" + pos.getX() + "\" y=\"" + pos.getY() + "\" "
278               + "style=\"font-family: " + font + ";font-size: " + size + ";fill: "
279               + colour.getColorSVG() + ";letter-spacing: " + letterSpacing + ";text-anchor: "
280               + textAnchor + "\">" + text + "</text>");
281 
282     }
283   }
284 
285   /**
286    * Create axes in polar coordinates.
287    * 
288    * @author p.baniukiewicz
289    *
290    */
291   public static class QPolarAxes extends SVGwritter {
292     private Rectangle rect;
293 
294     /**
295      * The colour.
296      */
297     public QColor colour;
298 
299     /**
300      * The thickness.
301      */
302     public double thickness;
303 
304     /**
305      * Show angle labels.
306      */
307     public boolean angleLabel = false;
308 
309     /**
310      * Font size. If one gets overlap letters, play with fontSize and {@link Qtext#letterSpacing}
311      */
312     public double fontSize = 0.2;
313 
314     /**
315      * Labels for radius. Must contain numofIntCircles entries only.
316      * 
317      * @see #numofIntCircles
318      */
319     public double[] radiusLabels = null;
320 
321     /**
322      * Number of circles in polar plot.
323      * 
324      * @see #radiusLabels
325      */
326     public int numofIntCircles = 5;
327 
328     /**
329      * Create polar axes.
330      * 
331      * @param rect boundaries
332      */
333     public QPolarAxes(Rectangle rect) {
334       this.rect = rect;
335       colour = new QColor(0.5, 0.5, 0.5);
336       thickness = 0.01;
337     }
338 
339     /*
340      * (non-Javadoc)
341      * 
342      * @see
343      * com.github.celldynamics.quimp.utils.graphics.svg.SVGwritter#draw(java.io.OutputStreamWriter)
344      */
345     @Override
346     public void draw(PrintWriter osw) throws IOException {
347       // plot parameters
348       double x0 = rect.getLocation().getX() + rect.getWidth() / 2;
349       double y0 = rect.getLocation().getY() + rect.getHeight() / 2;
350       double radius = rect.getHeight() / 2;
351 
352       // main circle
353       Qcircle qcmain = new Qcircle(x0, y0, radius);
354       qcmain.colour = new QColor(1, 1, 1);
355       qcmain.strokecolour = colour;
356       qcmain.thickness = thickness;
357       qcmain.draw(osw);
358 
359       // lines
360       for (int a = 0; a < 360; a += 20) {
361         double xend = radius * Math.cos(Math.toRadians(a)) + x0;
362         double yend = radius * Math.sin(Math.toRadians(a)) + y0;
363         Qline ql = new Qline(x0, y0, xend, yend);
364         ql.thickness = thickness;
365         ql.colour = colour;
366         ql.draw(osw);
367 
368         // labels on angles
369         if (angleLabel) {
370           Qtext qt = new Qtext(Integer.toString(a), fontSize, "New Roman",
371                   new ExtendedVector2d(xend, yend));
372           // qt.letterSpacing = fontSize / 2; // may not be important for fontSize>0.6 (?)
373           if (a < 90 || a > 270) {
374             qt.textAnchor = "start";
375           } else {
376             qt.textAnchor = "end";
377           }
378           qt.draw(osw);
379         }
380       }
381       // circles
382       double d = radius / (numofIntCircles + 1);
383       double gridrad = d;
384       int i = 0;
385       do {
386         Qcircle g1 = new Qcircle(x0, y0, gridrad);
387         g1.colour = null;
388         g1.strokecolour = colour;
389         g1.thickness = thickness;
390         g1.draw(osw);
391 
392         // rlabel
393         if (radiusLabels != null && i < numofIntCircles) {
394           String st = IJ.d2s(radiusLabels[i], 2, 4);
395           double x = gridrad * Math.cos(Math.toRadians(160)) + x0;
396           double y = gridrad * Math.sin(Math.toRadians(160)) + y0;
397           Qtext qt = new Qtext(st, fontSize - 0.04, "New Roman", new ExtendedVector2d(x, y));
398           // qt.letterSpacing = (fontSize - 0.04) / 2;
399           qt.textAnchor = "middle";
400           qt.draw(osw);
401         }
402         gridrad += d;
403       } while (++i < numofIntCircles);
404 
405       // circle red in middle
406       {
407         Qcircle qc = new Qcircle(rect.getLocation().getX() + rect.getWidth() / 2,
408                 rect.getLocation().getY() + rect.getHeight() / 2, thickness * 4);
409         qc.colour = new QColor(1, 0, 0);
410         qc.draw(osw);
411       }
412 
413       // green at 0deg
414       {
415         Qcircle qc = new Qcircle(radius * Math.cos(Math.toRadians(0)) + x0,
416                 radius * Math.sin(Math.toRadians(0)) + y0, thickness * 4);
417         qc.colour = new QColor(0, 1, 0);
418         qc.draw(osw);
419       }
420 
421       // green at 180deg
422       {
423         Qcircle qc = new Qcircle(radius * Math.cos(Math.toRadians(180)) + x0,
424                 radius * Math.sin(Math.toRadians(180)) + y0, thickness * 4);
425         qc.colour = new QColor(0, 0, 1);
426         qc.draw(osw);
427       }
428 
429     }
430   }
431 
432   /**
433    * Plot scale bar on svg.
434    * 
435    * @author rtyson
436    *
437    */
438   public static class QScaleBar extends SVGwritter {
439 
440     private double length;
441     private String units;
442     private int value;
443     /**
444      * Line thickness.
445      */
446     public double thickness;
447     /**
448      * Text colour.
449      */
450     public QColor colour;
451     private ExtendedVector2d location;
452     private SVGwritter.Qtext text;
453 
454     /**
455      * Constructor.
456      * 
457      * @param l position of text
458      * @param u units
459      * @param v value
460      * @param s scale
461      */
462     public QScaleBar(ExtendedVector2d l, String u, int v, double s) {
463       location = l;
464       units = u;
465       value = v;
466       thickness = 1;
467       colour = new QColor(1, 1, 1);
468       this.setScale(s);
469       text = new SVGwritter.Qtext(IJ.d2s(value, 0) + units, 6, "Courier", l);
470       text.colour = colour;
471     }
472 
473     /**
474      * Set scale.
475      * 
476      * @param s scale
477      */
478     public void setScale(double s) {
479       length = value / s;
480     }
481 
482     /*
483      * (non-Javadoc)
484      * 
485      * @see
486      * com.github.celldynamics.quimp.utils.graphics.svg.SVGwritter#draw(java.io.OutputStreamWriter)
487      */
488     @Override
489     public void draw(PrintWriter osw) throws IOException {
490       SVGwritter.Qline body;
491 
492       double tickSize = 2 * thickness;
493 
494       ExtendedVector2dimp/geom/ExtendedVector2d.html#ExtendedVector2d">ExtendedVector2d end = new ExtendedVector2d(location.getX(), location.getY());
495       end.addVec(new ExtendedVector2d(length, 0));
496       body = new SVGwritter.Qline(location.getX(), location.getY(), end.getX(), end.getY());
497       body.thickness = thickness;
498       body.colour = colour;
499 
500       SVGwritter.Qline ltick;
501       SVGwritter.Qline rtick;
502       ltick = new SVGwritter.Qline(location.getX(), location.getY() + tickSize, location.getX(),
503               location.getY() - tickSize);
504       rtick = new SVGwritter.Qline(end.getX(), end.getY() + tickSize, end.getX(),
505               end.getY() - tickSize);
506       ltick.thickness = thickness;
507       ltick.colour = colour;
508       rtick.thickness = thickness;
509       rtick.colour = colour;
510 
511       ltick.draw(osw);
512       rtick.draw(osw);
513       body.draw(osw);
514 
515       // centre the text
516       int textLength = 2 + Integer.toString(value).length();
517       textLength = textLength * 4;
518       double textDis = (body.length() - textLength) / 2;
519       text.pos = new ExtendedVector2d(location.getX() + textDis, location.getY() - 2);
520       text.draw(osw);
521     }
522 
523   }
524 }