package com.isti.traceview.data; import java.awt.Color; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Observable; import java.util.Observer; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import org.apache.log4j.Logger; import com.isti.traceview.TraceViewException; import com.isti.traceview.common.IEvent; import com.isti.traceview.common.Station; import com.isti.traceview.common.TimeInterval; import com.isti.traceview.filters.IFilter; import com.isti.traceview.gui.ColorModeBySource; import com.isti.traceview.gui.IColorModeState; import com.isti.traceview.processing.FilterFacade; import com.isti.traceview.processing.RemoveGain; import com.isti.traceview.processing.RemoveGainException; import com.isti.traceview.processing.Rotation; /** * <p> * Class for trace representation, produces and holds pixelized trace view and manage pixalization * policy. * </p> * <p> * The concept of a pixelized view is a realization of a simple idea that the computer screen can * fit up to a couple of thousand horizontal data points (pixels) at most, and we cannot show all * trace points on the computer's screen. So we have to pixelize raw data, i.e each pixelized * visible screen point contain information about some time range of raw data. In this way we can * significantly speed-up plotting capability of the program. * </p> * <p> * Observed by associated ChannelView, i.e can report of changes to observing instances. * </p> * * @author Max Kokoulin */ public class PlotDataProvider extends RawDataProvider implements Observer { public static final long serialVersionUID = 1; private static final Logger logger = Logger.getLogger(PlotDataProvider.class); /** * Point count which we have in RAM for whole time range */ private static final int initPointCount = 10000; /** * Set of events attached to this data provider */ protected transient SortedSet<IEvent> events; /** * Time of last access to data */ private transient Date lastAccessed = null; /** * Time range of last query of data */ private transient TimeInterval viewingInterval = null; /** * List of precalculated {@link PlotDataPoint}s on the full time range of channel to use on * wide zooms */ private List<PlotDataPoint[]> pointsCache = null; /** * May be used by ColorModeByTrace to color trace in manual mode. */ private Color manualColor = Color.BLACK; /** * Current plots applied rotation */ private Rotation rotation = null; public PlotDataProvider(String channelName, Station station, String networkName, String locationName) { super(channelName, station, networkName, locationName); events = Collections.synchronizedSortedSet(new TreeSet<IEvent>()); } public PlotDataProvider() { super(); events = Collections.synchronizedSortedSet(new TreeSet<IEvent>()); } /** * Initialize point cache, fill it with initPointCount points, this cache is used to show big * parts of data, and raw data access during zooming happens only to limited small parts of data */ public void initPointCache(IColorModeState colorMode) { try { logger.debug("== ENTER"); pointsCache = pixelize(getTimeRange(), initPointCount, null, colorMode); logger.debug("== EXIT"); } catch (PlotDataException e) { logger.error("PlotDataException:", e); } } /** * Sets rotation. Null means rotation doesn't affected. Selected traces will be redrawn with * rotation with using of "selection" mode. * * @param rotation * rotation to set to set */ public void setRotation(Rotation rotation) { this.rotation = rotation; } /** * Gets the rotation. * * @return current rotation, null if rotation is not present */ public Rotation getRotation() { return this.rotation; } /** * Returns whether the current channel is rotated or not * @return true if channel is rotated otherwise false */ public boolean isRotated() { if(this.rotation != null && this.rotation.getRotationType() != null) return true; else return false; } /** * From interface Observer */ public void update(Observable o, Object arg) { logger.debug(this + ": update request from " + o); TimeInterval ti = (TimeInterval) arg; //logger.debug("PlotDataProvider " + this + " updating for range " + ti + " due to request from " + o.getClass().getName()); //System.out.println("PlotDataProvider " + this + " updating for range " + ti + " due to request from " + o.getClass().getName()); if ((viewingInterval == null) || viewingInterval.isIntersect(ti)) { notifyObservers(ti); } } /** * Generate plot data * * @param ti * Requested time interval * @param pointCount - * requested count of points * @param rotation - * rotation data, if null no rotation * @param filter - * filter to apply * @return generated plot data to draw * @throws TraceViewException if thrown in {@link com.isti.traceview.processing.Rotation#rotate(PlotDataProvider, TimeInterval, int, IFilter, IColorModeState)} */ public PlotData getPlotData(TimeInterval ti, int pointCount, IFilter filter, RemoveGain rg, IColorModeState colorMode) throws TraceViewException, RemoveGainException { if (rg != null && rg.removestate == true && this.rotation == null){ return rg.removegain(this, ti, pointCount, filter, colorMode); } else if (this.rotation != null && this.rotation.getRotationType() != null){ return rotation.rotate(this, ti, pointCount, filter, colorMode); } else { return getPlotData(ti, pointCount, filter, colorMode); } } /** * Generate original plot data * * @param ti * Requested time interval * @param pointCount - * requested count of points * @param rotation - * rotation data, if null no rotation * @param filter - * filter to apply * @return generated plot data to draw from original dataset * @throws TraceViewException if thrown in {@link com.isti.traceview.processing.Rotation#rotate(PlotDataProvider, TimeInterval, int, IFilter, IColorModeState)} */ public PlotData getOriginalPlotData(TimeInterval ti, int pointCount, IFilter filter, RemoveGain rg, IColorModeState colorMode) throws TraceViewException, RemoveGainException { return getPlotData(ti, pointCount, filter, colorMode); } /** * Generate plot data * * @param ti * Requested time interval * @param pointCount * requested count of points * @param filter * filter to apply * @return generated plot data to draw */ private PlotData getPlotData(TimeInterval ti, int pointCount, IFilter filter, IColorModeState colorMode) { logger.debug(this + "; " + ti + "(" + ti.getStart() + "-" + ti.getEnd() + ")" + "; pointCount " + pointCount); // This list used when we cannot use pointsCache due to too small zoom, calculated every // time afresh. List<PlotDataPoint[]> points = null; if (pointsCache == null) { initPointCache(colorMode); } // Time range need to be pixelized - intersection of requested pixalization range and // channel's time range PlotData ret = new PlotData(this.getName(), this.getColor()); TimeInterval effectiveTimeRange = TimeInterval.getIntersect(ti, getTimeRange()); //Double durationTest = new Double(effectiveTimeRange.getDuration()) / new Double(getTimeRange().getDuration()); //Double pointsCacheSize = pointsCache.size() * durationTest; if (effectiveTimeRange != null) { if ((pointCount > pointsCache.size() * new Double(effectiveTimeRange.getDuration()) / new Double(getTimeRange().getDuration())) || filter != null) { try { points = pixelize(effectiveTimeRange, new Double(2 * pointCount * effectiveTimeRange.getDuration() / new Double(ti.getDuration()).intValue()).intValue(), filter, colorMode); } catch (PlotDataException e) { logger.error("PlotDataException:", e); } } else { points = new ArrayList<PlotDataPoint[]>(); int startIndex = new Double((effectiveTimeRange.getStart() - getTimeRange().getStart()) * initPointCount / getTimeRange().getDuration()).intValue(); if (startIndex < 0) { for (int i = -startIndex; i < 0; i++) { // lg.debug("getPlotData: add empty points in the beginning"); PlotDataPoint[] intervalPoints = new PlotDataPoint[1]; intervalPoints[0] = new PlotDataPoint(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, -1, -1, -1, null); points.add(intervalPoints); } startIndex = 0; } int endIndex = new Double((effectiveTimeRange.getEnd() - getTimeRange().getStart()) * initPointCount / getTimeRange().getDuration()).intValue(); if (endIndex > initPointCount) { // MTH: We don't seem to go in here points.addAll(pointsCache.subList(startIndex, initPointCount)); for (int i = initPointCount; i < endIndex; i++) { PlotDataPoint[] intervalPoints = new PlotDataPoint[1]; intervalPoints[0] = new PlotDataPoint(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, -1, -1, -1, null); points.add(intervalPoints); } } else { points.addAll(pointsCache.subList(startIndex, endIndex)); } // lg.debug("Use data points from cache to calculate data, indexes: " + startIndex + // "-" + endIndex); } // Second level of pixelization related to screen size (i.e. width) double timeRatio = (ti.getDuration()) / new Double(pointCount); for (int i = 0; i < pointCount; i++) { // we divide requested time range into pointCount time slices and calculate data to // display for every slice double startSlice = ti.getStart() + i * timeRatio; // start slice time double endSlice = ti.getStart() + ((i + 1.0) * timeRatio); // end slice time if (!((startSlice >= effectiveTimeRange.getEnd() && endSlice >= effectiveTimeRange.getEnd()) || (startSlice <= effectiveTimeRange .getStart() && endSlice <= effectiveTimeRange.getStart()))) { // if effective time range intersects this time slice int startIndex = new Long(Math.round(new Double((startSlice - effectiveTimeRange.getStart()) * points.size()) / new Double(effectiveTimeRange.getDuration()))).intValue(); if (startIndex < 0) { startIndex = 0; } int endIndex = new Long(Math.round(new Double((endSlice - effectiveTimeRange.getStart()) * points.size()) / new Double(effectiveTimeRange.getDuration()))).intValue(); if (endIndex > points.size()) { endIndex = points.size(); } if ((startIndex == endIndex) && (endIndex != points.size())) { // to avoid gaps on very large zoom endIndex = endIndex + 1; } List<PlotDataPoint[]> data = points.subList(startIndex, endIndex); List<SliceData> sliceDataList = new ArrayList<SliceData>(); @SuppressWarnings("unused") int j =0; for (PlotDataPoint[] sublist: data) { int k =0; for(PlotDataPoint value: sublist){ // why loop when sublist has 1 value in PlotDataPoint[] //lg.debug("Index " + (startIndex + j) + ", set " + k + ", value " + value); if(sliceDataList.size()<=k){ sliceDataList.add(new SliceData()); } if (value.getTop() > sliceDataList.get(k).top) { sliceDataList.get(k).top = value.getTop(); } if (value.getBottom() < sliceDataList.get(k).bottom) { sliceDataList.get(k).bottom = value.getBottom(); } if (value.getMean() != Double.POSITIVE_INFINITY) { sliceDataList.get(k).sum = sliceDataList.get(k).sum + value.getMean(); sliceDataList.get(k).segmentNumber = value.getSegmentNumber(); sliceDataList.get(k).continueAreaNumber = value.getContinueAreaNumber(); sliceDataList.get(k).rdpNumber = value.getRawDataProviderNumber(); sliceDataList.get(k).dataPointCount++; } k++; } j++; } if (events == null) { events = Collections.synchronizedSortedSet(new TreeSet<IEvent>()); // class was deserialized } SortedSet<EventWrapper> evts = new TreeSet<EventWrapper>(); for (IEvent event: events) { long eventTime = event.getStartTime().getTime(); if (eventTime > startSlice && eventTime <= endSlice) { evts.add(new EventWrapper(event, true)); } else if (!((eventTime >= endSlice && eventTime + event.getDuration() >= endSlice || (eventTime <= startSlice && eventTime + event.getDuration() <= startSlice)))) { evts.add(new EventWrapper(event, false)); } } /* * if (new Double(endSlice).longValue() - new Double(startSlice).longValue() > * 0) { evts = events.subSet(new DefaultEvent(new Date(new * Double(startSlice).longValue())), new DefaultEvent(new Date(new Double( * endSlice).longValue()))); } */ //lg.debug("getPlotData: PlotDataProvider " + this + ": Adding plot data points set " + i + "; time " + TimeInterval.formatDate(new Date(new Double(startSlice).longValue()),TimeInterval.DateFormatType.DATE_FORMAT_NORMAL) + " - " + //TimeInterval.formatDate(new Date(new Double(endSlice).longValue()),TimeInterval.DateFormatType.DATE_FORMAT_NORMAL) + "(" + startSlice + "-" + endSlice+ ")" + ", for indexes " + startIndex + " - " + endIndex); PlotDataPoint[] pdpArray = new PlotDataPoint[sliceDataList.size()]; int m = 0; for(SliceData sliceData:sliceDataList){ // if gaps exist m > 1 pdpArray[m] = sliceData.getPoint(evts); //lg.debug("Added point " + m + ": " + pdpArray[m].toString()); m++; } ret.addPixel(pdpArray); if (evts.size() > 0) { logger.debug("Event time: " + TimeInterval.formatDate(evts.first().getEvent().getStartTime(), TimeInterval.DateFormatType.DATE_FORMAT_NORMAL) + "(" + evts.first().getEvent().getStartTime().getTime() + ")" + "; point number " + ret.getPointCount()); } } else { //lg.debug("if effective time range doesn't contain this time slice - added empty point"); PlotDataPoint[] pdpArray = new PlotDataPoint[1]; pdpArray[0] = new PlotDataPoint(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, -1, -1, -1, null); ret.addPixel(pdpArray); } } lastAccessed = new Date(); } logger.debug("== END: " + this); return ret; } /** * Pixelize raw data * * @param ti * Time interval to pixelize * @param pointCount * Requested count of points * @param filter * filter to apply to raw data before pixelization * @return List of PlotDataPoint */ private List<PlotDataPoint[]> pixelize(TimeInterval ti, int pointCount, IFilter filter, IColorModeState colorMode) throws PlotDataException { //logger.debug("pixelizing " + this +"; "+ ti + "; "+ "pointCount " + pointCount); // Why is 'pointSet' synchronized with no threading? List<PlotDataPoint[]> pointSet = Collections.synchronizedList(new ArrayList<PlotDataPoint[]>(pointCount)); // waiting if data still is not loaded int attemptCount = 0; while (!isLoaded()) { try { if (attemptCount > 60) throw new PlotDataException("Channel " + this + " wait for data more than " + attemptCount + " seconds"); logger.debug("Channel " + this + " getPlotData() is waiting for data loading"); Thread.sleep(500); attemptCount++; } catch (InterruptedException e) { // do nothing logger.error("InterruptedException:", e); } } List<SegmentData> rawData = new ArrayList<SegmentData>(); List<Segment> segments = getRawData(ti); int numSegments = segments.size(); // Combine segments if no gap and colormode is not by source, to correct filtering for (int i = 0; i < numSegments; i++) { //ALL requested for pixelization time range in this segment Segment segment = segments.get(i); TimeInterval currentSegmentDataTI = TimeInterval.getIntersect(ti, new TimeInterval(segment.getStartTime(), segment.getEndTime())); SegmentData segmentData = segment.getData(currentSegmentDataTI); if(i==0 || colorMode instanceof ColorModeBySource || Segment.isDataBreak(segments.get(i-1).getEndTime().getTime(), segmentData.startTime, segmentData.sampleRate)){ rawData.add(segmentData); } else { // concatenate previous data and current data // replace with array copying (src, srcPos, dest, destPost, srcLen) SegmentData last = rawData.get(rawData.size()-1); int lastLength = last.data.length; int currentLength = segmentData.data.length; last.data = Arrays.copyOf(last.data, lastLength+currentLength); // allocate space for copy System.arraycopy(segmentData.data, 0, last.data, lastLength, currentLength); // replaces loop } } //filtering if(filter != null){ FilterFacade ff = new FilterFacade(filter, this); List<SegmentData> filteredRawData = new ArrayList<SegmentData>(); for(SegmentData segmentData: rawData){ filteredRawData.add(new SegmentData(segmentData.startTime, segmentData.sampleRate, segmentData.sourceSerialNumber, segmentData.channelSerialNumber, segmentData.continueAreaNumber, segmentData.previous, segmentData.next, ff.filter(segmentData.data))); } rawData = filteredRawData; } double interval = (ti.getDuration()) / new Double(pointCount); double time = ti.getStart(); for (int i = 0; i < pointCount; i++) { //lg.debug("Iteration # "+ i + ", processing interval " + time + " - " + (time+interval)); // Get segmentData objects in the interval (time, time+interval) SegmentData[] intervalData = getSegmentData(rawData, time, time+interval); if (intervalData != null) { int k = 0; int intervalDataLength = intervalData.length; // number of continuous segmentData objects PlotDataPoint[] intervalPoints = new PlotDataPoint[intervalDataLength]; for (SegmentData segData: intervalData) { TimeInterval currentSegmentDataTI = new TimeInterval(segData.startTime, segData.endTime()); //lg.debug("Processing segment " + segment + "on interval " + currentSegmentDataTI); logger.debug("Processing segment [seg] on interval " + currentSegmentDataTI); double top = Double.NEGATIVE_INFINITY; double bottom = Double.POSITIVE_INFINITY; double sum = 0.0; int rawDataPointCount = 0; SegmentData data; if (i == (pointCount - 1)) { data = segData.getData(time, ti.getEnd()); // last chunk } else { data = segData.getData(time, time + interval); // interval sized chunks } rawDataPointCount = data.data.length; if (rawDataPointCount > 0) { //lg.debug("Data present, meaning interval"); for (int value: data.data) { if (value > top) { top = value; } if (value < bottom) { bottom = value; } sum = sum + value; } intervalPoints[k] = new PlotDataPoint(top, bottom, sum / rawDataPointCount, segData.channelSerialNumber, segData.sourceSerialNumber, segData.continueAreaNumber, null); //lg.debug("Data present, point " + k + " added: " + intervalPoints[k]); } else { if (currentSegmentDataTI.isContain(new Double(time).longValue())) { rawDataPointCount = 1; double value = segData.interpolateValue(time); //lg.debug("Interpolated value, point " + k + " added: " + value); intervalPoints[k] = new PlotDataPoint(value, value, value, segData.channelSerialNumber, segData.sourceSerialNumber, segData.continueAreaNumber, null); } else { //lg.debug("Interpolated value, point " + k + " absent"); intervalPoints[k] = new PlotDataPoint(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, -1, -1, -1, null); } } k++; } pointSet.add(intervalPoints); } else { //lg.debug("Pixelizing : segment null"); PlotDataPoint[] intervalPoints = new PlotDataPoint[1]; intervalPoints[0] = new PlotDataPoint(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, -1, -1, -1, null); pointSet.add(intervalPoints); } time = time + interval; } logger.debug("pixelizing end " + this); return pointSet; } /** * @param sps * Segments list * @param start * start time * @param end * end time * @return subset of segment list which lies in the requested time interval. For one channel, * the normal situation is none or one segment, but it can be bigger count in the case * of segment overlapping or gaps. If no segments found, return null. */ private static SegmentData[] getSegmentData(List<SegmentData> sps, double start, double end) { List<SegmentData> ret = new ArrayList<SegmentData>(); Iterator<SegmentData> it = sps.iterator(); while (it.hasNext()) { SegmentData segData = it.next(); long retStart = segData.startTime; long retEnd = segData.endTime(); if (!((start >= retEnd && end >= retEnd) || (start <= retStart && end <= retStart))) { ret.add(segData); } } if (ret.size() == 0) { return null; } else { return ret.toArray(new SegmentData[1]); } } /** * @param sps * Segments list * @param start * start time * @param end * end time * @return subset of segment list which lies in the requested time interval. For one channel, * the normal situation is none or one segment, but it can be bigger count in the case * of segment overlapping. If no segments found, return null. */ @SuppressWarnings("unused") private static Segment[] getSegment(List<Segment> sps, double start, double end) { List<Segment> ret = new ArrayList<Segment>(); Iterator<Segment> it = sps.iterator(); while (it.hasNext()) { Segment seg = it.next(); long retStart = seg.getStartTime().getTime(); long retEnd = seg.getEndTime().getTime(); if (!((start >= retEnd && end >= retEnd) || (start <= retStart && end <= retStart))) { ret.add(seg); } } if (ret.size() == 0) { return null; } else { return ret.toArray(new Segment[1]); } } /** * Getter of the property <tt>Events</tt> * * @return set of all events */ public SortedSet<IEvent> getEvents() { return events; } /** * Get set of events which have given start time * * @param time the Date to find events near (not Time, bug?) * @return set of events */ public SortedSet<IEvent> getEvents(Date time) { return getEvents(time, 0); } /** * set of events which lies near given time * * @param time the Date to find events near (not Time, bug?) * @param precision * time range to find, in milliseconds * @return set of events */ public SortedSet<IEvent> getEvents(Date time, long precision) { SortedSet<IEvent> ret = Collections.synchronizedSortedSet(new TreeSet<IEvent>()); for (IEvent event: events) { if (event.getStartTime().getTime() > time.getTime() && event.getStartTime().getTime() < time.getTime() + 2 * precision) { ret.add(event); } } return ret; } /** * Adds event to plot data provider * * @param event * Event to add * @return <tt>true</tt> if events set did not already contain the given one */ public boolean addEvent(IEvent event) { if (events == null) { events = Collections.synchronizedSortedSet(new TreeSet<IEvent>()); } return events.add(event); } /** * Remove event * * @param event * to remove * @return <tt>true</tt> if events set contained the specified event */ public boolean removeEvent(IEvent event) { if (events == null) { events = Collections.synchronizedSortedSet(new TreeSet<IEvent>()); } return events.remove(event); } /** * Adds event set to plot data provider * * @param evt the events to add */ public void addEvents(Set<IEvent> evt) { logger.debug("Adding " + evt.size() + " events to plot data provider" + this); if (events == null) { events = Collections.synchronizedSortedSet(new TreeSet<IEvent>()); } events.addAll(evt); } public Date getLastAccessed() { return lastAccessed; } /** * Get rotated raw data for a given time interval * * @param rotation * to process data * @return rotated raw data */ public List<Segment> getRawData(Rotation rotation, TimeInterval ti) { if (rotation == null) { return super.getRawData(); } else { try { return rotation.rotate(this, ti); } catch (TraceViewException e) { logger.error("TraceViewException:", e); return null; } } } /** * Returns a string representation of the PlotDataProvider for debug purposes. * * @return a string representation of the PlotDataProvider. */ public String toString() { return "PlotDataProvider: " + getName(); } /** * Get plot data provider name, currently we use name of underlies raw data provider * * @return a string representation of the PlotDataProvider. */ public String getName() { return super.getName(); } /** * Dumps trace to file in temporary storage in internal format */ public void dump(String serialFileName) { ObjectOutputStream out = null; try { logger.debug("== ENTER: serfialFileName=" + serialFileName); out = new ObjectOutputStream(new FileOutputStream(serialFileName + ".SER")); setDataStream(serialFileName + ".DATA"); synchronized (this) { logger.info("Serializing " + this + " to file " + serialFileName); out.writeObject(this); notifyAll(); } } catch (Exception ex) { logger.error("Can't save channel: ", ex); } finally { try { setDataStream(null); out.close(); } catch (IOException e) { // Do nothing logger.error("IOException:", e); } } logger.debug("== EXIT"); } /** * Loads trace from serialized file in temporary storage */ public static PlotDataProvider load(String fileName) { logger.debug("\n== ENTER: Deserialize channel from file:" + fileName); PlotDataProvider channel = null; ObjectInputStream ois = null; //String serialDataFileName = TemporaryStorage.getDataFileName(fileName); try { Object objRead = null; ois = new ObjectInputStream(new FileInputStream(fileName)); logger.debug("== call ois.readObject()"); objRead = ois.readObject(); logger.debug("== call ois.readObject() DONE"); channel = (PlotDataProvider) objRead; channel.setStation(DataModule.getOrAddStation(channel.getStation().getName())); //MTH: added Segment.isLoaded boolean List<Segment> segs = channel.getRawData(); for (Segment seg : segs) { seg.setIsLoaded(true); } } catch (FileNotFoundException e) { logger.error("FileNotFoundException:", e); } catch (IOException e) { logger.error("IOException:", e); } catch (ClassNotFoundException e) { logger.error("ClassNotFoundException:", e); } finally { try { ois.close(); } catch (IOException e) { // Do nothing logger.error("IOException:", e); } } logger.debug("== load(fileName=%s) -- EXIT\n"); return channel; } /** * print debug output to the console */ public void printout() { System.out.println(toString()); } /** * get color to color traces in manual mode */ public Color getColor(){ return manualColor; } /** * set color to color traces in manual mode */ public void setColor(Color color){ this.manualColor = color; } /** * Temporary class to accumulate slice statistics * */ private class SliceData { double top = Double.NEGATIVE_INFINITY; // max value for slice double bottom = Double.POSITIVE_INFINITY; // min value for slice double sum = 0.0; int dataPointCount = 0; int segmentNumber = -1; int continueAreaNumber = -1; int rdpNumber = -1; PlotDataPoint getPoint(SortedSet<EventWrapper> evts){ double mean = dataPointCount == 0.0 ? Double.POSITIVE_INFINITY : sum / new Double(dataPointCount); return new PlotDataPoint(top, bottom, mean, segmentNumber, continueAreaNumber, rdpNumber, evts); } } /** * MTH: Provide a way for DataModule to set pointsCache=null * in order to mix -t and -d data */ public void nullPointsCache() { pointsCache = null; } }