Java tutorial
/*! * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program 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 Lesser General Public License for more details. * * Copyright (c) 2002-2017 Hitachi Vantara.. All rights reserved. */ package org.pentaho.plugin.jfreereport.reportcharts; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.CategoryLabelPosition; import org.jfree.chart.axis.CategoryLabelPositions; import org.jfree.chart.axis.CategoryLabelWidthType; import org.jfree.chart.axis.DateAxis; import org.jfree.chart.axis.DateTickUnit; import org.jfree.chart.axis.LogarithmicAxis; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.axis.NumberTickUnit; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.labels.ItemLabelAnchor; import org.jfree.chart.labels.ItemLabelPosition; import org.jfree.chart.labels.StandardCategoryItemLabelGenerator; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.renderer.category.CategoryItemRenderer; import org.jfree.data.category.CategoryDataset; import org.jfree.data.general.Dataset; import org.jfree.data.time.Day; import org.jfree.data.time.Hour; import org.jfree.data.time.Minute; import org.jfree.data.time.Month; import org.jfree.data.time.Second; import org.jfree.data.time.Year; import org.jfree.text.TextBlockAnchor; import org.jfree.ui.RectangleAnchor; import org.jfree.ui.TextAnchor; import org.pentaho.plugin.jfreereport.reportcharts.backport.FastNumberTickUnit; import org.pentaho.reporting.engine.classic.core.ClassicEngineBoot; import org.pentaho.reporting.engine.classic.core.LegacyUpdateHandler; import org.pentaho.reporting.engine.classic.core.function.Expression; import org.pentaho.reporting.libraries.base.util.StringUtils; import org.pentaho.reporting.libraries.formatting.FastDecimalFormat; import java.awt.Font; import java.math.RoundingMode; import java.text.DateFormatSymbols; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.text.SimpleDateFormat; /** * This class allows you to embed categorical charts into JFreeReport XML definitions. * * @author mbatchel * @noinspection UnusedDeclaration */ public abstract class CategoricalChartExpression extends AbstractChartExpression implements LegacyUpdateHandler { private static final long serialVersionUID = -402500824047401239L; private static final double DEFAULT_SCALE_FACTOR = 1.0; private String valueAxisLabel; private String categoryAxisLabel; private boolean horizontal; private boolean showGridlines; private Double labelRotation; private Float maxCategoryLabelWidthRatio; private Font categoryTitleFont; private Font categoryTickFont; private String categoricalLabelFormat; private String categoricalLabelDecimalFormat; private String categoricalLabelDateFormat; private Double categoricalItemLabelRotation; private boolean humanReadableLogarithmicFormat; private boolean logarithmicAxis; private String categoricalAxisMessageFormat; private Font rangeTitleFont; private Font rangeTickFont; private double rangeMinimum; private double rangeMaximum; private boolean rangeIncludesZero; private boolean rangeStickyZero; private NumberFormat rangeTickFormat; private String rangeTickFormatString; private Class rangeTimePeriod; private double rangePeriodCount; private boolean autoRange; private double scaleFactor; private Double lowerMargin; private Double upperMargin; private Double categoryMargin; protected CategoricalChartExpression() { categoricalAxisMessageFormat = "{0}"; categoricalLabelFormat = "{2}"; rangeMaximum = 1; rangeMinimum = 0; showGridlines = true; rangePeriodCount = 0; autoRange = true; scaleFactor = DEFAULT_SCALE_FACTOR; } public Font getCategoryTitleFont() { return categoryTitleFont; } public void setCategoryTitleFont(final Font categoryTitleFont) { this.categoryTitleFont = categoryTitleFont; } public Font getCategoryTickFont() { return categoryTickFont; } public void setCategoryTickFont(final Font categoryTickFont) { this.categoryTickFont = categoryTickFont; } public String getRangeTickFormatString() { return rangeTickFormatString; } public void setRangeTickFormatString(final String rangeTickFormatString) { this.rangeTickFormatString = rangeTickFormatString; } public String getCategoricalAxisMessageFormat() { return categoricalAxisMessageFormat; } public void setCategoricalAxisMessageFormat(final String categoricalAxisMessageFormat) { this.categoricalAxisMessageFormat = categoricalAxisMessageFormat; } /** * Return the java.awt.Font to be used to display the range axis tick labels * * @return Font The Font for the range axis tick labels */ public Font getRangeTickFont() { return rangeTickFont; } /** * @param rangeTickFont The rangeTitleFont to set. */ public void setRangeTickFont(final Font rangeTickFont) { this.rangeTickFont = rangeTickFont; } /** * Return the range axis' minimum value * * @return double Range axis' minimum value */ public double getRangeMinimum() { return rangeMinimum; } /** * @param rangeMinimum Set the minimum value of the range axis. */ public void setRangeMinimum(final double rangeMinimum) { this.rangeMinimum = rangeMinimum; } /** * Return the range axis' maximum value * * @return double Range axis' maximum value */ public double getRangeMaximum() { return rangeMaximum; } /** * @param rangeMaximum Set the maximum value of the range axis. */ public void setRangeMaximum(final double rangeMaximum) { this.rangeMaximum = rangeMaximum; } /** * @return Returns the rangeTitleFont. */ public Font getRangeTitleFont() { return rangeTitleFont; } /** * @param rangeTitleFont The rangeTitleFont to set. */ public void setRangeTitleFont(final Font rangeTitleFont) { this.rangeTitleFont = rangeTitleFont; } /** * @return Returns the rangeTickFormat. */ public NumberFormat getRangeTickFormat() { return rangeTickFormat; } /** * @param rangeTickFormat The range tick number format to set. */ public void setRangeTickFormat(final NumberFormat rangeTickFormat) { this.rangeTickFormat = rangeTickFormat; } /** * @return Returns the rangeIncludeZero. */ public boolean isRangeIncludesZero() { return rangeIncludesZero; } /** * @param rangeIncludesZero The domainIncludesZero to set. */ public void setRangeIncludesZero(final boolean rangeIncludesZero) { this.rangeIncludesZero = rangeIncludesZero; } /** * @return Returns the rangeStickyZero. */ public boolean isRangeStickyZero() { return rangeStickyZero; } /** * @param rangeStickyZero The rangeStickyZero to set. */ public void setRangeStickyZero(final boolean rangeStickyZero) { this.rangeStickyZero = rangeStickyZero; } public boolean isLogarithmicAxis() { return logarithmicAxis; } public void setLogarithmicAxis(final boolean logarithmicAxis) { this.logarithmicAxis = logarithmicAxis; } public boolean isHumanReadableLogarithmicFormat() { return humanReadableLogarithmicFormat; } public void setHumanReadableLogarithmicFormat(final boolean humanReadableLogarithmicFormat) { this.humanReadableLogarithmicFormat = humanReadableLogarithmicFormat; } public Double getLowerMargin() { return lowerMargin; } public void setLowerMargin(final Double lowerMargin) { this.lowerMargin = lowerMargin; } public Double getUpperMargin() { return upperMargin; } public void setUpperMargin(final Double upperMargin) { this.upperMargin = upperMargin; } public Double getCategoryMargin() { return categoryMargin; } public void setCategoryMargin(final Double categoryMargin) { this.categoryMargin = categoryMargin; } public Double getLabelRotationDeg() { if (labelRotation == null) { return null; } else { return new Double(StrictMath.toDegrees(labelRotation.doubleValue())); } } public void setLabelRotationDeg(final Double value) { if (value == null) { labelRotation = null; } else { labelRotation = new Double(StrictMath.toRadians(value.doubleValue())); } } public Double getLabelRotation() { return labelRotation; } public void setLabelRotation(final Double value) { labelRotation = value; } public Double getCategoricalItemLabelRotationDeg() { if (categoricalItemLabelRotation == null) { return null; } else { return new Double(StrictMath.toDegrees(categoricalItemLabelRotation.doubleValue())); } } public void setCategoricalItemLabelRotationDeg(final Double value) { if (value == null) { categoricalItemLabelRotation = null; } else { categoricalItemLabelRotation = new Double(StrictMath.toRadians(value.doubleValue())); } } public Double getCategoricalItemLabelRotation() { return this.categoricalItemLabelRotation; } public void setCategoricalItemLabelRotation(final Double value) { this.categoricalItemLabelRotation = value; } public void setMaxCategoryLabelWidthRatio(final Float value) { maxCategoryLabelWidthRatio = value; } public Float getMaxCategoryLabelWidthRatio() { return maxCategoryLabelWidthRatio; } public boolean isShowGridlines() { return showGridlines; } public void setShowGridlines(final boolean value) { showGridlines = value; } public boolean isHorizontal() { return horizontal; } public void setHorizontal(final boolean value) { horizontal = value; } public String getValueAxisLabel() { return valueAxisLabel; } public void setValueAxisLabel(final String valueAxisLabel) { this.valueAxisLabel = valueAxisLabel; } public String getCategoryAxisLabel() { return categoryAxisLabel; } public void setCategoryAxisLabel(final String categoryAxisLabel) { this.categoryAxisLabel = categoryAxisLabel; } public void setCategoricalLabelFormat(final String value) { this.categoricalLabelFormat = value; } public String getCategoricalLabelFormat() { return this.categoricalLabelFormat; } public void setCategoricalLabelDecimalFormat(final String value) { this.categoricalLabelDecimalFormat = value; } public String getCategoricalLabelDecimalFormat() { return this.categoricalLabelDecimalFormat; } public void setCategoricalLabelDateFormat(final String value) { this.categoricalLabelDateFormat = value; } public String getCategoricalLabelDateFormat() { return this.categoricalLabelDateFormat; } public boolean isAutoRange() { return autoRange; } public void setAutoRange(final boolean autoRange) { this.autoRange = autoRange; } public double getScaleFactor() { return scaleFactor; } public void setScaleFactor(final double scaleFactor) { this.scaleFactor = scaleFactor; } protected JFreeChart computeChart(final Dataset dataset) { if (dataset instanceof CategoryDataset == false) { return computeCategoryChart(null); } final CategoryDataset categoryDataset = (CategoryDataset) dataset; return computeCategoryChart(categoryDataset); } protected JFreeChart computeCategoryChart(final CategoryDataset dataset) { return getChart(dataset); } /** * @param categoryDataset the dataset. * @return the generated chart. This implementation returns null. * @deprecated should not be public and should not be a getter. In fact. it will be removed in PRD-4.0 */ public JFreeChart getChart(final CategoryDataset categoryDataset) { return null; } protected PlotOrientation computePlotOrientation() { final PlotOrientation orientation; if (isHorizontal()) { orientation = PlotOrientation.HORIZONTAL; } else { orientation = PlotOrientation.VERTICAL; } return orientation; } protected void configureChart(final JFreeChart chart) { super.configureChart(chart); final CategoryPlot cpl = chart.getCategoryPlot(); final CategoryItemRenderer renderer = cpl.getRenderer(); if (StringUtils.isEmpty(getTooltipFormula()) == false) { renderer.setBaseToolTipGenerator( new FormulaCategoryTooltipGenerator(getRuntime(), getTooltipFormula())); } if (StringUtils.isEmpty(getUrlFormula()) == false) { renderer.setBaseItemURLGenerator(new FormulaCategoryURLGenerator(getRuntime(), getUrlFormula())); } if (this.categoricalLabelFormat != null) { final StandardCategoryItemLabelGenerator scilg; if (categoricalLabelDecimalFormat != null) { final DecimalFormat numFormat = new DecimalFormat(categoricalLabelDecimalFormat, new DecimalFormatSymbols(getRuntime().getResourceBundleFactory().getLocale())); numFormat.setRoundingMode(RoundingMode.HALF_UP); scilg = new StandardCategoryItemLabelGenerator(categoricalLabelFormat, numFormat); } else if (categoricalLabelDateFormat != null) { scilg = new StandardCategoryItemLabelGenerator(categoricalLabelFormat, new SimpleDateFormat( categoricalLabelDateFormat, getRuntime().getResourceBundleFactory().getLocale())); } else { final DecimalFormat formatter = new DecimalFormat(); formatter.setDecimalFormatSymbols( new DecimalFormatSymbols(getRuntime().getResourceBundleFactory().getLocale())); scilg = new StandardCategoryItemLabelGenerator(categoricalLabelFormat, formatter); } renderer.setBaseItemLabelGenerator(scilg); } renderer.setBaseItemLabelsVisible(Boolean.TRUE.equals(getItemsLabelVisible())); if (getItemLabelFont() != null) { renderer.setBaseItemLabelFont(getItemLabelFont()); } if (categoricalItemLabelRotation != null) { final ItemLabelPosition orgPosItemLabelPos = renderer.getBasePositiveItemLabelPosition(); if (orgPosItemLabelPos == null) { final ItemLabelPosition pos2 = new ItemLabelPosition(ItemLabelAnchor.OUTSIDE12, TextAnchor.BOTTOM_CENTER, TextAnchor.CENTER, categoricalItemLabelRotation.doubleValue()); renderer.setBasePositiveItemLabelPosition(pos2); } else { final ItemLabelPosition pos2 = new ItemLabelPosition(orgPosItemLabelPos.getItemLabelAnchor(), orgPosItemLabelPos.getTextAnchor(), orgPosItemLabelPos.getRotationAnchor(), categoricalItemLabelRotation.doubleValue()); renderer.setBasePositiveItemLabelPosition(pos2); } final ItemLabelPosition orgNegItemLabelPos = renderer.getBaseNegativeItemLabelPosition(); if (orgNegItemLabelPos == null) { final ItemLabelPosition pos2 = new ItemLabelPosition(ItemLabelAnchor.OUTSIDE12, TextAnchor.BOTTOM_CENTER, TextAnchor.CENTER, categoricalItemLabelRotation.doubleValue()); renderer.setBaseNegativeItemLabelPosition(pos2); } else { final ItemLabelPosition neg2 = new ItemLabelPosition(orgNegItemLabelPos.getItemLabelAnchor(), orgNegItemLabelPos.getTextAnchor(), orgNegItemLabelPos.getRotationAnchor(), categoricalItemLabelRotation.doubleValue()); renderer.setBaseNegativeItemLabelPosition(neg2); } } final Font labelFont = Font.decode(getLabelFont()); final CategoryAxis categoryAxis = cpl.getDomainAxis(); categoryAxis.setLabelFont(labelFont); categoryAxis.setTickLabelFont(labelFont); if (getCategoryTitleFont() != null) { categoryAxis.setLabelFont(getCategoryTitleFont()); } if (getCategoryTickFont() != null) { categoryAxis.setTickLabelFont(getCategoryTickFont()); } if (maxCategoryLabelWidthRatio != null) { categoryAxis.setMaximumCategoryLabelWidthRatio(maxCategoryLabelWidthRatio.floatValue()); } cpl.setDomainGridlinesVisible(showGridlines); if (labelRotation != null) { double angle = labelRotation.doubleValue(); CategoryLabelPosition top = createUpRotationCategoryLabelPosition(PlaneDirection.TOP, angle); CategoryLabelPosition bottom = createUpRotationCategoryLabelPosition(PlaneDirection.BOTTOM, angle); CategoryLabelPosition left = createUpRotationCategoryLabelPosition(PlaneDirection.LEFT, angle); CategoryLabelPosition right = createUpRotationCategoryLabelPosition(PlaneDirection.RIGHT, angle); CategoryLabelPositions rotationLabelPositions = new CategoryLabelPositions(top, bottom, left, right); categoryAxis.setCategoryLabelPositions(rotationLabelPositions); } final String[] colors = getSeriesColor(); for (int i = 0; i < colors.length; i++) { renderer.setSeriesPaint(i, parseColorFromString(colors[i])); } if (lowerMargin != null) { categoryAxis.setLowerMargin(lowerMargin.doubleValue()); } if (upperMargin != null) { categoryAxis.setUpperMargin(upperMargin.doubleValue()); } if (categoryMargin != null) { categoryAxis.setCategoryMargin(categoryMargin.doubleValue()); } configureRangeAxis(cpl, labelFont); } protected void configureRangeAxis(final CategoryPlot cpl, final Font labelFont) { final ValueAxis rangeAxis = cpl.getRangeAxis(); if (rangeAxis instanceof NumberAxis) { final NumberAxis numberAxis = (NumberAxis) rangeAxis; numberAxis.setAutoRangeIncludesZero(isRangeIncludesZero()); numberAxis.setAutoRangeStickyZero(isRangeStickyZero()); if (getRangePeriodCount() > 0) { if (getRangeTickFormat() != null) { numberAxis.setTickUnit(new NumberTickUnit(getRangePeriodCount(), getRangeTickFormat())); } else if (getRangeTickFormatString() != null) { final FastDecimalFormat formatter = new FastDecimalFormat(getRangeTickFormatString(), getResourceBundleFactory().getLocale()); numberAxis.setTickUnit(new FastNumberTickUnit(getRangePeriodCount(), formatter)); } else { numberAxis.setTickUnit(new FastNumberTickUnit(getRangePeriodCount())); } } else { if (getRangeTickFormat() != null) { numberAxis.setNumberFormatOverride(getRangeTickFormat()); } else if (getRangeTickFormatString() != null) { final DecimalFormat formatter = new DecimalFormat(getRangeTickFormatString(), new DecimalFormatSymbols(getResourceBundleFactory().getLocale())); numberAxis.setNumberFormatOverride(formatter); standardTickUnitsApplyFormat(numberAxis, formatter); } } } else if (rangeAxis instanceof DateAxis) { final DateAxis numberAxis = (DateAxis) rangeAxis; if (getRangePeriodCount() > 0 && getRangeTimePeriod() != null) { if (getRangeTickFormatString() != null) { final SimpleDateFormat formatter = new SimpleDateFormat(getRangeTickFormatString(), new DateFormatSymbols(getResourceBundleFactory().getLocale())); numberAxis.setTickUnit(new DateTickUnit(getDateUnitAsInt(getRangeTimePeriod()), (int) getRangePeriodCount(), formatter)); } else { numberAxis.setTickUnit( new DateTickUnit(getDateUnitAsInt(getRangeTimePeriod()), (int) getRangePeriodCount())); } } else if (getRangeTickFormatString() != null) { final SimpleDateFormat formatter = new SimpleDateFormat(getRangeTickFormatString(), new DateFormatSymbols(getResourceBundleFactory().getLocale())); numberAxis.setDateFormatOverride(formatter); } } if (rangeAxis != null) { rangeAxis.setLabelFont(labelFont); rangeAxis.setTickLabelFont(labelFont); if (getRangeTitleFont() != null) { rangeAxis.setLabelFont(getRangeTitleFont()); } if (getRangeTickFont() != null) { rangeAxis.setTickLabelFont(getRangeTickFont()); } final int level = getRuntime().getProcessingContext().getCompatibilityLevel(); if (ClassicEngineBoot.isEnforceCompatibilityFor(level, 3, 8)) { if (getRangeMinimum() != 0) { rangeAxis.setLowerBound(getRangeMinimum()); } if (getRangeMaximum() != 1) { rangeAxis.setUpperBound(getRangeMaximum()); } if (getRangeMinimum() == 0 && getRangeMaximum() == 0) { rangeAxis.setAutoRange(true); } } else { if (isAutoRange()) { rangeAxis.setAutoRange(isAutoRange()); } else { double factor = getScaleFactor(); if (factor > DEFAULT_SCALE_FACTOR) { // PRD-5340 hack // this method is invoked after all series were populated // hence the axis already has the graph's max and min values; double lower = rangeAxis.getLowerBound(); if (lower < 0) { lower *= factor; } else if (lower > 0) { lower /= factor; } double upper = rangeAxis.getUpperBound(); if (upper > 0) { upper *= factor; } else if (upper < 0) { upper /= factor; } rangeAxis.setRange(lower, upper); } else { // the 'scaleFactor' property is left intact or has an incorrect value rangeAxis.setUpperBound(getRangeMaximum()); rangeAxis.setLowerBound(getRangeMinimum()); } } } } } protected void configureLogarithmicAxis(final CategoryPlot plot) { if (isLogarithmicAxis()) { final LogarithmicAxis logarithmicAxis; if (isHumanReadableLogarithmicFormat()) { plot.getRenderer().setBaseItemLabelGenerator(new LogCategoryItemLabelGenerator()); logarithmicAxis = new ScalingLogarithmicAxis(getValueAxisLabel()); logarithmicAxis.setStrictValuesFlag(false); } else { logarithmicAxis = new LogarithmicAxis(getValueAxisLabel()); logarithmicAxis.setStrictValuesFlag(false); } plot.setRangeAxis(logarithmicAxis); } } public Class getRangeTimePeriod() { return rangeTimePeriod; } public void setRangeTimePeriod(final Class rangeTimePeriod) { this.rangeTimePeriod = rangeTimePeriod; } public double getRangePeriodCount() { return rangePeriodCount; } public void setRangePeriodCount(final double rangePeriodCount) { this.rangePeriodCount = rangePeriodCount; } /** * Return a completly separated copy of this function. The copy does no longer share any changeable objects with the * original function. * * @return a copy of this function. */ public Expression getInstance() { final CategoricalChartExpression expression = (CategoricalChartExpression) super.getInstance(); if (expression.rangeTickFormat != null) { expression.rangeTickFormat = (NumberFormat) expression.rangeTickFormat.clone(); } return expression; } protected int getDateUnitAsInt(final Class domainTimePeriod) { if (Second.class.equals(domainTimePeriod)) { return DateTickUnit.SECOND; } if (Minute.class.equals(domainTimePeriod)) { return DateTickUnit.MINUTE; } if (Hour.class.equals(domainTimePeriod)) { return DateTickUnit.HOUR; } if (Day.class.equals(domainTimePeriod)) { return DateTickUnit.DAY; } if (Month.class.equals(domainTimePeriod)) { return DateTickUnit.MONTH; } if (Year.class.equals(domainTimePeriod)) { return DateTickUnit.YEAR; } if (Second.class.equals(domainTimePeriod)) { return DateTickUnit.MILLISECOND; } return DateTickUnit.DAY; } public void reconfigureForCompatibility(final int versionTag) { if (ClassicEngineBoot.isEnforceCompatibilityFor(versionTag, 3, 8)) { setAutoRange(getRangeMinimum() == 0 && getRangeMaximum() == 1); } } /** * Used instead of <code>org.jfree.chart.axis.CategoryLabelPosition.createUpRotationLabelPositions</code>. * <p> * It additionally takes into consideration the axis position. * * @param axisPosition * @param labelAngle * @return */ protected CategoryLabelPosition createUpRotationCategoryLabelPosition(PlaneDirection axisPosition, double labelAngle) { RectangleAnchor categoryAnchor = axisPosition.opposite().asRectangleAnchor(); double labelAnchorDirectionAngle = axisPosition.opposite().asAngle() - labelAngle; PlaneDirection labelAnchorDirection = getTextAnchorDirectionOfAngle(labelAnchorDirectionAngle); TextBlockAnchor labelAnchor = labelAnchorDirection.asTextBlockAnchor(); TextAnchor rotationAnchor = labelAnchorDirection.asTextAnchor(); return new CategoryLabelPosition(categoryAnchor, labelAnchor, rotationAnchor, -labelAngle, CategoryLabelWidthType.RANGE, 0.50f); } /** * Chooses a proper anchor for a text label at a chart axis tick. * <p> * E.g. * <p> * Axis position is LEFT, label rotation = 0. So angle = 0. * <p> * Axis position is BOTTOM, label rotation = 90. So angle = 0. * <p> * Axis position is BOTTOM, label rotation = 0. So angle = pi/2 (90 degrees). * * @param angle can be assumed as the label-relative direction to the axis. * @return */ protected PlaneDirection getTextAnchorDirectionOfAngle(double angle) { //Divide to 32 sectors (0..31). Counterclockwise from RIGHT. int sectorIndex = ((int) ((((angle * 16 / Math.PI)) % 32) + 32)) % 32; switch (sectorIndex) { case 5: case 6: return PlaneDirection.TOP_RIGHT; case 7: case 8: return PlaneDirection.TOP; case 9: case 10: return PlaneDirection.TOP_LEFT; case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 19: case 20: return PlaneDirection.LEFT; case 21: case 22: return PlaneDirection.BOTTOM_LEFT; case 23: case 24: return PlaneDirection.BOTTOM; case 25: case 26: return PlaneDirection.BOTTOM_RIGHT; case 27: case 28: case 29: case 30: case 31: case 0: case 1: case 2: case 3: case 4: default: return PlaneDirection.RIGHT; } } /** * Local utility enum. * Used to calculate ahchors. */ static enum PlaneDirection { RIGHT, TOP_RIGHT, TOP, TOP_LEFT, LEFT, BOTTOM_LEFT, BOTTOM, BOTTOM_RIGHT; private static final int COUNT = values().length; public static PlaneDirection byUnlimitedIndex(int unlimitedIndex) { return values()[(unlimitedIndex % COUNT + COUNT) % COUNT]; } public PlaneDirection opposite() { return byUnlimitedIndex(this.ordinal() + COUNT / 2); } public RectangleAnchor asRectangleAnchor() { switch (this) { case RIGHT: return RectangleAnchor.RIGHT; case TOP_RIGHT: return RectangleAnchor.TOP_RIGHT; case TOP: return RectangleAnchor.TOP; case TOP_LEFT: return RectangleAnchor.TOP_LEFT; case LEFT: return RectangleAnchor.LEFT; case BOTTOM_LEFT: return RectangleAnchor.BOTTOM_LEFT; case BOTTOM: return RectangleAnchor.BOTTOM; case BOTTOM_RIGHT: return RectangleAnchor.BOTTOM_RIGHT; default: return null; } } public TextBlockAnchor asTextBlockAnchor() { switch (this) { case RIGHT: return TextBlockAnchor.CENTER_RIGHT; case TOP_RIGHT: return TextBlockAnchor.TOP_RIGHT; case TOP: return TextBlockAnchor.TOP_CENTER; case TOP_LEFT: return TextBlockAnchor.TOP_LEFT; case LEFT: return TextBlockAnchor.CENTER_LEFT; case BOTTOM_LEFT: return TextBlockAnchor.BOTTOM_LEFT; case BOTTOM: return TextBlockAnchor.BOTTOM_CENTER; case BOTTOM_RIGHT: return TextBlockAnchor.BOTTOM_RIGHT; default: return null; } } public TextAnchor asTextAnchor() { switch (this) { case RIGHT: return TextAnchor.CENTER_RIGHT; case TOP_RIGHT: return TextAnchor.TOP_RIGHT; case TOP: return TextAnchor.TOP_CENTER; case TOP_LEFT: return TextAnchor.TOP_LEFT; case LEFT: return TextAnchor.CENTER_LEFT; case BOTTOM_LEFT: return TextAnchor.BOTTOM_LEFT; case BOTTOM: return TextAnchor.BOTTOM_CENTER; case BOTTOM_RIGHT: return TextAnchor.BOTTOM_RIGHT; default: return null; } } public double asAngle() { return this.ordinal() * 0.25 * Math.PI; } } }