All Downloads are FREE. Search and download functionalities are using the official Maven repository.

smile.plot.swing.Contour Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2010-2021 Haifeng Li. All rights reserved.
 *
 * Smile is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Smile is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Smile.  If not, see .
 */

package smile.plot.swing;

import java.util.ArrayList;
import java.util.List;
import smile.math.MathEx;

/**
 * A contour plot is a graphical technique for representing a 3-dimensional
 * surface by plotting constant z slices, called contours, on a 2-dimensional
 * format. That is, given a value for z, lines are drawn for connecting the
 * (x, y) coordinates where that z value occurs. The contour plot is an
 * alternative to a 3-D surface plot.
 * 
 * @author Haifeng Li
 */
public class Contour extends Plot {
    /**
     * The x coordinate of surface.
     */
    private double[] x;
    /**
     * The y coordinate of surface.
     */
    private double[] y;
    /**
     * The two-dimensional surface.
     */
    private double[][] z;
    /**
     * Minimum of data matrix.
     */
    private double zMin;
    /**
     * Maximum of data matrix.
     */
    private double zMax;
    /**
     * Plot contours in logarithmic scale.
     */
    private boolean logScale = false;
    /**
     * The number of contours.
     */
    private int numLevels = 10;
    /**
     * The contour level values.
     */
    private double[] levels;
    /**
     * The set of contours.
     */
    private List contours;
    /**
     * If show axis ticks.
     */
    private boolean isTickVisible;
    /**
     * Show the level.
     */
    private boolean isLevelVisible = true;

    /**
     * Constructor.
     * @param z the data matrix to create contour plot.
     * @param numLevels the number of contour levels.
     * @param logScale true to interpolate contour levels in logarithmic scale.
     */
    public Contour(double[][] z, int numLevels, boolean logScale) {
        this.z = z;
        this.numLevels = numLevels;
        this.logScale = logScale;
        init();
    }

    /**
     * Constructor.
     * @param z the data matrix to create contour plot.
     * @param levels the level values of contours.
     */
    public Contour(double[][] z, double[] levels) {
        this.z = z;
        this.levels = levels;
        isLevelVisible = false;
        init();
    }

    /**
     * Constructor.
     * @param x the x coordinates of the data grid of z. Must be in ascending order.
     * @param y the y coordinates of the data grid of z. Must be in ascending order.
     * @param z the data matrix to create contour plot.
     * @param numLevels the number of contour levels.
     * @param logScale true to interpolate contour levels in logarithmical scale.
     */
    public Contour(double[] x, double[] y, double[][] z, int numLevels, boolean logScale) {
        if (x.length != z[0].length) {
            throw new IllegalArgumentException("x.length != z[0].length");
        }

        if (y.length != z.length) {
            throw new IllegalArgumentException("y.length != z.length");
        }

        this.x = x;
        this.y = y;
        this.z = z;
        this.numLevels = numLevels;
        this.logScale = logScale;
        init();
    }

    /**
     * Constructor.
     * @param x the x coordinates of the data grid of z. Must be in ascending order.
     * @param y the y coordinates of the data grid of z. Must be in ascending order.
     * @param z the data matrix to create contour plot.
     * @param levels the level values of contours. Must be strictly increasing and finite.
     */
    public Contour(double[] x, double[] y, double[][] z, double[] levels) {
        if (x.length != z[0].length) {
            throw new IllegalArgumentException("x.length != z[0].length");
        }

        if (y.length != z.length) {
            throw new IllegalArgumentException("y.length != z.length");
        }

        this.x = x;
        this.y = y;
        this.z = z;
        this.levels = levels;
        isLevelVisible = false;
        init();
    }

    /**
     * A line segment in contour line.
     */
    class Segment {

        double x0;
        double y0;
        double x1;
        double y1;
        Segment next;

        Segment(double x0, double y0, double x1, double y1, Segment next) {
            this.x0 = x0;
            this.y0 = y0;
            this.x1 = x1;
            this.y1 = y1;
            this.next = next;
        }

        void swap() {
            double x, y;
            x = x0;
            y = y0;
            x0 = x1;
            y0 = y1;
            x1 = x;
            y1 = y;
        }
    }

