]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.trend/src/org/simantics/trend/impl/ItemNode.java
Fixed history data collection minmax stream updates and plot rendering
[simantics/platform.git] / bundles / org.simantics.trend / src / org / simantics / trend / impl / ItemNode.java
index f57997e747da60ae289d0ea100f91c0f2602773d..699b441ca226c916aa6e725aa8c10a6df6c09b53 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2007, 2011 Association for Decentralized Information Management in\r
- * Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- *     VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.trend.impl;\r
-\r
-import java.awt.AlphaComposite;\r
-import java.awt.BasicStroke;\r
-import java.awt.Color;\r
-import java.awt.Composite;\r
-import java.awt.Graphics2D;\r
-import java.awt.RenderingHints;\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.Path2D;\r
-import java.awt.geom.Point2D;\r
-import java.awt.geom.Rectangle2D;\r
-import java.util.Arrays;\r
-import java.util.List;\r
-import java.util.TreeSet;\r
-import java.util.logging.Level;\r
-import java.util.logging.Logger;\r
-\r
-import org.simantics.databoard.Bindings;\r
-import org.simantics.databoard.accessor.StreamAccessor;\r
-import org.simantics.databoard.accessor.error.AccessorException;\r
-import org.simantics.databoard.binding.Binding;\r
-import org.simantics.databoard.binding.BooleanBinding;\r
-import org.simantics.databoard.binding.ByteBinding;\r
-import org.simantics.databoard.binding.error.BindingException;\r
-import org.simantics.databoard.binding.error.RuntimeBindingException;\r
-import org.simantics.databoard.type.BooleanType;\r
-import org.simantics.databoard.type.Datatype;\r
-import org.simantics.databoard.type.NumberType;\r
-import org.simantics.databoard.type.RecordType;\r
-import org.simantics.databoard.util.Bean;\r
-import org.simantics.history.HistoryException;\r
-import org.simantics.history.HistoryItem;\r
-import org.simantics.history.HistoryManager;\r
-import org.simantics.history.ItemManager;\r
-import org.simantics.history.util.Stream;\r
-import org.simantics.history.util.ValueBand;\r
-import org.simantics.history.util.subscription.SamplingFormat;\r
-import org.simantics.scenegraph.g2d.G2DNode;\r
-import org.simantics.trend.configuration.LineQuality;\r
-import org.simantics.trend.configuration.Scale;\r
-import org.simantics.trend.configuration.TrendItem;\r
-import org.simantics.trend.configuration.TrendItem.DrawMode;\r
-import org.simantics.trend.configuration.TrendItem.Renderer;\r
-import org.simantics.trend.util.KvikDeviationBuilder;\r
-\r
-/**\r
- * Data node for a TrendItem\r
- * \r
- * @author toni.kalajainen\r
- */\r
-public class ItemNode extends G2DNode implements TrendLayout {\r
-\r
-       private static final long serialVersionUID = -4741446944761752871L;\r
-\r
-       public static final AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .61f);\r
-\r
-       public TrendItem item;\r
-       VertRuler ruler;\r
-       TrendNode trendNode;\r
-\r
-       /** History Items */\r
-       Bean historyItems[];\r
-       \r
-       /** A list of items that is ordered in by interval value, starting from the lowest interval */\r
-       Bean[] renderableItems = new Bean[0];\r
-       \r
-       /** Format for the minmax stream, or null */  \r
-       Bean minmaxItem;\r
-       \r
-       /** Known item min->max value range, and from->end time range */\r
-       double from=Double.NaN, end=Double.NaN, min=0, max=1;\r
-\r
-       BasicStroke stroke;\r
-       Color color;\r
-       \r
-       Stream openStream;\r
-       StreamAccessor openStreamAccessor;\r
-       Bean openStreamItem;\r
-       \r
-       Logger log = Logger.getLogger( this.getClass().getName() );     \r
-       \r
-       boolean disabled = false;\r
-       Point2D.Double pt = new Point2D.Double();\r
-       Point2D.Double pt2 = new Point2D.Double();\r
-       \r
-       // Cached shapes\r
-       KvikDeviationBuilder dev = new KvikDeviationBuilder();\r
-       Path2D.Double line = new Path2D.Double();\r
-       \r
-       /**\r
-        * Set trend item and initialize history\r
-        * @param ti\r
-        * @param items all items in history\r
-        */\r
-       public void setTrendItem(TrendItem ti, ItemManager items) {\r
-               if (openStream!=null) {\r
-                       openStream.close();\r
-                       openStream = null;\r
-                       openStreamAccessor = null;\r
-                       openStreamItem = null;\r
-               }\r
-               this.item = ti;\r
-               this.minmaxItem = null;\r
-               this.renderableItems = new HistoryItem[0];\r
-               disabled = true;\r
-               if (ti==null) return;\r
-               \r
-               try {\r
-                       List<Bean> trendItems = items.search("groupId", ti.groupId, "groupItemId", ti.groupItemId, "variableId", ti.variableId);\r
-                       this.historyItems = trendItems.toArray( new Bean[trendItems.size()] );\r
-                       Arrays.sort( this.historyItems, SamplingFormat.INTERVAL_COMPARATOR );\r
-                       \r
-                       // Read renderable formats, and minmax format\r
-                       TreeSet<Bean> streamFormats = new TreeSet<Bean>( SamplingFormat.INTERVAL_COMPARATOR );\r
-                       for (Bean item : trendItems) {\r
-                               SamplingFormat format = new SamplingFormat();\r
-                               format.readAvailableFields(item);\r
-                               RecordType rt = (RecordType) format.format;\r
-                               Boolean enabled = (Boolean) item.getField("enabled");\r
-                               if (!enabled) continue;\r
-                                       \r
-                               boolean isMinMaxFormat = format.interval==Double.MAX_VALUE &&\r
-                                               format.deadband==Double.MAX_VALUE &&\r
-                                               rt.getComponentIndex2("min")>=0 &&\r
-                                               rt.getComponentIndex2("max")>=0;\r
-       \r
-                               if (isMinMaxFormat) {\r
-                                       this.minmaxItem = item;\r
-                               } else {\r
-                                       streamFormats.add(item);\r
-                               }                                       \r
-                       }\r
-                       if (streamFormats.isEmpty()) return;\r
-                       \r
-                       renderableItems = streamFormats.toArray( new Bean[streamFormats.size()] );\r
-                       disabled = false;\r
-               } catch (BindingException e) {\r
-                       throw new RuntimeException( e );\r
-               }\r
-       }\r
-\r
-       /**\r
-        * Draw to graphics context as time,value pairs are.\r
-        * \r
-        * Phases, 0-Init data, 1-Draw deviation, 2-Draw line, 3-Cleanup \r
-        * \r
-        * @param g\r
-        * @param phase \r
-        */\r
-       public void draw(Graphics2D g, int phase, boolean bold) {\r
-               boolean devAndLine = \r
-                               item.drawMode==DrawMode.DeviationAndAverage || \r
-                               item.drawMode==DrawMode.DeviationAndLine || \r
-                               item.drawMode==DrawMode.DeviationAndSample;\r
-               \r
-               // Draw deviation\r
-               Object newQuality = getTrendNode().printing||getTrendNode().quality.lineQuality==LineQuality.Antialias?RenderingHints.VALUE_ANTIALIAS_ON:RenderingHints.VALUE_ANTIALIAS_OFF;\r
-               if (phase == 1) {                       \r
-                       g.setColor(color);\r
-                       if (!dev.isEmpty()) {\r
-                               Object old = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);\r
-                               g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, newQuality);\r
-\r
-                               if (item.renderer == Renderer.Binary) {\r
-                                       dev.drawRectangles(g);\r
-                               } \r
-                               \r
-                               if (item.renderer == Renderer.Analog) {\r
-                                       if (devAndLine) {\r
-                                               // Draw opaque\r
-                                               Composite c = g.getComposite();\r
-                                               g.setComposite( composite );\r
-                                               dev.drawRectangles(g);\r
-                                               g.setComposite( c );\r
-                                       } else if (item.drawMode == DrawMode.Deviation) {\r
-                                               // Draw solid\r
-                                               dev.drawRectangles(g);\r
-                                       }\r
-                               }\r
-                   g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, old);\r
-                       }                       \r
-               }\r
-                       \r
-               // Draw line\r
-               if (phase == 2) {\r
-                       g.setColor(color);\r
-                       Object old = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);\r
-                       g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, newQuality);\r
-                       g.draw(line);                   \r
-            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, old);\r
-               }\r
-               \r
-       }\r
-       \r
-       private boolean validSample(ValueBand band) throws HistoryException {\r
-               if(band.getTimeDouble() > 1e50) return false;\r
-               if(band.getTimeDouble() > band.getEndTimeDouble()) return false;\r
-//             if(band.getTimeDouble() < 0) return false;\r
-//             if(band.getEndTimeDouble() < 0) return false;\r
-               //if(band.getEndTimeDouble()-band.getTimeDouble() < 1e-9) return false;\r
-               return true;\r
-       }\r
-       \r
-       // Draw state needed for clipping\r
-       double currentX;\r
-       double currentY;\r
-       double minX;\r
-       double maxX;\r
-       \r
-       void moveTo(double x, double y) {\r
-               line.moveTo(x, y);\r
-               currentX = x;\r
-               currentY = y;\r
-       }\r
-\r
-       void lineToOrdered(double x, double y) {\r
-               \r
-               if(x < minX) return;\r
-               if(currentX > maxX) return;\r
-               \r
-               // We have something to draw\r
-               \r
-               // Clip left\r
-               if(currentX < minX) {\r
-                       double ny = currentY + (y-currentY) * (minX-currentX) / (x-currentX);\r
-                       line.moveTo(minX, ny);\r
-                       currentX = minX;\r
-                       currentY = ny;\r
-               }\r
-\r
-               // Right clip\r
-               if(x > maxX) {\r
-                       double ny = currentY + (y-currentY) * (maxX-currentX) / (x-currentX);\r
-                       line.lineTo(maxX, ny);\r
-                       line.moveTo(x, y);              \r
-               } else {\r
-                       line.lineTo(x, y);              \r
-               }\r
-               \r
-       }\r
-       \r
-       void lineTo(double x, double y) {\r
-\r
-               // First assert ordering - if samples are in wrong order draw nothing\r
-               if(currentX <= x) {\r
-                       lineToOrdered(x, y);\r
-               } else {\r
-                       line.moveTo(x, y);\r
-               }\r
-               \r
-               currentX = x;\r
-               currentY = y;\r
-               \r
-       }\r
-       \r
-\r
-       /**\r
-        * This method prepares line and deviation shapes.\r
-        * @param from\r
-        * @param end\r
-        * @param secondsPerPixel\r
-        * @param at \r
-        * @throws HistoryException \r
-        * @throws AccessorException \r
-        */\r
-       public void prepareLine(double from, double end, double pixelsPerSecond, AffineTransform at) throws HistoryException, AccessorException\r
-       {\r
-//             boolean devAndLine = \r
-//                             item.drawMode==DrawMode.DeviationAndAverage || \r
-//                             item.drawMode==DrawMode.DeviationAndLine || \r
-//                             item.drawMode==DrawMode.DeviationAndSample;\r
-//             boolean deviationEnabled = devAndLine || \r
-//                             item.drawMode == DrawMode.Deviation;\r
-\r
-               boolean devAndLine = false;\r
-               boolean deviationEnabled = false;\r
-               \r
-               // Collect data\r
-               dev.reset();\r
-               line.reset();\r
-               Stream s = openStream(pixelsPerSecond);\r
-               if (s==null) return;\r
-               ValueBand vb = new ValueBand(s.sampleBinding, s.sampleBinding.createDefaultUnchecked());\r
-               boolean hasDeviation = vb.hasMin() && vb.hasMax();\r
-               boolean drawDeviation = hasDeviation & deviationEnabled;\r
-               \r
-               Datatype valueType = vb.getValueBinding().type();\r
-               s.reset();\r
-               if ( valueType instanceof BooleanType == false && valueType instanceof NumberType == false ) return;\r
-                               \r
-               int start = s.binarySearch(Bindings.DOUBLE, from);\r
-               int count = s.count();\r
-               if (start<0) start = -2-start;\r
-               if (start<0) start = 0;\r
-                               \r
-               // moveTo, on next draw, if true\r
-               boolean lineNotAttached = true; // =next draw should be move to\r
-               boolean devNotAttached = true;\r
-               \r
-               currentX = Double.NaN;\r
-               currentY = Double.NaN;\r
-\r
-               pt.setLocation(from, 0);\r
-               at.transform(pt, pt);\r
-               minX = pt.x;\r
-               \r
-               pt.setLocation(end, 0);\r
-               at.transform(pt, pt);\r
-               maxX = pt.x;\r
-                               \r
-               s.reset();\r
-               \r
-               boolean wentOver = false;\r
-               \r
-               for (int i=start; i<count; i++) {\r
-                       // Read sample\r
-                       s.accessor.get(i, s.sampleBinding, vb.getSample());\r
-                       \r
-                       if(!validSample(vb)) {\r
-                               System.err.println("-invalid value band: " + i + "/" + count + ":" + vb);\r
-                               continue;\r
-                       }\r
-                       // Non-continuation point\r
-                       if (vb.hasQuality()) {\r
-                               Byte b = (Byte) vb.getQuality(Bindings.BYTE);\r
-                               boolean noncontinuation = b.equals( ValueBand.QUALITY_NOVALUE ); \r
-                               if ( noncontinuation ) {\r
-                                       lineNotAttached = true;\r
-                                       devNotAttached = true;\r
-                                       continue;\r
-                               }\r
-                       }\r
-                                       \r
-                       // Line\r
-                       double t1 = vb.getTimeDouble();\r
-                       double t2 = vb.hasEndTime() ? vb.getEndTimeDouble() : t1;\r
-                                       \r
-                       // Analog signal\r
-                       if (item.renderer == Renderer.Analog) {\r
-                                               \r
-                               // Add points to line\r
-                               if (item.drawMode == DrawMode.Deviation && hasDeviation) {\r
-                                       // ...\r
-                               } else {\r
-                                       \r
-                                       double yy = Double.NaN;\r
-//                                     boolean showBand = true;\r
-                                       boolean flat = false;\r
-\r
-                                       if( trendNode != null && trendNode.drawSamples ) {\r
-                                               yy = vb.getValueDouble();\r
-                                               flat = true;\r
-                                       } else {\r
-                                               yy = vb.getValueDouble();\r
-                                               // Only render the last band\r
-//                                             if(i < count-1) showBand = false;                                       \r
-                                       }\r
-                                       \r
-//                                     if (item.drawMode == DrawMode.DeviationAndAverage) {\r
-//                                             yy = vb.hasMedian() ? vb.getMedianDouble() : ( vb.hasAvg() ? vb.getAvgDouble() : vb.getValueDouble() );\r
-//                                     } else if (item.drawMode == DrawMode.Average || item.drawMode == DrawMode.DeviationAndAverage) {\r
-//                                             yy = vb.hasAvg() ? vb.getAvgDouble() : vb.getValueDouble();\r
-//                                     } else if (item.drawMode == DrawMode.Line || item.drawMode == DrawMode.DeviationAndLine) {\r
-//                                             yy = vb.getValueDouble();\r
-//                                             // Only render the last band\r
-//                                             if(i < count-1) showBand = false;                                       \r
-//                                     } else /* if (item.drawMode == DrawMode.Sample || item.drawMode == DrawMode.DeviationAndSample) */ {\r
-//                                             yy = vb.getValueDouble();\r
-//                                             flat = true;\r
-//                                     } \r
-                       \r
-                                       if ( Double.isNaN(yy) ) {\r
-                                               lineNotAttached = true;\r
-                                       } else {\r
-//                                             pt.setLocation(t1, yy);\r
-//                                             at.transform(pt, pt);\r
-//                                             if ( t1==t2 ) {\r
-//                                                     if (lineNotAttached) {\r
-//                                                             moveTo(pt.getX(), pt.getY());\r
-//                                                     } else {\r
-//                                                             if(flat) {\r
-//                                                                     \r
-//                                                             } else {\r
-//                                                                     lineTo(pt.getX(), pt.getY());\r
-//                                                             }\r
-//                                                     }\r
-//                                             } else {\r
-                                                       // Static variables may have months data that consists of single value\r
-                                                       // When zoomed in, the single line may be drawn from -1e10, ... 1e10\r
-                                                       // This is too much for Graphics2D, it refuses to draw.\r
-//                                                     if (t1<from) {\r
-//                                                             t1 = from;\r
-//                                                     }\r
-//                                                     if (t2>end) {\r
-//                                                             t2 = end;\r
-//                                                     }\r
-                                                                       \r
-                                                       pt.setLocation(t1, yy);\r
-                                                       at.transform(pt, pt);\r
-                                                       pt2.setLocation(t2, yy);\r
-                                                       at.transform(pt2, pt2);\r
-\r
-                                                       double x1 = pt.x, y1 = pt.y, x2 = pt2.x, y2 = pt2.y;\r
-\r
-                                                       if (lineNotAttached) {\r
-                                                               moveTo(x1, y1);\r
-                                                       } else {\r
-                                                               if(flat) {\r
-                                                                       lineTo(x1, currentY);\r
-                                                                       lineTo(x1, y1);\r
-                                                               } else {\r
-                                                                       lineTo(x1, y1);\r
-                                                               }\r
-                                                       }\r
-\r
-                                                       lineTo(x2, y2);\r
-                                                       \r
-//                                                     if(showBand) {\r
-//                                                             lineTo(x2, y2);\r
-//                                                     }\r
-                                                       \r
-//                                             }\r
-                                               lineNotAttached = false;                                        \r
-                                       }\r
-                               }\r
-                                               \r
-                               if (drawDeviation) {\r
-                                       double min = vb.getMinDouble();\r
-                                       double max = vb.getMaxDouble();\r
-\r
-                                       pt.setLocation(t1, min);\r
-                                       at.transform(pt, pt);\r
-                                       pt2.setLocation(t2, max);\r
-                                       at.transform(pt2, pt2);\r
-                                       double x1 = pt.x;\r
-                                       double x2 = pt2.x;\r
-                                       double y1 = pt.y;\r
-                                       double y2 = pt2.y;\r
-                                                       \r
-                                       double width = x2-x1;\r
-                                       boolean tooWide = devAndLine && (width>2.0);\r
-                                       if (Double.isNaN(min) || Double.isNaN(max) || tooWide || width<=0.0 || y1==y2) {\r
-                                               devNotAttached = true;\r
-                                       } else {\r
-                                               if (devNotAttached) {\r
-                                                       dev.addRectangle(x1, x2, y1, y2);\r
-                                               } else {\r
-                                                       dev.extendRectangle(x1);\r
-                                                       dev.addRectangle(x1, x2, y1, y2);\r
-                                               }\r
-                                               devNotAttached = false;\r
-                                       }\r
-                               }\r
-                       }\r
-                                       \r
-                       // Binary signal\r
-                       if (item.renderer == Renderer.Binary) {\r
-                               byte value = 0;\r
-                               if (vb.getValueBinding() instanceof BooleanBinding) {\r
-                                       value = ((Boolean) vb.getValue(Bindings.BOOLEAN)) ? (byte)0 : (byte)1;\r
-                               } else if (vb.getValueBinding() instanceof ByteBinding) {\r
-                                       value = ((Byte) vb.getValue(Bindings.BYTE));\r
-                               } else if (vb.hasMax()) {\r
-                                       value = (Byte) vb.getMax(Bindings.BYTE);\r
-                               } else {\r
-                                       value = (Byte) vb.getValue(Bindings.BYTE);\r
-                               }\r
-                               pt.setLocation(t1, value==1 ? BINARY[1] : BINARY[0]);\r
-                               at.transform(pt, pt);\r
-                               pt2.setLocation(t2, BINARY[2]);\r
-                               at.transform(pt2, pt2);\r
-                               double x1 = pt.x;\r
-                               double x2 = pt2.x;\r
-                               double y1 = pt.y;\r
-                               double y2 = pt2.y;\r
-                               dev.extendRectangle(x1);\r
-                               dev.addRectangle(x1, x2, y1, y2);\r
-                               devNotAttached = false;                                 \r
-                       }\r
-                                       \r
-                       // Already over => break\r
-                       if(wentOver) break;\r
-                       \r
-                       // Out of range\r
-                       if (t2>=end) {\r
-                               wentOver = true;\r
-                       }\r
-               }       \r
-               \r
-       }\r
-       \r
-       public boolean readMinMaxFromEnd() {\r
-               if (disabled) return false;\r
-               HistoryManager history = getTrendNode().historian;\r
-\r
-               boolean hasVariable = !item.variableId.isEmpty() && !item.groupItemId.isEmpty() && !item.groupId.isEmpty();\r
-               boolean canReadMinMax = minmaxItem != null;\r
-               boolean manualScale = item.scale instanceof Scale.Manual;\r
-               \r
-               if ( !hasVariable ) {\r
-                       min = 0;\r
-                       max = 1;\r
-                       from = Double.NaN;\r
-                       end = Double.NaN;\r
-                       return false;\r
-               }\r
-               \r
-               try {\r
-                       if (canReadMinMax && !manualScale) {            \r
-                               String id = (String) minmaxItem.getFieldUnchecked("id");\r
-                               StreamAccessor sa = history.openStream(id, "r");\r
-                               if ( sa==null ) {\r
-                                       min = 0;\r
-                                       max = 1;\r
-                                       from = Double.NaN;\r
-                                       end = Double.NaN;\r
-                                       return false;\r
-                               } else \r
-                               try {\r
-                                       if (sa.size()==0) return false;\r
-                                       min = Double.MAX_VALUE;\r
-                                       max = -Double.MAX_VALUE;\r
-                                       from = Double.MAX_VALUE;\r
-                                       end = -Double.MAX_VALUE;\r
-                                       for (int i=0; i<sa.size(); i++) {\r
-                                               Binding binding = Bindings.getBinding( sa.type().componentType() );\r
-                                               Object sample = sa.get(i, binding);\r
-                                               ValueBand vb = new ValueBand(binding, sample);\r
-                                               if (!vb.isNullValue() && !vb.isNanSample()) {\r
-                                                       min = Math.min(min, vb.getMinDouble());\r
-                                                       max = Math.max(max, vb.getMaxDouble());\r
-                                               } \r
-                                               if (!vb.isNullValue()) {\r
-                                                       from = Math.min(from, vb.getTimeDouble());\r
-                                                       end = Math.max(end, vb.getEndTimeDouble());\r
-                                               }\r
-                                       }\r
-                                       if ( min==Double.MAX_VALUE || max==-Double.MAX_VALUE) {\r
-                                               min = 0; max = 1.0;\r
-                                       }\r
-                                       if ( from==Double.MAX_VALUE || end==-Double.MAX_VALUE) {\r
-                                               from = 0; end = 100.0;\r
-                                       }\r
-                                       return true;\r
-                               } finally {\r
-                                       try { sa.close(); } catch (AccessorException e) {}\r
-                               } \r
-                       } else {\r
-                               \r
-                               if (manualScale) {\r
-                                       Scale.Manual ms = (Scale.Manual) item.scale;\r
-                                       min = ms.min;\r
-                                       max = ms.max;\r
-                               } \r
-                               \r
-                               // Read from, end from any stream\r
-                               if (openStreamAccessor==null) {\r
-                                       openStream(1);\r
-                               } \r
-                               if (openStreamAccessor!=null){\r
-                                       // Open some stream\r
-                                       StreamAccessor sa = openStreamAccessor;\r
-                                       sa.reset();\r
-                                       int count = sa.size();\r
-                                       if (count>0) {\r
-                                               Binding binding = Bindings.getBinding( sa.type().componentType() );\r
-                                               Object sample = sa.get(0, binding);\r
-                                               ValueBand vb = new ValueBand(binding, sample);\r
-                                               from = vb.getTimeDouble();\r
-                                               sa.get(count-1, binding, sample);\r
-                                               end = vb.hasEndTime() ? vb.getEndTimeDouble() : vb.getTimeDouble();\r
-                                       }\r
-                                       return true;\r
-                               } else {\r
-                                       return false;\r
-                               }\r
-                       }\r
-               } catch (AccessorException e) {\r
-                       log.log(Level.FINE, e.toString(), e);\r
-                       return false;\r
-               } catch (HistoryException e) {\r
-                       log.log(Level.FINE, e.toString(), e);\r
-                       return false;\r
-               }\r
-       }\r
-       \r
-       @Override\r
-       public void cleanup() {\r
-               trendNode = null;\r
-               if (openStreamAccessor != null) {\r
-                       try {\r
-                               openStreamAccessor.close();\r
-                       } catch (AccessorException e) {\r
-                       }\r
-                       openStreamAccessor = null;\r
-                       openStreamItem = null;\r
-               }\r
-               super.cleanup();\r
-       }\r
-       \r
-       Bean getFormat(double pixelsPerSecond) {\r
-               Bean result = null;\r
-               if (renderableItems == null)\r
-                       return null;\r
-               \r
-               for (Bean format : renderableItems)\r
-               {\r
-                       double interval = 0.0;\r
-                       try {\r
-                               interval = format.hasField("interval") ? (Double) format.getFieldUnchecked("interval") : 0.0;\r
-                       } catch (RuntimeBindingException e) {\r
-                       } catch (BindingException e) {\r
-                       }\r
-                       if (Double.isNaN( interval ) || interval<=pixelsPerSecond) {\r
-                               result = format;\r
-                       } else {\r
-                               break;\r
-                       }\r
-               }               \r
-               if (result==null) {\r
-                       if ( renderableItems.length == 0 ) {\r
-                               result = null;\r
-                       } else {\r
-                               result = renderableItems[0];\r
-                       }\r
-               }\r
-               \r
-               return result;\r
-       }\r
-       \r
-       public Stream openStream(double pixelsPerSecond) {\r
-               Bean f = getFormat(pixelsPerSecond);\r
-               if (f==openStreamItem) return openStream;\r
-               \r
-               if (openStream != null) {\r
-                       openStream.close();\r
-                       openStreamAccessor = null;\r
-                       openStreamItem = null;\r
-                       openStream = null;\r
-               }\r
-               \r
-               if (disabled) return null;\r
-               \r
-               if (f!=null) {\r
-                       HistoryManager historian = getTrendNode().historian;\r
-                       try {\r
-                               String id = (String) f.getFieldUnchecked("id");\r
-                               openStreamAccessor = historian.openStream(id, "r");\r
-                               if ( openStreamAccessor!=null ) {\r
-                                       openStream = new Stream(openStreamAccessor);\r
-                                       openStreamItem = f;                     \r
-                               } else {\r
-                                       openStream = null;\r
-                               }\r
-                       } catch (HistoryException e) {\r
-                               log.log(Level.FINE, e.toString(), e);\r
-                       }\r
-               }\r
-               \r
-               return openStream;\r
-       }\r
-       \r
-       @Override\r
-       public void render(Graphics2D g2d) {\r
-       }\r
-\r
-       @Override\r
-       public Rectangle2D getBoundsInLocal() {\r
-               return null;\r
-       }\r
-       \r
-       TrendNode getTrendNode() {\r
-               return (TrendNode) getParent();\r
-       }\r
-       \r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2011 Association for Decentralized Information Management in
+ * Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.trend.impl;
+
+import java.awt.AlphaComposite;
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.Arrays;
+import java.util.List;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.simantics.databoard.Bindings;
+import org.simantics.databoard.accessor.StreamAccessor;
+import org.simantics.databoard.accessor.error.AccessorException;
+import org.simantics.databoard.binding.Binding;
+import org.simantics.databoard.binding.BooleanBinding;
+import org.simantics.databoard.binding.ByteBinding;
+import org.simantics.databoard.binding.error.BindingException;
+import org.simantics.databoard.binding.error.RuntimeBindingException;
+import org.simantics.databoard.type.BooleanType;
+import org.simantics.databoard.type.Datatype;
+import org.simantics.databoard.type.NumberType;
+import org.simantics.databoard.type.RecordType;
+import org.simantics.databoard.util.Bean;
+import org.simantics.history.HistoryException;
+import org.simantics.history.HistoryItem;
+import org.simantics.history.HistoryManager;
+import org.simantics.history.ItemManager;
+import org.simantics.history.util.Stream;
+import org.simantics.history.util.ValueBand;
+import org.simantics.history.util.subscription.SamplingFormat;
+import org.simantics.scenegraph.g2d.G2DNode;
+import org.simantics.trend.configuration.LineQuality;
+import org.simantics.trend.configuration.Scale;
+import org.simantics.trend.configuration.TrendItem;
+import org.simantics.trend.configuration.TrendItem.DrawMode;
+import org.simantics.trend.configuration.TrendItem.Renderer;
+import org.simantics.trend.util.KvikDeviationBuilder;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Data node for a TrendItem
+ * 
+ * @author toni.kalajainen
+ */
+public class ItemNode extends G2DNode implements TrendLayout {
+
+       private static final long serialVersionUID = -4741446944761752871L;
+       private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(ItemNode.class);
+       
+       public static final AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .61f);
+
+       public TrendItem item;
+       VertRuler ruler;
+       TrendNode trendNode;
+
+       /** History Items */
+       Bean historyItems[];
+       
+       /** A list of items that is ordered in by interval value, starting from the lowest interval */
+       Bean[] renderableItems = new Bean[0];
+       
+       /** Format for the minmax stream, or null */  
+       Bean minmaxItem;
+       
+       /** Known item min->max value range, and from->end time range */
+       double from=Double.NaN, end=Double.NaN, min=0, max=1;
+
+       BasicStroke stroke;
+       Color color;
+       
+       Stream openStream;
+       StreamAccessor openStreamAccessor;
+       Bean openStreamItem;
+       
+       Logger log = Logger.getLogger( this.getClass().getName() );     
+       
+       boolean disabled = false;
+       Point2D.Double pt = new Point2D.Double();
+       Point2D.Double pt2 = new Point2D.Double();
+       
+       // Cached shapes
+       KvikDeviationBuilder dev = new KvikDeviationBuilder();
+       Path2D.Double line = new Path2D.Double();
+       
+       /**
+        * Set trend item and initialize history
+        * @param ti
+        * @param items all items in history
+        */
+       public void setTrendItem(TrendItem ti, ItemManager items) {
+               if (openStream!=null) {
+                       openStream.close();
+                       openStream = null;
+                       openStreamAccessor = null;
+                       openStreamItem = null;
+               }
+               this.item = ti;
+               this.minmaxItem = null;
+               this.renderableItems = new HistoryItem[0];
+               disabled = true;
+               if (ti==null) return;
+               
+               try {
+                       List<Bean> trendItems = items.search("groupId", ti.groupId, "groupItemId", ti.groupItemId, "variableId", ti.variableId);
+                       this.historyItems = trendItems.toArray( new Bean[trendItems.size()] );
+                       Arrays.sort( this.historyItems, SamplingFormat.INTERVAL_COMPARATOR );
+                       
+                       // Read renderable formats, and minmax format
+                       TreeSet<Bean> streamFormats = new TreeSet<Bean>( SamplingFormat.INTERVAL_COMPARATOR );
+                       for (Bean item : trendItems) {
+                               SamplingFormat format = new SamplingFormat();
+                               format.readAvailableFields(item);
+                               RecordType rt = (RecordType) format.format;
+                               Boolean enabled = (Boolean) item.getField("enabled");
+                               if (!enabled) continue;
+                                       
+                               boolean isMinMaxFormat = format.interval==Double.MAX_VALUE &&
+                                               format.deadband==Double.MAX_VALUE &&
+                                               rt.getComponentIndex2("min")>=0 &&
+                                               rt.getComponentIndex2("max")>=0;
+       
+                               if (isMinMaxFormat) {
+                                       this.minmaxItem = item;
+                               } else {
+                                       streamFormats.add(item);
+                               }                                       
+                       }
+                       if (streamFormats.isEmpty()) return;
+                       
+                       renderableItems = streamFormats.toArray( new Bean[streamFormats.size()] );
+                       disabled = false;
+               } catch (BindingException e) {
+                       throw new RuntimeException( e );
+               }
+       }
+
+       /**
+        * Draw to graphics context as time,value pairs are.
+        * 
+        * Phases, 0-Init data, 1-Draw deviation, 2-Draw line, 3-Cleanup 
+        * 
+        * @param g
+        * @param phase 
+        */
+       public void draw(Graphics2D g, int phase, boolean bold) {
+               boolean devAndLine = 
+                               item.drawMode==DrawMode.DeviationAndAverage || 
+                               item.drawMode==DrawMode.DeviationAndLine || 
+                               item.drawMode==DrawMode.DeviationAndSample;
+               
+               // Draw deviation
+               Object newQuality = getTrendNode().printing||getTrendNode().quality.lineQuality==LineQuality.Antialias?RenderingHints.VALUE_ANTIALIAS_ON:RenderingHints.VALUE_ANTIALIAS_OFF;
+               if (phase == 1) {                       
+                       g.setColor(color);
+                       if (!dev.isEmpty()) {
+                               Object old = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
+                               g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, newQuality);
+
+                               if (item.renderer == Renderer.Binary) {
+                                       dev.drawRectangles(g);
+                               } 
+                               
+                               if (item.renderer == Renderer.Analog) {
+                                       if (devAndLine) {
+                                               // Draw opaque
+                                               Composite c = g.getComposite();
+                                               g.setComposite( composite );
+                                               dev.drawRectangles(g);
+                                               g.setComposite( c );
+                                       } else if (item.drawMode == DrawMode.Deviation) {
+                                               // Draw solid
+                                               dev.drawRectangles(g);
+                                       }
+                               }
+                   g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, old);
+                       }                       
+               }
+                       
+               // Draw line
+               if (phase == 2) {
+                       g.setColor(color);
+                       Object old = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
+                       g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, newQuality);
+                       g.draw(line);                   
+            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, old);
+               }
+               
+       }
+       
+       private boolean validSample(ValueBand band) throws HistoryException {
+               if(band.getTimeDouble() > 1e50) return false;
+               if(band.getTimeDouble() > band.getEndTimeDouble()) return false;
+//             if(band.getTimeDouble() < 0) return false;
+//             if(band.getEndTimeDouble() < 0) return false;
+               //if(band.getEndTimeDouble()-band.getTimeDouble() < 1e-9) return false;
+               return true;
+       }
+       
+       // Draw state needed for clipping
+       double currentX;
+       double currentY;
+       double minX;
+       double maxX;
+       
+       void moveTo(double x, double y) {
+               line.moveTo(x, y);
+               currentX = x;
+               currentY = y;
+       }
+
+       void lineToOrdered(double x, double y) {
+               
+               if(x < minX) return;
+               if(currentX > maxX) return;
+               
+               // We have something to draw
+               
+               // Clip left
+               if(currentX < minX) {
+                       double ny = currentY + (y-currentY) * (minX-currentX) / (x-currentX);
+                       line.moveTo(minX, ny);
+                       currentX = minX;
+                       currentY = ny;
+               }
+
+               // Right clip
+               if(x > maxX) {
+                       double ny = currentY + (y-currentY) * (maxX-currentX) / (x-currentX);
+                       line.lineTo(maxX, ny);
+                       line.moveTo(x, y);              
+               } else {
+                       if (x != currentX || y != currentY) {
+                               line.lineTo(x, y);
+                       }
+               }
+               
+       }
+       
+       void lineTo(double x, double y) {
+
+               // First assert ordering - if samples are in wrong order draw nothing
+               if(currentX <= x) {
+                       lineToOrdered(x, y);
+               } else {
+                       line.moveTo(x, y);
+               }
+               
+               currentX = x;
+               currentY = y;
+               
+       }
+       
+
+       /**
+        * This method prepares line and deviation shapes.
+        * @param from
+        * @param end
+        * @param secondsPerPixel
+        * @param at 
+        * @throws HistoryException 
+        * @throws AccessorException 
+        */
+       public void prepareLine(double from, double end, double pixelsPerSecond, AffineTransform at) throws HistoryException, AccessorException
+       {
+//             boolean devAndLine = 
+//                             item.drawMode==DrawMode.DeviationAndAverage || 
+//                             item.drawMode==DrawMode.DeviationAndLine || 
+//                             item.drawMode==DrawMode.DeviationAndSample;
+//             boolean deviationEnabled = devAndLine || 
+//                             item.drawMode == DrawMode.Deviation;
+
+               boolean devAndLine = false;
+               boolean deviationEnabled = false;
+               
+               // Collect data
+               dev.reset();
+               line.reset();
+               Stream s = openStream(pixelsPerSecond);
+               if (s==null) return;
+               ValueBand vb = new ValueBand(s.sampleBinding, s.sampleBinding.createDefaultUnchecked());
+               boolean hasDeviation = vb.hasMin() && vb.hasMax();
+               boolean drawDeviation = hasDeviation & deviationEnabled;
+               
+               Datatype valueType = vb.getValueBinding().type();
+               s.reset();
+               if ( valueType instanceof BooleanType == false && valueType instanceof NumberType == false ) return;
+                               
+               int start = s.binarySearch(Bindings.DOUBLE, from);
+               int count = s.count();
+               if (start<0) start = -2-start;
+               if (start<0) start = 0;
+                               
+               // moveTo, on next draw, if true
+               boolean lineNotAttached = true; // =next draw should be move to
+               boolean devNotAttached = true;
+               
+               currentX = Double.NaN;
+               currentY = Double.NaN;
+
+               pt.setLocation(from, 0);
+               at.transform(pt, pt);
+               minX = pt.x;
+               
+               pt.setLocation(end, 0);
+               at.transform(pt, pt);
+               maxX = pt.x;
+                               
+               s.reset();
+               
+               boolean wentOver = false;
+               
+               for (int i=start; i<count; i++) {
+                       // Read sample
+                       s.accessor.get(i, s.sampleBinding, vb.getSample());
+                       
+                       if(!validSample(vb)) {
+                               LOGGER.warn("-invalid value band: " + i + "/" + count + ":" + vb);
+                               continue;
+                       }
+                       // Non-continuation point
+                       if (vb.hasQuality()) {
+                               Byte b = (Byte) vb.getQuality(Bindings.BYTE);
+                               boolean noncontinuation = b.equals( ValueBand.QUALITY_NOVALUE ); 
+                               if ( noncontinuation ) {
+                                       lineNotAttached = true;
+                                       devNotAttached = true;
+                                       continue;
+                               }
+                       }
+                                       
+                       // Line
+                       double t1 = vb.getTimeDouble();
+                       double t2 = vb.hasEndTime() ? vb.getEndTimeDouble() : t1;
+                                       
+                       // Analog signal
+                       if (item.renderer == Renderer.Analog) {
+                                               
+                               // Add points to line
+                               if (item.drawMode == DrawMode.Deviation && hasDeviation) {
+                                       // ...
+                               } else {
+                                       
+                                       double yy = Double.NaN;
+//                                     boolean showBand = true;
+                                       boolean flat = false;
+
+                                       if( trendNode != null && trendNode.drawSamples ) {
+                                               yy = vb.getValueDouble();
+                                               flat = true;
+                                       } else {
+                                               yy = vb.getValueDouble();
+                                               // Only render the last band
+//                                             if(i < count-1) showBand = false;                                       
+                                       }
+                                       
+//                                     if (item.drawMode == DrawMode.DeviationAndAverage) {
+//                                             yy = vb.hasMedian() ? vb.getMedianDouble() : ( vb.hasAvg() ? vb.getAvgDouble() : vb.getValueDouble() );
+//                                     } else if (item.drawMode == DrawMode.Average || item.drawMode == DrawMode.DeviationAndAverage) {
+//                                             yy = vb.hasAvg() ? vb.getAvgDouble() : vb.getValueDouble();
+//                                     } else if (item.drawMode == DrawMode.Line || item.drawMode == DrawMode.DeviationAndLine) {
+//                                             yy = vb.getValueDouble();
+//                                             // Only render the last band
+//                                             if(i < count-1) showBand = false;                                       
+//                                     } else /* if (item.drawMode == DrawMode.Sample || item.drawMode == DrawMode.DeviationAndSample) */ {
+//                                             yy = vb.getValueDouble();
+//                                             flat = true;
+//                                     } 
+                       
+                                       if ( Double.isNaN(yy) ) {
+                                               lineNotAttached = true;
+                                       } else {
+//                                             pt.setLocation(t1, yy);
+//                                             at.transform(pt, pt);
+//                                             if ( t1==t2 ) {
+//                                                     if (lineNotAttached) {
+//                                                             moveTo(pt.getX(), pt.getY());
+//                                                     } else {
+//                                                             if(flat) {
+//                                                                     
+//                                                             } else {
+//                                                                     lineTo(pt.getX(), pt.getY());
+//                                                             }
+//                                                     }
+//                                             } else {
+                                                       // Static variables may have months data that consists of single value
+                                                       // When zoomed in, the single line may be drawn from -1e10, ... 1e10
+                                                       // This is too much for Graphics2D, it refuses to draw.
+//                                                     if (t1<from) {
+//                                                             t1 = from;
+//                                                     }
+//                                                     if (t2>end) {
+//                                                             t2 = end;
+//                                                     }
+                                                                       
+                                                       pt.setLocation(t1, yy);
+                                                       at.transform(pt, pt);
+                                                       pt2.setLocation(t2, yy);
+                                                       at.transform(pt2, pt2);
+
+                                                       double x1 = pt.x, y1 = pt.y, x2 = pt2.x, y2 = pt2.y;
+
+                                                       if (lineNotAttached) {
+                                                               moveTo(x1, y1);
+                                                       } else {
+                                                               if(flat) {
+                                                                       lineTo(x1, currentY);
+                                                                       lineTo(x1, y1);
+                                                               } else {
+                                                                       lineTo(x1, y1);
+                                                               }
+                                                       }
+
+                                                       if (flat)
+                                                               lineTo(x2, y2);
+                                                       
+//                                                     if(showBand) {
+//                                                             lineTo(x2, y2);
+//                                                     }
+                                                       
+//                                             }
+                                               lineNotAttached = false;                                        
+                                       }
+                               }
+                                               
+                               if (drawDeviation) {
+                                       double min = vb.getMinDouble();
+                                       double max = vb.getMaxDouble();
+
+                                       pt.setLocation(t1, min);
+                                       at.transform(pt, pt);
+                                       pt2.setLocation(t2, max);
+                                       at.transform(pt2, pt2);
+                                       double x1 = pt.x;
+                                       double x2 = pt2.x;
+                                       double y1 = pt.y;
+                                       double y2 = pt2.y;
+                                                       
+                                       double width = x2-x1;
+                                       boolean tooWide = devAndLine && (width>2.0);
+                                       if (Double.isNaN(min) || Double.isNaN(max) || tooWide || width<=0.0 || y1==y2) {
+                                               devNotAttached = true;
+                                       } else {
+                                               if (devNotAttached) {
+                                                       dev.addRectangle(x1, x2, y1, y2);
+                                               } else {
+                                                       dev.extendRectangle(x1);
+                                                       dev.addRectangle(x1, x2, y1, y2);
+                                               }
+                                               devNotAttached = false;
+                                       }
+                               }
+                       }
+                                       
+                       // Binary signal
+                       else if (item.renderer == Renderer.Binary) {
+                               byte value = 0;
+                               if (vb.getValueBinding() instanceof BooleanBinding) {
+                                       value = ((Boolean) vb.getValue(Bindings.BOOLEAN)) ? (byte)0 : (byte)1;
+                               } else if (vb.getValueBinding() instanceof ByteBinding) {
+                                       value = ((Byte) vb.getValue(Bindings.BYTE));
+                               } else if (vb.hasMax()) {
+                                       value = (Byte) vb.getMax(Bindings.BYTE);
+                               } else {
+                                       value = (Byte) vb.getValue(Bindings.BYTE);
+                               }
+                               pt.setLocation(t1, value==1 ? BINARY[1] : BINARY[0]);
+                               at.transform(pt, pt);
+                               pt2.setLocation(t2, BINARY[2]);
+                               at.transform(pt2, pt2);
+                               double x1 = pt.x;
+                               double x2 = pt2.x;
+                               double y1 = pt.y;
+                               double y2 = pt2.y;
+                               dev.extendRectangle(x1);
+                               dev.addRectangle(x1, x2, y1, y2);
+                               devNotAttached = false;                                 
+                       }
+                                       
+                       // Already over => break
+                       if(wentOver) break;
+                       
+                       // Out of range
+                       if (t2>=end) {
+                               wentOver = true;
+                       }
+               }       
+               
+       }
+       
+       public boolean readMinMaxFromEnd() {
+               if (disabled) return false;
+               HistoryManager history = getTrendNode().historian;
+
+               boolean hasVariable = !item.variableId.isEmpty() && !item.groupItemId.isEmpty() && !item.groupId.isEmpty();
+               boolean canReadMinMax = minmaxItem != null;
+               boolean manualScale = item.scale instanceof Scale.Manual;
+               
+               if ( !hasVariable ) {
+                       min = 0;
+                       max = 1;
+                       from = Double.NaN;
+                       end = Double.NaN;
+                       return false;
+               }
+               
+               try {
+                       if (canReadMinMax && !manualScale) {            
+                               String id = (String) minmaxItem.getFieldUnchecked("id");
+                               StreamAccessor sa = history.openStream(id, "r");
+                               if ( sa==null ) {
+                                       min = 0;
+                                       max = 1;
+                                       from = Double.NaN;
+                                       end = Double.NaN;
+                                       return false;
+                               } else 
+                               try {
+                                       if (sa.size()==0) return false;
+                                       min = Double.MAX_VALUE;
+                                       max = -Double.MAX_VALUE;
+                                       from = Double.MAX_VALUE;
+                                       end = -Double.MAX_VALUE;
+                                       for (int i=0; i<sa.size(); i++) {
+                                               Binding binding = Bindings.getBinding( sa.type().componentType() );
+                                               Object sample = sa.get(i, binding);
+                                               ValueBand vb = new ValueBand(binding, sample);
+                                               if (!vb.isNullValue() && !vb.isNanSample()) {
+                                                       min = Math.min(min, vb.getMinDouble());
+                                                       max = Math.max(max, vb.getMaxDouble());
+                                               } 
+                                               if (!vb.isNullValue()) {
+                                                       from = Math.min(from, vb.getTimeDouble());
+                                                       end = Math.max(end, vb.getEndTimeDouble());
+                                               }
+                                       }
+                                       if ( min==Double.MAX_VALUE || max==-Double.MAX_VALUE) {
+                                               min = 0; max = 1.0;
+                                       }
+                                       if ( from==Double.MAX_VALUE || end==-Double.MAX_VALUE) {
+                                               from = 0; end = 100.0;
+                                       }
+                                       return true;
+                               } finally {
+                                       try { sa.close(); } catch (AccessorException e) {}
+                               } 
+                       } else {
+                               
+                               if (manualScale) {
+                                       Scale.Manual ms = (Scale.Manual) item.scale;
+                                       min = ms.min;
+                                       max = ms.max;
+                               } 
+                               
+                               // Read from, end from any stream
+                               if (openStreamAccessor==null) {
+                                       openStream(1);
+                               } 
+                               if (openStreamAccessor!=null){
+                                       // Open some stream
+                                       StreamAccessor sa = openStreamAccessor;
+                                       sa.reset();
+                                       int count = sa.size();
+                                       if (count>0) {
+                                               Binding binding = Bindings.getBinding( sa.type().componentType() );
+                                               Object sample = sa.get(0, binding);
+                                               ValueBand vb = new ValueBand(binding, sample);
+                                               from = vb.getTimeDouble();
+                                               sa.get(count-1, binding, sample);
+                                               end = vb.hasEndTime() ? vb.getEndTimeDouble() : vb.getTimeDouble();
+                                       }
+                                       return true;
+                               } else {
+                                       return false;
+                               }
+                       }
+               } catch (AccessorException e) {
+                       log.log(Level.FINE, e.toString(), e);
+                       return false;
+               } catch (HistoryException e) {
+                       log.log(Level.FINE, e.toString(), e);
+                       return false;
+               }
+       }
+       
+       @Override
+       public void cleanup() {
+               trendNode = null;
+               if (openStreamAccessor != null) {
+                       try {
+                               openStreamAccessor.close();
+                       } catch (AccessorException e) {
+                       }
+                       openStreamAccessor = null;
+                       openStreamItem = null;
+               }
+               super.cleanup();
+       }
+       
+       Bean getFormat(double pixelsPerSecond) {
+               Bean result = null;
+               if (renderableItems == null)
+                       return null;
+               
+               for (Bean format : renderableItems)
+               {
+                       double interval = 0.0;
+                       try {
+                               interval = format.hasField("interval") ? (Double) format.getFieldUnchecked("interval") : 0.0;
+                       } catch (RuntimeBindingException e) {
+                       } catch (BindingException e) {
+                       }
+                       if (Double.isNaN( interval ) || interval<=pixelsPerSecond) {
+                               result = format;
+                       } else {
+                               break;
+                       }
+               }               
+               if (result==null) {
+                       if ( renderableItems.length == 0 ) {
+                               result = null;
+                       } else {
+                               result = renderableItems[0];
+                       }
+               }
+               
+               return result;
+       }
+       
+       public Stream openStream(double pixelsPerSecond) {
+               Bean f = getFormat(pixelsPerSecond);
+               if (f==openStreamItem) return openStream;
+               
+               if (openStream != null) {
+                       openStream.close();
+                       openStreamAccessor = null;
+                       openStreamItem = null;
+                       openStream = null;
+               }
+               
+               if (disabled) return null;
+               
+               if (f!=null) {
+                       HistoryManager historian = getTrendNode().historian;
+                       try {
+                               String id = (String) f.getFieldUnchecked("id");
+                               openStreamAccessor = historian.openStream(id, "r");
+                               if ( openStreamAccessor!=null ) {
+                                       openStream = new Stream(openStreamAccessor);
+                                       openStreamItem = f;                     
+                               } else {
+                                       openStream = null;
+                               }
+                       } catch (HistoryException e) {
+                               log.log(Level.FINE, e.toString(), e);
+                       }
+               }
+               
+               return openStream;
+       }
+       
+       @Override
+       public void render(Graphics2D g2d) {
+       }
+
+       @Override
+       public Rectangle2D getBoundsInLocal() {
+               return null;
+       }
+       
+       TrendNode getTrendNode() {
+               return (TrendNode) getParent();
+       }
+       
+}