/*******************************************************************************
 * Copyright (c) 2010 Haifeng Li
 *   
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package smile.plot;

import java.awt.Color;
import java.awt.Font;
import java.util.Arrays;
import java.util.HashMap;
import smile.math.Math;
import smile.sort.QuickSort;

/**
 * This class describes an axis of a coordinate system.
 *
 * @author Haifeng Li
 */
public class Axis {

    /**
     * The font for axis label.
     */
    private static final Font axisLabelFont = new Font("Arial", Font.BOLD, 14);
    /**
     * The font for axis label.
     */
    private static final Font gridLabelFont = new Font("BitStream Vera Sans", Font.PLAIN, 12);
    /**
     * The base coordinate space.
     */
    private Base base;
    /**
     * The index of coordinate associated with this axis.
     */
    private int index;
    /**
     * Mapping of the data on this axis, which is the association between values
     * along this axis as String and double numbers.
     */
    private HashMap labels;
    /**
     * Visibility of label and tick marks.
     */
    private boolean labelVisible = true;
    /**
     * Visibility of the grid.
     */
    private boolean gridVisible = true;
    /**
     * Visibility of the frame.
     */
    private boolean frameVisible = true;
    /**
     * The number of slices in linear scale.
     */
    private int linearSlices = 10;
    /**
     * The slicing location.
     */
    private double[] linesSlicing;
    /**
     * The slicing labels.
     */
    private double[] labelsSlicing;
    /**
     * The coordinates of origin point.
     */
    private double[] origin;
    /**
     * The axis label.
     */
    private BaseLabel axisLabel;
    /**
     * The grid lines.
     */
    private Line[][] gridLines;
    /**
     * The grid labels.
     */
    private Label[] gridLabels;
    /**
     * The grid label strings.
     */
    private String[] gridLabelStrings;
    /**
     * The rotation degree of grid labels.
     */
    private double rotation = 0.0;

    /**
     * Constructor.
     */
    public Axis(Base base, int index) {
        this(base, index, null);
    }

    /**
     * Constructor.
     */
    public Axis(Base base, int index, String label) {
        this.base = base;
        this.index = index;
        setAxisLabel(label);
        init();
    }

    /**
     * Sets the axis to its default initial value.
     */
    private void init() {
        initOrigin();
        setSlice();
        initGridLines();
        initGridLabels();
    }

    /**
     * Initialize the origin point.
     */
    private void initOrigin() {
        origin = base.getCoordinateSpace()[0];
    }

    /**
     * Set the slices of axis.
     */
    private void setSlice() {
        // slicing initialisation
        if (labels == null) {
            double min = base.getPrecisionUnit()[index] * Math.ceil(base.getLowerBounds()[index] / base.getPrecisionUnit()[index]);
            double max = base.getPrecisionUnit()[index] * Math.floor(base.getUpperBounds()[index] / base.getPrecisionUnit()[index]);
            linearSlices = (int) Math.ceil(Math.round((max - min) / base.getPrecisionUnit()[index], 1));
            if (linearSlices <= 0) {
                linearSlices = 1;
            }

            if (linearSlices < 3) {
                linearSlices *= 2;
            }

            linesSlicing = new double[linearSlices + 3];
            labelsSlicing = new double[linearSlices + 3];

            double pitch = (max - min) / linearSlices;
            for (int i = 1; i <= linearSlices + 1; i++) {
                // lines and labels slicing are the same
                linesSlicing[i] = min + (i - 1) * pitch;
                labelsSlicing[i] = min + (i - 1) * pitch;
            }

            linesSlicing[0] = base.getLowerBounds()[index];
            labelsSlicing[0] = base.getLowerBounds()[index];
            linesSlicing[linearSlices + 2] = base.getUpperBounds()[index];
            labelsSlicing[linearSlices + 2] = base.getUpperBounds()[index];

        } else {
            linesSlicing = new double[labels.size() + 2];
            labelsSlicing = new double[labels.size()];
            gridLabelStrings = new String[labels.size()];

            linesSlicing[0] = base.getLowerBounds()[index];

            int i = 1;
            for (String string : labels.keySet()) {
                linesSlicing[i] = labels.get(string);
                labelsSlicing[i - 1] = labels.get(string);
                gridLabelStrings[i - 1] = string;
                i++;
            }

            linesSlicing[i] = base.getUpperBounds()[index];
            Arrays.sort(linesSlicing);
            QuickSort.sort(labelsSlicing, gridLabelStrings);
        }
    }

    /**
     * Initialize grid line labels.
     */
    private void initGridLabels() {
        int dim = base.getDimension();
        double[] offset = new double[dim];
        for (int j = 0; j < dim; j++) {
            if (j != index) {
                offset[j] = -(base.getUpperBounds()[j] - base.getLowerBounds()[j]) / 100;
            }
        }

        int decimal = 0;
        String label;

        gridLabels = new Label[labelsSlicing.length];

        for (int i = 0; i < gridLabels.length; i++) {

            double[] labelCoord = new double[base.getDimension()];
            System.arraycopy(base.getCoordinateSpace()[index + 1], 0, labelCoord, 0, base.getDimension());
            labelCoord[index] = labelsSlicing[i];

            if (dim == 3) {
                if (index == 0) {
                    labelCoord[2] = base.getUpperBounds()[2] - 2 * offset[2];
                } else if (index == 1) {
                    labelCoord[0] = base.getUpperBounds()[0] - 2 * offset[0];
                } else if (index == 2) {
                    labelCoord[1] = base.getUpperBounds()[1] - 2 * offset[1];
                }
            }

            decimal = base.getPrecisionDigits()[index];

            if (gridLabelStrings != null) {
                label = gridLabelStrings[i % gridLabelStrings.length];
            } else {
                String format = "%.0f";
                if (decimal < 0) {
                    format = String.format("%%.%df", -decimal);
                }
                label = String.format(format, labelsSlicing[i]);
            }

            for (int j = 0; j < dim; j++) {
                labelCoord[j] += offset[j];
            }

            if (base.getDimension() == 2) {
                if (index == 0) {
                    if (rotation == 0.0) {
                        gridLabels[i] = new Label(label, 0.5, 1.0, labelCoord);
                    } else {
                        gridLabels[i] = new Label(label, 1.0, 0.5, rotation, labelCoord);
                    }
                } else {
                    gridLabels[i] = new Label(label, 1.0, 0.5, labelCoord);
                }
            } else {
                if (index == 0) {
                    gridLabels[i] = new Label(label, 0.5, -0.5, labelCoord);
                } else if (index == 1) {
                    gridLabels[i] = new Label(label, 0.5, 1.0, labelCoord);
                } else if (index == 2) {
                    gridLabels[i] = new Label(label, 0.0, 0.5, labelCoord);
                }
            }

            gridLabels[i].setFont(gridLabelFont);
        }

        gridLabelStrings = null;
    }

    /**
     * Initialize grid lines.
     */
    private void initGridLines() {
        gridLines = new Line[base.getDimension() - 1][linesSlicing.length];

        int i2 = 0;

        for (int i = 0; i < base.getDimension() - 1; i++) {
            if (i2 == index) {
                i2++;
            }

            for (int j = 0; j < gridLines[i].length; j++) {
                double[] originBase = new double[base.getDimension()];
                double[] endBase = new double[base.getDimension()];

                System.arraycopy(origin, 0, originBase, 0, base.getDimension());
                System.arraycopy(origin, 0, endBase, 0, base.getDimension());

                endBase[i2] = base.getCoordinateSpace()[i2 + 1][i2];
                originBase[index] = linesSlicing[j];
                endBase[index] = linesSlicing[j];

                if (j == 0 || j == gridLines[i].length - 1) {
                    gridLines[i][j] = new Line(originBase, endBase);
                } else {
                    gridLines[i][j] = new Line(Line.Style.DOT, Color.lightGray, originBase, endBase);
                }
            }
            i2++;
        }
    }

    /**
     * Set the base coordinate space.
     */
    public Axis setBase(Base base) {
        this.base = base;
        if (getAxisLabel() != null) {
            setAxisLabel(getAxisLabel());
        }
        init();
        return this;
    }
    
    /**
     * Sets the rotation degree of grid label strings.
     * @param rotation rotation degree.
     */
    public Axis setRotation(double rotation) {
        this.rotation = rotation;
        return this;
    }

    /**
     * Add a label to the axis at given location.
     */
    public Axis addLabel(String label, double location) {
        if (labels == null) {
            labels = new HashMap<>();
        }

        labels.put(label, location);
        setSlice();
        initGridLines();
        initGridLabels();
        return this;
    }

    /**
     * Add a label to the axis at given location.
     */
    public Axis addLabel(String[] label, double[] location) {
        if (label.length != location.length) {
            throw new IllegalArgumentException("Label size and location size don't match.");
        }

        if (labels == null) {
            labels = new HashMap<>();
        }

        for (int i = 0; i < label.length; i++) {
            labels.put(label[i], location[i]);
        }

        setSlice();
        initGridLines();
        initGridLabels();
        return this;
    }

    /**
     * Remove a label from the axis.
     */
    public Axis removeLabel(String label) {
        if (labels == null) {
            throw new IllegalStateException();
        }

        labels.remove(label);
        setSlice();
        initGridLabels();
        return this;
    }

