/** * Copyright 2015-2017 Knowm Inc. (http://knowm.org) and contributors. * Copyright 2011-2015 Xeiam LLC (http://xeiam.com) and contributors. * * 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 org.knowm.xchart.internal.chartpart; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.font.FontRenderContext; import java.awt.font.TextLayout; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.util.List; import org.knowm.xchart.internal.series.AxesChartSeries; import org.knowm.xchart.internal.series.Series; import org.knowm.xchart.style.AxesChartStyler; import org.knowm.xchart.style.CategoryStyler; import org.knowm.xchart.style.Styler.LegendPosition; /** * Axis * * @author timmolter */ public class Axis<ST extends AxesChartStyler, S extends Series> implements ChartPart { public enum AxisDataType { Number, Date, String } private final Chart<AxesChartStyler, AxesChartSeries> chart; private Rectangle2D bounds; private final AxesChartStyler stylerAxesChart; /** * the axisDataType */ private AxisDataType axisDataType; /** * the axis title */ private final AxisTitle<AxesChartStyler, AxesChartSeries> axisTitle; /** * the axis tick */ private final AxisTick<AxesChartStyler, AxesChartSeries> axisTick; /** * the axis tick calculator */ private AxisTickCalculator_ axisTickCalculator; /** * the axis direction */ private final Direction direction; private double min; private double max; /** * An axis direction */ public enum Direction { /** * the constant to represent X axis */ X, /** * the constant to represent Y axis */ Y } /** * Constructor * * @param chart the Chart * @param direction the axis direction (X or Y) */ public Axis(Chart<AxesChartStyler, AxesChartSeries> chart, Direction direction) { this.chart = chart; this.stylerAxesChart = chart.getStyler(); this.direction = direction; axisTitle = new AxisTitle<AxesChartStyler, AxesChartSeries>(chart, direction); axisTick = new AxisTick<AxesChartStyler, AxesChartSeries>(chart, direction); } /** * Reset the default min and max values in preparation for calculating the actual min and max */ void resetMinMax() { min = Double.MAX_VALUE; max = -1 * Double.MAX_VALUE; } /** * @param min * @param max */ void addMinMax(double min, double max) { // System.out.println(min); // System.out.println(max); // NaN indicates String axis data, so min and max play no role if (this.min == Double.NaN || min < this.min) { this.min = min; } if (this.max == Double.NaN || max > this.max) { this.max = max; } // System.out.println(this.min); // System.out.println(this.max); } @Override public void paint(Graphics2D g) { bounds = new Rectangle2D.Double(); // determine Axis bounds if (direction == Direction.Y) { // Y-Axis - gets called first // calculate paint zone // ---- // | // | // | // | // ---- double xOffset = stylerAxesChart.getChartPadding(); // double yOffset = chart.getChartTitle().getBounds().getHeight() < .1 ? axesChartStyler.getChartPadding() : chart.getChartTitle().getBounds().getHeight() // + axesChartStyler.getChartPadding(); double yOffset = chart.getChartTitle().getBounds().getHeight() + stylerAxesChart.getChartPadding(); ///////////////////////// int i = 1; // just twice through is all it takes double width = 60; // arbitrary, final width depends on Axis tick labels double height; do { // System.out.println("width before: " + width); double approximateXAxisWidth = chart.getWidth() - width // y-axis approx. width - (stylerAxesChart.getLegendPosition() == LegendPosition.OutsideE ? chart.getLegend().getBounds().getWidth() : 0) - 2 * stylerAxesChart.getChartPadding() - (stylerAxesChart.isYAxisTicksVisible() ? (stylerAxesChart.getPlotMargin()) : 0) - (stylerAxesChart.getLegendPosition() == LegendPosition.OutsideE && stylerAxesChart.isLegendVisible() ? stylerAxesChart.getChartPadding() : 0); height = chart.getHeight() - yOffset - chart.getXAxis().getXAxisHeightHint(approximateXAxisWidth) - stylerAxesChart.getPlotMargin() - stylerAxesChart.getChartPadding(); width = getYAxisWidthHint(height); // System.out.println("width after: " + width); // System.out.println("height: " + height); } while (i-- > 0); ///////////////////////// bounds = new Rectangle2D.Double(xOffset, yOffset, width, height); // g.setColor(Color.yellow); // g.draw(bounds); // fill in Axis with sub-components axisTitle.paint(g); axisTick.paint(g); // now we know the real bounds width after ticks and title are painted width = (stylerAxesChart.isYAxisTitleVisible() ? axisTitle.getBounds().getWidth() : 0) + axisTick.getBounds().getWidth(); bounds = new Rectangle2D.Double(xOffset, yOffset, width, height); // g.setColor(Color.yellow); // g.draw(bounds); } else { // X-Axis // calculate paint zone // |____________________| double xOffset = chart.getYAxis().getBounds().getWidth() + (stylerAxesChart.isYAxisTicksVisible() ? stylerAxesChart.getPlotMargin() : 0) + stylerAxesChart.getChartPadding(); double yOffset = chart.getYAxis().getBounds().getY() + chart.getYAxis().getBounds().getHeight() + stylerAxesChart.getPlotMargin(); double width = chart.getWidth() - chart.getYAxis().getBounds().getWidth() // y-axis was already painted - (stylerAxesChart.getLegendPosition() == LegendPosition.OutsideE ? chart.getLegend().getBounds().getWidth() : 0) - 2 * stylerAxesChart.getChartPadding() - (stylerAxesChart.isYAxisTicksVisible() ? (stylerAxesChart.getPlotMargin()) : 0) - (stylerAxesChart.getLegendPosition() == LegendPosition.OutsideE && stylerAxesChart.isLegendVisible() ? stylerAxesChart.getChartPadding() : 0); // double height = this.getXAxisHeightHint(width); // System.out.println("height: " + height); // the Y-Axis was already draw at this point so we know how much vertical room is left for the X-Axis double height = chart.getHeight() - chart.getYAxis().getBounds().getY() - chart.getYAxis().getBounds().getHeight() - stylerAxesChart.getChartPadding() - stylerAxesChart.getPlotMargin(); // System.out.println("height2: " + height2); bounds = new Rectangle2D.Double(xOffset, yOffset, width, height); // g.setColor(Color.yellow); // g.draw(bounds); // now paint the X-Axis given the above paint zone this.axisTickCalculator = getAxisTickCalculator(bounds.getWidth()); axisTitle.paint(g); axisTick.paint(g); } } /** * The vertical Y-Axis is drawn first, but to know the lower bounds of it, we need to know how high the X-Axis paint zone is going to be. Since the tick labels could be rotated, we need to actually * determine the tick labels first to get an idea of how tall the X-Axis tick labels will be. * * @return */ private double getXAxisHeightHint(double workingSpace) { // Axis title double titleHeight = 0.0; if (chart.getXAxisTitle() != null && !chart.getXAxisTitle().trim().equalsIgnoreCase("") && stylerAxesChart.isXAxisTitleVisible()) { TextLayout textLayout = new TextLayout(chart.getXAxisTitle(), stylerAxesChart.getAxisTitleFont(), new FontRenderContext(null, true, false)); Rectangle2D rectangle = textLayout.getBounds(); titleHeight = rectangle.getHeight() + stylerAxesChart.getAxisTitlePadding(); } this.axisTickCalculator = getAxisTickCalculator(workingSpace); // Axis tick labels double axisTickLabelsHeight = 0.0; if (stylerAxesChart.isXAxisTicksVisible()) { // get some real tick labels // System.out.println("XAxisHeightHint"); // System.out.println("workingSpace: " + workingSpace); String sampleLabel = ""; // find the longest String in all the labels for (int i = 0; i < axisTickCalculator.getTickLabels().size(); i++) { // System.out.println("label: " + axisTickCalculator.getTickLabels().get(i)); if (axisTickCalculator.getTickLabels().get(i) != null && axisTickCalculator.getTickLabels().get(i).length() > sampleLabel.length()) { sampleLabel = axisTickCalculator.getTickLabels().get(i); } } // System.out.println("sampleLabel: " + sampleLabel); // get the height of the label including rotation TextLayout textLayout = new TextLayout(sampleLabel.length() == 0 ? " " : sampleLabel, stylerAxesChart.getAxisTickLabelsFont(), new FontRenderContext(null, true, false)); AffineTransform rot = stylerAxesChart.getXAxisLabelRotation() == 0 ? null : AffineTransform.getRotateInstance(-1 * Math.toRadians(stylerAxesChart.getXAxisLabelRotation())); Shape shape = textLayout.getOutline(rot); Rectangle2D rectangle = shape.getBounds(); axisTickLabelsHeight = rectangle.getHeight() + stylerAxesChart.getAxisTickPadding() + stylerAxesChart.getAxisTickMarkLength(); } return titleHeight + axisTickLabelsHeight; } private double getYAxisWidthHint(double workingSpace) { // Axis title double titleHeight = 0.0; if (chart.getYAxisTitle() != null && !chart.getYAxisTitle().trim().equalsIgnoreCase("") && stylerAxesChart.isYAxisTitleVisible()) { TextLayout textLayout = new TextLayout(chart.getYAxisTitle(), stylerAxesChart.getAxisTitleFont(), new FontRenderContext(null, true, false)); Rectangle2D rectangle = textLayout.getBounds(); titleHeight = rectangle.getHeight() + stylerAxesChart.getAxisTitlePadding(); } this.axisTickCalculator = getAxisTickCalculator(workingSpace); // Axis tick labels double axisTickLabelsHeight = 0.0; if (stylerAxesChart.isYAxisTicksVisible()) { // get some real tick labels // System.out.println("XAxisHeightHint"); // System.out.println("workingSpace: " + workingSpace); String sampleLabel = ""; // find the longest String in all the labels for (int i = 0; i < axisTickCalculator.getTickLabels().size(); i++) { if (axisTickCalculator.getTickLabels().get(i) != null && axisTickCalculator.getTickLabels().get(i).length() > sampleLabel.length()) { sampleLabel = axisTickCalculator.getTickLabels().get(i); } } // get the height of the label including rotation TextLayout textLayout = new TextLayout(sampleLabel.length() == 0 ? " " : sampleLabel, stylerAxesChart.getAxisTickLabelsFont(), new FontRenderContext(null, true, false)); Rectangle2D rectangle = textLayout.getBounds(); axisTickLabelsHeight = rectangle.getWidth() + stylerAxesChart.getAxisTickPadding() + stylerAxesChart.getAxisTickMarkLength(); } return titleHeight + axisTickLabelsHeight; } private AxisTickCalculator_ getAxisTickCalculator(double workingSpace) { // X-Axis if (getDirection() == Direction.X) { if (stylerAxesChart instanceof CategoryStyler) { List<?> categories = (List<?>) chart.getSeriesMap().values().iterator().next().getXData(); AxisDataType axisType = chart.getAxisPair().getXAxis().getAxisDataType(); return new AxisTickCalculator_Category(getDirection(), workingSpace, categories, axisType, stylerAxesChart); } else if (getAxisDataType() == AxisDataType.Date) { return new AxisTickCalculator_Date(getDirection(), workingSpace, min, max, stylerAxesChart); } else if (stylerAxesChart.isXAxisLogarithmic()) { return new AxisTickCalculator_Logarithmic(getDirection(), workingSpace, min, max, stylerAxesChart); } else { return new AxisTickCalculator_Number(getDirection(), workingSpace, min, max, stylerAxesChart); } } // Y-Axis else { if (stylerAxesChart.isYAxisLogarithmic() && getAxisDataType() != AxisDataType.Date) { return new AxisTickCalculator_Logarithmic(getDirection(), workingSpace, min, max, stylerAxesChart); } else { return new AxisTickCalculator_Number(getDirection(), workingSpace, min, max, stylerAxesChart); } } } // Getters ///////////////////////////////////////////////// AxisDataType getAxisDataType() { return axisDataType; } public void setAxisDataType(AxisDataType axisDataType) { if (axisDataType != null && this.axisDataType != null && this.axisDataType != axisDataType) { throw new IllegalArgumentException("Different Axes (e.g. Date, Number, String) cannot be mixed on the same chart!!"); } this.axisDataType = axisDataType; } double getMin() { return min; } void setMin(double min) { this.min = min; } double getMax() { return max; } void setMax(double max) { this.max = max; } AxisTick<AxesChartStyler, AxesChartSeries> getAxisTick() { return axisTick; } private Direction getDirection() { return direction; } AxisTitle<AxesChartStyler, AxesChartSeries> getAxisTitle() { return axisTitle; } public AxisTickCalculator_ getAxisTickCalculator() { return this.axisTickCalculator; } @Override public Rectangle2D getBounds() { return bounds; } }