package org.das2.graph;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import javax.swing.Icon;
import org.das2.dataset.DataSetDescriptor;
import org.das2.dataset.RebinDescriptor;
import org.das2.qds.DataSetUtil;
import org.das2.datum.DatumRange;
import org.das2.datum.Units;
import org.das2.datum.Datum;
import org.das2.DasException;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ImageIcon;
import org.das2.dataset.NoDataInIntervalException;
import org.das2.datum.InconvertibleUnitsException;
import org.das2.datum.UnitsConverter;
import org.das2.datum.UnitsUtil;
import org.das2.util.LoggerManager;
import org.das2.qds.ArrayDataSet;
import org.das2.qds.DDataSet;
import org.das2.qds.DataSetOps;
import org.das2.qds.FDataSet;
import org.das2.qds.JoinDataSet;
import org.das2.qds.QDataSet;
import org.das2.qds.RankZeroDataSet;
import org.das2.qds.SemanticOps;
import org.das2.qds.WeightsDataSet;
import org.das2.qds.WritableDataSet;
import org.das2.qds.ops.Ops;
/**
* HugeScatterRenderer
*
* This renderer can handle vector data sets with hundreds of thousands of points
* by histogramming the points and then creating a greyscale spectrogram of
* the histogram. The property "saturationHitCount" defines the number of pixel
* hits that will make the pixel black.
*
* This has been modified a lot over the years:
* - connecting lines when the data is of timeseries,
*
- alternate modes are used when we zoom in closely,
*
- support for QDataSets and waveform scheme data,
*
- lone pixels are highlighted.
*
*
* Created on April 14, 2005, 8:45 PM
* @author Jeremy
*/
public class HugeScatterRenderer extends Renderer {
protected static final Logger logger= LoggerManager.getLogger("das2.graphics.renderer.hugeScatter");
BufferedImage plotImage;
Rectangle plotImageBounds;
DatumRange imageXRange;
private Color color = Color.BLACK;
/** pixels, limit of x increment before line break */
private int ixstepLimitSq=1000000;
/**
* cache the data cadence, which is expensive to calculate.
*/
private Datum xcadence;
/**
* pixels, limit of x increment before line break
*/
private Shape selectionArea;
public HugeScatterRenderer(DataSetDescriptor dsd) {
super(dsd);
}
@Override
public Icon getListIcon() {
BufferedImage i = new BufferedImage(15, 10, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = (Graphics2D) i.getGraphics();
DasPlot parent= getParent();
g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
if ( parent!=null ) g.setBackground(parent.getBackground());
// leave transparent if not white
if (color.equals(Color.white)) {
g.setColor(Color.GRAY);
} else {
g.setColor(new Color(0, 0, 0, 0));
}
g.fillRect(0, 0, 15, 10);
g.setColor(color);
Stroke stroke0 = g.getStroke();
g.setStroke( new BasicStroke( 0.5f ) );
g.drawLine( 2, 3, 13, 7 );
g.setStroke(stroke0);
i.setRGB( 7, 5, color.getRGB() );
return new ImageIcon(i);
}
//TODO: other Renderers use similar code and probably don't handle all-fill properly.
private static QDataSet doRange( QDataSet xds ) {
QDataSet xrange= Ops.extent(xds);
if ( xrange.value(1)==xrange.value(0) ) {
QDataSet wds= WeightsDataSet.applyRules( xds,xrange );
if ( wds.value(0)*wds.value(1) ==0 ) {
xrange= DDataSet.createRank1Bins( 0, 10, SemanticOps.getUnits(xrange) );
return xrange;
} else {
if ( !"log".equals( xrange.property(QDataSet.SCALE_TYPE)) ) {
Units xunits= SemanticOps.getUnits(xrange);
if ( UnitsUtil.isTimeLocation(xunits) ) {
Datum dx= Units.nanoseconds.createDatum(1000).convertTo(xunits.getOffsetUnits());
xrange= DDataSet.createRank1Bins( xrange.value(0)-dx.value(),xrange.value(1)+dx.value(), xunits);
} else {
xrange= DDataSet.createRank1Bins( xrange.value(0)-1, xrange.value(1)+1, xunits );
}
} else {
xrange= DDataSet.createRank1Bins( xrange.value(0)/10, xrange.value(1)*10, SemanticOps.getUnits(xrange) );
}
}
}
boolean isLog= "log".equals( xrange.property(QDataSet.SCALE_TYPE));
if ( isLog && xrange.value(0)==0 ) {
//xrange= Ops.rescaleRangeLogLin( xrange, -0.1, 1.1 );
//xrange= Ops.rescaleRange( xrange, 0, 1.1 );
} else {
xrange= Ops.rescaleRangeLogLin( xrange, -0.1, 1.1 );
}
return xrange;
}
@Override
public void setDataSet(QDataSet ds) {
this.xcadence= null;
super.setDataSet(ds);
}
private static QDataSet fastRank2Range( QDataSet ds ) {
double min= Double.POSITIVE_INFINITY;
double max= Double.NEGATIVE_INFINITY;
QDataSet wds= DataSetUtil.weightsDataSet(ds);
for ( int i=0; i0 ) {
double d= ds.value(i,j);
min= d < min ? d : min;
max= d > max ? d : max;
}
}
}
Units u= SemanticOps.getUnits(ds);
DDataSet result= DDataSet.createRank1(2);
result.putValue( 0, min );
result.putValue( 1, max );
result.putProperty( QDataSet.UNITS, u );
return Ops.rescaleRangeLogLin( result, -0.1, 1.1 );
}
public static QDataSet doAutorange( QDataSet ds ) {
QDataSet xrange;
QDataSet yrange;
QDataSet xds= SemanticOps.xtagsDataSet(ds);
if ( SemanticOps.isRank2Waveform(ds) ) {
yrange= fastRank2Range(ds);
QDataSet offsrange= doRange( (QDataSet) ds.property(QDataSet.DEPEND_1) );
xrange= doRange( xds );
xrange= Ops.add( xrange, offsrange );
} else if ( ds.rank()==2 && SemanticOps.isBundle(ds) ) {
QDataSet vds= DataSetOps.unbundleDefaultDataSet( ds );
yrange= doRange( vds );
xrange= doRange( xds );
} else {
yrange= doRange( ds );
xrange= doRange( xds );
}
JoinDataSet bds= new JoinDataSet(2);
bds.join(xrange);
bds.join(yrange);
return bds;
}
@Override
public void render(java.awt.Graphics2D g1, DasAxis xAxis, DasAxis yAxis ) {
// logger.entering is just past this check.
DasPlot parent= getParent();
if ( ds==null ) {
if (lastException != null) {
if (lastException instanceof NoDataInIntervalException) {
parent.postMessage(this, "no data in interval:!c" + lastException.getMessage(), DasPlot.WARNING, null, null);
} else {
parent.postException(this, lastException);
}
return;
} else {
parent.postMessage(this, "no data set", DasPlot.INFO, null, null);
return;
}
}
BufferedImage localPlotImage;
synchronized (this) {
localPlotImage= this.plotImage;
}
logger.entering( "org.das2.graph.HugeScatterRenderer", "render");
QDataSet xds = SemanticOps.xtagsDataSet(ds);
Units yunits;
if ( ds.rank()==2 && SemanticOps.isBundle(ds) ) {
QDataSet vds = DataSetOps.unbundleDefaultDataSet( ds );
yunits= SemanticOps.getUnits( vds );
} else {
QDataSet vds = ds;
yunits= SemanticOps.getUnits( vds );
}
if ( !xAxis.getUnits().isConvertibleTo( SemanticOps.getUnits((QDataSet) xds) ) ) {
parent.postMessage(this, "inconvertible xaxis units", DasPlot.INFO, null, null);
}
if ( !yAxis.getUnits().isConvertibleTo( yunits ) ) {
parent.postMessage(this, "inconvertible yaxis units", DasPlot.INFO, null, null);
}
Graphics2D g2 = (Graphics2D) g1;
if (localPlotImage == null) {
if (lastException != null) {
if (lastException instanceof NoDataInIntervalException) {
parent.postMessage(this, "no data in interval:!c" + lastException.getMessage(), DasPlot.WARNING, null, null);
} else {
parent.postException(this, lastException);
}
} else {
if (getDataSet() == null) {
parent.postMessage(this, "no data set", DasPlot.INFO, null, null);
} else if (getDataSet().length() == 0) {
parent.postMessage(this, "empty data set", DasPlot.INFO, null, null);
}
}
} else {
Point2D p;
p = new Point2D.Float(plotImageBounds.x, plotImageBounds.y);
int x = (int) (p.getX());
int y = (int) (p.getY());
if (parent.getCanvas().isPrintingThread() && print300dpi) {
AffineTransformOp atop = new AffineTransformOp(AffineTransform.getScaleInstance(4, 4), AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
BufferedImage image300 = atop.filter((BufferedImage) localPlotImage, null);
AffineTransform atinv;
try {
atinv = atop.getTransform().createInverse();
} catch (NoninvertibleTransformException ex) {
throw new RuntimeException(ex);
}
atinv.translate(x * 4, y * 4);
g2.drawImage(image300, atinv, getParent());
} else {
g2.drawImage(localPlotImage, x, y, getParent());
}
}
logger.exiting( "org.das2.graph.HugeScatterRenderer", "render");
}
/**
* render each point with a 1-pixel dot and line connecting.
* @param xAxis the xAxis
* @param yAxis the yAxis
* @param ds rank 2 bundle or rank 1 dataset.
* @param plotImageBounds2 the bounds
*/
private void renderPointsOfRank1(DasAxis xAxis, DasAxis yAxis, QDataSet ds, Rectangle plotImageBounds2) {
int ny = plotImageBounds2.height;
int nx = plotImageBounds2.width;
logger.entering( "org.das2.graph.HugeScatterRenderer", "renderPointsOfRank1");
BufferedImage image = new BufferedImage(nx, ny, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = (Graphics2D) image.getGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(color);
g.setStroke( new BasicStroke( 1 ) );
g.translate( -plotImageBounds2.x, -plotImageBounds2.y);
imageXRange= GraphUtil.invTransformRange( xAxis, plotImageBounds2.x, plotImageBounds2.x+plotImageBounds2.width );
DatumRange visibleRange = imageXRange;
QDataSet xds = SemanticOps.xtagsDataSet(ds);
QDataSet vds;
if ( ds.rank()==2 && SemanticOps.isBundle(ds) ) {
vds= DataSetOps.unbundleDefaultDataSet( ds );
} else {
vds= ds;
}
boolean xmono = Boolean.TRUE == SemanticOps.isMonotonic(xds);
int firstIndex = xmono ? DataSetUtil.getPreviousIndex(xds, visibleRange.min()) : 0;
int lastIndex = xmono ? ( DataSetUtil.getNextIndex(xds, visibleRange.max()) + 1 ) : xds.length();
final int STATE_LINETO = -991;
final int STATE_MOVETO = -992;
int state = STATE_MOVETO;
int ix0 = 0, iy0 = 0;
if (vds.length() > 0) {
QDataSet wds = DataSetUtil.weightsDataSet(vds);
Units dsunits= SemanticOps.getUnits(vds);
Units xunits= SemanticOps.getUnits(xds);
if ( !dsunits.isConvertibleTo(yAxis.getUnits()) ) {
dsunits= yAxis.getUnits();
}
if ( !xunits.isConvertibleTo(xAxis.getUnits() ) ) {
xunits= xAxis.getUnits();
}
for (int i = firstIndex; i < lastIndex; i++) {
boolean isValid = wds.value(i)>0;
if (!isValid) {
state = STATE_MOVETO;
} else {
int iy = (int) yAxis.transform( vds.value(i), dsunits );
int ix = (int) xAxis.transform( xds.value(i), xunits );
if ( (ix-ix0)*(ix-ix0) > ixstepLimitSq ) state=STATE_MOVETO;
switch (state) {
case STATE_MOVETO:
g.fillRect(ix, iy, 1, 1);
ix0 = ix;
iy0 = iy;
break;
case STATE_LINETO:
g.draw(new Line2D.Float(ix0, iy0, ix, iy));
g.fillRect(ix, iy, 1, 1);
ix0 = ix;
iy0 = iy;
break;
default:
logger.log(Level.INFO, "state: {0}", state);
}
state = STATE_LINETO;
}
}
}
logger.exiting( "org.das2.graph.HugeScatterRenderer", "renderPointsOfRank1");
synchronized (this) {
plotImage = image;
selectionArea= null;
}
}
/**
* Super-fast implementation for rank 2 waveform dataset, where DEPEND_1 is the offset from DEPEND_0.
* Each point is painted, so this assumes this can be done quickly.
*
* @param xAxis the x-axis
* @param yAxis the y-axis
* @param ds a rank 2 waveform dataset
* @param plotImageBounds2 the boundaries.
*/
private void renderPointsOfRank2Waveform( BufferedImage image, DasAxis xAxis, DasAxis yAxis, QDataSet ds, Rectangle plotImageBounds2) {
logger.entering( "org.das2.graph.HugeScatterRenderer", "renderPointsOfRank2Waveform");
Graphics2D g = (Graphics2D) image.getGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(color);
g.setStroke(new BasicStroke(1.f / saturationHitCount));
g.translate( -plotImageBounds2.x, -plotImageBounds2.y);
imageXRange= GraphUtil.invTransformRange( xAxis, plotImageBounds2.x, plotImageBounds2.x+plotImageBounds2.width );
DatumRange visibleRange = imageXRange;
QDataSet xds = SemanticOps.xtagsDataSet(ds);
boolean xmono = Boolean.TRUE == SemanticOps.isMonotonic(xds);
int firstIndex = xmono ? DataSetUtil.getPreviousIndex(xds, visibleRange.min()) : 0;
int lastIndex = xmono ? ( DataSetUtil.getNextIndex(xds, visibleRange.max()) + 1 ) : xds.length();
final int STATE_LINETO = -991;
final int STATE_MOVETO = -992;
int state = STATE_MOVETO;
int ix0 = 0, iy0 = 0;
if (xds.length() > 0) {
QDataSet wds = DataSetUtil.weightsDataSet(ds);
Units dsunits= SemanticOps.getUnits(ds);
Units xunits= SemanticOps.getUnits(xds);
if ( !dsunits.isConvertibleTo(yAxis.getUnits()) ) {
dsunits= yAxis.getUnits();
}
if ( !xunits.isConvertibleTo(xAxis.getUnits() ) ) {
xunits= xAxis.getUnits();
}
ArrayDataSet xoffsets= ArrayDataSet.copy( ( QDataSet) ds.property(QDataSet.DEPEND_1) );
if ( !UnitsUtil.isTimeLocation( SemanticOps.getUnits(xoffsets) ) ) {
xoffsets= resetUnits( xoffsets, SemanticOps.getUnits(xds).getOffsetUnits() );
} else {
xds= Ops.zeros( xoffsets.length() );
}
int xmin= xAxis.getColumn().getDMinimum()-xAxis.getColumn().getWidth();
int xmax= xAxis.getColumn().getDMaximum()+xAxis.getColumn().getWidth();
int xdmin= xAxis.getColumn().getDMinimum();
int xdmax= xAxis.getColumn().getDMaximum();
int ydmin= yAxis.getRow().getDMinimum();
int ydmax= yAxis.getRow().getDMaximum();
for (int i = firstIndex; i < lastIndex; i++) {
int nj= ds.length(i);
int xoffsetsRank= xoffsets.rank();
for ( int j=0; j0;
if (!isValid) {
state = STATE_MOVETO;
} else {
int iy = (int) yAxis.transform( ds.value(i,j), dsunits, ydmax, ydmin );
int ix;
if ( xoffsetsRank==1 ) {
ix= (int) xAxis.transform( xds.value(i) + xoffsets.value(j), xunits, xdmin, xdmax );
} else {
ix= (int) xAxis.transform( xds.value(i) + xoffsets.value(i,j), xunits, xdmin, xdmax );
}
if ( (ix-ix0)*(ix-ix0) > ixstepLimitSq ) state=STATE_MOVETO;
switch (state) {
case STATE_MOVETO:
if ( ix>xmin && ixxmin && ix0 ) {
fds.putValue( i,j,saturationHitCount );
}
}
}
logger.exiting( "org.das2.graph.HugeScatterRenderer", "darkenHistogram");
}
private FDataSet histogram( FDataSet tds, RebinDescriptor ddx, RebinDescriptor ddy, QDataSet ds, int firstIndex, int lastIndex ) {
logger.entering( "org.das2.graph.HugeScatterRenderer", "histogram");
ddx.setOutOfBoundsAction(RebinDescriptor.MINUSONE);
ddy.setOutOfBoundsAction(RebinDescriptor.MINUSONE);
if (ds.length() > 0) {
QDataSet xds = SemanticOps.xtagsDataSet(ds);
QDataSet vds;
ArrayDataSet xoffsets= null;
boolean isWaveform= SemanticOps.isRank2Waveform(ds);
if ( ds.rank()==2 && SemanticOps.isBundle(ds) && !isWaveform ) {
vds = DataSetOps.unbundleDefaultDataSet( ds );
} else if ( isWaveform ) {
vds= ds;
xds = SemanticOps.xtagsDataSet(ds);
isWaveform= true;
} else {
vds = (QDataSet) ds;
}
Units xunits = SemanticOps.getUnits(xds);
Units yunits = SemanticOps.getUnits(vds);
if ( !yunits.isConvertibleTo(ddy.getUnits()) ) {
yunits= ddy.getUnits();
}
if ( !xunits.isConvertibleTo(ddx.getUnits() ) ) {
xunits= ddx.getUnits();
}
int i = firstIndex;
int n = lastIndex;
int nj= isWaveform ? vds.length(i) : 1;
if ( isWaveform ) {
histogramRank2Waveform( ddx, i, n, nj, ddy, vds, yunits, tds);
} else {
Units targetXUnits= ddx.getUnits();
UnitsConverter xuc= xunits.getConverter(targetXUnits);
Units targetYUnits= ddy.getUnits();
UnitsConverter yuc= yunits.getConverter(targetYUnits);
Number ovmin= (Number)vds.property(QDataSet.VALID_MIN);
Number ovmax= (Number)vds.property(QDataSet.VALID_MAX);
Number ofill= (Number)vds.property(QDataSet.FILL_VALUE);
double vmax= ovmax==null ? Double.MAX_VALUE : ovmax.doubleValue();
double vmin= ovmin==null ? -Double.MAX_VALUE : ovmin.doubleValue();
double vfill= ofill==null ? Double.MAX_VALUE : ofill.doubleValue();
if ( xuc==UnitsConverter.IDENTITY && yuc==UnitsConverter.IDENTITY ) {
for (; i <= n; i++) {
double v= vds.value(i);
boolean isNotValid = v == vfill || Double.isNaN(v) || v > vmax || v < vmin;
if ( isNotValid ) {
} else {
int ix = ddx.whichBin( xds.value(i), targetXUnits);
int iy = ddy.whichBin( v, targetYUnits);
if (ix != -1 && iy != -1) {
tds.addValue( ix, iy, 1. );
}
}
}
} else {
for (; i <= n; i++) {
double v= vds.value(i);
boolean isNotValid = v == vfill || Double.isNaN(v) || v > vmax || v < vmin;
if ( isNotValid ) {
} else {
int ix = ddx.whichBin( xuc.convert( xds.value(i) ), targetXUnits);
int iy = ddy.whichBin( yuc.convert( v ), targetYUnits);
if (ix != -1 && iy != -1) {
tds.addValue( ix, iy, 1. );
}
}
}
}
}
}
logger.exiting("org.das2.graph.HugeScatterRenderer", "histogram");
return tds;
}
private static void histogramRank2Waveform( RebinDescriptor ddx, int first0, int last0, int nj, RebinDescriptor ddy, QDataSet vds, Units yunits, FDataSet tds) throws IllegalArgumentException {
logger.entering("HugeScatterRenderer", "histogramRank2Waveform");
QDataSet xds= (QDataSet) vds.property( QDataSet.DEPEND_0 );
Units xunits= SemanticOps.getUnits( xds );
QDataSet wds= SemanticOps.weightsDataSet( vds );
ArrayDataSet xoffsets = ArrayDataSet.copy((QDataSet) vds.property(QDataSet.DEPEND_1));
final UnitsConverter uc;
boolean oneRecPerPixelColumn;
if ( UnitsUtil.isTimeLocation( SemanticOps.getUnits(xoffsets) ) ) {
int ix0;
double dx0= xoffsets.rank()==1 ? xoffsets.value(0) : xoffsets.value(0,0);
ix0= ddx.whichBin( dx0, xunits );
int ix1;
int lastRec= xoffsets.length()-1;
double dx1= xoffsets.rank()==1 ? xoffsets.value(lastRec) : xoffsets.value(lastRec,xoffsets.length(lastRec)-1);
ix1= ddx.whichBin( dx1, xunits );
if ( ix0==-1 && first0+10;
if ( isValid ) {
int iy = ddy.whichBin( vds.value(first0,j), yunits );
if (iy != -1) {
double d = tds.value(ix, iy);
tds.putValue( ix, iy, d+1 );
//tds.addValue( ix, iy, 1 ); this should be faster
}
}
}
}
}
} else {
Units targetXUnits= ddx.getUnits();
UnitsConverter xuc= xunits.getConverter(targetXUnits);
Units targetYUnits= ddy.getUnits();
UnitsConverter yuc= yunits.getConverter(targetYUnits);
final int xoffsetsRank= xoffsets.rank();
for (; first0 <= last0; first0++) {
for ( int j=0; j0;
if ( isValid ) {
int ix,iy;
if ( xoffsetsRank==1 ) {
ix= ddx.whichBin(xuc.convert(xds.value(first0) + xoffsets.value(j) ), targetXUnits );
iy = ddy.whichBin( yuc.convert( vds.value(first0,j) ), targetYUnits );
} else {
ix= ddx.whichBin(xuc.convert(xds.value(first0) + xoffsets.value(first0,j) ), targetXUnits );
iy = ddy.whichBin( yuc.convert( vds.value(first0,j) ), targetYUnits );
}
if (ix != -1 && iy != -1) {
double d = tds.value(ix, iy);
tds.putValue( ix, iy, d+1 );
//tds.addValue( ix, iy, 1 ); this should be faster
}
}
}
}
}
logger.exiting("HugeScatterRenderer", "histogramRank2Waveform");
}
/**
* This is the typical route, where we are making a 2-D histogram of the data in pixel space, and
* using that with the saturation count to shade each pixel.
* @param xAxis the x-axis
* @param yAxis the y-axis
* @param ds the rank 2 waveform dataset, or rank 2 bundle, or rank 1 dataset.
* @param plotImageBounds2 the bounds.
*/
private void renderHistogram( BufferedImage plotImage1, DasAxis xAxis, DasAxis yAxis, QDataSet ds, Rectangle plotImageBounds2) {
logger.entering( "org.das2.graph.HugeScatterRenderer", "renderHistogram" );
DatumRange xrange = GraphUtil.invTransformRange( xAxis, plotImageBounds2.x, plotImageBounds2.x + plotImageBounds2.width);
DatumRange yrange = GraphUtil.invTransformRange( yAxis, plotImageBounds2.y + plotImageBounds2.height,plotImageBounds2.y);
RebinDescriptor ddx = new RebinDescriptor(
xrange.min(),
xrange.max(),
plotImageBounds2.width,
xAxis.isLog());
RebinDescriptor ddy = new RebinDescriptor(
yrange.min(),
yrange.max(),
plotImageBounds2.height,
yAxis.isLog());
FDataSet tds = FDataSet.createRank2( ddx.numberOfBins(), ddy.numberOfBins() );
if ( SemanticOps.isRank3JoinOfRank2Waveform(ds) ) {
for ( int k=0; k=firstIndex ) {
tds = histogram( tds, ddx, ddy, ds1, firstIndex, lastIndex );
} else {
logger.fine("dropping record because it is off screen");
}
}
//System.err.println("total: "+ Ops.total(tds) );
//
//try {
// new AsciiFormatter().formatToFile( "/tmp/ap.txt", tds );
//} catch (IOException ex) {
// Logger.getLogger(HugeScatterRenderer.class.getName()).log(Level.SEVERE, null, ex);
//}
} else {
QDataSet xds= SemanticOps.xtagsDataSet(ds);
boolean xmono= SemanticOps.isMonotonic(xds);
int firstIndex = xmono ? DataSetUtil.getPreviousIndex(xds, ddx.binStart(0) ) : 0;
int lastIndex = xmono ? DataSetUtil.getNextIndex(xds, ddx.binStop(ddx.numberOfBins() - 1) ) : ds.length()-1;
tds = histogram(tds, ddx, ddy, ds, firstIndex, lastIndex );
}
if ( false ) darkenHistogram( tds );
WritableDataSet newHist= tds;
if ( yAxis.isFlipped() && xAxis.isFlipped() ) {
newHist= DataSetOps.applyIndex( newHist, 1, Ops.linspace( newHist.length(0)-1, 0, newHist.length(0) ), false );
newHist= DataSetOps.applyIndex( newHist, 0, Ops.linspace( newHist.length()-1, 0, newHist.length() ), false );
} else if ( yAxis.isFlipped() ) {
newHist= DataSetOps.applyIndex( newHist, 1, Ops.linspace( newHist.length(0)-1, 0, newHist.length(0) ), false );
} else if ( xAxis.isFlipped() ) {
newHist= DataSetOps.applyIndex( newHist, 0, Ops.linspace( newHist.length()-1, 0, newHist.length() ), false );
}
//WritableTableDataSet whist= (WritableTableDataSet)hist;
/* double histMax= TableUtil.tableMax(hist, Units.dimensionless);
for ( int i=0; i 0 && d < histMax*floorFactor )
whist.setDouble( i,j, histMax*floorFactor, Units.dimensionless );
}
} */
int h = ddy.numberOfBins();
int w = ddx.numberOfBins();
logger.log(Level.FINE, "ghostlyImage: h={0} w={1}", new Object[]{h, w});
int[] raster = new int[h * w];
int colorInt = color.getRGB() & 0x00ffffff;
// Following code for scatter plot temporarily removed
// Need to add switch between scatter plot and envelope
/*for (int i = 0; i < w; i++) {
for (int j = 0; j < h; j++) {
int index = (i - 0) + (h - j - 1) * w;
// alpha=0 for transparent, alpha=255 for opaque
int alpha = 255 * (int) newHist.getDouble(i, j, Units.dimensionless) / saturationHitCount;
int icolor = (alpha << 24) | colorInt;
raster[index] = icolor;
}
}*/
//show envelope
QDataSet xds= SemanticOps.xtagsDataSet( ds );
boolean xmono = SemanticOps.isMonotonic(xds);
int envelopeColor = ( 128 << 24) | colorInt; // 50% alpha
if ( envelope==1 ) envelopeColor= ( 128/saturationHitCount << 24) | colorInt; // 50% alpha
for (int i=0; i 0) {
if (ymin<0) ymin = j;
ymax = j;
}
}
if (ymin >= 0) {
for (int j=ymin; j<=ymax; j++) {
int index = i + (h-j-1) * w;
if ( !xmono || (!(envelope==2) && ( envelope==0 || newHist.value(i,j) > 0 ) )) {
int alpha = 255 * (int) newHist.value(i,j) / saturationHitCount;
if (alpha>255) alpha = 255; //Clip alpha; it's only 8 bits!
raster[index] = (alpha << 24) | colorInt;
} else {
raster[index] = envelopeColor;
}
}
}
}
WritableRaster r = plotImage1.getRaster();
r.setDataElements(0, 0, w, h, raster);
synchronized (this) {
plotImage= plotImage1;
selectionArea= null;
}
imageXRange = xrange;
logger.exiting( "org.das2.graph.HugeScatterRenderer", "renderHistogram" );
}
@Override
public void updatePlotImage(DasAxis xAxis, DasAxis yAxis, org.das2.util.monitor.ProgressMonitor monitor) throws DasException {
logger.entering( "org.das2.graph.HugeScatterRenderer", "updatePlotImage" );
long t0= System.currentTimeMillis();
super.incrementUpdateCount();
super.updatePlotImage(xAxis, yAxis, monitor);
QDataSet ds1 = getDataSet();
Datum xcad= xcadence;
if (ds1 == null) {
return;
}
QDataSet xds;
if ( ds1.rank()==3 ) {
xds= SemanticOps.xtagsDataSet( ds1.slice(0) );
} else {
xds= SemanticOps.xtagsDataSet( ds1 );
}
DasPlot parent= getParent();
if ( parent==null ) return;
if (!xAxis.getUnits().isConvertibleTo( SemanticOps.getUnits(xds) )) {
parent.postMessage(this, "inconvertible xaxis units", DasPlot.INFO, null, null);
}
if (!yAxis.getUnits().isConvertibleTo( SemanticOps.getUnits(ds1) )) {
parent.postMessage(this, "inconvertible yaxis units", DasPlot.INFO, null, null);
}
plotImageBounds= parent.getUpdateImageBounds();
DatumRange visibleRange = xAxis.getDatumRange();
boolean xmono = SemanticOps.isMonotonic(xds);
if ( ds1.rank()==3 ) xmono= false;
int firstIndex, lastIndex;
if ( xmono ) {
try {
firstIndex = DataSetUtil.getPreviousIndex(xds, visibleRange.min());
lastIndex = DataSetUtil.getNextIndex(xds, visibleRange.max()) ;
} catch ( InconvertibleUnitsException ex ) {
parent.postMessage(this, "inconvertible xaxis units", DasPlot.INFO, null, null );
logger.exiting( "org.das2.graph.HugeScatterRenderer", "updatePlotImage" );
return;
}
if ( xAxis.isLog() ) {
ixstepLimitSq= 100000000;
} else {
RankZeroDataSet d;
if ( xcad!=null ) {
d= DataSetUtil.asDataSet(xcad);
} else {
if ( SemanticOps.isRank2Waveform(ds1) ) {
QDataSet dep1= (QDataSet) ds1.property(QDataSet.DEPEND_1);
if ( dep1.rank()==1 ) {
d= DataSetUtil.guessCadenceNew( dep1, null );
} else {
d= DataSetUtil.guessCadenceNew( dep1.slice(0), null );
}
} else if ( ds1.rank()==3 && SemanticOps.isRank2Waveform(ds1.slice(0)) ) {
d= DataSetUtil.guessCadenceNew( (QDataSet) ds1.slice(0).property(QDataSet.DEPEND_1), null );
} else {
d= DataSetUtil.guessCadenceNew(xds,ds1);
}
if ( d==null ) {
xcadence= null;
} else {
xcadence= DataSetUtil.asDatum(d);
}
}
logger.log(Level.FINER, "xcadence={0}", xcadence);
if ( d!=null ) {
Datum sw = DataSetUtil.asDatum( d );
Datum xmax= xAxis.getDataMaximum();
int ixstepLimit;
if ( UnitsUtil.isRatiometric(sw.getUnits())) {
try {
ixstepLimit= 1 + (int) (xAxis.transform(xmax) - xAxis.transform(xmax.divide(sw)));
} catch ( IllegalArgumentException ex ) {
ixstepLimit= 1;
}
} else {
ixstepLimit= 1 + (int) (xAxis.transform(xmax) - xAxis.transform(xmax.subtract(sw)));
}
ixstepLimitSq= ixstepLimit * ixstepLimit;
} else {
ixstepLimitSq= 100000000;
}
}
} else {
firstIndex = 0;
lastIndex = ds1.length();
ixstepLimitSq= 100000000;
}
int nj= 1;
if ( SemanticOps.isRank2Waveform(ds1) ) nj= ds1.length(0);
else if ( SemanticOps.isRank3JoinOfRank2Waveform(ds1) ) {
nj= ds1.length(0,0);
for ( int k=1; k 20 * xAxis.getColumn().getWidth()) {
if ( lastIndex==firstIndex+1 ) {
renderPointsOfRank2Waveform( image, xAxis, yAxis, ds1, plotImageBounds);
} else {
renderHistogram( image, xAxis, yAxis, ds1, plotImageBounds);
}
} else {
if ( SemanticOps.isRank2Waveform(ds1) ) {
renderPointsOfRank2Waveform( image, xAxis, yAxis, ds1, plotImageBounds);
} else if ( ds1.rank()==3 ) {
//renderHistogram( image, xAxis, yAxis, ds1, plotImageBounds);
for ( int k=0; k 10) {
d = 10;
}
this.saturationHitCount = d;
this.update();
}
public int getSaturationHitCount() {
return this.saturationHitCount;
}
public void setColor(Color color) {
this.color = color;
updateCacheImage();
}
public Color getColor() {
return color;
}
protected int envelope = 0;
public static final String PROP_ENVELOPE = "envelope";
public int getEnvelope() {
return envelope;
}
/**
* 0=none. 1=faint envelope with points on top. 2=only envelope
* @param envelope
*/
public void setEnvelope(int envelope) {
int oldEnvelope = this.envelope;
this.envelope = envelope;
updateCacheImage();
propertyChangeSupport.firePropertyChange(PROP_ENVELOPE, oldEnvelope, envelope);
//meanCount=0;
}
@Override
public boolean acceptContext(int x, int y) {
BufferedImage im= getPlotImage();
if ( im==null ) return false;
Shape s= selectionArea();
return s.contains(x, y);
}
/**
* Holds value of property print300dpi.
*/
private boolean print300dpi;
/**
* Getter for property draw300dpi.
* @return Value of property draw300dpi.
*/
public boolean isPrint300dpi() {
return this.print300dpi;
}
/**
* Setter for property draw300dpi.
* @param print300dpi New value of property draw300dpi.
*/
public void setPrint300dpi(boolean print300dpi) {
this.print300dpi = print300dpi;
}
private synchronized BufferedImage getPlotImage() {
return this.plotImage;
}
/**
* calculate the area that describes roughly where the data lie. The
* variable "selectionArea" is set.
*
* This is fast, less than 50ms with 5 million points. When the image gets big, this gets slow...
*/
private void calcSelectionArea() {
BufferedImage lplotImage= getPlotImage(); // make local copy
logger.finer("in calc selection area");
long t0= System.currentTimeMillis();
if ( lplotImage==null ) return;
int w= lplotImage.getWidth();
int h= lplotImage.getHeight();
DasPlot parent= getParent();
int imagex = (int)parent.getCacheImageBounds().getX();
int parentx= parent.getX();
int dx= parent.getColumn().getDMinimum() - imagex;
int parenty = (int)parent.getCacheImageBounds().getY();
int overx= imagex - parentx;
GeneralPath result= new GeneralPath();
int d= 5;
int dd= 5;
if ( w*h>100000 ) {
d= 10;
dd= 10; // this is used to evaluate mouse click focus as well.
}
if ( w*h>500000 ) d= 30;
for ( int i=0; i0 ) {
// why 2*overx? I have no idea...
result.append( new Rectangle( (int)( 2* overx + i+x/n+parentx-dd/2 + dx ), (int)( j+y/n+parenty-dd/2 ), dd, dd ), true );
}
}
}
synchronized (this) {
selectionArea= result;
}
logger.log(Level.FINER, "done in calc selection area {0}ms", ( System.currentTimeMillis()-t0));
}
/**
* return a Shape object showing where the data lie and focus should be accepted.
* @return
*/
public Shape selectionArea() {
Shape localSelectionArea;
synchronized (this){
localSelectionArea= this.selectionArea;
}
if ( localSelectionArea==null ) {
calcSelectionArea();
synchronized (this) {
localSelectionArea= selectionArea;
}
}
return localSelectionArea==null ? SelectionUtil.NULL : localSelectionArea;
}
private static ArrayDataSet resetUnits(ArrayDataSet xoffsets, Units offsetUnits) {
final UnitsConverter uc= UnitsConverter.getConverter( SemanticOps.getUnits(xoffsets), offsetUnits );
if ( !uc.equals( UnitsConverter.IDENTITY ) ) {
switch (xoffsets.rank()) {
case 2:
for ( int j=0; j