# HG changeset patch # User Sascha L. Teichmann # Date 1262573382 0 # Node ID 20a480753ff935458f3548da6218536255019888 # Parent c7ca2fce041f75466f6ee36286e2f14c3ecfd85f Render labels in vertical cross section charts. gnv-artifacts/trunk@498 c6561f87-3c4e-4783-a992-168aeb5c3f6f diff -r c7ca2fce041f -r 20a480753ff9 gnv-artifacts/ChangeLog --- a/gnv-artifacts/ChangeLog Sun Jan 03 15:57:02 2010 +0000 +++ b/gnv-artifacts/ChangeLog Mon Jan 04 02:49:42 2010 +0000 @@ -1,3 +1,41 @@ +2010-01-04 Sascha L. Teichmann + + * src/main/java/de/intevation/gnv/jfreechart/PolygonSeriesLabelGenerator.java: + New. Interface to generate labels for polygon series. + + * src/main/java/de/intevation/gnv/jfreechart/PolygonRenderer.java: + Added logic to generate and render labels of polygons. + The implemented layout algorithm is greedy. For all + polygons with labels the label is placed on the center of + the ring, which center in terms of the indices of the vertices. + If the bounding box of the label intersects the bounding box + of a an already placed one alternative places are tried. + In level order positions at 1/4, 3/4, 1/8, 3/8, 5/8, 7/8 and + so on are evaluated for non intersections with former placed labels. + This terminates if a free place is found or all index positions + are exhausted. If no free position is found the label is omitted. + + The visual result is okay but could be improved by a more clever + algorithm e.g. tension reduction in the graph of labels. + + TODO: Improve clipping against chart borders. + + * src/main/java/de/intevation/gnv/jfreechart/LevelOrderIndices.java: + New. Little helper class to generate the level order index + traversal used in the label layout. Placed in the jfreechart + package to keep it clean from gnv dependencies. + + * src/main/java/de/intevation/gnv/jfreechart/PolygonPlot.java: + Added some methods to access the domain and range axis and + there edge position. Mainly C&P from JFreeCahrts XYPlot + to ease the coordinate transformation between Java2D and + the value spaces of the chart. Call the label generation + of the polygon renderer. + + * src/main/java/de/intevation/gnv/chart/VerticalCrossSectionChart.java: + Added an implementation of PolygonSeriesLabelGenerator to + generate localized labels for the iso lines. + 2010-01-03 Sascha L. Teichmann * src/main/java/de/intevation/gnv/chart/VerticalCrossSectionChart.java: diff -r c7ca2fce041f -r 20a480753ff9 gnv-artifacts/src/main/java/de/intevation/gnv/chart/VerticalCrossSectionChart.java --- a/gnv-artifacts/src/main/java/de/intevation/gnv/chart/VerticalCrossSectionChart.java Sun Jan 03 15:57:02 2010 +0000 +++ b/gnv-artifacts/src/main/java/de/intevation/gnv/chart/VerticalCrossSectionChart.java Mon Jan 04 02:49:42 2010 +0000 @@ -2,6 +2,8 @@ import java.util.Locale; +import java.text.NumberFormat; + import java.awt.Color; import java.awt.Paint; @@ -54,6 +56,25 @@ } } // class PalettePaintLookup + public static class LocalizedLabelGenerator + extends PolygonRenderer.DefaultLabelGenerator + { + protected NumberFormat format; + + public LocalizedLabelGenerator() { + } + + public LocalizedLabelGenerator(NumberFormat format) { + this.format = format; + } + + protected String toString(Object label) { + return label instanceof Number + ? format.format(((Number)label).doubleValue()) + : super.toString(label); + } + } // class LocalizedLabelGenerator + protected JFreeChart chart; protected AttributedXYColumns columns; @@ -95,8 +116,13 @@ } } + NumberFormat format = NumberFormat.getInstance(locale); + format.setMinimumFractionDigits(0); + format.setMaximumFractionDigits(2); + PolygonRenderer renderer = new PolygonRenderer( - new PalettePaintLookup(palette)); + new PalettePaintLookup(palette), + new LocalizedLabelGenerator(format)); ValueAxis domainAxis = new NumberAxis(xAxis); ValueAxis rangeAxis = new NumberAxis(yAxis); diff -r c7ca2fce041f -r 20a480753ff9 gnv-artifacts/src/main/java/de/intevation/gnv/jfreechart/LevelOrderIndices.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gnv-artifacts/src/main/java/de/intevation/gnv/jfreechart/LevelOrderIndices.java Mon Jan 04 02:49:42 2010 +0000 @@ -0,0 +1,58 @@ +package de.intevation.gnv.jfreechart; + +import java.util.LinkedList; + +/** + * @author Sascha L. Teichmann (sascha.teichmann@intevation.de) + */ +public class LevelOrderIndices +{ + public interface Visitor { + Object visit(int index); + } + + protected int from; + protected int to; + + public LevelOrderIndices() { + } + + public LevelOrderIndices(int to) { + this(0, to); + } + + public LevelOrderIndices(int from, int to) { + this.from = Math.min(from, to); + this.to = Math.max(from, to); + } + + public Object visit(Visitor visitor) { + LinkedList queue = new LinkedList(); + + queue.add(new int [] { from, to }); + + while (!queue.isEmpty()) { + int [] pair = queue.remove(); + + int mid = (pair[0] + pair[1]) >> 1; + + Object result = visitor.visit(mid); + + if (result != null) { + return result; + } + + if (mid-1 >= pair[0]) { + queue.add(new int [] { pair[0], mid-1 }); + } + + if (mid+1 <= pair[1]) { + pair[0] = mid+1; + queue.add(pair); + } + } + + return null; + } +} +// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 : diff -r c7ca2fce041f -r 20a480753ff9 gnv-artifacts/src/main/java/de/intevation/gnv/jfreechart/PolygonPlot.java --- a/gnv-artifacts/src/main/java/de/intevation/gnv/jfreechart/PolygonPlot.java Sun Jan 03 15:57:02 2010 +0000 +++ b/gnv-artifacts/src/main/java/de/intevation/gnv/jfreechart/PolygonPlot.java Mon Jan 04 02:49:42 2010 +0000 @@ -123,6 +123,25 @@ } } + public ValueAxis getDomainAxis() { + return getDomainAxis(0); + } + + public ValueAxis getDomainAxis(int index) { + return index < domainAxes.size() + ? (ValueAxis)domainAxes.get(index) + : null; + } + + public ValueAxis getRangeAxis() { + return getRangeAxis(0); + } + + public ValueAxis getRangeAxis(int index) { + return index < rangeAxes.size() + ? (ValueAxis)rangeAxes.get(index) + : null; + } public void configureRangeAxis() { // we just have 1 dataset @@ -191,6 +210,7 @@ if (!isEmptyOrNull(dataset)) { // draw data drawPolygons(savedG2, dataArea, info); + drawLabels(savedG2, dataArea, info); } g2.setClip(savedClip); @@ -270,12 +290,20 @@ } + private void drawLabels( + Graphics2D g2, + Rectangle2D area, + PlotRenderingInfo info + ) { + renderer.drawLabels(g2, this, area, dataset); + } + private void drawPolygons( Graphics2D g2, Rectangle2D area, PlotRenderingInfo info ) { - renderer.draw(g2, area, dataset); + renderer.drawPolygons(g2, area, dataset); } @@ -330,14 +358,14 @@ return space; } - private RectangleEdge getDomainAxisEdge() { + public RectangleEdge getDomainAxisEdge() { return Plot.resolveDomainAxisLocation( getDomainAxisLocation(), orientation ); } - private RectangleEdge getDomainAxisEdge(int idx) { + public RectangleEdge getDomainAxisEdge(int idx) { AxisLocation location = getDomainAxisLocation(idx); RectangleEdge result = Plot.resolveDomainAxisLocation( location, orientation @@ -350,14 +378,14 @@ } - private RectangleEdge getRangeAxisEdge() { + public RectangleEdge getRangeAxisEdge() { return Plot.resolveRangeAxisLocation( getRangeAxisLocation(), orientation ); } - private RectangleEdge getRangeAxisEdge(int idx) { + public RectangleEdge getRangeAxisEdge(int idx) { AxisLocation location = getRangeAxisLocation(idx); RectangleEdge result = Plot.resolveRangeAxisLocation( location, diff -r c7ca2fce041f -r 20a480753ff9 gnv-artifacts/src/main/java/de/intevation/gnv/jfreechart/PolygonRenderer.java --- a/gnv-artifacts/src/main/java/de/intevation/gnv/jfreechart/PolygonRenderer.java Sun Jan 03 15:57:02 2010 +0000 +++ b/gnv-artifacts/src/main/java/de/intevation/gnv/jfreechart/PolygonRenderer.java Mon Jan 04 02:49:42 2010 +0000 @@ -1,54 +1,83 @@ package de.intevation.gnv.jfreechart; +import java.util.ArrayList; + import java.awt.Color; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.Shape; import java.awt.BasicStroke; +import java.awt.FontMetrics; +import java.awt.Font; import java.awt.geom.GeneralPath; import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D.Double; +import java.awt.geom.AffineTransform; import org.jfree.data.Range; +import org.jfree.chart.axis.ValueAxis; + +import org.jfree.ui.RectangleEdge; + import org.apache.log4j.Logger; +import org.jfree.text.TextUtilities; + + /** - * @author Ingo Weinzierl + * @author Ingo Weinzierl (ingo.weinzierl@intevation.de) + * @author Sascha L. Teichmann (sascha.teichmann@intevation.de) */ public class PolygonRenderer { private static Logger log = Logger.getLogger( PolygonRenderer.class); - public static final int AREA = 1; - public static final int LINES = 2; - public static final int AREA_AND_LINES = AREA | LINES; - public interface PaintLookup { Paint getPaint(int index); } // interface PaintLookup - protected int type; + public static class DefaultLabelGenerator + implements PolygonSeriesLabelGenerator + { + public DefaultLabelGenerator() { + } - protected PaintLookup lookup; + public String generateLabel(PolygonSeries series) { + Object label = series.getAttribute("label"); + return label != null + ? toString(label) + : null; + } - protected PolygonPlot plot; + protected String toString(Object label) { + return label.toString(); + } + } // class DefaultLabelGenerator + public static final PolygonSeriesLabelGenerator + DEFAULT_LABEL_GENERATOR_INSTANCE = new DefaultLabelGenerator(); + + protected PaintLookup lookup; + protected PolygonSeriesLabelGenerator labelGenerator; public PolygonRenderer(PaintLookup lookup) { - this(lookup, AREA); + this(lookup, null); } - public PolygonRenderer(PaintLookup lookup, int type) { - this.lookup = lookup; - this.type = type; + public PolygonRenderer( + PaintLookup lookup, + PolygonSeriesLabelGenerator labelGenerator + ) { + this.lookup = lookup; + this.labelGenerator = labelGenerator; } - public void draw( + public void drawPolygons( Graphics2D graphics, Rectangle2D rectangle, PolygonDataset dataset @@ -66,6 +95,8 @@ sy = -sy; // mirror } + AffineTransform xform = graphics.getTransform(); + graphics.translate(tx, ty); graphics.scale(sx, sy); @@ -88,6 +119,73 @@ graphics.draw(constructShape(series, false)); } } + + graphics.setTransform(xform); + } + + public void drawLabels( + final Graphics2D graphics, + final PolygonPlot plot, + final Rectangle2D area, + PolygonDataset dataset + ) { + if (labelGenerator == null) { + return; + } + + final ArrayList bboxes = new ArrayList(); + + Font font = graphics.getFont(); + font = font.deriveFont(Font.PLAIN, Math.max(8, font.getSize()-3)); + graphics.setFont(font); + FontMetrics metrics = graphics.getFontMetrics(font); + + for (int i = dataset.getSeriesCount()-1; i >= 0; --i) { + PolygonSeries series = dataset.getSeries(i); + + String label = labelGenerator.generateLabel(series); + if (label == null) { + continue; + } + + final Rectangle2D box = TextUtilities.getTextBounds( + label, graphics, metrics); + + for (int j = series.getItemCount()-1; j >= 0; --j) { + final CompactXYItems ring = series.getItem(j); + LevelOrderIndices loi = new LevelOrderIndices(ring.size()-1); + Rectangle2D r = (Rectangle2D)loi.visit( + new LevelOrderIndices.Visitor() + { + ValueAxis da = plot.getDomainAxis(); + ValueAxis ra = plot.getRangeAxis(); + RectangleEdge de = plot.getDomainAxisEdge(); + RectangleEdge re = plot.getRangeAxisEdge(); + Rectangle2D.Double r = new Rectangle2D.Double( + 0d, 0d, box.getWidth(), box.getHeight()); + + public Object visit(int index) { + r.x = da.valueToJava2D(ring.getX(index), area, de) + - 0.5*box.getWidth(); + r.y = ra.valueToJava2D(ring.getY(index), area, re) + + 0.5*box.getHeight(); + + for (Rectangle2D b: bboxes) { + if (b.intersects(r)) { + return null; + } + } + return r; + } + }); + + if (r != null) { + bboxes.add(r); + graphics.drawString( + label, (float)r.getX(), (float)r.getY()); + } + } // for all items in series + } // for all series } protected Shape constructShape(PolygonSeries series, boolean close) { diff -r c7ca2fce041f -r 20a480753ff9 gnv-artifacts/src/main/java/de/intevation/gnv/jfreechart/PolygonSeriesLabelGenerator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gnv-artifacts/src/main/java/de/intevation/gnv/jfreechart/PolygonSeriesLabelGenerator.java Mon Jan 04 02:49:42 2010 +0000 @@ -0,0 +1,10 @@ +package de.intevation.gnv.jfreechart; + +/** + * @author Sascha L. Teichmann (sascha.teichmann@intevation.de) + */ +public interface PolygonSeriesLabelGenerator +{ + String generateLabel(PolygonSeries series); +} +// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :