changeset 1884:4ae9c92feb8c

StableXYDifferenceRenderer: Make rendering work with definition holes. flys-artifacts/trunk@3243 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Sun, 13 Nov 2011 14:02:35 +0000
parents eb671699fbc2
children 110dd067bb8f
files flys-artifacts/ChangeLog flys-artifacts/src/main/java/de/intevation/flys/artifacts/charts/CrossSectionApp.java flys-artifacts/src/main/java/de/intevation/flys/jfree/StableXYDifferenceRenderer.java
diffstat 3 files changed, 355 insertions(+), 63 deletions(-) [+]
line wrap: on
line diff
--- a/flys-artifacts/ChangeLog	Sat Nov 12 22:26:27 2011 +0000
+++ b/flys-artifacts/ChangeLog	Sun Nov 13 14:02:35 2011 +0000
@@ -1,3 +1,17 @@
+2011-11-13	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/charts/CrossSectionApp.java:
+	  Added system properties 'waterlevel' and 'km'. Useful to
+	  init the UI with a given waterlevel and drawing the cross-sections
+	  at the given km.
+
+	* src/main/java/de/intevation/flys/jfree/StableXYDifferenceRenderer.java:
+	  Spliting by NaNs definition holes _should_ work now. Needs 
+	  some more testing.
+	  TODOs: 
+	  - Use log4j instead of println for logging.
+	  - Subclass XYDifferenceRenderer instead of replacing it totally.
+
 2011-11-12	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
 
 	* src/main/java/de/intevation/flys/artifacts/charts/CrossSectionApp.java:
--- a/flys-artifacts/src/main/java/de/intevation/flys/artifacts/charts/CrossSectionApp.java	Sat Nov 12 22:26:27 2011 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/charts/CrossSectionApp.java	Sun Nov 13 14:02:35 2011 +0000
@@ -83,7 +83,11 @@
 {
     public static final String RIVER = System.getProperty("river", "Saar");
 
-    public static final double EPSILON   = 1e-4;
+    public static final String WATER_LEVEL = System.getProperty("waterlevel");
+
+    public static final String KM = System.getProperty("km");
+
+    public static final double EPSILON = 1e-4;
 
     protected Session session;
 
@@ -171,9 +175,9 @@
         public Class<?> getColumnClass(int columnIndex) {
             switch (columnIndex) {
                 case 0: return String.class;
-                case 1: return Boolean.class;
-                case 2: return Boolean.class;
-                case 3: return Boolean.class;
+                case 1:
+                case 2:
+                case 3: 
                 case 4: return Boolean.class;
             }
             return null;
@@ -238,16 +242,16 @@
         return query.list();
     }
 
-    protected Map<Double, List<Pair<CrossSection, CrossSectionLine>>> loadAllLines(
-        List<CrossSection> crossSections
-    ) {
+    protected Map<Double, List<Pair<CrossSection, CrossSectionLine>>>
+        loadAllLines(List<CrossSection> crossSections) {
         Map<Double, List<Pair<CrossSection, CrossSectionLine>>> km2lines =
             new TreeMap<Double, List<Pair<CrossSection, CrossSectionLine>>>();
         for (CrossSection cs: crossSections) {
             List<CrossSectionLine> lines = cs.getLines();
             for (CrossSectionLine csl: lines) {
                 Double km = Math.round(csl.getKm().doubleValue() * 1000d)/1000d;
-                List<Pair<CrossSection, CrossSectionLine>> ls = km2lines.get(km);
+                List<Pair<CrossSection, CrossSectionLine>> ls
+                    = km2lines.get(km);
                 if (ls == null) {
                     ls = new ArrayList<Pair<CrossSection, CrossSectionLine>>(2);
                     km2lines.put(km, ls);
@@ -279,6 +283,30 @@
 
         crossSectionLinesCB = new JComboBox(dcbm);
 
+        if (KM != null) {
+            try {
+                double km = Double.parseDouble(KM);
+
+                CrossSectionLineItem found = null;
+
+                for (Object o: clis) {
+                    CrossSectionLineItem csli = (CrossSectionLineItem)o;
+                    if (Math.abs(csli.km - km) < EPSILON) {
+                        found = csli;
+                        break;
+                    }
+                }
+
+                if (found != null) {
+                    crossSectionLinesCB.setSelectedItem(found);
+                }
+            }
+            catch (NumberFormatException nfe) {
+                System.err.println("km is not a number: "
+                    + nfe.getMessage());
+            }
+        }
+
         nav.add(crossSectionLinesCB);
 
         crossSectionLinesCB.addItemListener(new ItemListener() {
@@ -292,6 +320,17 @@
 
         waterlevelTF = new JTextField(5);
 
+        if (WATER_LEVEL != null) {
+            try {
+                waterlevelTF.setText(
+                    (lastWaterLevel = Double.valueOf(WATER_LEVEL)).toString());
+            }
+            catch (NumberFormatException nfe) {
+                System.err.println("Water level not a number: " +
+                    nfe.getMessage());
+            }
+        }
+
         waterlevelTF.addActionListener(new ActionListener() {
             @Override
             public void actionPerformed(ActionEvent ae) {
@@ -343,7 +382,7 @@
     protected void waterLevelChanged() {
         String value = waterlevelTF.getText();
         try {
-            lastWaterLevel = Double.parseDouble(value);
+            lastWaterLevel = Double.valueOf(value);
         }
         catch (NumberFormatException nfe) {
             waterlevelTF.setText(
@@ -528,6 +567,21 @@
             List<Point2D> points = null;
             CrossSection cs = crossSections.get(i);
 
+            if (drawGround[i]) {
+                for (Pair<CrossSection, CrossSectionLine> csl: csli.lines) {
+                    if (csl.getA() == cs) {
+                        if (points == null) {
+                            points = csl.getB().fetchCrossSectionLinesPoints();
+                        }
+                        generateGround(
+                            points,
+                            cs.getDescription() + "/Boden",
+                            datasets);
+                        break;
+                    }
+                }
+            }
+
             if (drawFill[i]) {
                 for (Pair<CrossSection, CrossSectionLine> csl: csli.lines) {
                     if (csl.getA() == cs) {
@@ -568,20 +622,6 @@
                 }
             }
 
-            if (drawGround[i]) {
-                for (Pair<CrossSection, CrossSectionLine> csl: csli.lines) {
-                    if (csl.getA() == cs) {
-                        if (points == null) {
-                            points = csl.getB().fetchCrossSectionLinesPoints();
-                        }
-                        generateGround(
-                            points,
-                            cs.getDescription() + "/Boden",
-                            datasets);
-                        break;
-                    }
-                }
-            }
         }
 
         return datasets;
--- a/flys-artifacts/src/main/java/de/intevation/flys/jfree/StableXYDifferenceRenderer.java	Sat Nov 12 22:26:27 2011 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/jfree/StableXYDifferenceRenderer.java	Sun Nov 13 14:02:35 2011 +0000
@@ -116,6 +116,8 @@
 
 import gnu.trove.TDoubleArrayList;
 
+import de.intevation.flys.artifacts.math.Linear;
+
 /**
  * A renderer for an {@link XYPlot} that highlights the differences between two
  * series.  The example shown here is generated by the
@@ -364,56 +366,292 @@
         return 2;
     }
 
-    public static List<XYDataset> splitByNaNs(XYDataset dataset)  {
+    private static final void addSeries(
+        DefaultXYDataset ds,
+        Comparable       key,
+        TDoubleArrayList xs,
+        TDoubleArrayList ys
+    ) {
+        ds.addSeries(
+            key,
+            new double [][] {
+                xs.toNativeArray(),
+                ys.toNativeArray()
+            });
+    }
+
+    protected static List<XYDataset> splitByNaNsOneSeries(
+        XYDataset dataset
+    ) {
         List<XYDataset> datasets = new ArrayList<XYDataset>();
 
-        switch (dataset.getSeriesCount()) {
-            case 0:
-                return datasets;
-            case 1:
-                int N = dataset.getItemCount(0);
-                TDoubleArrayList xs = new TDoubleArrayList(N);
-                TDoubleArrayList ys = new TDoubleArrayList(N);
-                for (int i = 0; i < N; ++i) {
-                    double x = dataset.getXValue(0, i);
-                    double y = dataset.getYValue(0, i);
-                    if (Double.isNaN(x) || Double.isNaN(y)) {
-                        if (!xs.isEmpty()) {
-                            DefaultXYDataset ds = new DefaultXYDataset();
-                            datasets.add(ds);
-                            ds.addSeries(
-                                dataset.getSeriesKey(0),
-                                new double [][] {
-                                    xs.toNativeArray(),
-                                    ys.toNativeArray() });
-                            xs.reset();
-                            ys.reset();
-                        }
-                    }
-                    else {
-                        xs.add(x);
-                        ys.add(y);
-                    }
-                }
+        int N = dataset.getItemCount(0);
+        TDoubleArrayList xs = new TDoubleArrayList(N);
+        TDoubleArrayList ys = new TDoubleArrayList(N);
+        for (int i = 0; i < N; ++i) {
+            double x = dataset.getXValue(0, i);
+            double y = dataset.getYValue(0, i);
+            if (Double.isNaN(x) || Double.isNaN(y)) {
                 if (!xs.isEmpty()) {
                     DefaultXYDataset ds = new DefaultXYDataset();
+                    addSeries(ds, dataset.getSeriesKey(0), xs, ys);
                     datasets.add(ds);
-                    ds.addSeries(
-                        dataset.getSeriesKey(0),
-                        new double [][] {
-                            xs.toNativeArray(),
-                            ys.toNativeArray() });
+                    xs.reset();
+                    ys.reset();
                 }
-                break;
-            default: // two or more
-                // TODO: split two parts
-                datasets.add(dataset);
-                break;
+            }
+            else {
+                xs.add(x);
+                ys.add(y);
+            }
+        }
+        if (!xs.isEmpty()) {
+            DefaultXYDataset ds = new DefaultXYDataset();
+            addSeries(ds, dataset.getSeriesKey(0), xs, ys);
+            datasets.add(ds);
         }
 
         return datasets;
     }
 
+    private static final boolean add(TDoubleArrayList xs, double x) {
+        int N = xs.size();
+        if (N == 0 || xs.getQuick(N-1) < x) {
+            xs.add(x);
+            return true;
+        }
+        System.err.println("pushed smaller");
+        return false;
+    }
+
+    protected static List<XYDataset> splitByNaNsTwoSeries(
+        XYDataset dataset
+    ) {
+        List<XYDataset> datasets = new ArrayList<XYDataset>();
+
+        int N = dataset.getItemCount(0);
+        int M = dataset.getItemCount(1);
+
+        int i = 0, j = 0;
+        // ignore leading NaNs
+        for (; i < N; ++i) {
+            double x = dataset.getXValue(0, i);
+            double y = dataset.getYValue(0, i);
+            if (!Double.isNaN(x) && !Double.isNaN(y)) {
+                break;
+            }
+        }
+
+        for (; j < M; ++j) {
+            double x = dataset.getXValue(1, j);
+            double y = dataset.getYValue(1, j);
+            if (!Double.isNaN(x) && !Double.isNaN(y)) {
+                break;
+            }
+        }
+
+        TDoubleArrayList six = new TDoubleArrayList();
+        TDoubleArrayList siy = new TDoubleArrayList();
+        TDoubleArrayList sjx = new TDoubleArrayList();
+        TDoubleArrayList sjy = new TDoubleArrayList();
+
+        while (i < N && j < M) {
+            int ni = i+1;
+            for (; ni < N && !Double.isNaN(dataset.getXValue(0, ni)); ++ni);
+            for (; ni < N &&  Double.isNaN(dataset.getXValue(0, ni)); ++ni);
+
+            int nj = j+1;
+            for (; nj < M && !Double.isNaN(dataset.getXValue(1, nj)); ++nj);
+            for (; nj < M &&  Double.isNaN(dataset.getXValue(1, nj)); ++nj);
+
+            if (ni == N && nj == M) { // no more splits
+                System.err.println("no more splits ....");
+                for (; i < ni; ++i) {
+                    double x = dataset.getXValue(0, i);
+                    double y = dataset.getYValue(0, i);
+                    if (!Double.isNaN(x) 
+                    &&  !Double.isNaN(y)
+                    &&  add(six, x)) {
+                        siy.add(y);
+                    }
+                }
+                for (; j < nj; ++j) {
+                    double x = dataset.getXValue(1, j);
+                    double y = dataset.getYValue(1, j);
+                    if (!Double.isNaN(x)
+                    &&  !Double.isNaN(y)
+                    &&  add(sjx, x)) {
+                        sjy.add(y);
+                    }
+                }
+                if (!six.isEmpty() && !sjx.isEmpty()) {
+                    DefaultXYDataset ds = new DefaultXYDataset();
+                    addSeries(ds, dataset.getSeriesKey(0), six, siy);
+                    addSeries(ds, dataset.getSeriesKey(1), sjx, sjy);
+                    datasets.add(ds);
+                }
+                break;
+            }
+
+            System.err.println("ni: " + ni + " " + N);
+            System.err.println("nj: " + nj + " " + M);
+
+            double xni = ni < N
+                ? dataset.getXValue(0, ni)
+                : Double.MAX_VALUE;
+
+            double xnj = nj < M
+                ? dataset.getXValue(1, nj)
+                : Double.MAX_VALUE;
+
+            double xns = Math.min(xni, xnj);
+
+            double pushxi = Double.NaN;
+            double pushyi = Double.NaN;
+            double pushxj = Double.NaN;
+            double pushyj = Double.NaN;
+
+            for (; i < ni; ++i) {
+                double x = dataset.getXValue(0, i);
+                double y = dataset.getYValue(0, i);
+                if (Double.isNaN(x) || Double.isNaN(y)) {
+                    continue;
+                }
+                if (x < xns) {
+                    if (add(six, x)) {
+                        siy.add(y);
+                    }
+                    continue;
+                }
+                if (x == xns) { // exact match
+                    if (add(six, x)) {
+                        siy.add(y);
+                    }
+                    pushxi = x; pushyi = y;
+                }
+                else { // x > xns: intersection
+                    System.err.println("xns: " + xns);
+                    System.err.println("x/y: " + x + " / " + y);
+                    int SIX = six.size();
+                    if (SIX > 0) { // should always be true
+                        double yns = Linear.linear(
+                            xns,
+                            six.getQuick(SIX-1), x, 
+                            siy.getQuick(SIX-1), y);
+                        System.err.println("intersection at: " + yns);
+                        if (add(six, xns)) {
+                            siy.add(yns);
+                        }
+                        pushxi = xns;
+                        pushyi = yns;
+                    }
+                }
+                break; // Split point reached.
+            }
+
+            for (; j < nj; ++j) {
+                double x = dataset.getXValue(1, j);
+                double y = dataset.getYValue(1, j);
+                if (Double.isNaN(x) || Double.isNaN(y)) {
+                    continue;
+                }
+                if (x < xns) {
+                    if (add(sjx, x)) {
+                        sjy.add(y);
+                    }
+                    continue;
+                }
+                if (x == xns) { // exact match
+                    if (add(sjx, x)) {
+                        sjy.add(y);
+                    }
+                    pushxj = x; pushyj = y;
+                }
+                else { // x > xns: intersection
+                    int SJX = sjx.size();
+                    if (SJX > 0) { // should always be true
+                        double yns = Linear.linear(
+                            xns,
+                            sjx.getQuick(SJX-1), x,
+                            sjy.getQuick(SJX-1), y);
+                        System.err.println("intersection at: " + yns);
+                        if (add(sjx, xns)) {
+                            sjy.add(yns);
+                        }
+                        pushxj = xns; pushyj = yns;
+                    }
+                }
+                break; // Split point reached.
+            }
+
+            if (!six.isEmpty() && !sjx.isEmpty()) {
+                DefaultXYDataset ds = new DefaultXYDataset();
+                addSeries(ds, dataset.getSeriesKey(0), six, siy);
+                addSeries(ds, dataset.getSeriesKey(1), sjx, sjy);
+                datasets.add(ds);
+            }
+
+            six.reset(); siy.reset();
+            sjx.reset(); sjy.reset();
+
+            // Push split points.
+            if (!Double.isNaN(pushxi)) {
+                six.add(pushxi);
+                siy.add(pushyi);
+            }
+
+            if (!Double.isNaN(pushxj)) {
+                sjx.add(pushxj);
+                sjy.add(pushyj);
+            }
+        }
+
+        // Copy the rest.
+        for (; i < N; ++i) {
+            double x = dataset.getXValue(0, i);
+            double y = dataset.getXValue(0, i);
+            if (!Double.isNaN(x) 
+            &&  !Double.isNaN(y)
+            &&  add(six, x)) {
+                siy.add(y);
+            }
+        }
+
+        for (; j < M; ++j) {
+            double x = dataset.getXValue(1, j);
+            double y = dataset.getXValue(1, j);
+            if (!Double.isNaN(x)
+            &&  !Double.isNaN(y)
+            &&  add(sjx, x)) {
+                sjy.add(y);
+            }
+        }
+
+        // Build final dataset.
+        if (!six.isEmpty() && !sjx.isEmpty()) {
+            DefaultXYDataset ds = new DefaultXYDataset();
+            addSeries(ds, dataset.getSeriesKey(0), six, siy);
+            addSeries(ds, dataset.getSeriesKey(1), sjx, sjy);
+            datasets.add(ds);
+        }
+
+        System.err.println("datasets after split: " + datasets.size());
+
+        return datasets;
+    }
+
+    public static List<XYDataset> splitByNaNs(XYDataset dataset)  {
+
+        switch (dataset.getSeriesCount()) {
+            case 0: 
+                return Collections.emptyList();
+            case 1:
+                return splitByNaNsOneSeries(dataset);
+            default: // two or more
+                return splitByNaNsTwoSeries(dataset);
+        }
+    }
+
     /**
      * Draws the visual representation of a single data item.
      *
@@ -449,7 +687,7 @@
                 for (XYDataset ds: splitByNaNs(dataset)) {
                     drawItemPass0(g2, dataArea, info, 
                         plot, domainAxis, rangeAxis,
-                        dataset, series, item, crosshairState);
+                        ds, series, item, crosshairState);
                 }
                 break;
             case 1:

http://dive4elements.wald.intevation.org