diff artifacts/src/main/java/org/dive4elements/river/jfree/EnhancedLineAndShapeRenderer.java @ 5838:5aa05a7a34b7

Rename modules to more fitting names.
author Sascha L. Teichmann <teichmann@intevation.de>
date Thu, 25 Apr 2013 15:23:37 +0200
parents flys-artifacts/src/main/java/org/dive4elements/river/jfree/EnhancedLineAndShapeRenderer.java@bd047b71ab37
children 4897a58c8746
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/jfree/EnhancedLineAndShapeRenderer.java	Thu Apr 25 15:23:37 2013 +0200
@@ -0,0 +1,576 @@
+package org.dive4elements.river.jfree;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.Paint;
+import java.awt.Shape;
+import java.awt.geom.Rectangle2D;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.chart.entity.EntityCollection;
+import org.jfree.chart.plot.CrosshairState;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
+import org.jfree.data.xy.XYDataset;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+import org.jfree.text.TextUtilities;
+import org.jfree.ui.RectangleEdge;
+import org.jfree.ui.TextAnchor;
+import org.jfree.util.BooleanList;
+import org.jfree.util.ShapeUtilities;
+
+/**
+ * Renderer with additional the additional functionality of renderering minima
+ * and/or maxima of dataseries contained in datasets.
+ */
+public class EnhancedLineAndShapeRenderer extends XYLineAndShapeRenderer {
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 1L;
+
+    /** Own logger. */
+    private static final Logger logger =
+        Logger.getLogger(EnhancedLineAndShapeRenderer.class);
+
+    protected BooleanList isMinimumShapeVisible;
+    protected BooleanList isMaximumShapeVisible;
+    protected BooleanList showLineLabel;
+
+    protected Map<Integer, Double> seriesMinimum;
+    protected Map<Integer, Double> seriesMinimumX;
+    protected Map<Integer, Double> seriesMaximum;
+
+    protected Map<Integer, Font> lineLabelFonts;
+    protected Map<Integer, Color> lineLabelTextColors;
+    protected BooleanList showLineLabelBG;
+    protected Map<Integer, Color> lineLabelBGColors;
+
+
+    public EnhancedLineAndShapeRenderer(boolean lines, boolean shapes) {
+        super(lines, shapes);
+        this.isMinimumShapeVisible = new BooleanList();
+        this.isMaximumShapeVisible = new BooleanList();
+        this.showLineLabel         = new BooleanList();
+        this.showLineLabelBG       = new BooleanList();
+        this.seriesMinimum         = new HashMap<Integer, Double>();
+        this.seriesMaximum         = new HashMap<Integer, Double>();
+        this.seriesMinimumX        = new HashMap<Integer, Double>();
+        this.lineLabelFonts        = new HashMap<Integer, Font>();
+        this.lineLabelTextColors   = new HashMap<Integer, Color>();
+        this.lineLabelBGColors     = new HashMap<Integer, Color>();
+    }
+
+
+    /**
+     * Draw a background-box of a text to render.
+     * @param g2 graphics device to use
+     * @param text text to draw
+     * @param textX x-position for text
+     * @param textY y-position for text
+     * @param bgColor color to fill box with.
+     */
+    public static void drawTextBox(Graphics2D g2,
+        String text, float textX, float textY, Color bgColor
+    ) {
+        Rectangle2D hotspotBox = g2.getFontMetrics().getStringBounds(text, g2);
+        float w = (float) hotspotBox.getWidth(), h = (float) hotspotBox.getHeight();
+        hotspotBox.setRect(textX, textY-h, w, h);
+        Color oldColor = g2.getColor();
+        g2.setColor(bgColor);
+        g2.fill(hotspotBox);
+        g2.setColor(oldColor);
+    }
+
+
+    /**
+     * Whether or not a specific item in a series (maybe the maxima) should
+     * be rendered with shape.
+     */
+    public boolean getItemShapeVisible(XYDataset dataset, int series, int item){
+        if (super.getItemShapeVisible(series, item)) {
+            return true;
+        }
+
+        if (isMinimumShapeVisible(series) && isMinimum(dataset, series, item)) {
+            return true;
+        }
+
+        if (isMaximumShapeVisible(series) && isMaximum(dataset, series, item)) {
+            return true;
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Rectangle used to draw maximums shape.
+     */
+    public Shape getMaximumShape(int series, int column) {
+        return new Rectangle2D.Double(-5d, -5d, 10d, 10d);
+    }
+
+
+    /**
+     * Rectangle used to draw minimums shape.
+     */
+    public Shape getMinimumShape(int series, int column) {
+        return new Rectangle2D.Double(-5d, -5d, 10d, 10d);
+    }
+
+
+    /** Get fill paint for the maximum indicators. */
+    public Paint getMaximumFillPaint(int series, int column) {
+        Paint p = getItemPaint(series, column);
+
+        if (p instanceof Color) {
+            Color c = (Color) p;
+            Color b = c;
+
+            for (int i = 0; i < 2; i++) {
+                b = b.darker();
+            }
+
+            return b;
+        }
+
+        logger.warn("Item paint is no instance of Color!");
+        return p;
+    }
+
+
+    /** Get fill paint for the minimum indicators. */
+    public Paint getMinimumFillPaint(int series, int column) {
+        Paint p = getItemPaint(series, column);
+
+        if (p instanceof Color) {
+            Color c = (Color) p;
+            Color b = c;
+
+            for (int i = 0; i < 2; i++) {
+                b = b.darker();
+            }
+
+            return b;
+        }
+
+        logger.warn("Item paint is no instance of Color!");
+        return p;
+    }
+
+
+    /**
+     * Overrides XYLineAndShapeRenderer.drawSecondaryPass() to call an adapted
+     * method getItemShapeVisible() which now takes an XYDataset. So, 99% of
+     * code equal the code in XYLineAndShapeRenderer.
+     */
+    @Override
+    protected void drawSecondaryPass(
+        Graphics2D       g2,
+        XYPlot           plot,
+        XYDataset        dataset,
+        int              pass,
+        int              series,
+        int              item,
+        ValueAxis        domainAxis,
+        Rectangle2D      dataArea,
+        ValueAxis        rangeAxis,
+        CrosshairState   crosshairState,
+        EntityCollection entities
+    ) {
+        Shape entityArea = null;
+
+        // get the data point...
+        double x1 = dataset.getXValue(series, item);
+        double y1 = dataset.getYValue(series, item);
+        if (Double.isNaN(y1) || Double.isNaN(x1)) {
+            return;
+        }
+
+        PlotOrientation orientation = plot.getOrientation();
+        RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
+        RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
+        double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
+        double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
+
+        if (getItemShapeVisible(dataset, series, item)) {
+            Shape shape = null;
+
+            // OPTIMIZE: instead of calculating minimum and maximum for every
+            //           point, calculate it just once (assume that dataset
+            //           content does not change during rendering).
+            // NOTE:     Above OPTIMIZE might already be fulfilled to most extend.
+            boolean isMinimum = isMinimumShapeVisible(series)
+                && isMinimum(dataset, series, item);
+
+            boolean isMaximum = isMaximumShapeVisible(series)
+                && isMaximum(dataset, series, item);
+
+            if (isMinimum) {
+                logger.debug("Create a Minimum shape.");
+                shape = getMinimumShape(series, item);
+            }
+            else if (isMaximum) {
+                logger.debug("Create a Maximum shape.");
+                shape = getMaximumShape(series, item);
+            }
+            else {
+                shape = getItemShape(series, item);
+            }
+
+            if (orientation == PlotOrientation.HORIZONTAL) {
+                shape = ShapeUtilities.createTranslatedShape(shape, transY1,
+                        transX1);
+            }
+            else if (orientation == PlotOrientation.VERTICAL) {
+                shape = ShapeUtilities.createTranslatedShape(shape, transX1,
+                        transY1);
+            }
+            entityArea = shape;
+            if (shape.intersects(dataArea)) {
+                if (getItemShapeFilled(series, item)) {
+                    if (getUseFillPaint()) {
+                        g2.setPaint(getItemFillPaint(series, item));
+                    }
+                    else {
+                        g2.setPaint(getItemPaint(series, item));
+                    }
+                    g2.fill(shape);
+                }
+                if (getDrawOutlines()) {
+                    if (getUseOutlinePaint()) {
+                        g2.setPaint(getItemOutlinePaint(series, item));
+                    }
+                    else {
+                        g2.setPaint(getItemPaint(series, item));
+                    }
+                    g2.setStroke(getItemOutlineStroke(series, item));
+                    g2.draw(shape);
+                }
+
+                if (isMinimum) {
+                    g2.setPaint(getMinimumFillPaint(series, item));
+                    g2.fill(shape);
+                    g2.setPaint(getItemOutlinePaint(series, item));
+                    g2.setStroke(getItemOutlineStroke(series, item));
+                    g2.draw(shape);
+                }
+                else if (isMaximum) {
+                    g2.setPaint(getMaximumFillPaint(series, item));
+                    g2.fill(shape);
+                    g2.setPaint(getItemOutlinePaint(series, item));
+                    g2.setStroke(getItemOutlineStroke(series, item));
+                    g2.draw(shape);
+                }
+            }
+        } // if (getItemShapeVisible(dataset, series, item))
+
+        double xx = transX1;
+        double yy = transY1;
+        if (orientation == PlotOrientation.HORIZONTAL) {
+            xx = transY1;
+            yy = transX1;
+        }
+
+        // Draw the item label if there is one...
+        if (isItemLabelVisible(series, item)) {
+            drawItemLabel(g2, orientation, dataset, series, item, xx, yy,
+                    (y1 < 0.0));
+        }
+
+        // Draw label of line.
+        if (dataset instanceof XYSeriesCollection
+            && isShowLineLabel(series)
+            && isMinimumX (dataset, series, item)
+            ) {
+            XYSeries xYSeries = ((XYSeriesCollection) dataset).getSeries(series);
+            String waterlevelLabel = (xYSeries instanceof HasLabel)
+                ? ((HasLabel)xYSeries).getLabel()
+                : xYSeries.getKey().toString();
+            // TODO Force water of some German rivers to flow direction mountains.
+
+            Font oldFont = g2.getFont();
+
+            Color oldColor = g2.getColor();
+            g2.setFont(this.getLineLabelFont(series));
+            g2.setColor(this.getLineLabelTextColor(series));
+            g2.setBackground(Color.black);
+
+            // Try to always display label if the data is visible.
+            if (!isPointInRect(dataArea, xx, yy)) {
+                // Move into the data area.
+                xx = Math.max(xx, dataArea.getMinX());
+                xx = Math.min(xx, dataArea.getMaxX());
+                yy = Math.max(yy, dataArea.getMinY());
+                yy = Math.min(yy, dataArea.getMaxY());
+            }
+
+            // Move to right until no collisions exist anymore
+            Shape hotspot = TextUtilities.calculateRotatedStringBounds(
+                waterlevelLabel, g2, (float)xx, (float)yy-3f,
+                TextAnchor.CENTER_LEFT,
+                0f, TextAnchor.CENTER_LEFT);
+            while (JFreeUtil.collides(hotspot, entities,
+                                      CollisionFreeLineLabelEntity.class)) {
+                xx += 5f;
+                hotspot = TextUtilities.calculateRotatedStringBounds(
+                    waterlevelLabel, g2, (float)xx, (float)yy-3f, TextAnchor.CENTER_LEFT,
+                    0f, TextAnchor.CENTER_LEFT);
+            }
+
+            // Register to avoid collissions.
+            entities.add(new CollisionFreeLineLabelEntity(hotspot,
+                1, "", ""));
+
+            // Fill background.
+            if (isShowLineLabelBG(series)) {
+                drawTextBox(g2, waterlevelLabel, (float)xx, (float)yy-3f,
+                    getLineLabelBGColor(series));
+            }
+
+            g2.drawString(waterlevelLabel, (float)xx, (float)yy-3f);
+
+            g2.setFont(oldFont);
+            g2.setColor(oldColor);
+        }
+
+        int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
+        int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
+        updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex,
+                rangeAxisIndex, transX1, transY1, orientation);
+
+        // Add an entity for the item, but only if it falls within the data
+        // area...
+        if (entities != null && isPointInRect(dataArea, xx, yy)) {
+            addEntity(entities, entityArea, dataset, series, item, xx, yy);
+        }
+    }
+
+
+    /**
+     * Sets whether or not the minimum should be rendered with shape.
+     */
+    public void setIsMinimumShapeVisisble(int series, boolean isVisible) {
+        this.isMinimumShapeVisible.setBoolean(series, isVisible);
+    }
+
+
+    /**
+     * Whether or not the minimum should be rendered with shape.
+     */
+    public boolean isMinimumShapeVisible(int series) {
+        if (this.isMinimumShapeVisible.size() <= series) {
+            return false;
+        }
+
+        return isMinimumShapeVisible.getBoolean(series);
+    }
+
+
+    /**
+     * Sets whether or not the maximum should be rendered with shape.
+     */
+    public void setIsMaximumShapeVisible(int series, boolean isVisible) {
+        this.isMaximumShapeVisible.setBoolean(series, isVisible);
+    }
+
+
+    /**
+     * Whether or not the maximum should be rendered with shape.
+     */
+    public boolean isMaximumShapeVisible(int series) {
+        if (this.isMaximumShapeVisible.size() <= series) {
+            return false;
+        }
+
+        return isMaximumShapeVisible.getBoolean(series);
+    }
+
+    /** Whether or not a label should be shown for series. */
+    public boolean isShowLineLabel(int series) {
+        if (this.showLineLabel.size() <= series) {
+            return false;
+        }
+
+        return showLineLabel.getBoolean(series);
+    }
+
+
+    /** Sets whether or not a label should be shown for series. */
+    public void setShowLineLabel(boolean showLineLabel, int series) {
+        this.showLineLabel.setBoolean(series, showLineLabel);
+    }
+
+
+    /** Whether or not a label should be shown for series. */
+    public boolean isShowLineLabelBG(int series) {
+        if (this.showLineLabelBG.size() <= series) {
+            return false;
+        }
+
+        return showLineLabelBG.getBoolean(series);
+    }
+
+
+    public void setShowLineLabelBG(int series, boolean doShow) {
+        this.showLineLabelBG.setBoolean(series, doShow);
+    }
+
+    public Color getLineLabelBGColor(int series) {
+        if (this.lineLabelBGColors.size() <= series) {
+            return null;
+        }
+
+        return this.lineLabelBGColors.get(series);
+    }
+
+    public void setLineLabelBGColor(int series, Color color) {
+        this.lineLabelBGColors.put(series, color);
+    }
+
+    public Color getLineLabelTextColor(int series) {
+        if (this.lineLabelTextColors.size() <= series) {
+            return null;
+        }
+
+        return this.lineLabelTextColors.get(series);
+    }
+
+    public void setLineLabelTextColor(int series, Color color) {
+        this.lineLabelTextColors.put(series, color);
+    }
+
+    public void setLineLabelFont(Font font, int series) {
+        this.lineLabelFonts.put(series, font);
+    }
+
+    public Font getLineLabelFont(int series) {
+        return this.lineLabelFonts.get(series);
+    }
+
+
+    /**
+     * True if the given item of given dataset has the smallest
+     * X value within this set.
+     */
+    public boolean isMinimumX(XYDataset dataset, int series, int item) {
+        return dataset.getXValue(series, item) == getMinimumX(dataset, series);
+    }
+
+
+    /**
+     * Get Minimum X Value of a given series in a dataset.
+     * The value is stored for later use if queried the first time.
+     */
+    public double getMinimumX(XYDataset dataset, int series) {
+        Integer key = Integer.valueOf(series);
+        Double  old = seriesMinimumX.get(key);
+
+        if (old != null) {
+            return old.doubleValue();
+        }
+
+        logger.debug("Compute minimum of Series: " + series);
+
+        double min = Double.MAX_VALUE;
+
+        for (int i = 0, n = dataset.getItemCount(series); i < n; i++) {
+            double tmpValue = dataset.getXValue(series, i);
+
+            if (tmpValue < min) {
+                min = tmpValue;
+            }
+        }
+
+        seriesMinimumX.put(key, Double.valueOf(min));
+
+        return min;
+    }
+
+
+    /**
+     * True if the given item of given dataset has the smallest
+     * Y value within this set.
+     */
+    public boolean isMinimum(XYDataset dataset, int series, int item) {
+        return dataset.getYValue(series, item) == getMinimum(dataset, series);
+    }
+
+
+    /**
+     * Get Minimum Y Value of a given series in a dataset.
+     * The value is stored for later use if queried the first time.
+     */
+    public double getMinimum(XYDataset dataset, int series) {
+        Integer key = Integer.valueOf(series);
+        Double  old = seriesMinimum.get(key);
+
+        if (old != null) {
+            return old.doubleValue();
+        }
+
+        logger.debug("Compute minimum of Series: " + series);
+
+        double min = Double.MAX_VALUE;
+
+        for (int i = 0, n = dataset.getItemCount(series); i < n; i++) {
+            double tmpValue = dataset.getYValue(series, i);
+
+            if (tmpValue < min) {
+                min = tmpValue;
+            }
+        }
+
+        seriesMinimum.put(key, Double.valueOf(min));
+
+        return min;
+    }
+
+
+    /**
+     * True if the given item of given dataset has the biggest
+     * Y value within this set.
+     */
+    public boolean isMaximum(XYDataset dataset, int series, int item) {
+        return dataset.getYValue(series, item) == getMaximum(dataset, series);
+    }
+
+
+    /**
+     * Get maximum Y Value of a given series in a dataset.
+     * The value is stored for later use if queried the first time.
+     */
+    public double getMaximum(XYDataset dataset, int series) {
+        Integer key = Integer.valueOf(series);
+        Double  old = seriesMaximum.get(key);
+
+        if (old != null) {
+            return old.doubleValue();
+        }
+
+        logger.debug("Compute maximum of Series: " + series);
+
+        double max = -Double.MAX_VALUE;
+
+        for (int i = 0, n = dataset.getItemCount(series); i < n; i++) {
+            double tmpValue = dataset.getYValue(series, i);
+
+            if (tmpValue > max) {
+                max = tmpValue;
+            }
+        }
+
+        seriesMaximum.put(key, Double.valueOf(max));
+
+        return max;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org