package org.autoplot.dom; import java.awt.Component; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.List; import java.util.logging.Level; import org.das2.datum.Datum; import org.das2.datum.DatumRange; import org.das2.datum.DatumRangeUtil; import org.das2.datum.Units; import org.das2.graph.DasAxis; import org.das2.graph.DasAxis.Lock; import org.das2.graph.DasColumn; import org.das2.graph.DasRow; import org.das2.util.LoggerManager; import org.jdesktop.beansbinding.Converter; /** * @author jbf */ public class AxisController extends DomNodeController { DasAxis dasAxis; private DasColumn column; private DasRow row; private Application dom; Plot plot; final Axis axis; private final static Object PENDING_RANGE_TWEAK="pendingRangeTweak"; private boolean defaultOppositeRight= false; public AxisController(Application dom, Plot plot, Axis axis, DasAxis dasAxis) { super( axis ); this.dom = dom; this.dasAxis = dasAxis; this.plot= plot; this.axis = axis; axis.controller = this; if ( plot.zaxis==axis ) { defaultOppositeRight= true; } bindTo(); axis.addPropertyChangeListener(rangeChangeListener); } /** * checks to see that the axis is still valid and clears the autoRange property. */ private final PropertyChangeListener rangeChangeListener = new PropertyChangeListener() { private DatumRange logCheckRange(DatumRange range, boolean log) { Units u = range.getUnits(); double dmin= range.min().doubleValue(u); double dmax= range.max().doubleValue(u); boolean changed = false; if ( log && dmax <= 0.) { dmax = 1000; changed = true; } if ( log && dmin <= 0.) { dmin = dmax / 10000; changed = true; } //disable this near-zero test because with lightweight bindings we get stuck in a loop. //if ( !log && dmin>0 && dmin<=dmax/10000 ) { // dmin = 0; // changed = true; //} if (changed) { return new DatumRange(dmin, dmax, u); } else { return range; } } @Override public synchronized void propertyChange(PropertyChangeEvent evt) { LoggerManager.logPropertyChangeEvent(evt); // ensure that log doesn't make axis invalid, or min trivially close to zero. if ( dom.controller.isValueAdjusting() || valueIsAdjusting() ) return; if ( evt.getPropertyName().equals( Axis.PROP_RANGE ) || evt.getPropertyName().equals( Axis.PROP_LOG ) ) axis.setAutoRange(false); switch (evt.getPropertyName()) { case Axis.PROP_LABEL: axis.setAutoLabel(false); break; case Axis.PROP_LOG: { if ( isPendingChanges() ) return; DatumRange oldRange = axis.range; final DatumRange range = logCheckRange(axis.range, axis.log); if (!range.equals(oldRange)) { if ( new Exception().getStackTrace().length > 280 ) { changesSupport.registerPendingChange(this,PENDING_RANGE_TWEAK); changesSupport.performingChange(this, PENDING_RANGE_TWEAK); axis.setLog(false); changesSupport.changePerformed(this, PENDING_RANGE_TWEAK); } else { changesSupport.registerPendingChange(this,PENDING_RANGE_TWEAK); changesSupport.performingChange(this, PENDING_RANGE_TWEAK); axis.setRange(range); changesSupport.changePerformed(this, PENDING_RANGE_TWEAK); } } break; } case Axis.PROP_RANGE: { if ( isPendingChanges() ) return; DatumRange oldRange = axis.range; final DatumRange range = logCheckRange(axis.range, axis.log); if (!range.equals(oldRange)) { if ( new Exception().getStackTrace().length > 280 ) { changesSupport.registerPendingChange(this,PENDING_RANGE_TWEAK); changesSupport.performingChange(this, PENDING_RANGE_TWEAK); axis.setLog(false); changesSupport.changePerformed(this, PENDING_RANGE_TWEAK); } else { changesSupport.registerPendingChange(this,PENDING_RANGE_TWEAK); changesSupport.performingChange(this, PENDING_RANGE_TWEAK); if ( axis.isLog() ) axis.setLog(false); // pretty sure it is. axis.setRange(range); changesSupport.changePerformed(this, PENDING_RANGE_TWEAK); } } break; } case Axis.PROP_SCALE: if ( dasAxis!=null ) { // the scale has changed, so let's see if we can reset the range to match the scale int npixels; npixels= dasAxis.isHorizontal() ? dasAxis.getColumn().getWidth() : dasAxis.getRow().getHeight(); Datum w; Units u= dasAxis.getUnits(); if ( u!=u.getOffsetUnits() ) { w= axis.getRange().width(); } else if ( dasAxis.isLog() ) { w= Units.log10Ratio.createDatum( Math.log10( axis.getRange().max().divide(axis.getRange().min() ).value() ) ); } else { w= axis.getRange().width(); } Datum oldScale= w.divide(npixels); Datum newScale= (Datum)evt.getNewValue(); if ( !oldScale.getUnits().isConvertibleTo(newScale.getUnits()) ) { return; } if ( !oldScale.equals(newScale) ) { //System.err.println("105: need to reset scale"); Datum scale = (Datum)evt.getNewValue(); DatumRange otherRange = dasAxis.getDatumRange(); u= otherRange.getUnits(); Datum otherw; if ( u!=u.getOffsetUnits() ) { otherw= otherRange.width(); } else if ( dasAxis.isLog() ) { otherw= Units.log10Ratio.createDatum( Math.log10( otherRange.max().divide( otherRange.min() ).value() ) ); } else { otherw= otherRange.width(); } Datum otherScale = otherw.divide(npixels); double expand = (scale.divide(otherScale).value() - 1) / 2; if (Math.abs(expand) > 0.0001) { logger.log(Level.FINER, "expand={0} scale={1} otherScale={2}", new Object[]{expand, scale, otherScale}); try { DatumRange newOtherRange; if ( dasAxis.isLog() ) { newOtherRange= DatumRangeUtil.rescaleLog(otherRange, 0 - expand, 1 + expand); } else { newOtherRange= DatumRangeUtil.rescale(otherRange, 0 - expand, 1 + expand); } axis.setRange(newOtherRange); } catch ( IllegalArgumentException ex ) { System.err.println("here129"); } } //DatumRange nr= } } break; default: break; } } }; /** * true if the Axis is adjusting, or the DasAxis which implements. * @return true if the Axis is adjusting, or the DasAxis which implements. */ public boolean valueIsAdjusting() { return super.isValueAdjusting() || dasAxis.valueIsAdjusting(); } /** * set the range without affecting the auto state. * @param range the new range * @param log true if the axis should be log. */ public void setRangeAutomatically( DatumRange range, boolean log ) { boolean oldAutoRange= axis.autoRange; axis.setRange(range); axis.setLog(log); axis.autoRange=oldAutoRange; } /** * set the label, leaving its autoLabel property true. * @param label */ public void setLabelAutomatically( String label ) { if ( axis.getLabel().contains("%{RANGE}") && !label.contains("%{RANGE}") ) { return; } axis.setLabel(label); axis.setAutoLabel(true); } /** * reset the axis units to a new unit which is convertible. * @param nu */ public void resetAxisUnits( Units nu ) { DatumRange oldRange=dasAxis.getDatumRange(); DatumRange newRange= DatumRange.newDatumRange( oldRange.min().doubleValue(nu), oldRange.max().doubleValue(nu), nu ); dasAxis.resetRange(newRange); axis.setRange(newRange); } /** * reset the axis range, without the units check. * @param newRange */ public void resetAxisRange( DatumRange newRange ) { dasAxis.resetRange(newRange); axis.setRange(newRange); } private Converter getOppositeConverter( Axis axis, final DasAxis dasAxis ) { return new Converter() { @Override public Object convertForward(Object s) { int orientation; if ( dasAxis.isHorizontal() ) { if ( s.equals(Boolean.TRUE) ) { orientation= DasAxis.TOP; } else { orientation=DasAxis.BOTTOM; } } else if ( AxisController.this.defaultOppositeRight ) { if ( s.equals(Boolean.TRUE) ) { orientation= DasAxis.LEFT; } else { orientation= DasAxis.RIGHT; } } else { if ( s.equals(Boolean.TRUE) ) { orientation= DasAxis.RIGHT; } else { orientation= DasAxis.LEFT; } } return orientation; } @Override public Object convertReverse(Object t) { int orientation= (Integer)t; return orientation==DasAxis.TOP || orientation==DasAxis.RIGHT; } }; } protected LabelConverter labelConverter; public final synchronized void bindTo() { ApplicationController ac = dom.controller; ac.bind(axis, Axis.PROP_RANGE, dasAxis, DasAxis.PROPERTY_DATUMRANGE); ac.bind(axis, Axis.PROP_LOG, dasAxis, DasAxis.PROP_LOG); labelConverter= new LabelConverter( dom, plot, axis, null, null ); ac.bind(axis, Axis.PROP_LABEL, dasAxis, DasAxis.PROP_LABEL, labelConverter ); ac.bind(axis, Axis.PROP_FONTSIZE, dasAxis, DasAxis.PROP_FONTSIZE ); ac.bind(axis, Axis.PROP_DRAWTICKLABELS, dasAxis, "tickLabelsVisible"); ac.bind(axis, Axis.PROP_FLIPPED, dasAxis, DasAxis.PROP_FLIPPED ); ac.bind(axis, Axis.PROP_VISIBLE, dasAxis, "visible" ); ac.bind(axis, Axis.PROP_OPPOSITE, dasAxis, "orientation", getOppositeConverter(axis,dasAxis) ); ac.bind(axis, Axis.PROP_TICKVALUES, dasAxis, DasAxis.PROP_TICKVALUES ); ac.bind(axis, Axis.PROP_REFERENCE, dasAxis, DasAxis.PROP_REFERENCE ); ac.bind(axis, Axis.PROP_FOREGROUND, dasAxis, "foreground" ); ac.bind(axis, Axis.PROP_AXISOFFSET, dasAxis, DasAxis.PROP_AXISOFFSET ); column= dasAxis.getColumn(); row= dasAxis.getRow(); if ( dasAxis.isHorizontal() ) { column.addPropertyChangeListener(scaleListener); } else { row.addPropertyChangeListener(scaleListener); } axis.addPropertyChangeListener( Axis.PROP_RANGE, scaleListener ); axis.addPropertyChangeListener( Axis.PROP_LOG, scaleListener ); } public final synchronized void removeBindings() { //System.err.println("removeBindings for "+axis + " " +scaleListener );//bug2053 axis.removePropertyChangeListener( Axis.PROP_RANGE, scaleListener ); axis.removePropertyChangeListener( Axis.PROP_LOG, scaleListener ); labelConverter= null; if ( dasAxis.isHorizontal() ) { //if ( this.axis.getId().equals("xaxis_2") ) { // System.err.println("here2 rm xaxis_2="+this.axis+ "@"+ this.axis.hashCode() + "dasColumn=@"+dasAxis.getColumn().hashCode()+" dasAxis="+ dasAxis.getDasName() + "@" + dasAxis.hashCode()); // System.err.println(); //} column.removePropertyChangeListener(scaleListener); } else { row.removePropertyChangeListener(scaleListener); } dom.controller.unbind(axis); axis.removePropertyChangeListener( rangeChangeListener ); } /** * remove any references this object has before as it is deleted. */ public final synchronized void removeReferences() { labelConverter= null; //this.dom= null; //this.axis= null; //System.err.println("* removeReferences for "+scaleListener + " " + this.axis); //this.dasAxis= null; //this.plot= null; } private final PropertyChangeListener scaleListener= new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { logger.finer("scaleListener"); int npixels; if ( dasAxis==null ) { return; } npixels= dasAxis.isHorizontal() ? dasAxis.getColumn().getWidth() : dasAxis.getRow().getHeight(); Datum w; Units u= dasAxis.getUnits(); if ( u.getOffsetUnits()!=u ) { w= axis.getRange().width(); // we have to do this, it doesn't matter if it is log. } else if ( dasAxis.isLog() ) { w= Units.log10Ratio.createDatum( Math.log10( axis.getRange().max().divide(axis.getRange().min() ).value() ) ); } else { w= axis.getRange().width(); } boolean rangeWasChanged= evt.getPropertyName().equals(Axis.PROP_RANGE); Datum scale= w.divide(npixels); if ( !axis.getScale().equals(scale) ) { // when scale is bound, change the range. When it is not bound, go ahead and just reset the scale List bms= dom.getController().findBindings( axis, Axis.PROP_SCALE ); if ( !rangeWasChanged && bms.size()>0 ) { logger.log(Level.FINEST, "{0}: the scale is bound, so adjust the range instead", axis.id); DatumRange dr= axis.getRange(); Datum newW= axis.getScale().multiply(npixels); DatumRange newRange= DatumRangeUtil.createCentered( dr.middle(), newW ); axis.setRange(newRange); } else { axis.setScale( scale ); } } } @Override public String toString() {//bug2053 return "scaleListener " + hashCode() + " for "+axis; } }; public DasAxis getDasAxis() { return dasAxis; } void syncTo(DomNode n,List exclude ) { Lock lock = null; if ( dasAxis!=null ) { lock= dasAxis.mutatorLock(); lock.lock(); } //TODO: should call ((DomNode)n).syncTo(n); Axis that = (Axis) n; if ( !exclude.contains( Axis.PROP_LOG ) ) axis.setLog(that.isLog()); if ( !exclude.contains( Axis.PROP_FLIPPED ) ) axis.setFlipped(that.isFlipped()); if ( !exclude.contains( Axis.PROP_OPPOSITE ) ) axis.setOpposite(that.isOpposite()); if ( !exclude.contains( Axis.PROP_RANGE ) ) axis.setRange(that.getRange()); if ( !exclude.contains( Axis.PROP_SCALE ) ) axis.setScale(that.getScale()); if ( !exclude.contains( Axis.PROP_LABEL ) ) axis.setLabel(that.getLabel()); if ( !exclude.contains( Axis.PROP_FONTSIZE ) ) axis.setFontSize( that.getFontSize() ); if ( !exclude.contains( Axis.PROP_AUTORANGE ) ) axis.setAutoRange(that.isAutoRange()); if ( !exclude.contains( Axis.PROP_AUTOLABEL ) ) axis.setAutoLabel(that.isAutoLabel()); if ( !exclude.contains( Axis.PROP_AUTORANGEHINTS ) ) axis.setAutoRangeHints(that.getAutoRangeHints()); if ( !exclude.contains( Axis.PROP_DRAWTICKLABELS ) ) axis.setDrawTickLabels( that.isDrawTickLabels() ); if ( !exclude.contains( Axis.PROP_TICKVALUES ) ) axis.setTickValues( that.getTickValues() ); if ( !exclude.contains( Axis.PROP_REFERENCE ) ) axis.setReference( that.getReference() ); if ( !exclude.contains( Axis.PROP_VISIBLE ) ) axis.setVisible( that.isVisible() ); if ( !exclude.contains( Axis.PROP_FOREGROUND ) ) axis.setForeground( that.getForeground() ); if ( !exclude.contains( Axis.PROP_AXISOFFSET ) ) axis.setAxisOffset( that.getAxisOffset() ); if ( lock!=null ) lock.unlock(); } }