/* File: DasLabelAxis.java * Copyright (C) 2002-2003 The University of Iowa * Created by: Jeremy Faden * Jessica Swanner * Edward E. West * * This file is part of the das2 library. * * das2 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 2 of the License, or * (at your option) any later version. * * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.das2.graph; import java.awt.Font; import java.awt.Graphics2D; import java.text.DecimalFormat; import java.util.Arrays; import org.das2.datum.Datum; import org.das2.datum.DatumRange; import org.das2.datum.DatumUtil; import org.das2.datum.DatumVector; import org.das2.datum.Units; import org.das2.datum.format.DatumFormatter; import org.das2.graph.event.DasUpdateEvent; import org.das2.graph.event.DasUpdateListener; import org.das2.util.GrannyTextRenderer; import org.das2.qds.DataSetUtil; import org.das2.qds.QDataSet; public class DasLabelAxis extends DasAxis implements DasUpdateListener { DecimalFormat nfy = null; DatumVector labels = null; double[] labelValues = null; Units labelUnits = null; int[] labelPositions = null; DatumFormatter df = null; int indexMinimum; // first label to be displayed int indexMaximum; /** Holds value of property outsidePadding. */ private int outsidePadding = 5; /** Holds value of property floppyltemSpacing. */ private boolean floppyItemSpacing = false; // last label to be displayed private void setLabels(DatumVector labels) { if (labels.getLength() == 0) { throw new IllegalArgumentException("labels can not be a zero-length array!"); } this.labels = labels; this.labelPositions = new int[labels.getLength()]; indexMinimum = 0; indexMaximum = labels.getLength() - 1; labelUnits = labels.getUnits(); labelValues = labels.toDoubleArray(labelUnits); this.df = DatumUtil.bestFormatter(labels); } /** * vg1pws needed a way to explicitly set this. */ public void setLabelFormatter(DatumFormatter df) { this.df = df; } protected DasLabelAxis(DatumVector labels, DataRange dataRange, int orientation) { super(dataRange, orientation); setLabels(labels); getDataRange().addUpdateListener(this); } public DasLabelAxis(DatumVector labels, int orientation) { super(labels.get(0), labels.get(labels.getLength() - 1), orientation, false); setLabels(labels); getDataRange().addUpdateListener(this); } public DasLabelAxis( QDataSet labels, int orientation ) { this( DataSetUtil.asDatumVector(labels), orientation ); } public int[] getLabelPositions() { return Arrays.copyOf( this.labelPositions, this.labelPositions.length ); } private void updateTickPositions() { if (isDisplayable()) { int nlabel = indexMaximum - indexMinimum + 1; int size; int min; double interItemSpacing; if (this.getOrientation() == DasAxis.HORIZONTAL) { size = getColumn().getWidth() - outsidePadding * 2; interItemSpacing = ((float) size) / nlabel; if (!floppyItemSpacing) { interItemSpacing = (int) interItemSpacing; } min = (getColumn().getDMinimum() + outsidePadding + (int) (interItemSpacing / 2)); } else { size = getRow().getHeight() - outsidePadding * 2; interItemSpacing = -1 * ((float) size) / nlabel; if (!floppyItemSpacing) { interItemSpacing = (int) interItemSpacing; } min = getRow().getDMaximum() - outsidePadding + (int) (interItemSpacing / 2); } for (int i = 0; i < labelPositions.length; i++) { labelPositions[i] = min + (int) (interItemSpacing * ((i - indexMinimum) + 0)); } firePropertyChange("labelPositions", null, labelPositions); } } @Override public Datum findTick(Datum xDatum, double direction, boolean minor) { // somehow tickv.minor is set to non-zero, and Axis.findTick gets messed up. // This is a work-around... return xDatum; } @Override public void updateTickV() { //super.updateTickV(); updateTickPositions(); } @Override public TickVDescriptor getTickV() { TickVDescriptor result = new TickVDescriptor(); result.units = getUnits(); result.tickV = labels.getSubVector(indexMinimum, indexMaximum + 1); result.minorTickV = DatumVector.newDatumVector(new double[0], result.units); return result; } @Override public double transform(double value, Units units) { if (units != this.labelUnits) { throw new IllegalArgumentException("units don't match"); } int iclose = findClosestIndex(labelValues, value); return labelPositions[iclose]; } private int findClosestIndex(int[] data, int searchFor) { int iclose = 0; double closest = Math.abs(data[iclose] - searchFor); for (int i = 0; i < labelPositions.length; i++) { double c1 = Math.abs(data[i] - searchFor); if (c1 < closest) { iclose = i; closest = c1; } } return iclose; } private int findClosestIndex(double[] data, double searchFor) { int iclose = 0; double closest = Math.abs(data[iclose] - searchFor); for (int i = 0; i < labelPositions.length; i++) { double c1 = Math.abs(data[i] - searchFor); if (c1 < closest) { iclose = i; closest = c1; } } return iclose; } @Override public Datum invTransform(double d) { int iclose = findClosestIndex(labelPositions, (int) d); return labels.get(iclose); } /** * override this to allow a single Datum. * @param dr * @return */ @Override protected boolean rangeIsAcceptable(DatumRange dr) { return true; } @Override protected String tickFormatter( Datum t ) { return df.format( t ); } @Override protected String[] tickFormatter(DatumVector tickV, DatumRange datumRange) { return df.axisFormat( tickV, datumRange ); } public int getInterItemSpace() { return (int) Math.abs(transform(labels.get(1)) - transform(labels.get(0))); } /** * get the minimum pixel location of the bin allocated to the Datum. * @param d * @return pixel location of the min. */ public int getItemMin(Datum d) { Units units = d.getUnits(); double value = d.doubleValue(units); int iclose = findClosestIndex(labelValues, units.convertDoubleTo(this.getUnits(), value)); int tickPosition = labelPositions[iclose]; int w = getInterItemSpace(); return tickPosition - w / 2; } /** * get the maximum pixel location of the bin allocated to the Datum. * @param d * @return pixel location of the max. */ public int getItemMax(Datum d) { int w = getInterItemSpace(); return getItemMin(d) + w; } public DasAxis createAttachedAxis(DasRow row, DasColumn column) { DasLabelAxis result = new DasLabelAxis(labels, getDataRange(), this.getOrientation()); return result; } @Override public DasAxis createAttachedAxis(int orientation) { return new DasLabelAxis(labels, getDataRange(), orientation); } public void update(DasUpdateEvent e) { double minimum = getDataRange().getMinimum(); double maximum = getDataRange().getMaximum(); if (getDataRange().getUnits() != this.labelUnits) { throw new IllegalArgumentException("units don't match"); } this.indexMinimum = findClosestIndex(labelValues, minimum); this.indexMaximum = findClosestIndex(labelValues, maximum); if (this.indexMinimum > this.indexMaximum) { int t = this.indexMaximum; this.indexMaximum = this.indexMinimum; this.indexMinimum = t; } } @Override protected void paintHorizontalAxis(java.awt.Graphics2D g) { boolean bottomTicks = (getOrientation() == BOTTOM || isOppositeAxisVisible()); boolean bottomTickLabels = (getOrientation() == BOTTOM && isTickLabelsVisible()); boolean bottomLabel = (getOrientation() == BOTTOM && !axisLabel.equals("")); boolean topTicks = (getOrientation() == TOP || isOppositeAxisVisible()); boolean topTickLabels = (getOrientation() == TOP && isTickLabelsVisible()); boolean topLabel = (getOrientation() == TOP && !axisLabel.equals("")); int topPosition = getRow().getDMinimum() - 1; int bottomPosition = getRow().getDMaximum(); int DMax = getColumn().getDMaximum(); int DMin = getColumn().getDMinimum(); Font labelFont = getTickLabelFont(); TickVDescriptor ticks = getTickV(); if (bottomTicks) { g.drawLine(DMin, bottomPosition, DMax, bottomPosition); } if (topTicks) { g.drawLine(DMin, topPosition, DMax, topPosition); } int tickLengthMajor = labelFont.getSize() * 2 / 3; int tickLength; String[] llabels= tickFormatter( ticks.tickV, getDatumRange() ); for (int i = 0; i < ticks.tickV.getLength(); i++) { Datum d = ticks.tickV.get(i); int w = getInterItemSpace(); int tickPosition = (int) Math.floor(transform(d) + 0.5) - w / 2; tickLength = tickLengthMajor; if (bottomTicks) { g.drawLine(getItemMin(d), bottomPosition, getItemMin(d), bottomPosition + tickLength); if (i == ticks.tickV.getLength() - 1) { g.drawLine(getItemMax(d), bottomPosition, getItemMax(d), bottomPosition + tickLength); } if (bottomTickLabels) { drawLabel(g, d, llabels[i], i, tickPosition + w / 2, bottomPosition + tickLength); } } if (topTicks) { g.drawLine(getItemMin(d), topPosition, getItemMin(d), topPosition - tickLength); if (i == ticks.tickV.getLength() - 1) { g.drawLine(getItemMax(d), topPosition, getItemMax(d), topPosition - tickLength); } if (topTickLabels) { drawLabel(g, d, llabels[i], i, tickPosition + w / 2, topPosition - tickLength); } } } if (!axisLabel.equals("")) { Graphics2D g2 = (Graphics2D) g.create(); int titlePositionOffset = getTitlePositionOffset(); GrannyTextRenderer gtr = new GrannyTextRenderer(); gtr.setString(g2, axisLabel); int titleWidth = (int) gtr.getWidth(); int baseline; int leftEdge; g2.setFont(getLabelFont()); if (bottomLabel) { leftEdge = DMin + (DMax - DMin - titleWidth) / 2; baseline = bottomPosition + titlePositionOffset; gtr.draw(g2, (float) leftEdge, (float) baseline); } if (topLabel) { leftEdge = DMin + (DMax - DMin - titleWidth) / 2; baseline = topPosition - titlePositionOffset; gtr.draw(g2, (float) leftEdge, (float) baseline); } g2.dispose(); } } @Override protected void paintVerticalAxis(java.awt.Graphics2D g) { boolean leftTicks = (getOrientation() == LEFT || isOppositeAxisVisible()); boolean leftTickLabels = (getOrientation() == LEFT && isTickLabelsVisible()); boolean leftLabel = (getOrientation() == LEFT && !axisLabel.equals("")); boolean rightTicks = (getOrientation() == RIGHT || isOppositeAxisVisible()); boolean rightTickLabels = (getOrientation() == RIGHT && isTickLabelsVisible()); boolean rightLabel = (getOrientation() == RIGHT && !axisLabel.equals("")); int leftPosition = getColumn().getDMinimum() - 1; int rightPosition = getColumn().getDMaximum(); int DMax = getRow().getDMaximum(); int DMin = getRow().getDMinimum(); Font labelFont = getTickLabelFont(); TickVDescriptor ticks = getTickV(); if (leftTicks) { g.drawLine(leftPosition, DMin, leftPosition, DMax); } if (rightTicks) { g.drawLine(rightPosition, DMin, rightPosition, DMax); } int tickLengthMajor = labelFont.getSize() * 2 / 3; int tickLength; String[] llabels= tickFormatter( ticks.tickV, getDatumRange() ); for (int i = 0; i < ticks.tickV.getLength(); i++) { Datum datum = ticks.tickV.get(i); int tickPosition = (getItemMax(datum) + getItemMin(datum)) / 2 - g.getFontMetrics().getAscent() / 5; if (getRow().contains(tickPosition)) { tickLength = tickLengthMajor; if (leftTicks) { if (i == ticks.tickV.getLength() - 1) { g.drawLine(leftPosition, getItemMin(datum), leftPosition - tickLength, getItemMin(datum)); } g.drawLine(leftPosition, getItemMax(datum), leftPosition - tickLength, getItemMax(datum)); if (leftTickLabels) { drawLabel(g, datum, llabels[i], i, leftPosition - tickLength, tickPosition); } } if (rightTicks) { if (i == ticks.tickV.getLength() - 1) { g.drawLine(rightPosition, getItemMin(datum), rightPosition + tickLength, getItemMin(datum)); } g.drawLine(rightPosition, getItemMax(datum), rightPosition + tickLength, getItemMax(datum)); if (rightTickLabels) { drawLabel(g, datum, llabels[i], i, rightPosition + tickLength, tickPosition); } } } } if (!axisLabel.equals("")) { Graphics2D g2 = (Graphics2D) g.create(); int titlePositionOffset = getTitlePositionOffset(); GrannyTextRenderer gtr = new GrannyTextRenderer(); gtr.setString(g2, axisLabel); int titleWidth = (int) gtr.getWidth(); int baseline; int leftEdge; g2.setFont(getLabelFont()); if (leftLabel) { g2.rotate(-Math.PI / 2.0); leftEdge = -DMax + (DMax - DMin - titleWidth) / 2; baseline = leftPosition - titlePositionOffset; gtr.draw(g2, (float) leftEdge, (float) baseline); } if (rightLabel) { g2.rotate(Math.PI / 2.0); leftEdge = DMin + (DMax - DMin - titleWidth) / 2; baseline = -rightPosition - titlePositionOffset; gtr.draw(g2, (float) leftEdge, (float) baseline); } g2.dispose(); } } /** Getter for property outsidePadding. * @return Value of property outsidePadding. * */ public int getOutsidePadding() { return this.outsidePadding; } /** Setter for property outsidePadding. * @param outsidePadding New value of property outsidePadding. * */ public void setOutsidePadding(int outsidePadding) { int oldValue = outsidePadding; this.outsidePadding = outsidePadding; firePropertyChange("setOutsidePadding", oldValue, outsidePadding); updateTickPositions(); update(); } /** Getter for property floppyltemSpacing. * @return Value of property floppyltemSpacing. * */ public boolean isFloppyItemSpacing() { return this.floppyItemSpacing; } /** Setter for property floppyltemSpacing. * @param floppyItemSpacing New value of property floppyltemSpacing. * */ public void setFloppyItemSpacing(boolean floppyItemSpacing) { boolean oldValue = this.floppyItemSpacing; this.floppyItemSpacing = floppyItemSpacing; firePropertyChange("floppyItemSpacing", oldValue, floppyItemSpacing); updateTickPositions(); update(); } @Override public java.awt.geom.AffineTransform getAffineTransform(Memento memento, java.awt.geom.AffineTransform at) { return at; //equals doesn't seem to work /*if ( this.getMemento().equals( memento ) ) { return at; } else { return null; }*/ } }