    /**
     * Initialize the contour lines.
     */
    private void init() {
        isTickVisible = x != null || y != null;

        if (x == null) {
            x = new double[z[0].length];
            for (int i = 0; i < x.length; i++) {
                x[i] = i + 0.5;
            }

        }

        if (y == null) {
            y = new double[z.length];
            for (int i = 0; i < y.length; i++) {
                y[i] = i + 0.5;
            }

            double[][] zz = new double[z.length][];
            for (int i = 0; i < z.length; i++) {
                zz[i] = z[z.length - i - 1];
            }
            z = zz;
        }

        zMin = MathEx.min(z);
        zMax = MathEx.max(z);

        contours = new ArrayList<>(numLevels);

        if (logScale && zMin <= 0.0) {
            throw new IllegalArgumentException("Log scale is not support for non-positive data");
        }

        if (levels == null) {
            if (logScale) {
                double lowerBound = Math.ceil(Math.log10(zMin));
                double upperBound = Math.floor(Math.log10(zMax));

                int n = (int) Math.round(upperBound - lowerBound);
                if (n == 0) {
                    logScale = false;
                }

                numLevels = n + 1;
                levels = new double[numLevels];

                for (int i = 0; i < numLevels; i++) {
                    levels[i] = Math.pow(10, lowerBound + i);
                }
            }

            if (levels == null) {
                double digits = Math.log10(Math.abs(zMax - zMin));
                double residual = digits - Math.floor(digits);
                if (residual < 0.4) {
                    // If the range is less than 20 units, we reduce one level.
                    digits -= 1.0;
                }

                double precisionDigits = (int) Math.floor(digits);
                double precisionUnit = Math.pow(10, precisionDigits);

                if (residual >= 0.4 && residual <= 0.7) {
                    // In case of too few grids, we use a half of precision unit.
                    precisionUnit /= 2;
                    precisionDigits -= 1;
                }

                double lowerBound = precisionUnit * (Math.ceil(zMin / precisionUnit));
                double upperBound = precisionUnit * (Math.floor(zMax / precisionUnit));

                numLevels = (int) Math.round((upperBound - lowerBound) / precisionUnit) + 1;
                levels = new double[numLevels];

                for (int i = 0; i < numLevels; i++) {
                    levels[i] = lowerBound + i * precisionUnit;
                }
            }
        }

        double[] xx = new double[4];
        double[] yy = new double[4];
        int[] ij = new int[2];

        int nx = x.length;
        int ny = y.length;
        Segment[][] segments = new Segment[ny][nx];
        double atom = 1E-7 * (zMax - zMin);

        for (int c = 0; c < levels.length; c++) {
            double zc = levels[c];
            for (int i = 0; i < nx; i++) {
                for (int j = 0; j < ny; j++) {
                    segments[j][i] = null;
                }
            }

            for (int i = 0; i < nx - 1; i++) {
                double xl = x[i];
                double xh = x[i + 1];
                for (int j = 0; j < ny - 1; j++) {
                    double yl = y[j];
                    double yh = y[j + 1];

                    double zll = z[j][i];
                    double zhl = z[j][i + 1];
                    double zlh = z[j + 1][i];
                    double zhh = z[j + 1][i + 1];

                    // If the value at a corner is exactly equal to a contour level,
                    // change that value by a tiny amount
                    if (zll == zc) {
                        zll += atom;
                    }

                    if (zhl == zc) {
                        zhl += atom;
                    }

                    if (zlh == zc) {
                        zlh += atom;
                    }

                    if (zhh == zc) {
                        zhh += atom;
                    }

                    // Check for intersections with sides
                    int nacode = 0;
                    if (!Double.isInfinite(zll)) {
                        nacode += 1;
                    }
                    if (!Double.isInfinite(zhl)) {
                        nacode += 2;
                    }
                    if (!Double.isInfinite(zlh)) {
                        nacode += 4;
                    }
                    if (!Double.isInfinite(zhh)) {
                        nacode += 8;
                    }

                    int k = 0;
                    switch (nacode) {
                        case 15:
                            if (isIntersect(zll, zhl, zc)) {
                                double f = getIntersectRatio(zll, zhl, zc);
                                xx[k] = xl + f * (xh - xl);
                                yy[k] = yl;
                                k++;
                            }

                            if (isIntersect(zll, zlh, zc)) {
                                double f = getIntersectRatio(zll, zlh, zc);
                                yy[k] = yl + f * (yh - yl);
                                xx[k] = xl;
                                k++;
                            }

                            if (isIntersect(zhl, zhh, zc)) {
                                double f = getIntersectRatio(zhl, zhh, zc);
                                yy[k] = yl + f * (yh - yl);
                                xx[k] = xh;
                                k++;
                            }

                            if (isIntersect(zlh, zhh, zc)) {
                                double f = getIntersectRatio(zlh, zhh, zc);
                                xx[k] = xl + f * (xh - xl);
                                yy[k] = yh;
                                k++;
                            }
                            break;

                        case 14:
                            if (isIntersect(zhl, zhh, zc)) {
                                double f = getIntersectRatio(zhl, zhh, zc);
                                yy[k] = yl + f * (yh - yl);
                                xx[k] = xh;
                                k++;
                            }
                            if (isIntersect(zlh, zhh, zc)) {
                                double f = getIntersectRatio(zlh, zhh, zc);
                                xx[k] = xl + f * (xh - xl);
                                yy[k] = yh;
                                k++;
                            }
                            if (isIntersect(zlh, zhl, zc)) {
                                double f = getIntersectRatio(zlh, zhl, zc);
                                xx[k] = xl + f * (xh - xl);
                                yy[k] = yh + f * (yl - yh);
                                k++;
                            }
                            break;

                        case 13:
                            if (isIntersect(zll, zlh, zc)) {
                                double f = getIntersectRatio(zll, zlh, zc);
                                yy[k] = yl + f * (yh - yl);
                                xx[k] = xl;
                                k++;
                            }
                            if (isIntersect(zlh, zhh, zc)) {
                                double f = getIntersectRatio(zlh, zhh, zc);
                                xx[k] = xl + f * (xh - xl);
                                yy[k] = yh;
                                k++;
                            }
                            if (isIntersect(zll, zhh, zc)) {
                                double f = getIntersectRatio(zll, zhh, zc);
                                xx[k] = xl + f * (xh - xl);
                                yy[k] = yl + f * (yh - yl);
                                k++;
                            }
                            break;

                        case 11:
                            if (isIntersect(zhl, zhh, zc)) {
                                double f = getIntersectRatio(zhl, zhh, zc);
                                yy[k] = yl + f * (yh - yl);
                                xx[k] = xh;
                                k++;
                            }
                            if (isIntersect(zll, zhl, zc)) {
                                double f = getIntersectRatio(zll, zhl, zc);
                                xx[k] = xl + f * (xh - xl);
                                yy[k] = yl;
                                k++;
                            }
                            if (isIntersect(zll, zhh, zc)) {
                                double f = getIntersectRatio(zll, zhh, zc);
                                xx[k] = xl + f * (xh - xl);
                                yy[k] = yl + f * (yh - yl);
                                k++;
                            }
                            break;

                        case 7:
                            if (isIntersect(zll, zlh, zc)) {
                                double f = getIntersectRatio(zll, zlh, zc);
                                yy[k] = yl + f * (yh - yl);
                                xx[k] = xl;
                                k++;
                            }
                            if (isIntersect(zll, zhl, zc)) {
                                double f = getIntersectRatio(zll, zhl, zc);
                                xx[k] = xl + f * (xh - xl);
                                yy[k] = yl;
                                k++;
                            }
                            if (isIntersect(zlh, zhl, zc)) {
                                double f = getIntersectRatio(zlh, zhl, zc);
                                xx[k] = xl + f * (xh - xl);
                                yy[k] = yh + f * (yl - yh);
                                k++;
                            }
                            break;
                    }

                    // We now have k(=2,4) endpoints. Decide which to join.
                    Segment seglist = null;
                    if (k > 0) {
                        if (k == 2) {
                            seglist = new Segment(xx[0], yy[0], xx[1], yy[1], seglist);
                        } else if (k == 4) {
                            for (k = 3; k >= 1; k--) {
                                int m = k;
                                xl = xx[k];
                                for (int l = 0; l < k; l++) {
                                    if (xx[l] > xl) {
                                        xl = xx[l];
                                        m = l;
                                    }
                                }

                                if (m != k) {
                                    xl = xx[k];
                                    yl = yy[k];
                                    xx[k] = xx[m];
                                    yy[k] = yy[m];
                                    xx[m] = xl;
                                    yy[m] = yl;
                                }
                            }

                            seglist = new Segment(xx[0], yy[0], xx[1], yy[1], seglist);
                            seglist = new Segment(xx[2], yy[2], xx[3], yy[3], seglist);
                        } else {
                            throw new IllegalStateException("k != 2 or 4");
                        }
                    }

                    segments[j][i] = seglist;
                }
            }

            // Begin following contours.
            // 1. Grab a segment
            // 2. Follow its tail
            // 3. Follow its head
            // 4. Save the contour
            for (int i = 0; i < nx - 1; i++) {
                for (int j = 0; j < ny - 1; j++) {
                    Segment seglist = null;
                    while ((seglist = segments[j][i]) != null) {
                        ij[0] = i;
                        ij[1] = j;

                        Segment start = seglist;
                        Segment end = seglist;
                        segments[j][i] = seglist.next;

                        double xend = seglist.x1;
                        double yend = seglist.y1;

                        int dir = 0;
                        while ((dir = segdir(xend, yend, ij)) != 0) {
                            // tail
                            int ii = ij[0];
                            int jj = ij[1];
                            Segment[] seg = {segments[jj][ii], null};
                            segments[jj][ii] = segupdate(xend, yend, dir, true, seg);
                            if (seg[1] == null) {
                                break;
                            }
                            end.next = seg[1];
                            end = seg[1];
                            xend = end.x1;
                            yend = end.y1;
                        }

                        end.next = null;
                        ij[0] = i;
                        ij[1] = j;
                        xend = seglist.x0;
                        yend = seglist.y0;
                        dir = 0;
                        while ((dir = segdir(xend, yend, ij)) != 0) {
                            // head
                            int ii = ij[0];
                            int jj = ij[1];
                            Segment[] seg = {segments[jj][ii], null};
                            segments[jj][ii] = segupdate(xend, yend, dir, false, seg);
                            if (seg[1] == null) {
                                break;
                            }
                            seg[1].next = start;
                            start = seg[1];
                            xend = start.x0;
                            yend = start.y0;
                        }

                        // Save the contour locations into the list of contours
                        Isoline contour = new Isoline(zc, isLevelVisible);

                        Segment s = start;
                        contour.add(s.x0, s.y0);
                        while (s.next != null) {
                            s = s.next;
                            contour.add(s.x0, s.y0);
                        }
                        contour.add(s.x1, s.y1);
                        
                        if (!contour.isEmpty()) {
                            contours.add(contour);
                        }
                    }
                }
            }
        }
    }

    /**
     * Returns true if zc is between z0 and z1.
     */
    private boolean isIntersect(double z0, double z1, double zc) {
        if ((z0 - zc) * (z1 - zc) < 0.0) {
            return true;
        }

        return false;
    }

    /**
     * Returns the ratio of (zc - z0) / (z1 - z0)
     */
    private double getIntersectRatio(double z0, double z1, double zc) {
        return (zc - z0) / (z1 - z0);
    }

    private boolean XMATCH(double x0, double x1) {
        return Math.abs(x0 - x1) == 0;
    }

    private boolean YMATCH(double y0, double y1) {
        return Math.abs(y0 - y1) == 0;
    }

    /**
     * Determine the entry direction to the next cell  and update the cell
     * indices.
     */
    private int segdir(double xend, double yend, int[] ij) {
        if (YMATCH(yend, y[ij[1]])) {
            if (ij[1] == 0) {
                return 0;
            }
            ij[1] -= 1;
            return 3;
        }

        if (XMATCH(xend, x[ij[0]])) {
            if (ij[0] == 0) {
                return 0;
            }
            ij[0] -= 1;
            return 4;
        }

        if (YMATCH(yend, y[ij[1] + 1])) {
            if (ij[1] >= y.length - 1) {
                return 0;
            }
            ij[1] += 1;
            return 1;
        }

        if (XMATCH(xend, x[ij[0] + 1])) {
            if (ij[0] >= x.length - 1) {
                return 0;
            }
            ij[0] += 1;
            return 2;
        }

        return 0;
    }