    /**
     * Returns the number of slices in linear scale.
     */
    public int getLinearSlices() {
        return linearSlices;
    }

    /**
     * Set the visibility of the grid lines and their labels.
     */
    public Axis setGridVisible(boolean v) {
        gridVisible = v;
        return this;
    }

    /**
     * Returns the visibility of the grid lines and their labels.
     */
    public boolean isGridVisible() {
        return gridVisible;
    }

    /**
     * Set the visibility of the frame grid lines and their labels.
     */
    public Axis setFrameVisible(boolean v) {
        frameVisible = v;
        return this;
    }

    /**
     * Returns the visibility of the frame grid lines and their labels.
     */
    public boolean isFrameVisible() {
        return frameVisible;
    }

    /**
     * Set the visibility of the axis label.
     */
    public Axis setLabelVisible(boolean v) {
        labelVisible = v;
        return this;
    }

    /**
     * Returns the visibility of the axis label.
     */
    public boolean isLabelVisible() {
        return labelVisible;
    }

    /**
     * Sets the label of this axis.
     */
    public Axis setAxisLabel(String label) {
        if (label == null) {
            if (index == 0) {
                label = "X";
            } else if (index == 1) {
                label = "Y";
            } else if (index == 2) {
                label = "Z";
            }
        }

        if (label != null) {
            double[] position = new double[base.getDimension()];

            if (base.getDimension() == 2) {
                position[index] = 0.5;
                if (index == 0) {
                    position[1] = -0.1;
                    axisLabel = new BaseLabel(label, 0.5, 1.0, position);
                } else {
                    position[0] = -0.15;
                    axisLabel = new BaseLabel(label, 0.5, 0.5, -Math.PI / 2, position);
                }
            } else {
                if (index == 0) {
                    position[2] = 1.0;
                    position[index] = 0.5;
                    axisLabel = new BaseLabel(label, 0.5, -2.0, position);
                } else if (index == 1) {
                    position[0] = 1.0;
                    position[index] = 0.5;
                    axisLabel = new BaseLabel(label, 0.5, 3.0, position);
                } else if (index == 2) {
                    position[1] = 1.0;
                    position[index] = 1.0;
                    axisLabel = new BaseLabel(label, -0.5, -1.0, position);
                }
            }

            axisLabel.setFont(axisLabelFont);
        }
        return this;
    }

    /**
     * Returns the label of the axis.
     */
    public String getAxisLabel() {
        if (axisLabel == null) {
            return null;
        } else {
            return axisLabel.getText();
        }
    }

    /**
     * Draw the axis.
     */
    public void paint(Graphics g) {
        if (gridLines != null) {
            if (gridVisible) {
                for (int i = 0; i < gridLines.length; i++) {
                    for (int j = 1; j < gridLines[i].length - 1; j++) {
                        gridLines[i][j].paint(g);
                    }
                }
            }

            if (frameVisible) {
                for (int i = 0; i < gridLines.length; i++) {
                    gridLines[i][0].paint(g);
                    gridLines[i][gridLines[i].length - 1].paint(g);
                }
            }
        }

        if (labelVisible) {
            if (gridLabels != null) {
                int[] xy = g.projection.screenProjection(gridLabels[1].getCoordinate());
                int prevx = xy[0];
                int prevy = xy[1];
                for (int i = 0; i < gridLabels.length; i++) {
                    if (!gridLabels[i].text.isEmpty()) {
                        double[] coord = gridLabels[i].getCoordinate();
                        xy = g.projection.screenProjection(coord);
                        int x = xy[0];
                        int y = xy[1];
                        
                        if (base.getDimension() == 2 && index == 0 && rotation != 0.0) {
                            if ((prevx == x && prevy == y)
                              || Math.abs(x - prevx) > gridLabels[i].font.getSize()) {
                                gridLabels[i].paint(g);
                                prevx = x;
                                prevy = y;
                            }
                        } else if (base.getDimension() == 2 && index == 1) {
                            if ((prevx == x && prevy == y && i == 0)
                              || Math.abs(prevy - y) > gridLabels[i].font.getSize()) {
                                gridLabels[i].paint(g);
                                prevx = x;
                                prevy = y;
                            }
                        } else {
                            if ((prevx == x && prevy == y)
                              || Math.abs(x - prevx) > g.g2d.getFontMetrics(gridLabels[i].font).stringWidth(gridLabels[i].text)
                              || Math.abs(prevy - y) > gridLabels[i].font.getSize()) {
                                gridLabels[i].paint(g);
                                prevx = x;
                                prevy = y;
                            }
                        }
                    }
                }
            }

            if (axisLabel != null) {
                axisLabel.paint(g);
            }
        }
    }
}

 

Ads help maintain this website.