1 /*******************************************************************************
2 * Copyright (c) 2007, 2011 Association for Decentralized Information Management in
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.trend.impl;
14 import java.awt.AlphaComposite;
15 import java.awt.BasicStroke;
16 import java.awt.Color;
17 import java.awt.Composite;
18 import java.awt.Graphics2D;
19 import java.awt.RenderingHints;
20 import java.awt.geom.AffineTransform;
21 import java.awt.geom.Path2D;
22 import java.awt.geom.Point2D;
23 import java.awt.geom.Rectangle2D;
24 import java.util.Arrays;
25 import java.util.List;
26 import java.util.TreeSet;
27 import java.util.logging.Level;
28 import java.util.logging.Logger;
30 import org.simantics.databoard.Bindings;
31 import org.simantics.databoard.accessor.StreamAccessor;
32 import org.simantics.databoard.accessor.error.AccessorException;
33 import org.simantics.databoard.binding.Binding;
34 import org.simantics.databoard.binding.BooleanBinding;
35 import org.simantics.databoard.binding.ByteBinding;
36 import org.simantics.databoard.binding.error.BindingException;
37 import org.simantics.databoard.binding.error.RuntimeBindingException;
38 import org.simantics.databoard.type.BooleanType;
39 import org.simantics.databoard.type.Datatype;
40 import org.simantics.databoard.type.NumberType;
41 import org.simantics.databoard.type.RecordType;
42 import org.simantics.databoard.util.Bean;
43 import org.simantics.history.HistoryException;
44 import org.simantics.history.HistoryItem;
45 import org.simantics.history.HistoryManager;
46 import org.simantics.history.ItemManager;
47 import org.simantics.history.util.Stream;
48 import org.simantics.history.util.ValueBand;
49 import org.simantics.history.util.subscription.SamplingFormat;
50 import org.simantics.scenegraph.g2d.G2DNode;
51 import org.simantics.trend.configuration.LineQuality;
52 import org.simantics.trend.configuration.Scale;
53 import org.simantics.trend.configuration.TrendItem;
54 import org.simantics.trend.configuration.TrendItem.DrawMode;
55 import org.simantics.trend.configuration.TrendItem.Renderer;
56 import org.simantics.trend.util.KvikDeviationBuilder;
57 import org.slf4j.LoggerFactory;
60 * Data node for a TrendItem
62 * @author toni.kalajainen
64 public class ItemNode extends G2DNode implements TrendLayout {
66 private static final long serialVersionUID = -4741446944761752871L;
67 private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(ItemNode.class);
69 public static final AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .61f);
71 public TrendItem item;
78 /** A list of items that is ordered in by interval value, starting from the lowest interval */
79 Bean[] renderableItems = new Bean[0];
81 /** Format for the minmax stream, or null */
84 /** Known item min->max value range, and from->end time range */
85 double from=Double.NaN, end=Double.NaN, min=0, max=1;
91 StreamAccessor openStreamAccessor;
94 Logger log = Logger.getLogger( this.getClass().getName() );
96 boolean disabled = false;
97 Point2D.Double pt = new Point2D.Double();
98 Point2D.Double pt2 = new Point2D.Double();
101 KvikDeviationBuilder dev = new KvikDeviationBuilder();
102 Path2D.Double line = new Path2D.Double();
105 * Set trend item and initialize history
107 * @param items all items in history
109 public void setTrendItem(TrendItem ti, ItemManager items) {
110 if (openStream!=null) {
113 openStreamAccessor = null;
114 openStreamItem = null;
117 this.minmaxItem = null;
118 this.renderableItems = new HistoryItem[0];
120 if (ti==null) return;
123 List<Bean> trendItems = items.search("groupId", ti.groupId, "groupItemId", ti.groupItemId, "variableId", ti.variableId);
124 this.historyItems = trendItems.toArray( new Bean[trendItems.size()] );
125 Arrays.sort( this.historyItems, SamplingFormat.INTERVAL_COMPARATOR );
127 // Read renderable formats, and minmax format
128 TreeSet<Bean> streamFormats = new TreeSet<Bean>( SamplingFormat.INTERVAL_COMPARATOR );
129 for (Bean item : trendItems) {
130 SamplingFormat format = new SamplingFormat();
131 format.readAvailableFields(item);
132 RecordType rt = (RecordType) format.format;
133 Boolean enabled = (Boolean) item.getField("enabled");
134 if (!enabled) continue;
136 boolean isMinMaxFormat = format.interval==Double.MAX_VALUE &&
137 format.deadband==Double.MAX_VALUE &&
138 rt.getComponentIndex2("min")>=0 &&
139 rt.getComponentIndex2("max")>=0;
141 if (isMinMaxFormat) {
142 this.minmaxItem = item;
144 streamFormats.add(item);
147 if (streamFormats.isEmpty()) return;
149 renderableItems = streamFormats.toArray( new Bean[streamFormats.size()] );
151 } catch (BindingException e) {
152 throw new RuntimeException( e );
157 * Draw to graphics context as time,value pairs are.
159 * Phases, 0-Init data, 1-Draw deviation, 2-Draw line, 3-Cleanup
164 public void draw(Graphics2D g, int phase, boolean bold) {
166 item.drawMode==DrawMode.DeviationAndAverage ||
167 item.drawMode==DrawMode.DeviationAndLine ||
168 item.drawMode==DrawMode.DeviationAndSample;
171 Object newQuality = getTrendNode().printing||getTrendNode().quality.lineQuality==LineQuality.Antialias?RenderingHints.VALUE_ANTIALIAS_ON:RenderingHints.VALUE_ANTIALIAS_OFF;
174 if (!dev.isEmpty()) {
175 Object old = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
176 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, newQuality);
178 if (item.renderer == Renderer.Binary) {
179 dev.drawRectangles(g);
182 if (item.renderer == Renderer.Analog) {
185 Composite c = g.getComposite();
186 g.setComposite( composite );
187 dev.drawRectangles(g);
189 } else if (item.drawMode == DrawMode.Deviation) {
191 dev.drawRectangles(g);
194 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, old);
201 Object old = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
202 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, newQuality);
204 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, old);
209 private boolean validSample(ValueBand band) throws HistoryException {
210 if(band.getTimeDouble() > 1e50) return false;
211 if(band.getTimeDouble() > band.getEndTimeDouble()) return false;
212 // if(band.getTimeDouble() < 0) return false;
213 // if(band.getEndTimeDouble() < 0) return false;
214 //if(band.getEndTimeDouble()-band.getTimeDouble() < 1e-9) return false;
218 // Draw state needed for clipping
224 void moveTo(double x, double y) {
230 void lineToOrdered(double x, double y) {
233 if(currentX > maxX) return;
235 // We have something to draw
238 if(currentX < minX) {
239 double ny = currentY + (y-currentY) * (minX-currentX) / (x-currentX);
240 line.moveTo(minX, ny);
247 double ny = currentY + (y-currentY) * (maxX-currentX) / (x-currentX);
248 line.lineTo(maxX, ny);
251 if (x != currentX || y != currentY) {
258 void lineTo(double x, double y) {
260 // First assert ordering - if samples are in wrong order draw nothing
274 * This method prepares line and deviation shapes.
277 * @param secondsPerPixel
279 * @throws HistoryException
280 * @throws AccessorException
282 public void prepareLine(double from, double end, double pixelsPerSecond, AffineTransform at) throws HistoryException, AccessorException
284 // boolean devAndLine =
285 // item.drawMode==DrawMode.DeviationAndAverage ||
286 // item.drawMode==DrawMode.DeviationAndLine ||
287 // item.drawMode==DrawMode.DeviationAndSample;
288 // boolean deviationEnabled = devAndLine ||
289 // item.drawMode == DrawMode.Deviation;
291 boolean devAndLine = false;
292 boolean deviationEnabled = false;
297 Stream s = openStream(pixelsPerSecond);
299 ValueBand vb = new ValueBand(s.sampleBinding, s.sampleBinding.createDefaultUnchecked());
300 boolean hasDeviation = vb.hasMin() && vb.hasMax();
301 boolean drawDeviation = hasDeviation & deviationEnabled;
303 Datatype valueType = vb.getValueBinding().type();
305 if ( valueType instanceof BooleanType == false && valueType instanceof NumberType == false ) return;
307 int start = s.binarySearch(Bindings.DOUBLE, from);
308 int count = s.count();
309 if (start<0) start = -2-start;
310 if (start<0) start = 0;
312 // moveTo, on next draw, if true
313 boolean lineNotAttached = true; // =next draw should be move to
314 boolean devNotAttached = true;
316 currentX = Double.NaN;
317 currentY = Double.NaN;
319 pt.setLocation(from, 0);
320 at.transform(pt, pt);
323 pt.setLocation(end, 0);
324 at.transform(pt, pt);
329 boolean wentOver = false;
331 for (int i=start; i<count; i++) {
333 s.accessor.get(i, s.sampleBinding, vb.getSample());
335 if(!validSample(vb)) {
336 LOGGER.warn("-invalid value band: " + i + "/" + count + ":" + vb);
339 // Non-continuation point
340 if (vb.hasQuality()) {
341 Byte b = (Byte) vb.getQuality(Bindings.BYTE);
342 boolean noncontinuation = b.equals( ValueBand.QUALITY_NOVALUE );
343 if ( noncontinuation ) {
344 lineNotAttached = true;
345 devNotAttached = true;
351 double t1 = vb.getTimeDouble();
352 double t2 = vb.hasEndTime() ? vb.getEndTimeDouble() : t1;
355 if (item.renderer == Renderer.Analog) {
357 // Add points to line
358 if (item.drawMode == DrawMode.Deviation && hasDeviation) {
362 double yy = Double.NaN;
363 // boolean showBand = true;
364 boolean flat = false;
366 if( trendNode != null && trendNode.drawSamples ) {
367 yy = vb.getValueDouble();
370 yy = vb.getValueDouble();
371 // Only render the last band
372 // if(i < count-1) showBand = false;
375 // if (item.drawMode == DrawMode.DeviationAndAverage) {
376 // yy = vb.hasMedian() ? vb.getMedianDouble() : ( vb.hasAvg() ? vb.getAvgDouble() : vb.getValueDouble() );
377 // } else if (item.drawMode == DrawMode.Average || item.drawMode == DrawMode.DeviationAndAverage) {
378 // yy = vb.hasAvg() ? vb.getAvgDouble() : vb.getValueDouble();
379 // } else if (item.drawMode == DrawMode.Line || item.drawMode == DrawMode.DeviationAndLine) {
380 // yy = vb.getValueDouble();
381 // // Only render the last band
382 // if(i < count-1) showBand = false;
383 // } else /* if (item.drawMode == DrawMode.Sample || item.drawMode == DrawMode.DeviationAndSample) */ {
384 // yy = vb.getValueDouble();
388 if ( Double.isNaN(yy) ) {
389 lineNotAttached = true;
391 // pt.setLocation(t1, yy);
392 // at.transform(pt, pt);
394 // if (lineNotAttached) {
395 // moveTo(pt.getX(), pt.getY());
400 // lineTo(pt.getX(), pt.getY());
404 // Static variables may have months data that consists of single value
405 // When zoomed in, the single line may be drawn from -1e10, ... 1e10
406 // This is too much for Graphics2D, it refuses to draw.
414 pt.setLocation(t1, yy);
415 at.transform(pt, pt);
416 pt2.setLocation(t2, yy);
417 at.transform(pt2, pt2);
419 double x1 = pt.x, y1 = pt.y, x2 = pt2.x, y2 = pt2.y;
421 if (lineNotAttached) {
425 lineTo(x1, currentY);
432 // gitlab #35: t2 > t1 ensure that the value band is
433 // rendered as a horizontal line until the the time when
434 // the band's value went out of dead-band.
443 lineNotAttached = false;
448 double min = vb.getMinDouble();
449 double max = vb.getMaxDouble();
451 pt.setLocation(t1, min);
452 at.transform(pt, pt);
453 pt2.setLocation(t2, max);
454 at.transform(pt2, pt2);
460 double width = x2-x1;
461 boolean tooWide = devAndLine && (width>2.0);
462 if (Double.isNaN(min) || Double.isNaN(max) || tooWide || width<=0.0 || y1==y2) {
463 devNotAttached = true;
465 if (devNotAttached) {
466 dev.addRectangle(x1, x2, y1, y2);
468 dev.extendRectangle(x1);
469 dev.addRectangle(x1, x2, y1, y2);
471 devNotAttached = false;
477 else if (item.renderer == Renderer.Binary) {
479 if (vb.getValueBinding() instanceof BooleanBinding) {
480 value = ((Boolean) vb.getValue(Bindings.BOOLEAN)) ? (byte)0 : (byte)1;
481 } else if (vb.getValueBinding() instanceof ByteBinding) {
482 value = ((Byte) vb.getValue(Bindings.BYTE));
483 } else if (vb.hasMax()) {
484 value = (Byte) vb.getMax(Bindings.BYTE);
486 value = (Byte) vb.getValue(Bindings.BYTE);
488 pt.setLocation(t1, value==1 ? BINARY[1] : BINARY[0]);
489 at.transform(pt, pt);
490 pt2.setLocation(t2, BINARY[2]);
491 at.transform(pt2, pt2);
496 dev.extendRectangle(x1);
497 dev.addRectangle(x1, x2, y1, y2);
498 devNotAttached = false;
501 // Already over => break
512 public boolean readMinMaxFromEnd() {
513 if (disabled) return false;
514 HistoryManager history = getTrendNode().historian;
516 boolean hasVariable = !item.variableId.isEmpty() && !item.groupItemId.isEmpty() && !item.groupId.isEmpty();
517 boolean canReadMinMax = minmaxItem != null;
518 boolean manualScale = item.scale instanceof Scale.Manual;
520 if ( !hasVariable ) {
529 if (canReadMinMax && !manualScale) {
530 String id = (String) minmaxItem.getFieldUnchecked("id");
531 StreamAccessor sa = history.openStream(id, "r");
540 if (sa.size()==0) return false;
541 min = Double.MAX_VALUE;
542 max = -Double.MAX_VALUE;
543 from = Double.MAX_VALUE;
544 end = -Double.MAX_VALUE;
545 for (int i=0; i<sa.size(); i++) {
546 Binding binding = Bindings.getBinding( sa.type().componentType() );
547 Object sample = sa.get(i, binding);
548 ValueBand vb = new ValueBand(binding, sample);
549 if (!vb.isNullValue() && !vb.isNanSample()) {
550 min = Math.min(min, vb.getMinDouble());
551 max = Math.max(max, vb.getMaxDouble());
553 if (!vb.isNullValue()) {
554 from = Math.min(from, vb.getTimeDouble());
555 end = Math.max(end, vb.getEndTimeDouble());
558 if ( min==Double.MAX_VALUE || max==-Double.MAX_VALUE) {
561 if ( from==Double.MAX_VALUE || end==-Double.MAX_VALUE) {
562 from = 0; end = 100.0;
566 try { sa.close(); } catch (AccessorException e) {}
571 Scale.Manual ms = (Scale.Manual) item.scale;
576 // Read from, end from any stream
577 if (openStreamAccessor==null) {
580 if (openStreamAccessor!=null){
582 StreamAccessor sa = openStreamAccessor;
584 int count = sa.size();
586 Binding binding = Bindings.getBinding( sa.type().componentType() );
587 Object sample = sa.get(0, binding);
588 ValueBand vb = new ValueBand(binding, sample);
589 from = vb.getTimeDouble();
590 sa.get(count-1, binding, sample);
591 end = vb.hasEndTime() ? vb.getEndTimeDouble() : vb.getTimeDouble();
598 } catch (AccessorException e) {
599 log.log(Level.FINE, e.toString(), e);
601 } catch (HistoryException e) {
602 log.log(Level.FINE, e.toString(), e);
608 public void cleanup() {
610 if (openStreamAccessor != null) {
612 openStreamAccessor.close();
613 } catch (AccessorException e) {
615 openStreamAccessor = null;
616 openStreamItem = null;
621 Bean getFormat(double pixelsPerSecond) {
623 if (renderableItems == null)
626 for (Bean format : renderableItems)
628 double interval = 0.0;
630 interval = format.hasField("interval") ? (Double) format.getFieldUnchecked("interval") : 0.0;
631 } catch (RuntimeBindingException e) {
632 } catch (BindingException e) {
634 if (Double.isNaN( interval ) || interval<=pixelsPerSecond) {
641 if ( renderableItems.length == 0 ) {
644 result = renderableItems[0];
651 public Stream openStream(double pixelsPerSecond) {
652 Bean f = getFormat(pixelsPerSecond);
653 if (f==openStreamItem) return openStream;
655 if (openStream != null) {
657 openStreamAccessor = null;
658 openStreamItem = null;
662 if (disabled) return null;
665 HistoryManager historian = getTrendNode().historian;
667 String id = (String) f.getFieldUnchecked("id");
668 openStreamAccessor = historian.openStream(id, "r");
669 if ( openStreamAccessor!=null ) {
670 openStream = new Stream(openStreamAccessor);
675 } catch (HistoryException e) {
676 log.log(Level.FINE, e.toString(), e);
684 public void render(Graphics2D g2d) {
688 public Rectangle2D getBoundsInLocal() {
692 TrendNode getTrendNode() {
693 return (TrendNode) getParent();