    /**
     * Search seglist for a segment with endpoint (xend, yend).
     * The cell entry direction is dir, and if tail=1/0 we are
     * building the tail/head of a contour. The matching segment
     * is pointed to by seg and the updated segment list (with
     * the matched segment stripped) is returned by the function.
     */
    private Segment segupdate(double xend, double yend, int dir, boolean tail, Segment[] seg) {
        Segment seglist = seg[0];
        if (seglist == null) {
            seg[1] = null;
            return null;
        }

        switch (dir) {
            case 1:
            case 3:
                if (YMATCH(yend, seglist.y0)) {
                    if (!tail) {
                        seglist.swap();
                    }
                    seg[1] = seglist;
                    return seglist.next;
                }

                if (YMATCH(yend, seglist.y1)) {
                    if (tail) {
                        seglist.swap();
                    }
                    seg[1] = seglist;
                    return seglist.next;
                }

                if (YMATCH(yend, seglist.y1)) {
                    if (tail) {
                        seglist.swap();
                    }
                    seg[1] = seglist;
                    return seglist.next;
                }
                break;

            case 2:
            case 4:
                if (XMATCH(xend, seglist.x0)) {
                    if (!tail) {
                        seglist.swap();
                    }
                    seg[1] = seglist;
                    return seglist.next;
                }

                if (XMATCH(xend, seglist.x1)) {
                    if (tail) {
                        seglist.swap();
                    }
                    seg[1] = seglist;
                    return seglist.next;
                }
                break;
        }

        Segment[] seg2 = {seglist.next, seg[1]};
        seglist.next = segupdate(xend, yend, dir, tail, seg2);
        seg[1] = seg2[1];

        return seglist;
    }

