X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.trend%2Fsrc%2Forg%2Fsimantics%2Ftrend%2Fimpl%2FItemNode.java;fp=bundles%2Forg.simantics.trend%2Fsrc%2Forg%2Fsimantics%2Ftrend%2Fimpl%2FItemNode.java;h=f57997e747da60ae289d0ea100f91c0f2602773d;hb=969bd23cab98a79ca9101af33334000879fb60c5;hp=0000000000000000000000000000000000000000;hpb=866dba5cd5a3929bbeae85991796acb212338a08;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.trend/src/org/simantics/trend/impl/ItemNode.java b/bundles/org.simantics.trend/src/org/simantics/trend/impl/ItemNode.java new file mode 100644 index 000000000..f57997e74 --- /dev/null +++ b/bundles/org.simantics.trend/src/org/simantics/trend/impl/ItemNode.java @@ -0,0 +1,688 @@ +/******************************************************************************* + * 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; + +/** + * Data node for a TrendItem + * + * @author toni.kalajainen + */ +public class ItemNode extends G2DNode implements TrendLayout { + + private static final long serialVersionUID = -4741446944761752871L; + + 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 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 streamFormats = new TreeSet( 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 { + 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; iend) { +// 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); + } + } + + 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 + 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; i0) { + 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(); + } + +}