resolve the plot type: spectrogram, lineplot, stack of lineplot, etc, using AutoplotUtil.guessRenderType(fillDs);
*
reset the plot element, setting the plot type and creating children if needed. For example, a B-GSM (demo 5) is
* resolved by creating three children, and handing the components off to them.
*
if the component property is not empty, then we implement the component and display that.
*
adjusting the component slice index will not affect ranging when the index is changed.
*
* @author jbf
*/
public class PlotElementController extends DomNodeController {
private static final Logger logger= org.das2.util.LoggerManager.getLogger( "autoplot.dom.pec" );
private static final String PENDING_CREATE_DAS_PEER = "createDasPeer";
private static final String PENDING_RESET_RANGE = "resetRanges";
private static final String PENDING_SET_DATASET= "setDataSet";
private static final String PENDING_COMPONENT_OP= "componentOp";
private static final String PENDING_UPDATE_DATASET= "updateDataSet";
private static final String PENDING_RESET_DATASOURCEFILTERID="resetDataSourceFilterId";
/**
* we need to reset the render type, but we can't do this from the event thread.
*/
private static final String PENDING_RESET_RENDER_TYPE= "resetRenderType";
final private Application dom;
private PlotElement plotElement;
private PlotElement parentPlotElement;
private DataSourceFilter dsf; // This is the one we are listening to.
/**
* switch over between fine and course points.
*/
public static final int SYMSIZE_DATAPOINT_COUNT = 500;
public static final int LARGE_DATASET_COUNT = 30000;
private final Object processLock= new Object();
private QDataSet processDataSet= null;
String procressStr= null;
// introduced as tool for making sure PlotElementControllers are properly garbage collected. This
// will be set to true as the PlotElement and its controller are deleted.
boolean deleted= false;
public PlotElementController(final ApplicationModel model, final Application dom, final PlotElement plotElement) {
super(plotElement);
this.dom = dom;
this.plotElement = plotElement;
plotElement.addPropertyChangeListener(PlotElement.PROP_RENDERTYPE, plotElementListener);
plotElement.addPropertyChangeListener(PlotElement.PROP_DATASOURCEFILTERID, plotElementListener);
plotElement.addPropertyChangeListener(PlotElement.PROP_COMPONENT, plotElementListener);
plotElement.addPropertyChangeListener(PlotElement.PROP_PARENT, parentElementListener );
plotElement.getStyle().addPropertyChangeListener(styleListener);
}
/**
* remove all property change listeners.
*/
protected void disconnect() {
plotElement.removePropertyChangeListener(PlotElement.PROP_RENDERTYPE, plotElementListener);
plotElement.removePropertyChangeListener(PlotElement.PROP_DATASOURCEFILTERID, plotElementListener);
plotElement.removePropertyChangeListener(PlotElement.PROP_COMPONENT, plotElementListener);
plotElement.removePropertyChangeListener(PlotElement.PROP_PARENT, plotElementListener);
plotElement.getStyle().removePropertyChangeListener(styleListener);
PlotElement parent= getParentPlotElement();
if ( parent!=null ) {
parent.removePropertyChangeListener( getParentComponentLister() );
}
plotElement.removePropertyChangeListener( getParentComponentLister() );
}
/**
* remove any direct references this controller has as it is being deleted.
*/
protected void removeReferences() {
this.processDataSet= null;
//this.plotElement= null; // bug 2054: if the thing we link to has no other references, then there is no reason to clear the reference to it.
//this.dsf= null;
this.deleted= true;
//this.dom= null;
}
/**
* return child plotElements, which are plotElements that share a datasource but pull out
* a component of the data.
* @return
*/
public List getChildPlotElements() {
ArrayList result= new ArrayList();
for ( PlotElement pp: dom.plotElements ) {
if ( pp.getParent().equals( plotElement.getId() ) ) result.add(pp);
}
return result;
}
/**
* set the child plotElements.
* @param plotElements
*/
protected void setChildPlotElements(List plotElements) {
for ( PlotElement p: plotElements ) {
p.setParent(plotElement.getId());
}
}
/**
* set the parent plotElement. this is used when copying.
* @param p
*/
protected void setParentPlotElement(PlotElement p) {
plotElement.setParent( p.getId() );
}
/**
* return the parent plotElement, or null if the plotElement doesn't have a parent.
* @return
*/
public PlotElement getParentPlotElement() {
if ( plotElement.getParent().equals("") ) {
return null;
} else {
for ( PlotElement pp: dom.plotElements ) {
if ( pp.getId().equals( plotElement.getParent() ) ) return pp;
}
return null; // TODO: maybe throw exception!
}
}
/**
* return the plot element.
* @return the plot element.
*/
public PlotElement getPlotElement() {
return plotElement;
}
/**
* remove any bindings and listeners
*/
void unbindDsf() {
if ( dsf!=null ) { //TODO: This should never be null, but it is
dsf.removePropertyChangeListener(DataSourceFilter.PROP_FILTERS, dsfListener);
dsf.controller.removePropertyChangeListener(DataSourceController.PROP_FILLDATASET, fillDataSetListener);
dsf.controller.removePropertyChangeListener(DataSourceController.PROP_DATASOURCE, dataSourceDataSetListener);
dsf.controller.removePropertyChangeListener(DataSourceController.PROP_EXCEPTION, exceptionListener);
}
}
PropertyChangeListener dsfListener = new PropertyChangeListener() {
@Override
public String toString() {
return "" + PlotElementController.this;
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
LoggerManager.logPropertyChangeEvent(evt,"dsfListener");
if ( evt.getPropertyName().equals(DataSourceFilter.PROP_FILTERS) ) {
if ( evt.getOldValue().toString().trim().equals( evt.getNewValue().toString().trim() ) ) {
return;
}
logger.log(Level.FINE, "property change in DSF means I need to autorange: {0}", evt.getPropertyName());
setResetRanges(true);
maybeSetPlotAutorange();
}
}
};
private void resetRenderTypeImp( RenderType oldRenderType, RenderType newRenderType ) {
logger.entering( "PlotElementController", "resetRenderTypeImp", new Object[] { oldRenderType, newRenderType } );
PlotElement parentEle= getParentPlotElement();
if (parentEle != null) {
logger.finest("parentEle!=null branch");
if ( parentEle.getRenderType().equals(newRenderType) ) {
if ( plotElement.getPlotId().length()>0 ) { //https://sourceforge.net/p/autoplot/bugs/1038/
doResetRenderTypeInt(newRenderType);
updateDataSet();
}
} else {
parentEle.setRenderType(newRenderType);
}
} else {
if ( axisDimensionsChange(oldRenderType, newRenderType) ) {
logger.finest("axisDimensionsChange branch");
resetRanges= true;
if ( plotElement.getComponent().equals("") ) {
resetPlotElement(getDataSourceFilter().getController().getFillDataSet(), plotElement.getRenderType(), "");
} else {
QDataSet sliceDs= getDataSet();
if ( sliceDs==null ) {
// This happens when we load a vap.
sliceDs= getDataSourceFilter().getController().getFillDataSet(); // Since this is null, I suspect we can do the same behavior in either case.
resetPlotElement( sliceDs, plotElement.getRenderType(), "" );
} else {
resetPlotElement( sliceDs, plotElement.getRenderType(), ""); // I'm assuming that getDataSet() has been set already, which should be the case.
}
}
updateDataSet();
} else {
logger.finest("axis dimensions don't change, just reset render type.");
doResetRenderType(newRenderType);
updateDataSet();
}
setResetPlotElement(false);
}
logger.exiting("PlotElementController", "resetRenderTypeImp" );
}
/**
* return true if the two are the same operation, but have only
* different arguments. For example, slice1(0) and slice1(10) are
* the same operation, but slice1(0) and slice2(0) are not.
* @param c1 an operation, like "slice1(0)"
* @param c2 an operation, like "slice1(10)"
* @return true if the two are the same operation.
*/
private boolean sameOperation( String c1, String c2 ) {
int ic1= c1.indexOf("(");
int ic2= c2.indexOf("(");
if ( ic1!=ic2 ) {
return false;
} else {
if ( c1.substring(0,ic1).equals(c2.substring(0,ic1) ) ) {
return true;
} else {
return false;
}
}
}
/**
* return true when the childComponents are compatible with the
* parentComponents, but then have additional operations.
* Apologies for using component and operation interchangeably.
* @param parentComponents ['slice1(0)']
* @param childComponents ['slice1(0)','unbundle(Bx)']
* @return true if the child components are an extension of the parent's
*/
private boolean extendedOperation( String[] parentComponents, String[] childComponents ) {
if ( childComponents.length{2}", new Object[]{evt.getPropertyName(), evt.getOldValue(), evt.getNewValue()});
if ( evt.getPropertyName().equals(PlotElement.PROP_RENDERTYPE) && !PlotElementController.this.isValueAdjusting() ) {
//if ( dom.getController().isValueAdjusting() ) {
//return; // occasional NullPointerException, bug 2988979
//}
final RenderType newRenderType = (RenderType) evt.getNewValue();
final RenderType oldRenderType = (RenderType) evt.getOldValue();
changesSupport.registerPendingChange( PlotElementController.this, PENDING_RESET_RENDER_TYPE );
Runnable run= () -> {
try {
changesSupport.performingChange( PlotElementController.this, PENDING_RESET_RENDER_TYPE );
resetRenderTypeImp( oldRenderType, newRenderType );
} finally {
changesSupport.changePerformed( PlotElementController.this, PENDING_RESET_RENDER_TYPE );
}
};
if ( SwingUtilities.isEventDispatchThread() ) {
new Thread(run,"updateDataSetOffEvent").start();
} else {
run.run();
}
} else if (evt.getPropertyName().equals(PlotElement.PROP_DATASOURCEFILTERID)) {
changeDataSourceFilter();
if ( dsfReset ) {
if ( evt.getOldValue()!=null ) {
setResetPlotElement(true);
setResetRanges(true);
}
if ( evt.getNewValue().equals("") ) {
//updateDataSet();
if ( getRenderer()!=null ) getRenderer().setDataSet(null); // transitional state associated with undo. https://sourceforge.net/tracker/?func=detail&aid=3316754&group_id=199733&atid=970682
} else {
changesSupport.registerPendingChange( PlotElementController.this, PENDING_RESET_DATASOURCEFILTERID );
Runnable run= () -> {
try {
changesSupport.performingChange( PlotElementController.this, PENDING_RESET_DATASOURCEFILTERID );
updateDataSet();
} finally {
changesSupport.changePerformed( PlotElementController.this, PENDING_RESET_DATASOURCEFILTERID );
}
};
if ( SwingUtilities.isEventDispatchThread() ) {
new Thread(run,"updateDataSetOffEvent").start();
} else {
run.run();
}
}
}
} else if ( evt.getPropertyName().equals( PlotElement.PROP_COMPONENT ) ) {
String oldv= (String)evt.getOldValue();
oldv= DataSetOps.makeProcessStringCanonical(oldv);
String newv= (String)evt.getNewValue();
newv= DataSetOps.makeProcessStringCanonical(newv);
if ( DataSetOps.changesDimensions( oldv, newv ) ) { //TODO: why two methods see axisDimensionsChange 10 lines above
if ( DataSetOps.changesIndependentDimensions( oldv,newv ) ) {
logger.log(Level.FINER, "component property change requires we reset render and dimensions: {0}->{1}", new Object[]{(String) evt.getOldValue(), (String) evt.getNewValue()});
setResetPlotElement(true);
setResetRanges(true);
if ( !dom.getController().isValueAdjusting() ) {
maybeSetPlotAutorange();
}
} else {
logger.log(Level.FINER, "component property change requires we reset just the y-axis: {0}->{1}", new Object[]{(String) evt.getOldValue(), (String) evt.getNewValue()});
setResetPlotElement(true);
setResetRanges(true);
if ( !dom.getController().isValueAdjusting() ) {
maybeSetPlotYZAutorange();
}
}
}
if ( sliceAutoranges ) {
setResetRanges(true);
if ( !dom.getController().isValueAdjusting() ) maybeSetPlotAutorange();
}
if ( newv.startsWith("|") ) dom.getOptions().setDataVisible(true);
if ( changesSupport==null ) {
logger.severe("changesSupport is null!!!");
logger.severe("this is a sad, leftover PlotElementController that should have been GC'd");
return;
}
changesSupport.registerPendingChange(plotElementListener, PENDING_COMPONENT_OP);
Runnable run= () -> {
if ( changesSupport==null ) {
logger.severe("changesSupport is null!!!");
return;
}
// we reenter this code, so only set lock once. See test.endtoend.Test015.java
// vap+cef:file:///home/jbf/ct/hudson/data.backup/cef/C1_CP_PEA_CP3DXPH_DNFlux__20020811_140000_20020811_150000_V061018.cef?Data__C1_CP_PEA_CP3DXPH_DNFlux
// bug 1480 insert breakpoint here
changesSupport.performingChange(plotElementListener, PENDING_COMPONENT_OP);
setStatus("busy: update data set");
try {
updateDataSet();
setStatus("done update data set");
} catch ( RuntimeException ex ) {
setStatus("warning: "+ex.toString());
throw ex;
} finally {
changesSupport.changePerformed(plotElementListener, PENDING_COMPONENT_OP);
}
};
RequestProcessor.invokeLater(run);
}
}
};
/**
* listen for changes in the parent plotElement that this child can respond to.
*/
PropertyChangeListener parentStyleListener= new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
LoggerManager.logPropertyChangeEvent(evt,"parentStyleListener");
try {
if ( evt.getPropertyName().equals("color") ) {
logger.fine("ignoring change of parent color.");
} else {
DomUtil.setPropertyValue(plotElement.style, evt.getPropertyName(), evt.getNewValue());
}
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
logger.log(Level.SEVERE, ex.getMessage(), ex);
}
}
};
/*
* listen for changes that might change the renderType. Try to pick one that is close. Don't
* fire changes.
*/
PropertyChangeListener styleListener= new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
LoggerManager.logPropertyChangeEvent(evt,"styleListener");
if ( evt.getPropertyName().equals( PlotElementStyle.PROP_REBINMETHOD ) ) {
if ( plotElement.getRenderType()==RenderType.nnSpectrogram || plotElement.getRenderType()==RenderType.spectrogram ) {
if ( evt.getNewValue()==SpectrogramRenderer.RebinnerEnum.nearestNeighbor ) {
plotElement.renderType= RenderType.nnSpectrogram;
} else if ( evt.getNewValue()==SpectrogramRenderer.RebinnerEnum.binAverage ) {
plotElement.renderType= RenderType.spectrogram;
}
}
// } else if ( evt.getPropertyName().equals( PlotElementStyle.PROP_SYMBOL_CONNECTOR ) ) {
// plotElement.setAutoRenderType( false );
// } else if ( evt.getPropertyName().equals( PlotElementStyle.PROP_SYMBOL_SIZE ) ) {
// plotElement.setAutoRenderType( false );
// } else if ( evt.getPropertyName().equals( PlotElementStyle.PROP_LINE_WIDTH ) ) {
// plotElement.setAutoRenderType( false );
// } else if ( evt.getPropertyName().equals( PlotElementStyle.PROP_PLOT_SYMBOL ) ) {
// plotElement.setAutoRenderType( false );
}
}
};
// private boolean needNewChildren(String[] labels, List childPeles) {
// if ( childPeles.isEmpty() ) return true;
// List ll= Arrays.asList(labels);
// for ( PlotElement p: childPeles ) {
// if ( !ll.contains( p.getComponent() ) ) {
// return true;
// }
// }
// return false;
// }
/**
* the DataSourceFilter id has changed, so we need to stop listening to the
* old one and connect to the new one. Also, if the old dataSourceFilter is
* now an orphan, delete it from the application.
*/
private void changeDataSourceFilter() {
if (dsf != null) {
unbindDsf();
List usages= DomUtil.dataSourceUsages(dom, dsf.getId() );
if ( usages.isEmpty() ) {
dom.controller.deleteDataSourceFilter(dsf);
}
}
assert (plotElement.getDataSourceFilterId() != null);
if ( plotElement.getDataSourceFilterId().equals("") ) return;
dsf = dom.controller.getDataSourceFilterFor(plotElement);
if ( dsf==null ) {
logger.log(Level.WARNING, "Unable to find datasource for plotElement {0}", plotElement);
return;
} else {
dsf.addPropertyChangeListener( DataSourceFilter.PROP_FILTERS, dsfListener );
}
setDataSourceFilterController( dsf.controller );
}
private Color deriveColor( Color color, int i ) {
float[] colorHSV = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
if (colorHSV[2] < 0.7f) {
colorHSV[2] = 0.7f;
}
if (colorHSV[1] < 0.7f) {
colorHSV[1] = 0.7f;
}
return Color.getHSBColor(i / 6.f, colorHSV[1], colorHSV[2]);
}
/**
* pop off to the root cause of the problem, since exceptions are wrapped again and again.
* @param exception
* @return
*/
private Exception getRootCause( Exception exception ) {
Throwable cause= exception.getCause();
while ( cause!=null && cause!=exception && cause instanceof Exception ) { // TODO: review this, I will probably regret...
exception= (Exception)cause;
cause= exception.getCause();
}
if ( cause!=null && cause instanceof Exception ) {
return (Exception)cause;
} else {
return exception;
}
}
/**
* apply process to the data. In general these can be done on the same thread (like
* slice1), but some are slow (like fftPower).
*
* @param c
* @param fillDs
* @return
* @throws RuntimeException
* @throws Exception when the processStr cannot be processed.
*/
private QDataSet processDataSet(String c, QDataSet fillDs) throws RuntimeException, Exception {
setStatus("busy: process dataset");
String label= null;
c= c.trim();
if ( c.length()>0 && !c.startsWith("|") ) { // grab the component, then apply processes after the pipe.
if (!plotElement.getComponent().equals("") && fillDs.length() > 0 && fillDs.rank() == 2) {
String[] labels = SemanticOps.getComponentNames(fillDs);
String comp= plotElement.getComponent();
int ip= comp.indexOf('|');
if ( ip!=-1 ) {
comp= comp.substring(0,ip);
}
comp= Ops.saferName(comp);
if ( fillDs.property(QDataSet.BUNDLE_1)!=null ) {
fillDs= DataSetOps.unbundle( fillDs,comp ); //TODO: illegal argument exception
label= comp;
} else {
boolean found= false;
for (int i = 0; i < labels.length; i++) {
if ( Ops.saferName(labels[i]).equals(comp)) {
fillDs = DataSetOps.slice1(fillDs, i);
label = labels[i];
found= true;
break;
}
}
if ( !found ) {
throw new IllegalArgumentException("component not found: "+comp );
}
}
if (label == null && !isPendingChanges()) {
RuntimeException ex = new RuntimeException("component not found: " + comp );
throw ex;
}
}
int idx= c.indexOf('|');
if ( idx==-1 ) {
c="";
} else {
c= c.substring(idx);
}
}
if (c.length() > 5 && c.startsWith("|")) {
logger.log( Level.FINE, "component={0}", c);
// slice and collapse specification
if ( DataSetOps.isProcessAsync(c) ) {
synchronized (processLock) {
if ( c.equals(this.procressStr) && this.processDataSet!=null ) { // caching
if ( logger.isLoggable( Level.FINE ) ) {
QDataSet bounds= DataSetOps.dependBounds( this.processDataSet );
logger.log(Level.FINE, "using cached dataset for {0} bounds:{1}", new Object[] { procressStr, bounds.slice(0) } );
}
fillDs= this.processDataSet;
} else {
this.processDataSet= null;
this.procressStr= null;
ProgressMonitor mon= DasProgressPanel.createComponentPanel( getDasPlot(), "process data set" );
fillDs = DataSetOps.sprocess(c, fillDs, mon );
if ( mon.isCancelled() ) {
this.processDataSet= null; //TODO: this is going to cause a problem because we'll reenter almost immediately. We need to cache the result and a flag that indicates it should be reloaded.
this.procressStr= null;
} else {
this.processDataSet= fillDs;
this.procressStr= c;
}
}
}
} else {
synchronized (processLock) {
this.processDataSet= null;
this.procressStr= null;
}
fillDs = DataSetOps.sprocess(c, fillDs, null);
}
}
setStatus("done, process dataset");
return fillDs;
}
/**
* calculate the interpreted metadata after the slicing.
* @param c
* @param properties
* @return
*/
private static Map processProperties( String c, Map properties ) {
c= c.trim();
if (c.length() > 5 && c.contains("|")) {
// slice and collapse specification
properties = MetadataUtil.sprocess(c, properties );
}
return properties;
}
private boolean rendererAcceptsData(QDataSet fillDs) {
if ( getRenderer() instanceof SpectrogramRenderer ) {
switch (fillDs.rank()) {
case 3:
QDataSet dep0= (QDataSet) fillDs.property( QDataSet.DEPEND_0 ); // only support das2 tabledataset scheme.
if ( dep0!=null ) return false;
return rendererAcceptsData( DataSetOps.slice0(fillDs,0) );
case 2:
// && !SemanticOps.isBundle(fillDs) ) {
return true;
default:
return fillDs.property(QDataSet.PLANE_0)!=null;
}
} else if ( getRenderer() instanceof SeriesRenderer) {
switch (fillDs.rank()) {
case 0:
return true;
case 1:
return true;
case 2:
return SemanticOps.isBundle(fillDs) || SemanticOps.isRank2Waveform(fillDs);
case 3:
return SemanticOps.isJoin(fillDs) && SemanticOps.isRank2Waveform(fillDs.slice(0));
default:
return false;
}
} else if ( getRenderer() instanceof HugeScatterRenderer ) {
switch (fillDs.rank()) {
case 1:
return true;
case 2:
return SemanticOps.isBundle(fillDs) || SemanticOps.isRank2Waveform(fillDs);
case 3:
return SemanticOps.isJoin(fillDs) && SemanticOps.isRank2Waveform(fillDs.slice(0));
default:
return false;
}
} else if ( getRenderer() instanceof RGBImageRenderer ) {
switch (fillDs.rank()) {
case 2:
return !SemanticOps.isBundle(fillDs);
case 3:
return fillDs.length(0,0) < 5;
default:
return false;
}
} else {
return true;
}
}
/**
* set the dataset that will be plotted. If the component property is non-null, then
* additional filtering will be performed. See http://papco.org/wiki/index.php/DataReductionSpecs
* @param fillDs
* @throws IllegalArgumentException
*/
private void setDataSet(QDataSet fillDs ) throws IllegalArgumentException {
// since we might delete sibling plotElements here, make sure each plotElement is still part of the application
if (!Arrays.asList(dom.getPlotElements()).contains(plotElement)) {
return;
}
String comp= plotElement.getComponent();
try {
if ( fillDs!=null ) {
if ( comp.length()>0 ) fillDs = processDataSet(comp, fillDs );
if ( doUnitsCheck( fillDs ) ) { // bug 3104572: slicing would drop units, so old vaps wouldn't work
Plot plot= this.dom.getController().getPlotFor(plotElement);
PlotController pc= plot.getController();
pc.doPlotElementDefaultsUnitsChange(plotElement);
}
Object ocontext= fillDs.property(QDataSet.CONTEXT_0);
if ( ocontext!=null ) {
Object altContext= fillDs.property( QDataSet.CONTEXT_1 );
if ( altContext!=null && altContext instanceof QDataSet ) {
Units acu= (Units)(((QDataSet)altContext).property(QDataSet.UNITS));
if ( acu!=null && UnitsUtil.isTimeLocation(acu) ) {
ocontext= altContext;
}
}
}
if ( ocontext!=null && !( ocontext instanceof QDataSet ) ) {
logger.warning("CONTEXT_0 is not a QDataSet");
ocontext= null;
}
QDataSet context= (QDataSet)ocontext;
if ( context!=null ) {
DatumRange cdr;
try {
if ( context.rank()==1 ) {
cdr= DataSetUtil.asDatumRange( context, true );
} else {
cdr= DatumRange.newRange( context.value(), context.value(), SemanticOps.getUnits(context) );
}
Plot plot= this.dom.getController().getPlotFor(plotElement);
plot.getController().getDasPlot().setDisplayContext( cdr ); // note this property is just a placeholder, and is sensed by ColumnColumnConnector.
} catch ( IllegalArgumentException ex ) {
logger.fine(ex.toString());
}
} else {
// TODO: ???
}
}
logger.log(Level.FINE, " postOpsDataSet: {0}", String.valueOf(fillDs) );
setDataSetInternal(fillDs);
} catch ( RuntimeException ex ) {
logger.log(Level.FINE, "runtime exception caught: {0}", new Object[] { ex });
if (getRenderer() != null) {
getRenderer().setDataSet(null);
getRenderer().setException(getRootCause(ex));
setDataSetInternal(null);
} else {
throw ex;
}
return;
} catch ( CancelledOperationException ex ) {
getRenderer().setDataSet(null);
getRenderer().setException(getRootCause(ex));
setDataSetInternal(null);
return;
} catch ( Exception ex ) {
getRenderer().setDataSet(null);
getRenderer().setException(getRootCause(ex));
setDataSetInternal(null);
return;
}
if ( fillDs!=null && getRenderer() != null) {
if (rendererAcceptsData(fillDs)) {
getRenderer().setDataSet(fillDs);
} else {
getRenderer().setDataSet(null);
getRenderer().setException(new Exception("renderer cannot plot " + fillDs));
}
}
}
/**
* the current dataset plotted, after operations (component property) has been applied.
*/
public static final String PROP_DATASET = "dataSet";
protected QDataSet dataSet = null;
/**
* the current dataset plotted, after operations (component property) has been applied.
* @return
*/
public QDataSet getDataSet() {
return dataSet;
}
/**
* finish off the components and data post processing, and set the
* dataset. This does not set the renderer dataset.
* @param dataSet
*/
private void setDataSetInternal(QDataSet dataSet) {
logger.log(Level.FINE, "setDataSetInternal {0}", dataSet);
QDataSet oldDataSet = this.dataSet;
this.dataSet = dataSet; //TODO: we should probably synchronize dataSet access.
if ( ( plotElement.getLegendLabel().contains("%{") || plotElement.getLegendLabel().contains("$(") ) && renderer!=null ) {
String s= (String)getLabelConverter().convertForward(plotElement.getLegendLabel());
renderer.setLegendLabel(s);
}
propertyChangeSupport.firePropertyChange(PROP_DATASET, oldDataSet, dataSet);
}
PropertyChangeListener exceptionListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
LoggerManager.logPropertyChangeEvent(evt,"exceptionListener");
changesSupport.performingChange( this, PENDING_SET_DATASET );
try {
Exception ex = dsf.controller.getException();
logger.log(Level.FINE, "{0} got exception: {1} ", new Object[]{plotElement, ex });
if ( resetComponent ) {
//if ( !plotElement.component.equals("") ) {
// plotElement.setComponent("");
//} else {
plotElement.setComponent("");
// plotElement.component=""; // we must avoid firing an event here, causes problems //TODO: why?
plotElement.autoComponent=true;
//}
setResetComponent(false);
}
renderer.setDataSet(null);
renderer.setException(ex);
} finally {
changesSupport.changePerformed( this, PENDING_SET_DATASET );
}
}
@Override
public String toString() {
return "" + PlotElementController.this;
}
};
PropertyChangeListener fillDataSetListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
synchronized ( processLock ) {
LoggerManager.logPropertyChangeEvent(evt,"fillDataSetListener");
logger.fine("enter fillDataSetListener propertyChange");
if (!Arrays.asList(dom.getPlotElements()).contains(plotElement)) {
//TODO: find a way to fix this properly or don't call it a kludge! logger.fine("kludge pec446 cannot be removed");
return; // TODO: kludge, I was deleted. I think this can be removed now. The applicationController was preventing GC.
}
changesSupport.performingChange( this, PENDING_SET_DATASET );
try {
QDataSet fillDs = dsf.controller.getFillDataSet();
logger.log(Level.FINE, "{0} got new dataset: {1} resetComponent={2} resetPele={3} resetRanges={4}", new Object[]{plotElement, fillDs, resetComponent, resetPlotElement, resetRanges});
if ( resetComponent ) {
plotElement.setComponentAutomatically("");
processDataSet= null;
procressStr= null;
setResetComponent(false);
} else {
processDataSet= null;
}
updateDataSet();
} finally {
changesSupport.changePerformed( this, PENDING_SET_DATASET );
}
}
}
@Override
public String toString() {
return "" + PlotElementController.this;
}
};
/**
* indicate if changing slice index should result in autoranging being redone. Presently, this is done when the
* dimension we're slicing is nominal (ordinal).
* @param fillDs
* @param component
* @return
* @throws NumberFormatException
*/
private boolean sliceShouldAutorange(QDataSet fillDs, String component) throws NumberFormatException {
Units[] us = getDimensionUnits(fillDs);
Pattern p = Pattern.compile("\\|slice(\\d)\\(\\d+\\)");
Matcher m = p.matcher(component);
if (m.matches()) {
int dim = Integer.parseInt(m.group(1));
if (UnitsUtil.isNominalMeasurement(us[dim])) {
return true;
}
}
return false;
}
/**
* Resolve the renderType and renderControl for the dataset. This may be
* set explicitly by the dataset, in its RENDER_TYPE property, or resolved
* using the dataset scheme.
*
* @param fillds
* @return the render string with canonical types. The result will always contain a greater than (>).
*/
public static String resolveRenderType( QDataSet fillds ) {
String srenderType= (String) fillds.property(QDataSet.RENDER_TYPE);
RenderType renderType;
String renderControl="";
if ( srenderType!=null && srenderType.trim().length()>0 ) {
int i= srenderType.indexOf('>');
if ( i==-1 ) {
renderControl= "";
} else {
renderControl= srenderType.substring(i+1);
srenderType= srenderType.substring(0,i);
}
boolean useHugeScatter= "true".equals( System.getProperty("useHugeScatter","true") );
switch (srenderType) {
case "time_series":
if ( useHugeScatter && fillds.length() > SERIES_SIZE_LIMIT) {
renderType = RenderType.hugeScatter;
} else {
renderType = RenderType.series;
}
break;
case "waveform":
if ( useHugeScatter ) {
renderType = RenderType.hugeScatter;
} else {
renderType = RenderType.series;
}
break;
case "spectrogram":
RenderType specPref= RenderType.spectrogram;
Options o= new Options();
Preferences prefs= AutoplotSettings.settings().getPreferences( o.getClass() ); //TODO: because this is static?
boolean nn= prefs.getBoolean(Options.PROP_NEARESTNEIGHBOR,o.isNearestNeighbor());
if ( nn ) {
specPref = RenderType.nnSpectrogram;
}
renderType= specPref;
break;
default:
try {
renderType= RenderType.valueOf(srenderType);
} catch ( IllegalArgumentException ex ) {
renderType= AutoplotUtil.guessRenderType(fillds);
}
break;
}
return renderType.toString() + ">" + renderControl;
} else {
renderType = AutoplotUtil.guessRenderType(fillds);
if ( renderType==RenderType.series ) {
if ( Schemes.isScalarSeriesWithErrors(fillds) ) {
renderControl= "drawError=T";
}
}
return renderType.toString() + ">" + renderControl;
}
}
/**
* Get the dataset from the DataSourceFilter and if resetPlotElement is true,
* then guess at a reasonable rendering method.
*
* @throws Exception
* @see #resolveRenderType(org.das2.qds.QDataSet)
*/
private void updateDataSetImmediately() throws Exception {
performingChange( this, PENDING_UPDATE_DATASET );
try {
if ( dsf==null ) {
getRenderer().setDataSet(null);
getRenderer().setException(new RuntimeException("Data Source Reference"));
return;
}
QDataSet fillDs = dsf.controller.getFillDataSet();
Exception renderException= null;
if (fillDs != null) {
final String comp= DataSetOps.makeProcessStringCanonical( plotElement.getComponent().trim() );
if ( logger.isLoggable(Level.FINE) ) {
logger.log(Level.FINE, "updateDataSetImmediately: {0} {1}", new Object[]{plotElement, plotElement.getRenderType() });
logger.log(Level.FINE, " resetPlotElement: {0}", resetPlotElement );
logger.log(Level.FINE, " resetRanges: {0}", resetRanges);
logger.log(Level.FINE, " resetRenderType: {0}", resetRenderType );
logger.log(Level.FINE, " component: {0}", comp );
logger.log(Level.FINE, " dataSet: {0}", String.valueOf(fillDs) );
}
//This was to support the CdawebVapServlet, where partial vaps are handled. See https://sourceforge.net/p/autoplot/bugs/1304/
//if ( plotElement.isAutoRenderType() ) {
//resetPlotElement= true;
//}
if (resetPlotElement) {
if (comp.equals("")) {
String s= resolveRenderType( fillDs );
int i= s.indexOf('>');
RenderType renderType= RenderType.valueOf(s.substring(0,i));
if ( !renderType.equals(plotElement.renderType) && getRenderer()!=null ) getRenderer().setDataSet(null); //bug1065
plotElement.renderType = renderType; // setRenderTypeAutomatically. We don't want to fire off event here.
resetPlotElement(fillDs, renderType, s.substring(i+1) );
setResetPlotElement(false);
} else if ( comp.startsWith("|") ) {
try {
QDataSet fillDs2 = fillDs;
//String srenderType= (String)fillDs2.property(QDataSet.RENDER_TYPE);
//if ( srenderType!=null ) {
// srenderType= resolveRenderType( fillDs2 );
//}
if ( comp.length()>0 ) fillDs2= processDataSet( comp, fillDs2 );
if ( fillDs2==null ) throw new NullPointerException("operations result in null: "+comp);
String s= resolveRenderType( fillDs2 );
//if ( comp.length()>0 && comp.startsWith("|unbundle(") && srenderType!=null ) { // vap+inline:ripplesVectorTimeSeries(200)&RENDER_TYPE=hugeScatter
// if ( !srenderType.contains(">") ) {
// srenderType= srenderType + ">";
// }
// s= srenderType;
//}
int i= s.indexOf('>');
if ( i==-1 ) i=s.length();
RenderType renderType= RenderType.valueOf(s.substring(0,i));
if ( !renderType.equals(plotElement.renderType) && getRenderer()!=null ) getRenderer().setDataSet(null); //bug1065
plotElement.renderType = renderType; // setRenderTypeAutomatically. We don't want to fire off event here.
resetPlotElement(fillDs2, renderType, s.substring(i+1) );
setResetPlotElement(false);
} catch ( CancelledOperationException ex ) {
setStatus("warning: Filters were cancelled: " + ex );
getRenderer().setDataSet(null);
getRenderer().setException(ex);
renderException= ex;
} catch ( RuntimeException ex ) {
if ( getRenderer()==null ) {
System.err.println("NullPointerEx has happened, see bug https://sourceforge.net/p/autoplot/bugs/2635/");
ex.printStackTrace();
}
setStatus("warning: Exception in process: " + ex );
getRenderer().setDataSet(null);
getRenderer().setException(getRootCause(ex));
renderException= ex;
}
} else {
if (renderer == null) maybeCreateDasPeer();
try {
if (resetRanges) doResetRanges();
setResetPlotElement(false);
} catch ( RuntimeException ex ) {
setStatus("warning: Exception in process: " + ex );
getRenderer().setDataSet(null);
getRenderer().setException(getRootCause(ex));
renderException= ex;
}
}
} else if (resetRanges) {
doResetRanges();
setResetRanges(false);
} else if (resetRenderType) {
doResetRenderType(plotElement.getRenderType());
}
}
if (fillDs == null) {
if (getRenderer() != null) {
getRenderer().setDataSet(null);
getRenderer().setException(null); // remove leftover message.
}
setDataSet(null);
} else {
if ( renderException==null ) {
setDataSet(fillDs);
}
}
} finally {
changePerformed( this, PENDING_UPDATE_DATASET );
}
}
/**
* get the dataset from the dataSourceFilter, and plot it possibly after
* slicing component. Changing the component (slice) will re-enter the
* code here.
* @throws IllegalArgumentException
*/
private void updateDataSet() throws IllegalArgumentException {
if ( EventQueue.isDispatchThread() ) {
logger.warning("updateDataSet called from event thread. Stack track follows.");
new Exception("updateDataSet called from event thread").printStackTrace();
}
//if ( getRenderer()!=null ) getRenderer().setDataSet(null); //bug 1073 bug 1065.
registerPendingChange( this, PENDING_UPDATE_DATASET );
//TODO: we should hand off the dataset here instead of mucking around with it...
if (!dom.controller.isValueAdjusting()) {
Runnable run= () -> {
// java complains about method not override.
try {
updateDataSetImmediately();
} catch ( Exception ex ) {
logger.log( Level.WARNING, ex.getMessage(), ex ); // wrapping somehow didn't show original exception.
throw new IllegalArgumentException(ex);
}
};
//RequestProcessor.invokeLater(run); // this allows listening PlotElements to each do their stuff.
run.run();
} else {
new RunLaterListener(ChangesSupport.PROP_VALUEADJUSTING, dom.controller, true ) {
@Override
public void run() {
try {
updateDataSetImmediately();
} catch ( Exception ex ) {
throw new IllegalArgumentException(ex);
}
}
};
}
}
/**
* true indicates that the new renderType makes the axis dimensions change.
* For example, switching from spectrogram to series (to get a stack of components)
* causes the z axis to become the yaxis.
* @param oldRenderType
* @param newRenderType
* @return true if the dimensions change.
*/
public static boolean axisDimensionsChange( RenderType oldRenderType, RenderType newRenderType ) {
if ( oldRenderType==newRenderType ) return false;
if ( newRenderType==RenderType.pitchAngleDistribution ) return true;
if ( newRenderType==RenderType.polar ) return true;
if ( oldRenderType==RenderType.spectrogram && newRenderType==RenderType.nnSpectrogram ) {
return false;
} else if ( oldRenderType==RenderType.nnSpectrogram && newRenderType==RenderType.spectrogram ) {
return false;
} else if ( newRenderType==RenderType.spectrogram || newRenderType==RenderType.nnSpectrogram ) {
return true;
} else {
if ( newRenderType==RenderType.eventsBar ) {
return false;
} else {
if ( oldRenderType==RenderType.spectrogram || oldRenderType==RenderType.nnSpectrogram ) {
return true;
} else {
if ( oldRenderType==RenderType.scatter
|| oldRenderType==RenderType.series
|| oldRenderType==RenderType.fillToZero
|| oldRenderType==RenderType.stairSteps ) {
return false;
} else {
return false;
}
}
}
}
}
private static String[] getDimensionNames( QDataSet ds ) {
String[] depNames = new String[ds.rank()];
for (int i = 0; i < ds.rank(); i++) {
depNames[i] = "dim" + i;
QDataSet dep0 = (QDataSet) ds.property("DEPEND_" + i);
if (dep0 != null) {
String dname = (String) dep0.property(QDataSet.NAME);
if (dname != null) {
depNames[i] = dname;
}
}
}
return depNames;
}
private static Units[] getDimensionUnits( QDataSet ds ) {
Units[] depUnits = new Units[ds.rank()];
for (int i = 0; i < ds.rank(); i++) {
depUnits[i] = Units.dimensionless;
QDataSet dep0 = (QDataSet) ds.property("DEPEND_" + i);
if (dep0 != null) {
Units u = (Units) dep0.property(QDataSet.UNITS);
if (u != null) {
depUnits[i] = u;
}
}
}
return depUnits;
}
/**
* calculate the slices based on the slices command. This results in
* a trivial amount of extra work but makes the code cleaner.
*
* @param fillDs
* @param slicePref
* @return
*/
private static String guessSliceSlices( QDataSet fillDs, List slicePref ) {
StringBuilder newResult= new StringBuilder( "|slices(" );
String[] slices= new String[fillDs.rank()];
for ( int i=0; i slicePref1= new ArrayList();
slicePref1.addAll(slicePref);
slicePref= slicePref1;
int ndim= fillDs.rank();
int nslice= fillDs.rank()-2;
List qube= new ArrayList(); // we remove elements from this one.
int[] a= DataSetUtil.qubeDims(fillDs);
for ( int i=0; i bestSlice) {
sliceIndex = i;
bestSlice = slicePref.get(i);
}
}
slicePref.set( sliceIndex, 0 );
slices[sliceIndex]= String.valueOf( qube.get(sliceIndex)/2 );
}
for ( int i=0; i slicePref = new ArrayList( rank );
for ( int i=0; i qube= new ArrayList(); // we remove elements from this one.
int[] a= DataSetUtil.qubeDims(fillDs);
if ( a==null ) {
return "|slice0(" + fillDs.length()/2+")";
}
for ( int i=0; i4 ) {
return newResult;
}
for ( int islice=0; islice0 && slicePref.get(i).equals(slicePref.get(i-1)) ) noPref= false;
if (slicePref.get(i) > bestSlice) {
sliceIndex = i;
bestSlice = slicePref.get(i);
}
}
// if we have large dims and one small dim (image), then pick small dim.
if ( noPref ) {
int[] qubeDims= DataSetUtil.qubeDims(fillDs);
if ( qubeDims!=null ) {
int imin= -1;
int min= Integer.MAX_VALUE;
int nextMin= Integer.MAX_VALUE;
for ( int i=0; i10 ) {
sliceIndex= imin;
}
}
}
if ( Schemes.isArrayOfBoundingBox(fillDs) ) {
return "";
}
// see line above about triangleMesh
// pick a slice index near the middle, which is less likely to be all fill.
int n= Math.max( 0, qube.get(sliceIndex)/2-1 );
result+= "|slice"+sliceIndex+"("+n+")";
if (lat > -1 && lon > -1 && lat < lon) {
result+="|transpose()";
transpose= true;
}
slicePref.remove(sliceIndex);
qube.remove(sliceIndex);
}
if ( transpose ) {
newResult+="|transpose()";
}
if ( nslice<2 ) {
return result;
} else {
return newResult;
}
//return result;
}
private boolean isLastDimBundle( QDataSet ds ) {
switch (ds.rank()) {
case 1:
return ds.property(QDataSet.BUNDLE_0)!=null;
case 2:
return ds.property(QDataSet.BUNDLE_1)!=null;
case 3:
boolean result= ds.property(QDataSet.BUNDLE_1,0)!=null;
QDataSet dep1= (QDataSet) ds.property(QDataSet.DEPEND_1,0);
if ( dep1!=null && ( dep1.property(QDataSet.UNITS) instanceof EnumerationUnits ) ) result=true;
return result;
default:
return false;
}
}
/**
* return true for a set of labels which seem to be describing different
* things. This is just simply checking to see:
*
if all the first characters are number, then it is similar
*
if all the first characters are the same letter, then it is similar
*
otherwise it is dissimilar.
* @param chs the array of labels.
* @return true if they appear to be differing.
* @see https://sourceforge.net/p/autoplot/bugs/2571/
*/
private boolean dissimilarChannels( String[] chs ) {
if ( chs[0].length()==0 ) return true; // unnamed channels probably shouldn't happen
char c= chs[0].charAt(0);
char allStartWith= c;
boolean allNumbers= Character.isDigit(c) || c=='.' ;
for ( String ch: chs ) {
if ( ch.length()==0 ) return true;
c= ch.charAt(0);
if ( allNumbers ) {
if ( !Character.isDigit(c) && c!='.' ) {
return true;
}
} else {
if ( c!=allStartWith ) {
return true;
}
}
}
return false;
}
/**
* This is the heart of the PlotElementController, and to some degree Autoplot. In this routine, we are given
* dataset and a renderType, and we need to reconfigure Autoplot to implement this. This will add child elements when
* children are needed, for example when a Vector time series is plotted, we need to add children for each component.
*
* preconditions:
*
the new renderType has been identified.
*
The dataset to be rendered has been identified.
*
* postconditions:
*
old child plotElements have been deleted.
*
child plotElements have been added when needed.
*
* @param fillDs
* @param renderType
* @param control renderer-type specific controls, see Renderer.setControl.
* @see Renderer#setControl(java.lang.String)
*/
private synchronized void resetPlotElement( QDataSet fillDs, RenderType renderType, String renderControl ) {
logger.log(Level.FINE, "resetPlotElement({0} {1}) ele={2}", new Object[]{fillDs, renderType, plotElement});
if (fillDs != null) {
//boolean lastDimBundle= isLastDimBundle( fillDs );
//boolean joinOfBundle= fillDs.property(QDataSet.JOIN_0)!=null && lastDimBundle;
int ndim= Ops.dimensionCount(fillDs);
boolean isxyz= SemanticOps.isBundle(fillDs) && fillDs.property(QDataSet.DEPEND_0)==null;
boolean shouldSlice= ( fillDs.rank()>2 && ndim>3 && plotElement.isAutoComponent() && !isxyz );
if ( renderType==RenderType.image && fillDs.rank()==3 ) {
shouldSlice= false; //TODO: some how render types should indicate they can handle a slice.
}
QDataSet sliceDs= fillDs; // dataset after initial slicing
String existingComponent= plotElement.getComponent();
//logger.fine("fillDs="+ fillDs + " renderType="+renderType + " existingComponent="+existingComponent );
if ( shouldSlice && existingComponent.length()>0 ) {
try {
sliceDs = DataSetOps.sprocess( existingComponent, fillDs, new NullProgressMonitor() );
} catch (Exception ex) {
logger.log(Level.SEVERE, ex.getMessage(), ex);
}
}
boolean isWaveform= false;
if ( SemanticOps.isRank2Waveform(fillDs) ) {
isWaveform= true;
}
boolean shouldHaveChildren=
fillDs.rank() == 2
&& !isxyz
&& !isWaveform
&& ( renderType == RenderType.hugeScatter
|| renderType==RenderType.series
|| renderType==RenderType.scatter
|| renderType==RenderType.stairSteps );
//if ( joinOfBundle ) shouldHaveChildren= true;
if ( fillDs.rank()==2 && SemanticOps.isBundle(fillDs) ) { //TODO: LANL has datasets with both BUNDLE_1 and DEPEND_1 set, so the user can pick.
QDataSet bdesc= (QDataSet) fillDs.property(QDataSet.BUNDLE_1);
Object context0= bdesc.property(QDataSet.CONTEXT_0,bdesc.length()-1); // in a bundleDescriptor, this can be a string.
if ( null!=context0 && context0 instanceof String ) {
shouldHaveChildren= false;
}
}
List children= getChildPlotElements();
boolean alreadyHaveChildren= !children.isEmpty();
if ( alreadyHaveChildren && shouldHaveChildren ) {
//shouldHaveChildren= false;
}
if ( logger.isLoggable(Level.FINER) ) {
synchronized (dom) {
logger.finer("###############");
logger.finer(""+this.plotElement.getId()+" " +getChildPlotElements() );
logger.finer("shouldHaveChildren: "+shouldHaveChildren );
logger.finer("###############");
}
}
String[] lnames = null;
String[] llabels= null;
if ( shouldHaveChildren ) {
lnames= SemanticOps.getComponentNames(fillDs);
llabels= SemanticOps.getComponentLabels(fillDs);
}
boolean weShallAddChildren= shouldHaveChildren;
if ( !shouldHaveChildren || weShallAddChildren ) { // delete any old child plotElements
List childEles= getChildPlotElements();
for ( PlotElement p : childEles ) {
PlotElementController pec= p.getController();
if ( dom.plotElements.contains(p) ) {
dom.controller.deletePlotElement(p); //TODO: there are times when things change between the contains(p) and deletePlotElement(p).
PropertyChangeListener parentListener= pec.getParentComponentLister();
if ( parentListener!=null ) {
this.plotElement.removePropertyChangeListener( parentListener );
}
this.removePropertyChangeListener(dsfListener);
}
plotElement.getStyle().removePropertyChangeListener( pec.parentStyleListener );
}
}
if ( !shouldSlice ) doResetRenderType(plotElement.getRenderType());
setResetPlotElement(false);
if ( resetRanges && !shouldSlice && !weShallAddChildren ) {
boolean doTurnOn= getParentPlotElement()==null && renderer.isActive()==false;
if ( doTurnOn ) {
renderer.setActive(true); // we need this to be on for doResetRanges
}
doResetRanges();
setResetRanges(false);
if ( doTurnOn ) {
renderer.setActive(false);
}
} else {
// get properties like the title and yrange for a stack of line plots.
if ( weShallAddChildren ) doResetRanges();
}
if ( shouldHaveChildren ) {
renderer.setActive(false);
plotElement.setDisplayLegend(false);
}
if ( shouldSlice ) {
String component= guessSlice( sliceDs );
setSliceAutoranges( sliceShouldAutorange(fillDs, component) );
if ( !existingComponent.equals("") ) {
if ( component.equals(existingComponent) ) {
logger.fine("here again...");
}
plotElement.setComponentAutomatically( existingComponent + component );
} else {
plotElement.setComponentAutomatically( component ); // it'll reenter this code now. problem--autorange is based on slice.
}
doResetRenderType(plotElement.getRenderType());
return;
}
// add additional plotElements when it's a bundle of rank1 datasets.
if ( weShallAddChildren ) {
DomLock lock = dom.controller.mutatorLock();
lock.lock("Add Child Elements");
try {
Color c = plotElement.getStyle().getColor();
Color fc= plotElement.getStyle().getFillColor();
Plot domPlot = dom.controller.getPlotFor(plotElement);
int count= Math.min(QDataSet.MAX_UNIT_BUNDLE_COUNT, fillDs.length(0));
List cp = new ArrayList<>(count);
int nsubsample= 1 + ( count-1 ) / 12; // 1-12 no subsample, 13-24 1 subsample, 25-36 2 subsample, etc.
// check for inconsistencies in names, which might indictate that these are not similar channels
boolean dissimilarChannels= dissimilarChannels(lnames);
if ( dissimilarChannels ) {
nsubsample= 1 + ( count-1 ) / 64; // 1-64 no subsample, 65...
}
//check for non-unique labels, or labels that are simply numbers.
boolean uniqLabels= true;
assert lnames!=null;
Pattern p= Pattern.compile("ch_\\d+");
for ( int i=0;i0 ) {
if ( context.value(0)==context.value(1) ) {
label1= context.slice(0).toString();
} else {
label1= context.toString();
}
} else {
label1= "fill";
}
} else {
if ( !"slice1".equals( context.property(QDataSet.NAME) ) ) { // check for default name.
label1= context.toString(); // rank 0.
}
}
s= lnames[i];
} else {
if ( uniqLabels ) {
label1= llabels[i];
s= lnames[i];
} else {
label1= "ch_"+i;
s= s+"|unbundle('ch_"+i+"')";
}
}
} else {
if ( uniqLabels ) {
s= s+"|unbundle('"+lnames[i]+"')";
} else {
s= s+"|unbundle('ch_"+i+"')";
}
//addParentComponentListener(plotElement,ele);
label1= llabels[i];
}
}
ele.setComponentAutomatically(s);
ele.setActive(false); // setComponentAutomatically resets this
ele.setDisplayLegend(true);
ele.setLegendLabelAutomatically(label1);
ele.setRenderTypeAutomatically(plotElement.getRenderType()); // this creates the das2 SeriesRenderer.
ele.controller.maybeCreateDasPeer();
//ele.controller.setDataSet(fillDs, false);
}
for ( int i=0; i l = DomUtil.findBindings( dom, p.xaxis, Axis.PROP_RANGE );
String xaxisId= p.getXaxis().getId();
for ( BindingModel bm : l ) {
if ( bm.getSrcId().equals(dom.getId() ) ) {
List l2 = DomUtil.findBindings( dom, dom, Application.PROP_TIMERANGE );
if ( l2.size()>1 ) isNotBound= false; // first one is the one we already found.
} else if ( bm.getSrcId().equals( xaxisId ) ) {
if ( !bm.getDstId().equals(xaxisId) && bm.getDstProperty().equals(Axis.PROP_RANGE) ) {
isNotBound= false;
}
} else if ( bm.getDstId().equals( xaxisId ) ) {
if ( !bm.getSrcId().equals(xaxisId) && bm.getSrcProperty().equals(Axis.PROP_RANGE ) ) {
isNotBound= false;
}
}
}
return isNotBound;
}
/**
* we'd like the plot to autorange, so check to see if we are the only
* plotElement, and if so, set its autorange and autoLabel flags.
*/
private void maybeSetPlotYZAutorange() {
Plot p= dom.controller.getPlotFor(plotElement);
if ( p==null ) return;
List eles= dom.controller.getPlotElementsFor(p);
if ( DomUtil.oneFamily(eles) ) {
p.getYaxis().setAutoRange(true);
p.getZaxis().setAutoRange(true);
p.getYaxis().setAutoLabel(true);
p.getZaxis().setAutoLabel(true);
p.setAutoLabel(true);
p.setAutoBinding(true);
}
}
/**
* we'd like the plot to autorange, so check to see if we are the only
* plotElement, and if so, set its autorange and autoLabel flags.
*/
private void maybeSetPlotAutorange() {
Plot p= dom.controller.getPlotFor(plotElement);
if ( p==null ) return;
List eles= dom.controller.getPlotElementsFor(p);
if ( DomUtil.oneFamily(eles) ) {
p.getXaxis().setAutoRange( xaxisFreeFromBindings(p) );
p.getYaxis().setAutoRange(true);
p.getZaxis().setAutoRange(true);
p.getXaxis().setAutoLabel(true);
p.getYaxis().setAutoLabel(true);
p.getZaxis().setAutoLabel(true);
p.setAutoLabel(true);
p.setAutoBinding(true);
}
}
private void setDataSourceFilterController(final DataSourceController dsc) {
dsc.addPropertyChangeListener(DataSourceController.PROP_FILLDATASET, fillDataSetListener);
dsc.addPropertyChangeListener(DataSourceController.PROP_DATASOURCE, dataSourceDataSetListener);
dsc.addPropertyChangeListener(DataSourceController.PROP_EXCEPTION, exceptionListener);
}
/**
* true indicates the controller should autorange next time the fillDataSet is changed.
*/
public static final String PROP_RESETRANGES = "resetRanges";
/**
* true indicates the controller should autorange next time the fillDataSet is changed.
*/
private boolean resetRanges = false;
public boolean isResetRanges() {
return resetRanges;
}
public void setResetRanges(boolean resetRanges) {
logger.log(Level.FINE, "{0}.setResetRanges({1})", new Object[] { plotElement.id, resetRanges } );
boolean oldResetRanges = this.resetRanges;
this.resetRanges = resetRanges;
propertyChangeSupport.firePropertyChange(PROP_RESETRANGES, oldResetRanges, resetRanges);
}
/**
* true indicates the controller should install a new renderer to implement the
* renderType selection. This may mean that we introduce or remove child plotElements.
* This implies resetRenderType.
*/
public static final String PROP_RESETPLOTELEMENT = "resetPlotElement";
private boolean resetPlotElement = false;
public boolean isResetPlotElement() {
return resetPlotElement;
}
public void setResetPlotElement(boolean resetPlotElement) {
logger.log(Level.FINE, "{0}.setResetPlotElement({1})", new Object[] { plotElement.id, resetPlotElement } );
boolean old = this.resetPlotElement;
this.resetPlotElement = resetPlotElement;
propertyChangeSupport.firePropertyChange(PROP_RESETPLOTELEMENT, old, resetPlotElement);
}
/**
* true indicates that the component should be reset when the dataset arrives. This
* is added as a controller property so that clients can clear this setting if
* they do not want the component to be reset. This is only considered if resetPlotElement is
* set.
*/
public static final String PROP_RESETCOMPONENT = "resetComponent";
private boolean resetComponent = false;
public boolean isResetComponent() {
return resetComponent;
}
public void setResetComponent(boolean resetComponent) {
boolean oldResetComponent = this.resetComponent;
this.resetComponent = resetComponent;
propertyChangeSupport.firePropertyChange(PROP_RESETCOMPONENT, oldResetComponent, resetComponent);
}
/**
* true indicates the peer should be reset to the current renderType.
*/
public static final String PROP_RESETRENDERTYPE = "resetRenderType";
private boolean resetRenderType = false;
public boolean isResetRenderType() {
return resetRenderType;
}
public void setResetRenderType(boolean resetRenderType) {
boolean oldResetRenderType = this.resetRenderType;
this.resetRenderType = resetRenderType;
propertyChangeSupport.firePropertyChange(PROP_RESETRENDERTYPE, oldResetRenderType, resetRenderType);
}
/**
* when true, a change in the DataSourceFilter should reset the plotElement.
*/
public static final String PROP_DSFRESET = "dsfReset";
private boolean dsfReset = true;
public boolean isDsfReset() {
return dsfReset;
}
public void setDsfReset(boolean dsfReset) {
boolean oldDsfReset = this.dsfReset;
this.dsfReset = dsfReset;
propertyChangeSupport.firePropertyChange(PROP_DSFRESET, oldDsfReset, dsfReset);
}
/**
* when true, changing the slice index should cause autorange.
*/
private boolean sliceAutoranges = false;
public static final String PROP_SLICEAUTORANGES = "sliceAutoranges";
public boolean isSliceAutoranges() {
return sliceAutoranges;
}
public void setSliceAutoranges(boolean sliceAutoranges) {
boolean oldSliceAutoranges = this.sliceAutoranges;
this.sliceAutoranges = sliceAutoranges;
propertyChangeSupport.firePropertyChange(PROP_SLICEAUTORANGES, oldSliceAutoranges, sliceAutoranges);
}
private static final AtomicInteger renderCount= new AtomicInteger();
protected Renderer renderer = null;
public Renderer getRenderer() {
return renderer;
}
/**
* set the renderer controlled by this PlotElement controller.
* This should be used with caution.
* @param renderer
* @see external.PlotCommand
*/
public void setRenderer(Renderer renderer) {
logger.entering("PlotElementController","setRenderer");
Renderer oldRenderer= this.renderer;
ApplicationController ac = this.dom.controller;
if ( oldRenderer!=null ) {
ac.unbind( plotElement, PlotElement.PROP_LEGENDLABEL, oldRenderer, Renderer.PROP_LEGENDLABEL );
ac.unbind( plotElement, PlotElement.PROP_DISPLAYLEGEND, oldRenderer, Renderer.PROP_DRAWLEGENDLABEL);
ac.unbind( plotElement, PlotElement.PROP_RENDERCONTROL, oldRenderer, Renderer.PROP_CONTROL );
ac.unbind( plotElement, PlotElement.PROP_ACTIVE, oldRenderer, Renderer.PROP_ACTIVE );
}
this.renderer = renderer;
ac.unbindImpl(node);
ac.unbindImpl(((PlotElement)node).getStyle());
if ( node!=plotElement ) {
logger.fine("node!=plotElement");
}
if (renderer instanceof SeriesRenderer) {
bindToSeriesRenderer((SeriesRenderer) renderer);
} else if (renderer instanceof SpectrogramRenderer) {
bindToSpectrogramRenderer((SpectrogramRenderer) renderer);
} else if (renderer instanceof HugeScatterRenderer) {
bindToImageVectorDataSetRenderer((HugeScatterRenderer) renderer);
} else if (renderer instanceof EventsRenderer ) {
bindToEventsRenderer((EventsRenderer)renderer);
} else if (renderer instanceof DigitalRenderer ) {
bindToDigitalRenderer((DigitalRenderer)renderer);
} else if (renderer instanceof PolarPlotRenderer ) {
bindToPolarPlotRenderer((PolarPlotRenderer)renderer);
} else if (renderer instanceof TickCurveRenderer ) {
bindToTickCurveRenderer((TickCurveRenderer)renderer);
} else if (renderer instanceof BoundsRenderer ) {
bindToBoundsRenderer((BoundsRenderer)renderer);
} else if (renderer instanceof ContoursRenderer ) {
bindToContoursRenderer((ContoursRenderer)renderer);
}
Plot mip= ac.getPlotFor(plotElement);
if ( mip!=null ) { // transitional state
JMenuItem mi= mip.getController().getPlotElementPropsMenuItem();
if ( mi!=null ) mi.setIcon( renderer.getListIcon() );
}
renderer.setId( "rend_"+plotElement.getId()+"_"+String.format( "%04d", PlotElementController.renderCount.incrementAndGet() ) ); // for debugging, make unique names
ac.bind(plotElement, PlotElement.PROP_LEGENDLABEL, renderer, Renderer.PROP_LEGENDLABEL, getLabelConverter() );
ac.bind(plotElement, PlotElement.PROP_DISPLAYLEGEND, renderer, Renderer.PROP_DRAWLEGENDLABEL);
ac.bind(plotElement, PlotElement.PROP_RENDERCONTROL, renderer, Renderer.PROP_CONTROL );
ac.bind(plotElement, PlotElement.PROP_ACTIVE, renderer, Renderer.PROP_ACTIVE );
logger.exiting("PlotElementController","setRenderer");
}
/**
*
Do initialization to get the plotElement and attached plot to have reasonable
* settings.
* preconditions:
*
renderType has been identified for the plotElement.
*
* postconditions:
*
plotElement's plotDefaults are set based on metadata and autoranging.
*
listening plot may invoke its resetZoom method.
*
*/
private void doResetRanges() {
setStatus("busy: do autorange");
changesSupport.performingChange(this, PENDING_RESET_RANGE);
DataSourceFilter ldsf= getDataSourceFilter(); // make local copy to avoid any synchronization problems and to clean up code.
DataSourceController dsc= ldsf!=null ? ldsf.getController() : null;
if ( dsc==null ) { // don't think this happens.
logger.warning("expected dsc to be non-null.");
return;
}
try {
Plot plot = dom.controller.getPlotFor(plotElement);
if ( plot==null ) {
throw new NullPointerException("unable to find plot for plotElement: "+plotElement );
}
logger.log(Level.FINE, "renderType: {0}", plotElement.getRenderType());
PlotElement peleCopy = (PlotElement) plotElement.copy();
peleCopy.setId("");
peleCopy.setParent("");
peleCopy.getPlotDefaults().syncTo( plot, Arrays.asList(DomNode.PROP_ID, Plot.PROP_ROWID, Plot.PROP_COLUMNID) );
logger.log(Level.FINE, "doResetRanges for {0}", dsc);
QDataSet fillDs = dsc.getFillDataSet();
Map props= dsc.getFillProperties();
if ( props==null || fillDs==null ) { // need a atomic operation here...
return;
}
String comp= plotElement.getComponent();
if ( comp.length()>0 ) {
try {
fillDs = processDataSet(comp, fillDs);
} catch (Exception ex) {
logger.log( Level.WARNING, null, ex );
return;
}
if ( fillDs==null ) {
throw new IllegalArgumentException("processDataSet resulted in null result: "+comp);
}
props= processProperties( comp, props ); //TODO: support components
if ( props.isEmpty() ) { // many of the filters drop the properties
props= AutoplotUtil.extractProperties(fillDs);
}
}
if (dom.getOptions().isAutolabelling()) { //TODO: this is pre-autoLabel property.
doMetadata(peleCopy, props, fillDs );
String appliedFilters = dsc.getAppliedFiltersString();
if ( appliedFilters!=null ) appliedFilters= appliedFilters.trim();
String title = peleCopy.getPlotDefaults().getTitle();
if ( fillDs.property(QDataSet.CONTEXT_0)!=null && dsc.reduceDataSetString!=null ) {
title += "!c%{CONTEXT}";
} else if ( !plotElement.getComponent().equals("") ) {
title += "!c%{CONTEXT}";
} else if ( appliedFilters != null && appliedFilters.length()>0 ) {
title += "!c"+appliedFilters;
} else if ( fillDs.property(QDataSet.CONTEXT_0)!=null ) {
title += "!c%{CONTEXT}";
}
peleCopy.getPlotDefaults().setTitle(title);
}
if (dom.getOptions().isAutoranging()) { //this is pre-autorange property, but saves time if we know we won't be autoranging.
logger.fine("doAutoranging");
//long t0= System.currentTimeMillis();
doAutoranging( peleCopy, props, fillDs, false );
RenderType rt= peleCopy.getRenderType();
if ( rt==RenderType.series || rt==RenderType.colorScatter || rt==RenderType.hugeScatter || rt==RenderType.fillToZero || rt==RenderType.stairSteps ) {
if (fillDs.length() > LARGE_DATASET_COUNT && !( rt==RenderType.colorScatter ) ) {
logger.fine("dataset has many points, turning off psym");
peleCopy.getStyle().setSymbolConnector(PsymConnector.SOLID); // Interesting... This was exactly the opposite of what I should do...
peleCopy.getStyle().setPlotSymbol(DefaultPlotSymbol.NONE);
} else {
if (fillDs.length() > SYMSIZE_DATAPOINT_COUNT) {
logger.fine("dataset has a more than few points, using small symbols");
peleCopy.getStyle().setSymbolSize(1.0);
peleCopy.getStyle().setPlotSymbol( DefaultPlotSymbol.CIRCLES ); // 1215: make this NONE eventually
} else {
logger.fine("dataset has few points, using small large symbols");
peleCopy.getStyle().setSymbolSize(3.0);
if ( rt==RenderType.stairSteps ) {
peleCopy.getStyle().setPlotSymbol(DefaultPlotSymbol.NONE);
} else {
peleCopy.getStyle().setPlotSymbol(DefaultPlotSymbol.CIRCLES);
}
}
}
}
//logger.fine(" "+( System.currentTimeMillis()-t0 )+" ms spent autoranging "+fillDs );
TimeSeriesBrowse tsb= dsc.getTsb();
if ( tsb!=null ) {
if ( fillDs.rank()==0 ) {
logger.fine("data is rank 0, no autoranging needs to be done.");
} else {
QDataSet xds= SemanticOps.xtagsDataSet(fillDs);
Units xunits;
if ( xds.rank()<=1 ) {
xunits= (Units)xds.property(QDataSet.UNITS);
} else {
//JOIN dataset
xunits= (Units)xds.property(QDataSet.UNITS,0);
}
if ( xunits!=null && UnitsUtil.isTimeLocation( xunits ) ) {
DatumRange tr= tsb.getTimeRange();
if ( tr==null ) {
logger.fine( "tsb contains no timerange");
}
peleCopy.getPlotDefaults().getXaxis().setRange( tr );
}
}
}
Renderer newRenderer = getRenderer();
if (newRenderer instanceof SeriesRenderer) {
QDataSet d = (QDataSet) fillDs.property(QDataSet.DEPEND_0);
if (d != null) {
((SeriesRenderer) newRenderer).setCadenceCheck((d.property(QDataSet.CADENCE) != null));
} else {
((SeriesRenderer) newRenderer).setCadenceCheck(true);
}
}
} else {
setStatus( "autoranging is disabled" );
logger.info( "autoranging is disabled" );
}
if ( plotElement.getComponent().equals("") && plotElement.isAutoLabel() ) plotElement.setLegendLabelAutomatically( peleCopy.getLegendLabel() );
// bugfix1157: the autorange property has a meaning now, and it is set for each plot element.
//peleCopy.getPlotDefaults().getXaxis().setAutoRange(true); // this is how we distinguish it from the original, useless plot defaults.
//peleCopy.getPlotDefaults().getYaxis().setAutoRange(true);
//peleCopy.getPlotDefaults().getZaxis().setAutoRange(true);
if ( logger.isLoggable(Level.FINER) ) {
logger.finer( String.format( "done, autorange x:%s, y:%s ",
peleCopy.getPlotDefaults().getXaxis().getRange().toString(),
peleCopy.getPlotDefaults().getYaxis().getRange().toString() ) );
}
plotElement.setPlotDefaults( peleCopy.getPlotDefaults() ); // bug https://sourceforge.net/p/autoplot/bugs/283/ runs through here
plotElement.style.syncTo( peleCopy.style );
plotElement.renderType= peleCopy.renderType; // don't fire an event
// and hope that the plot is listening.
if ( dom.getOptions().isAutoranging() ) {
setStatus("done, autorange");
}
} finally {
changesSupport.changePerformed(this, PENDING_RESET_RANGE);
}
}
/**
* extract properties from the data and metadata to get axis labels, fill values, and
* preconditions:
*
fillData is set.
*
fillProperties is set.
*
* postconditions:
*
metadata is inspected to get axis labels, fill values, etc.
*
renderType is determined and set.
*
* @param autorange
* @param interpretMetadata
*/
private static void doMetadata( PlotElement peleCopy, Map properties, QDataSet fillDs ) {
peleCopy.getPlotDefaults().getXaxis().setLabel("");
peleCopy.getPlotDefaults().getYaxis().setLabel("");
peleCopy.getPlotDefaults().getZaxis().setLabel("");
peleCopy.getPlotDefaults().setTitle("");
peleCopy.setLegendLabelAutomatically("");
doInterpretMetadata(peleCopy, properties, peleCopy.getRenderType());
PlotElementUtil.unitsCheck(properties, fillDs); // DANGER--this may cause overplotting problems in the future by removing units
}
/**
* pull out axis labels into plotDefaults.
* @param peleCopy
* @param properties
* @param spec
*/
private static void doInterpretMetadata( PlotElement peleCopy, Map properties, RenderType spec) {
Object v;
final Plot plotDefaults = peleCopy.getPlotDefaults();
if ((v = properties.get(QDataSet.TITLE)) != null) {
plotDefaults.setTitle((String) v);
}
String legendLabel= null;
if ((v = properties.get(QDataSet.NAME)) != null) {
legendLabel= (String)v;
}
if ((v = properties.get(QDataSet.LABEL)) != null) {
legendLabel= (String)v;
}
if ( legendLabel!=null ) {
peleCopy.setLegendLabelAutomatically((String) legendLabel);
}
if ( spec == RenderType.spectrogram || spec==RenderType.nnSpectrogram
|| spec==RenderType.stackedHistogram ) {
if ( (v = properties.get(QDataSet.SCALE_TYPE)) != null) {
plotDefaults.getZaxis().setLog(v.equals("log"));
}
if ( (v = properties.get(QDataSet.LABEL)) != null) {
plotDefaults.getZaxis().setLabel((String) v);
}
if ( (v = properties.get(QDataSet.DEPEND_1)) != null) {
Map m = (Map) v;
Object v2 = m.get(QDataSet.LABEL);
if (v2 != null) {
plotDefaults.getYaxis().setLabel((String) v2);
}
}
Map m= (Map)properties.get(QDataSet.PLANE_0);
if ( m!=null ) {
v = m.get(QDataSet.LABEL);
if (v != null) {
plotDefaults.getZaxis().setLabel((String) v);
}
v= m.get(QDataSet.TITLE);
if (v != null) {
plotDefaults.setTitle((String) v);
}
}
} else if ( spec == RenderType.image ) {
Map yprop, xprop=null, prop;
xprop= (Map) properties.get( QDataSet.DEPEND_0 );
yprop= (Map) properties.get( QDataSet.DEPEND_1 );
if ( xprop!=null ) {
v = xprop.get(QDataSet.LABEL);
if ( v!=null ) plotDefaults.xaxis.setLabel( (String)v );
v = xprop.get(QDataSet.SCALE_TYPE);
if ( v!=null) plotDefaults.getXaxis().setLog(v.equals("log"));
}
if ( yprop!=null ) {
v= (String) yprop.get(QDataSet.LABEL);
if ( v!=null ) plotDefaults.yaxis.setLabel( (String)v );
v = yprop.get(QDataSet.SCALE_TYPE);
if ( v!=null) plotDefaults.getYaxis().setLog(v.equals("log"));
}
} else { // hugeScatter okay
Map yprop, xprop=null, prop;
QDataSet bundle1= (QDataSet) properties.get(QDataSet.BUNDLE_1);
if ( bundle1!=null ) {
xprop= (Map) properties.get( QDataSet.DEPEND_0 );
if ( xprop==null ) {
xprop= DataSetUtil.getProperties( DataSetOps.slice0( bundle1, 0 ) );
}
if ( !(spec==RenderType.colorScatter ) ) { // why would you ever want to use second case? the nightly tests will tell for sure...
prop= properties;
yprop= properties;
} else {
prop= DataSetUtil.getProperties( DataSetOps.slice0( bundle1, bundle1.length()-1 ) );
yprop= DataSetUtil.getProperties( DataSetOps.slice0( bundle1, 1 ) ); // may be the same as prop.
}
} else {
prop= properties;
yprop= properties;
v = properties.get(QDataSet.PLANE_0);
if ( v!=null ) {
yprop= prop;
prop= (Map) v;
}
}
if ( (v = yprop.get(QDataSet.SCALE_TYPE)) != null) {
plotDefaults.getYaxis().setLog(v.equals("log"));
}
if ( (v = yprop.get(QDataSet.LABEL)) != null) {
plotDefaults.getYaxis().setLabel((String) v);
}
if ( xprop!=null && (v = xprop.get(QDataSet.LABEL)) != null) {
plotDefaults.getXaxis().setLabel((String) v);
}
if (spec == RenderType.colorScatter) {
v = prop.get(QDataSet.LABEL);
if (v != null) {
plotDefaults.getZaxis().setLabel((String) v);
}
v= prop.get(QDataSet.TITLE);
if (v != null) {
plotDefaults.setTitle((String) v);
}
}
}
if ((v = properties.get(QDataSet.DEPEND_0)) != null) {
Map m = (Map) v;
Object v2 = m.get(QDataSet.LABEL);
if ( v2 != null) {
plotDefaults.getXaxis().setLabel((String) v2);
}
}
}
/**
* This is the old updateFillSeries and updateFillSpectrogram code.
*
* This calculates
* ranges and preferred symbol settings, and puts the values in peleCopy.plotDefaults.
* The dom Plot containing this plotElement should be listening for changes in plotElement.plotDefaults,
* and can then decide if it wants to use the autorange settings.
*
* This also sets the style node of the plotElement copy, so its values should be sync'ed as well.
*
* @param peleCopy the plot element.
* @param props metadata provided by the data source, converted to uniform QDataSet scheme (e.g. get(DEPEND_0).get(TYPICAL_MIN) )
* @param fillDs the dataset
*/
public static void doAutoranging( PlotElement peleCopy, Map props, QDataSet fillDs ) {
doAutoranging( peleCopy, props, fillDs, false );
}
/**
* copy the settings in bounds to the plot. For example, datumRange(bounds.slice(0)) will be the new x-axis
* setting.
* @param p the plot
* @param bounds the bounding box or cube.
*/
private static void copyAutorange( Plot p, QDataSet bounds ) {
assert Schemes.isBoundingBox(bounds);
p.xaxis.setRange( DataSetUtil.asDatumRange( bounds.slice(0),true ) );
p.xaxis.setLog( "log".equals( bounds.slice(0).property(QDataSet.SCALE_TYPE) ) );
p.yaxis.setRange( DataSetUtil.asDatumRange( bounds.slice(1),true ) );
p.yaxis.setLog( "log".equals( bounds.slice(1).property(QDataSet.SCALE_TYPE) ) );
if ( bounds.length()>2 ) {
p.zaxis.setRange( DataSetUtil.asDatumRange( bounds.slice(2),true ) );
p.zaxis.setLog( "log".equals( bounds.slice(2).property(QDataSet.SCALE_TYPE) ) );
} else {
p.zaxis.setAutoRange(false);
}
}
/**
* This is the old updateFillSeries and updateFillSpectrogram code.
*
*
* This calculates ranges and preferred symbol settings, and puts the values
* in peleCopy.plotDefaults. The dom Plot containing this plotElement
* should be listening for changes in plotElement.plotDefaults,
* and can then decide if it wants to use the autorange settings.
*
* This also sets the style node of the plotElement copy, so its values
* should be sync'ed as well.
*
* This routine can be found by searching for "liver," since it is not the
* heart but pretty close to it. (2024-01-19: I forgot about liver, was
* thinking autorange9)
*
* @param peleCopy the plot element.
* @param props metadata provided by the data source, converted to uniform
* QDataSet scheme (e.g. get(DEPEND_0).get(TYPICAL_MIN) )
* @param fillDs the dataset
* @param ignoreDsProps
*/
public static void doAutoranging( PlotElement peleCopy, Map props, QDataSet fillDs, boolean ignoreDsProps ) {
RenderType spec = peleCopy.getRenderType();
if ( fillDs.rank()==0 ) {
if ( spec==RenderType.eventsBar ) {
// do nothing
} else {
//logger.fine("rank 0");
spec= RenderType.digital;
}
}
if (props == null) {
props = Collections.EMPTY_MAP;
}
logger.log(Level.FINE, "doAutoranging for {0}", spec);
if ( spec == RenderType.spectrogram || spec==RenderType.nnSpectrogram || spec==RenderType.stackedHistogram ) {
QDataSet xds = (QDataSet) SemanticOps.xtagsDataSet(fillDs);
if (xds == null) {
if ( fillDs.property(QDataSet.JOIN_0)!=null ) {
JoinDataSet ds= new JoinDataSet(2);
int xtag=0;
for ( int i=0; i so use implicit findgen dataset for Y.
if ( fillDs.rank()==2 && SemanticOps.isJoin(fillDs) ) {
yds= null; // code below calculates this.
}
Map yprops= (Map) props.get(QDataSet.DEPEND_1);
if (yds == null) {
if ( fillDs.property(QDataSet.JOIN_0)!=null ) {
JoinDataSet ds= new JoinDataSet(2);
for ( int i=0; i0
&& fillDs.property(QDataSet.DEPEND_0,0)!=null ) {
JoinDataSet ds= new JoinDataSet(2);
Units u= null;
for ( int i=0; i1 ) {
yds = DataSetUtil.indexGenDataSet(fillDs.length(0)); //TODO: QUBE assumed
} else {
yds = DataSetUtil.indexGenDataSet(10); // later the user will get a message "renderer cannot plot..."
yprops= null;
}
}
QDataSet zds;
if ( fillDs.rank()>1 && yds.length()==fillDs.length(0) && yds.length()>3 ) { // Dataset might have bundle, we need to ignore at the right time. If fillDs.length(0)==3 avoid a bug.
zds= fillDs;
} else {
zds= SemanticOps.getDependentDataSet(fillDs);
if ( Schemes.isLegacyXYZScatter(zds) ) zds= (QDataSet)fillDs.property(QDataSet.PLANE_0 );
}
Units xunits= SemanticOps.getUnits(xds);
Units yunits= SemanticOps.getUnits(yds);
Units zunits= SemanticOps.getUnits(zds);
if ( UnitsUtil.isOrdinalMeasurement( xunits ) || UnitsUtil.isOrdinalMeasurement(yunits) || UnitsUtil.isOrdinalMeasurement(zunits) ) {
return;
}
AutoRangeUtil.AutoRangeDescriptor desc;
desc = AutoRangeUtil.autoRange( zds, props, ignoreDsProps ); // do the Z autoranging first for debugging.
logger.log(Level.FINE, "desc.range={0}", desc.range);
AutoRangeUtil.AutoRangeDescriptor xdesc = AutoplotUtil.autoRange(xds, (Map) props.get(QDataSet.DEPEND_0), ignoreDsProps);
logger.log(Level.FINE, "xdesc.range={0}", xdesc.range);
AutoRangeUtil.AutoRangeDescriptor ydesc = AutoplotUtil.autoRange(yds, yprops, ignoreDsProps );
logger.log(Level.FINE, "ydesc.range={0}", ydesc.range);
peleCopy.getPlotDefaults().getZaxis().setRange(desc.range);
peleCopy.getPlotDefaults().getZaxis().setLog(desc.log);
logger.log(Level.FINE, "xaxis.isAutoRange={0}", peleCopy.getPlotDefaults().getXaxis().isAutoRange());
peleCopy.getPlotDefaults().getXaxis().setLog(xdesc.log);
peleCopy.getPlotDefaults().getXaxis().setRange(xdesc.range);
peleCopy.getPlotDefaults().getYaxis().setLog(ydesc.log);
peleCopy.getPlotDefaults().getYaxis().setRange(ydesc.range);
} else if ( spec==RenderType.pitchAngleDistribution ) {
QDataSet qube= PitchAngleDistributionRenderer.doAutorange( fillDs );
if ( qube==null ) {
// nothing
} else {
peleCopy.getPlotDefaults().getXaxis().setRange( DataSetUtil.asDatumRange( qube.slice(0),true ) );
String label= (String) qube.slice(0).property( QDataSet.LABEL );
peleCopy.getPlotDefaults().getXaxis().setLabel( label==null ? "" : label );
peleCopy.getPlotDefaults().getYaxis().setRange( DataSetUtil.asDatumRange( qube.slice(1),true ) );
label= (String) qube.slice(1).property( QDataSet.LABEL );
peleCopy.getPlotDefaults().getYaxis().setLabel( label==null ? "" : label );
peleCopy.getPlotDefaults().getZaxis().setRange( DataSetUtil.asDatumRange( qube.slice(2),true ) );
peleCopy.getPlotDefaults().getZaxis().setLog( "log".equals( qube.slice(2).property(QDataSet.SCALE_TYPE) ) );
}
} else if ( spec==RenderType.polar ) {
QDataSet qube= PolarPlotRenderer.doAutorange( fillDs );
if ( qube==null ) {
// nothing
} else {
copyAutorange( peleCopy.getPlotDefaults(), qube );
String label= (String) qube.slice(0).property( QDataSet.LABEL );
peleCopy.getPlotDefaults().getXaxis().setLabel( label==null ? "" : label );
label= (String) qube.slice(1).property( QDataSet.LABEL );
peleCopy.getPlotDefaults().getYaxis().setLabel( label==null ? "" : label );
}
} else if ( spec==RenderType.digital ) {
QDataSet qube= DigitalRenderer.doAutorange( fillDs );
if ( qube==null ) {
// nothing
} else {
copyAutorange( peleCopy.getPlotDefaults(), qube );
}
} else if ( spec==RenderType.contour ) {
QDataSet qube= ContoursRenderer.doAutorange( fillDs );
if ( qube==null ) {
// nothing
} else {
copyAutorange( peleCopy.getPlotDefaults(), qube );
}
} else if ( spec==RenderType.eventsBar ) {
QDataSet qube= EventsRenderer.doAutorange( fillDs );
if ( qube==null ) {
// nothing
} else {
copyAutorange( peleCopy.getPlotDefaults(), qube );
peleCopy.getPlotDefaults().getYaxis().setAutoRange(false);
peleCopy.getPlotDefaults().getYaxis().setVisible(false);
}
} else if ( spec==RenderType.vectorPlot ) { //TODO: this should be discoverable
QDataSet qube= VectorPlotRenderer.doAutorange( fillDs );
if ( qube==null ) {
// nothing
} else {
copyAutorange( peleCopy.getPlotDefaults(), qube );
}
} else if ( spec==RenderType.orbitPlot ) {
QDataSet qube= TickCurveRenderer.doAutorange( fillDs );
if ( qube==null ) {
// nothing
} else {
copyAutorange( peleCopy.getPlotDefaults(), qube );
}
} else if ( spec==RenderType.image ) {
QDataSet qube= RGBImageRenderer.doAutorange( fillDs );
if ( qube==null ) {
// nothing
} else {
copyAutorange( peleCopy.getPlotDefaults(), qube );
}
} else if ( spec==RenderType.internal ) {
// nothing
} else if ( spec==RenderType.bounds ) {
QDataSet qube= BoundsRenderer.doAutorange( fillDs );
if ( qube==null ) {
// nothing
} else {
DatumRange xrange= DataSetUtil.asDatumRange( qube.slice(0),true ); // angle maybe
DatumRange yrange= DataSetUtil.asDatumRange( qube.slice(1),true );
if ( props.containsKey(QDataSet.RENDER_TYPE) ) { // Let's kludge in a check for polar, whee!
// The renderer has an unfortunate mistake where the controls will affect the autoranging. This should
// probably be redone.
String rt= (String)props.get(QDataSet.RENDER_TYPE);
if ( rt.contains("polar=T") ) {
xrange= DatumRange.newRange( yrange.max().negative(), yrange.max() );
yrange= xrange;
}
}
peleCopy.getPlotDefaults().getXaxis().setRange( xrange );
peleCopy.getPlotDefaults().getYaxis().setRange( yrange );
peleCopy.getPlotDefaults().getZaxis().setAutoRange(false);
}
} else { // spec==RenderType.SERIES and spec==RenderType.HUGE_SCATTER
AutoRangeUtil.AutoRangeDescriptor ydesc; //TODO: QDataSet can model AutoRangeDescriptors, it should be used instead.
QDataSet depend0;
if ( SemanticOps.isBundle(fillDs) ) {
depend0= SemanticOps.xtagsDataSet(fillDs);
if ( spec==RenderType.colorScatter ) {
ydesc= AutoRangeUtil.autoRange( DataSetOps.unbundle(fillDs, 1 ), props, ignoreDsProps );
} else if ( spec==RenderType.scatter && fillDs.property(QDataSet.DEPEND_0)==null ) {
ydesc= AutoRangeUtil.autoRange( DataSetOps.unbundle(fillDs, 1 ), props, ignoreDsProps );
} else {
ydesc= AutoRangeUtil.autoRange( DataSetOps.unbundle(fillDs, fillDs.length(0)-1 ), props, ignoreDsProps );
Units u= ydesc.range.getUnits();
for ( int i=fillDs.length(0)-2; i>=0; i-- ) {
AutoRangeUtil.AutoRangeDescriptor ydesc1= AutoRangeUtil.autoRange( DataSetOps.unbundle(fillDs,i ), props, ignoreDsProps );
if ( ydesc1.range.getUnits().isConvertibleTo(u) ) { // Bx, By, Bz
if ( i==0 && fillDs.length(0)==2 ) {
// special case for T->X,Y where we are plotting X,Y, as in an orbit plot.
} else {
ydesc.range= DatumRangeUtil.union( ydesc.range, ydesc1.range );
}
} else {
Units u1;
u1= ydesc1.range.getUnits();
DatumRange r1= new DatumRange( ydesc1.range.min().doubleValue(u1), ydesc1.range.max().doubleValue(u1), u );
ydesc.range= DatumRangeUtil.union( ydesc.range, r1 );
}
}
}
} else {
ydesc = AutoRangeUtil.autoRange( fillDs, props, ignoreDsProps );
depend0 = (QDataSet) fillDs.property(QDataSet.DEPEND_0);
}
peleCopy.getPlotDefaults().getYaxis().setLog(ydesc.log);
peleCopy.getPlotDefaults().getYaxis().setRange(ydesc.range);
QDataSet xds= depend0;
if ( SemanticOps.isJoin(fillDs) && fillDs.length()>0 && fillDs.rank()==3 ) {
xds= (QDataSet) fillDs.slice(0).property(QDataSet.DEPEND_0);
if ( xds!=null ) {
for ( int i=1; i0 ) {
QDataSet yds1= (QDataSet)fillDs.property(QDataSet.DEPEND_1,0);
if ( yds1!=null ) {
JoinDataSet ds= new JoinDataSet(yds1.rank()+1);
for ( int i=0; i0
&& fillDs.property(QDataSet.DEPEND_0,0)!=null ) {
JoinDataSet ds= new JoinDataSet(2);
Units u= null;
for ( int i=0; i1 ) {
yds = DataSetUtil.indexGenDataSet(fillDs.length(0)); //TODO: QUBE assumed
} else {
yds = DataSetUtil.indexGenDataSet(10); // later the user will get a message "renderer cannot plot..."
}
}
xunits= SemanticOps.getUnits(xds);
yunits= SemanticOps.getUnits(yds);
zunits= SemanticOps.getUnits(fillDs);
} else if ( spec==RenderType.pitchAngleDistribution ) {
return true;
} else if ( spec==RenderType.polar ) {
return true;
} else if ( spec==RenderType.eventsBar ) {
return true;
} else if ( spec==RenderType.vectorPlot ) {
return true;
} else if ( spec==RenderType.orbitPlot ) {
return true;
} else if ( spec==RenderType.digital ) {
return true;
} else if ( spec==RenderType.internal ) { // we don't know what this is
return true;
} else {
QDataSet depend0;
if ( SemanticOps.isBundle(fillDs) ) {
depend0= (QDataSet) fillDs.property(QDataSet.DEPEND_0);
if ( depend0==null ) {
yunits= SemanticOps.getUnits( DataSetOps.unbundle(fillDs, 1 ) );
depend0= DataSetOps.unbundle(fillDs,0);
} else {
yunits= SemanticOps.getUnits( DataSetOps.unbundle(fillDs, 0 ) ); //TODO: why just the first?
}
} else {
yunits = SemanticOps.getUnits( fillDs );
depend0= (QDataSet) fillDs.property(QDataSet.DEPEND_0);
}
if ( fillDs.rank()==0 ) return true;
QDataSet xds= depend0;
if (xds == null) {
xds = DataSetUtil.indexGenDataSet(fillDs.length());
}
xunits= SemanticOps.getUnits(xds);
zunits= Units.dimensionless;
if (spec == RenderType.colorScatter) {
if ( fillDs.property(QDataSet.BUNDLE_1)!=null ) {
zunits= SemanticOps.getUnits( ( QDataSet) DataSetOps.unbundle( fillDs, 2 ) );
} else {
QDataSet plane0= (QDataSet) fillDs.property(QDataSet.PLANE_0);
if ( plane0!=null ) {
zunits= Units.dimensionless; // NOT SUPPORTED
} else {
logger.warning("expected color plane_0");
}
}
}
}
boolean change= false;
if ( xrange.getUnits()==Units.dimensionless && !UnitsUtil.isTimeLocation(xunits) && !UnitsUtil.isOrdinalMeasurement(xunits) && !xunits.isConvertibleTo( xrange.getUnits() ) ) {
plotElement.getPlotDefaults().getXaxis().setRange( new DatumRange( xrange.min().doubleValue(Units.dimensionless), xrange.max().doubleValue(Units.dimensionless), xunits ) );
change= true;
}
if ( yrange.getUnits()==Units.dimensionless && !UnitsUtil.isTimeLocation(yunits) && !UnitsUtil.isOrdinalMeasurement(yunits) && !yunits.isConvertibleTo( yrange.getUnits() ) ) {
plotElement.getPlotDefaults().getYaxis().setRange( new DatumRange( yrange.min().doubleValue(Units.dimensionless), yrange.max().doubleValue(Units.dimensionless), yunits ) );
change= true;
}
if ( zrange.getUnits()==Units.dimensionless && !UnitsUtil.isTimeLocation(zunits) && !UnitsUtil.isOrdinalMeasurement(zunits) && !zunits.isConvertibleTo( zrange.getUnits() ) ) {
plotElement.getPlotDefaults().getZaxis().setRange( new DatumRange( zrange.min().doubleValue(Units.dimensionless), zrange.max().doubleValue(Units.dimensionless), zunits ) );
change= true;
}
return change;
}
/**
* return the plot containing this plotElement's renderer, or null.
* @return the plot containing this plotElement's renderer, or null.
*/
public DasPlot getDasPlot() {
Plot p;
DomLock lock= this.mutatorLock();
lock.lock("getDasPlot");
try {
p= dom.controller.getPlotFor(plotElement);
} finally {
lock.unlock();
}
if ( p==null ) {
return null;
}
return p.controller.getDasPlot();
}
private DasColorBar getColorbar() {
Plot p;
DomLock lock= this.mutatorLock();
lock.lock("getColorbar");
try {
p= dom.controller.getPlotFor(plotElement);
} finally {
lock.unlock();
}
if ( p==null ) {
throw new IllegalArgumentException("no plot found for element ("+plotElement+","+plotElement.getPlotId()+")");
}
return p.controller.getDasColorBar();
}
/**
* return the data source and filter for this plotElement.
* @return
*/
public DataSourceFilter getDataSourceFilter() {
return dsf;
}
/**
* return the application that the plotElement belongs to.
* @return
*/
public Application getApplication() {
return dom;
}
/**
* copy style from renderType property to style node.
* @param ele
*/
private static void setupStyle( PlotElement ele ) {
PlotElementStyle s= ele.getStyle();
if ( null!=ele.getRenderType() ) switch (ele.getRenderType()) {
case colorScatter:
s.setPlotSymbol( DefaultPlotSymbol.CIRCLES );
s.setSymbolConnector(PsymConnector.NONE);
s.setFillToReference(false);
break;
case series:
s.setSymbolConnector(PsymConnector.SOLID);
int size= 0;
if ( ele.controller!=null ) { // kludge to turn off plot symbols for large datasets.
QDataSet processDataSet= ele.controller.processDataSet;
QDataSet dataSet= ele.controller.dataSet;
if ( processDataSet!=null ) {
size= processDataSet.length();
} else if ( dataSet!=null ) {
size= dataSet.rank()>0 ? dataSet.length() : 1;
}
} if ( size>SYMSIZE_DATAPOINT_COUNT ) {
s.setPlotSymbol( DefaultPlotSymbol.NONE );
} else {
s.setPlotSymbol( DefaultPlotSymbol.CIRCLES );
} s.setFillToReference(false);
break;
case scatter:
s.setSymbolConnector(PsymConnector.NONE);
s.setPlotSymbol(DefaultPlotSymbol.CIRCLES);
s.setFillToReference(false);
break;
case stairSteps:
s.setSymbolConnector(PsymConnector.SOLID);
s.setPlotSymbol(DefaultPlotSymbol.NONE);
s.setFillToReference(true);
break;
case fillToZero:
s.setSymbolConnector(PsymConnector.SOLID);
s.setFillToReference(true);
break;
case nnSpectrogram:
SpectrogramRenderer.RebinnerEnum r;
if ( "true".equals( System.getProperty("useLanlNearestNeighbor","false") ) ) {
r= SpectrogramRenderer.RebinnerEnum.lanlNearestNeighbor;
} else {
r= SpectrogramRenderer.RebinnerEnum.nearestNeighbor;
} s.setRebinMethod( r );
break;
case spectrogram:
s.setRebinMethod( SpectrogramRenderer.RebinnerEnum.binAverage );
break;
default:
break;
}
}
/**
* see button added to the slicer.
* @param ds
* @param y reference for dataset, similar to the CONTEXT property.
* @param above if true, then plot above instead of below.
*/
private void addPlotBelow( QDataSet ds, Datum y, boolean above) {
ApplicationController controller= dom.getController();
DomLock lock= mutatorLock();
lock.lock("adding slice below");
try {
Plot focus= controller.getPlotFor(plotElement);
Plot p= controller.addPlot( focus, above ? LayoutConstants.ABOVE : LayoutConstants.BELOW );
PlotElement pe=controller.addPlotElement( p, null );
DataSourceFilter dsfl= controller.getDataSourceFilterFor( pe );
dsfl.getController().setDataSetInternal(ds); // setDataSet doesn't autorange, etc.
p.getYaxis().syncTo(focus.zaxis);
List bms= controller.findBindings( dom, Application.PROP_TIMERANGE, focus.getXaxis(), Axis.PROP_RANGE );
if ( !bms.isEmpty() && UnitsUtil.isTimeLocation( p.getXaxis().getRange().getUnits() ) ) {
controller.bind( controller.getApplication(), Application.PROP_TIMERANGE, p.getXaxis(), Axis.PROP_RANGE );
}
p.setTitle(focus.getTitle() + " @ " + y );
} finally {
lock.unlock();
}
}
/**
* reset the autoRebinner property.
*/
PropertyChangeListener rebinnerListener= new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
LoggerManager.logPropertyChangeEvent(evt,"rebinnerListener");
if ( !PlotElementController.this.isValueAdjusting() ) {
plotElement.setAutoRenderType(false); // https://sourceforge.net/p/autoplot/bugs/1217/
}
}
};
/**
* create the peer that will actually do the painting. This may be called from either the event thread or off the event thread,
* but work will be done on the event thread in either case using SwingUtilities.invokeAndWait.
*
* preconditions: the new render type is identified. The plotElement may already contain a corresponding renderer for this or
* another type.
* postconditions: The correct renderer is installed in the plot.
*/
protected void maybeCreateDasPeer(){
if ( changesSupport.isPendingChanges( PENDING_CREATE_DAS_PEER ) ) {
logger.warning("someone else is also changing the peer.");
}
changesSupport.performingChange(this, PENDING_CREATE_DAS_PEER );
final Renderer oldRenderer = getRenderer();
Window p= null;
if ( !( "true".equals( System.getProperty("java.awt.headless") ) ) ) {
p= SwingUtilities.getWindowAncestor( plotElement.controller.getDasPlot().getCanvas() );
}
final Window parent= p;
//logger.fine( "oldRenderer= "+oldRenderer + " plotElementController="+ this + " ("+this.hashCode()+")" + " " + Thread.currentThread().getName() );
DasColorBar cb= null;
if ( RenderTypeUtil.needsColorbar(plotElement.getRenderType()) ) cb= getColorbar();
setupStyle( plotElement );
RenderType renderType = plotElement.getRenderType();
if ( getApplication().getOptions().nearestNeighbor && renderType==RenderType.spectrogram ) {
renderType= RenderType.nnSpectrogram;
}
final Renderer newRenderer =
AutoplotUtil.maybeCreateRenderer( renderType,
oldRenderer, cb, false );
if ( newRenderer!=oldRenderer && newRenderer instanceof SpectrogramRenderer ) {
((SpectrogramRenderer)newRenderer).setSliceRebinnedData( dom.getOptions().isSliceRebinnedData() );
newRenderer.addPropertyChangeListener( SpectrogramRenderer.PROP_REBINNER, rebinnerListener );
}
if ( newRenderer!=oldRenderer && oldRenderer instanceof SpectrogramRenderer ) {
oldRenderer.addPropertyChangeListener( SpectrogramRenderer.PROP_REBINNER, rebinnerListener );
}
if ( cb!=null
&& !dom.getController().isValueAdjusting()
&& RenderTypeUtil.needsColorbar(plotElement.getRenderType()) ) cb.setVisible( true );
if (oldRenderer != newRenderer || getDasPlot()!=newRenderer.getParent() ) {
if ( oldRenderer != newRenderer ) {
if ( newRenderer instanceof SpectrogramRenderer ) {
plotElement.getStyle().setRebinMethod( ((SpectrogramRenderer) newRenderer).getRebinner() );
((SpectrogramRenderer)newRenderer).getRebinner();
}
setRenderer(newRenderer);
//logger.fine( "getRenderer= "+getRenderer() + " plotElementController="+ this + " ("+this.hashCode()+")" );
if ( oldRenderer!=null ) {
oldRenderer.setActive(false);
oldRenderer.setColorBar(null);
}
if ( newRenderer.getColorBar()==cb && cb!=null && cb.isVisible() ) {
DasCanvas c= getDasPlot().getCanvas();
DasCanvasComponent[] dcc=c.getCanvasComponents();
boolean hasColorbarInstalled= false;
for ( DasCanvasComponent dc: dcc ) {
if ( dc==cb ) {
hasColorbarInstalled=true;
}
}
if ( !hasColorbarInstalled ) {
c.add( cb, cb.getRow(), cb.getColumn() );
}
}
}
Runnable run = new Runnable() {
@Override
public void run() {
try {
DasPlot plot = getDasPlot();
if ( plot==null ) {
System.err.println("pec2326: brace yourself for crash, plot is null!");
plot = getDasPlot(); // for debugging Spectrogram->Series
//if ( oldRenderer==null && dom.controller.isValueAdjusting() ) { // I think this is an undo, and the plot has already been deleted.
//}
if ( plot==null ) {
throw new IllegalStateException("getDasPlot() result was null.");
}
}
DasPlot oldPlot=null;
if (oldRenderer != null) {
oldPlot= oldRenderer.getParent();
if ( oldPlot!=null && oldPlot!=getDasPlot() ) oldRenderer.getParent().removeRenderer(oldRenderer);
if ( oldRenderer!=newRenderer ) plot.removeRenderer(oldRenderer);
}
if ( oldPlot==null || oldRenderer!=newRenderer ) {
synchronized ( dom ) {
if ( false && newRenderer instanceof SpectrogramRenderer ) { // https://sourceforge.net/p/autoplot/bugs/2013/
plot.addRenderer(0,newRenderer);
setUpSpectrogramActions(plot);
} else {
Renderer[] rends= plot.getRenderers();
int best=-1;
int myPos= -1;
for ( int i=0; i arends= Arrays.asList(rends);
Renderer lastRend= null;
int i;
for ( i=0; ibest && i {
addPlotBelow(ds,y,above);
});
}
});
DataSource dss= new AnonymousDataSource() {
@Override
public QDataSet getDataSet(ProgressMonitor mon) throws Exception {
return hmm.getSlicer().getDataSet();
}
};
hmm.getSlicer().addAction( ExportDataPanel.createExportDataAction( parent, dss ) );
}
mm= plot.getDasMouseInputAdapter().getModuleByLabel("Vertical Slice");
final VerticalSlicerMouseModule vmm= ((VerticalSlicerMouseModule)mm);
if ( vmm!=null ) { // for example in headless mode
DataSource dss= new AnonymousDataSource() {
@Override
public QDataSet getDataSet(ProgressMonitor mon) throws Exception {
return vmm.getSlicer().getDataSet();
}
};
vmm.getSlicer().addAction( ExportDataPanel.createExportDataAction( parent, dss ) );
}
mm= plot.getDasMouseInputAdapter().getModuleByLabel("Interval Average");
final HorizontalDragRangeSelectorMouseModule vsa= ((HorizontalDragRangeSelectorMouseModule)mm);
if ( vsa!=null ) { // for example in headless mode
if ( vsa.getDataRangeSelectionListenerCount()>0 ) {
DataRangeSelectionListener ddr= vsa.getDataRangeSelectionListener(0);
if ( ddr instanceof VerticalSpectrogramAverager ) {
DataSource dss= new AnonymousDataSource() {
@Override
public QDataSet getDataSet(ProgressMonitor mon) throws Exception {
return ((VerticalSpectrogramAverager)ddr).getDataSet();
}
};
((VerticalSpectrogramAverager)ddr).addAction( ExportDataPanel.createExportDataAction( parent, dss ) ); //TODO
}
}
}
}
};
if ( SwingUtilities.isEventDispatchThread() ) {
run.run();
} else {
try {
SwingUtilities.invokeAndWait(run);
} catch (InterruptedException | InvocationTargetException ex) {
logger.log(Level.SEVERE, ex.getMessage(), ex);
}
}
//if (getDataSourceFilter().controller.getFillDataSet() != null) {
// this is danger code, I think inserted to support changing render type.
// when we change renderType on vector dataset, this is called.
//setDataSet( getDataSourceFilter().controller.getFillDataSet(), false );
//}
} else {
// no changes needed.
changesSupport.changePerformed( PlotElementController.this, PENDING_CREATE_DAS_PEER );
}
}
/**
* just reset this plot element's rendertype, ignoring parent.
* @param renderType
*/
private void doResetRenderTypeInt( RenderType renderType ) {
DomLock lock= this.mutatorLock();
lock.lock("Reset Render Rype");
try {
plotElement.propertyChangeSupport.firePropertyChange( PlotElement.PROP_RENDERTYPE, null, renderType );
} finally {
lock.unlock();
}
//Renderer oldRenderer= getRenderer();
maybeCreateDasPeer();
//if ( getRenderer()!=null && getRenderer()!=oldRenderer ) {
//QDataSet oldDs= getDataSet(); // TODO: this needs review. There was a comment about slices, but this works fine. Old code
//if ( oldDs!=null ) {
//bug1355: This should not be done, I think.
//getRenderer().setDataSet(oldDs);
//}
//}
}
/**
* used to explicitly set the rendering type. This installs a das2 renderer
* into the plot to implement the render type.
*
* preconditions:
* renderer type has been identified.
* postconditions:
* das2 renderer peer is created and bindings made.
* @param renderType
*/
public void doResetRenderType(RenderType renderType) {
PlotElement parentPele= getParentPlotElement();
if ( parentPele != null ) {
parentPele.setRenderType(renderType);
return;
}
for ( PlotElement ch: getChildPlotElements() ) {
Renderer oldRenderer= ch.getController().getRenderer();
ch.renderType= renderType; // we don't want to enter doResetRenderType.
ch.getController().maybeCreateDasPeer();
if ( oldRenderer!=ch.getController().getRenderer() ) {
QDataSet oldDs= oldRenderer==null ? null : oldRenderer.getDataSet();
ch.getController().getRenderer().setDataSet(oldDs);
}
}
doResetRenderTypeInt(renderType);
}
public void bindToSeriesRenderer(SeriesRenderer seriesRenderer) {
ApplicationController ac = this.dom.controller;
ac.bind(plotElement.style, PlotElementStyle.PROP_LINE_WIDTH, seriesRenderer, "lineWidth");
ac.bind(plotElement.style, PlotElementStyle.PROP_COLOR, seriesRenderer, Renderer.CONTROL_KEY_COLOR );
ac.bind(plotElement.style, PlotElementStyle.PROP_SYMBOL_SIZE, seriesRenderer, "symSize");
ac.bind(plotElement.style, PlotElementStyle.PROP_SYMBOL_CONNECTOR, seriesRenderer, "psymConnector");
ac.bind(plotElement.style, PlotElementStyle.PROP_PLOT_SYMBOL, seriesRenderer, "psym");
ac.bind(plotElement.style, PlotElementStyle.PROP_FILLCOLOR, seriesRenderer, Renderer.CONTROL_KEY_FILL_COLOR );
ac.bind(plotElement.style, PlotElementStyle.PROP_FILL_TO_REFERENCE, seriesRenderer, "fillToReference");
ac.bind(plotElement.style, PlotElementStyle.PROP_REFERENCE, seriesRenderer, "reference");
ac.bind(plotElement.style, PlotElementStyle.PROP_FILL_DIRECTION, seriesRenderer, Renderer.CONTROL_KEY_FILL_DIRECTION );
ac.bind(plotElement.style, PlotElementStyle.PROP_SHOWLIMITS, seriesRenderer, SeriesRenderer.PROP_SHOWLIMITS );
ac.bind(plotElement.style, PlotElementStyle.PROP_DRAWERROR, seriesRenderer, Renderer.CONTROL_KEY_DRAW_ERROR );
ac.bind(plotElement.style, PlotElementStyle.PROP_ERRORBARTYPE, seriesRenderer, SeriesRenderer.PROP_ERRORBARTYPE );
ac.bind(plotElement.style, PlotElementStyle.PROP_ANTIALIASED, seriesRenderer, "antiAliased");
ac.bind(plotElement, PlotElement.PROP_CADENCECHECK, seriesRenderer, "cadenceCheck");
if ( seriesRenderer.getColorBar()!=null )
ac.bind(plotElement.style, PlotElementStyle.PROP_COLORTABLE, seriesRenderer.getColorBar(), "type");
}
public void bindToSpectrogramRenderer(SpectrogramRenderer spectrogramRenderer) {
ApplicationController ac = this.dom.controller;
ac.bind(plotElement.style, "rebinMethod", spectrogramRenderer, "rebinner");
ac.bind(plotElement, PlotElement.PROP_CADENCECHECK, spectrogramRenderer, "cadenceCheck");
if ( spectrogramRenderer.getColorBar()!=null ) {
ac.bind(plotElement.style, "colortable", spectrogramRenderer.getColorBar(), "type");
}
}
public void bindToImageVectorDataSetRenderer(HugeScatterRenderer renderer) {
ApplicationController ac = this.dom.controller;
ac.bind(plotElement.style, "color", renderer, "color");
}
public void bindToEventsRenderer(EventsRenderer renderer) {
ApplicationController ac = this.dom.controller;
ac.bind(plotElement.style, "color", renderer, "color");
}
public void bindToDigitalRenderer(DigitalRenderer renderer) {
ApplicationController ac = this.dom.controller;
ac.bind(plotElement.style, "color", renderer, "color");
}
public void bindToPolarPlotRenderer(PolarPlotRenderer renderer) {
ApplicationController ac = this.dom.controller;
ac.bind(plotElement.style, "color", renderer, "color");
ac.bind(plotElement.style, "lineWidth", renderer, "lineWidth");
}
public void bindToTickCurveRenderer( TickCurveRenderer renderer) {
ApplicationController ac = this.dom.controller;
ac.bind(plotElement.style, "color", renderer, "color");
ac.bind(plotElement.style, "lineWidth", renderer, "lineWidth");
}
public void bindToContoursRenderer(Renderer renderer) {
ApplicationController ac = this.dom.controller;
ac.bind(plotElement.style, "color", renderer, "color");
ac.bind(plotElement.style, "lineWidth", renderer, "lineThick");
}
private void bindToBoundsRenderer(BoundsRenderer renderer) {
ApplicationController ac = this.dom.controller;
ac.bind(plotElement.style, "color", renderer, "color");
}
/**
* special converter that fills in %{CONTEXT} macro, or inserts it when
* label is consistent with macro. Also now does %{COMPONENT}. Note
* this won't do both right now.
* @return JavaBeans property converter.
*/
private Converter getLabelConverter() {
LabelConverter r= new LabelConverter( dom, null, null, plotElement, null );
return r;
}
@Override
public boolean isPendingChanges() {
DataSourceFilter ldsf= getDataSourceFilter();
if ( ldsf!=null ) {
return ldsf.controller.isPendingChanges() || super.isPendingChanges();
} else {
return super.isPendingChanges();
}
}
@Override
public void pendingChanges(Map