    @Override
    public double[] getLowerBound() {
        double[] bound = {MathEx.min(x), MathEx.min(y)};
        return bound;
    }

    @Override
    public double[] getUpperBound() {
        double[] bound = {MathEx.max(x), MathEx.max(y)};
        return bound;
    }

    @Override
    public void paint(Graphics g) {
        for (Isoline contour : contours) {
            contour.paint(g);
        }
    }

    @Override
    public Canvas canvas() {
        Canvas canvas = new Canvas(getLowerBound(), getUpperBound(), false);
        canvas.add(this);

        if (!isTickVisible) {
            canvas.getAxis(0).setTickVisible(false);
            canvas.getAxis(1).setTickVisible(false);
        }

        return canvas;
    }

    /**
     * Creates a contour plot with 10 isolines.
     * @param z the data matrix to create contour plot.
     */
    public static Contour of(double[][] z) {
        return of(z, 10);
    }

    /**
     * Creates a contour plot.
     * @param z the data matrix to create contour plot.
     * @param numLevels the number of contour levels.
     */
    public static Contour of(double[][] z, int numLevels) {
        return new Contour(z, numLevels, false);
    }

    /**
     * Creates a contour plot with 10 isolines.
     * @param x the x coordinates of the data grid of z. Must be in ascending order.
     * @param y the y coordinates of the data grid of z. Must be in ascending order.
     * @param z the data matrix to create contour plot.
     */
    public static Contour of(double[] x, double[] y, double[][] z) {
        return of(x, y, z, 10);
    }

    /**
     * Creates a contour plot.
     * @param x the x coordinates of the data grid of z. Must be in ascending order.
     * @param y the y coordinates of the data grid of z. Must be in ascending order.
     * @param z the data matrix to create contour plot.
     * @param numLevels the number of contour levels.
     */
    public static Contour of(double[] x, double[] y, double[][] z, int numLevels) {
        return new Contour(x, y, z, numLevels, false);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy