]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.trend/src/org/simantics/trend/impl/TrendNode.java
1d19e2f1775c1df298d31fa864a02ac3c1be4242
[simantics/platform.git] / bundles / org.simantics.trend / src / org / simantics / trend / impl / TrendNode.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2011 Association for Decentralized Information Management in\r
3  * Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.trend.impl;\r
13 \r
14 import java.awt.BasicStroke;\r
15 import java.awt.Color;\r
16 import java.awt.Font;\r
17 import java.awt.Graphics2D;\r
18 import java.awt.Rectangle;\r
19 import java.awt.RenderingHints;\r
20 import java.awt.font.GlyphVector;\r
21 import java.awt.geom.Rectangle2D;\r
22 import java.util.ArrayList;\r
23 import java.util.Collections;\r
24 import java.util.Comparator;\r
25 import java.util.HashMap;\r
26 import java.util.HashSet;\r
27 import java.util.List;\r
28 import java.util.Map;\r
29 import java.util.Set;\r
30 import java.util.TreeSet;\r
31 \r
32 import org.simantics.databoard.Bindings;\r
33 import org.simantics.databoard.accessor.error.AccessorException;\r
34 import org.simantics.databoard.binding.error.BindingException;\r
35 import org.simantics.databoard.util.Bean;\r
36 import org.simantics.g2d.utils.GridUtil;\r
37 import org.simantics.history.Collector;\r
38 import org.simantics.history.History;\r
39 import org.simantics.history.HistoryException;\r
40 import org.simantics.history.HistoryManager;\r
41 import org.simantics.history.ItemManager;\r
42 import org.simantics.history.impl.CollectorImpl;\r
43 import org.simantics.history.util.Stream;\r
44 import org.simantics.history.util.ValueBand;\r
45 import org.simantics.scenegraph.g2d.G2DParentNode;\r
46 import org.simantics.scenegraph.utils.QualityHints;\r
47 import org.simantics.trend.configuration.ItemPlacement;\r
48 import org.simantics.trend.configuration.LineQuality;\r
49 import org.simantics.trend.configuration.Scale;\r
50 import org.simantics.trend.configuration.TimeFormat;\r
51 import org.simantics.trend.configuration.TimeWindow;\r
52 import org.simantics.trend.configuration.TrendItem;\r
53 import org.simantics.trend.configuration.TrendItem.Renderer;\r
54 import org.simantics.trend.configuration.TrendQualitySpec;\r
55 import org.simantics.trend.configuration.TrendSpec;\r
56 import org.simantics.trend.configuration.Viewport;\r
57 import org.simantics.trend.configuration.Viewport.AxisViewport;\r
58 import org.simantics.trend.configuration.YAxisMode;\r
59 import org.simantics.utils.format.ValueFormat;\r
60 \r
61 import gnu.trove.map.TObjectIntMap;\r
62 import gnu.trove.map.hash.TObjectIntHashMap;\r
63 import gnu.trove.procedure.TIntProcedure;\r
64 import gnu.trove.procedure.TObjectProcedure;\r
65 \r
66 public class TrendNode extends G2DParentNode implements TrendLayout {\r
67 \r
68         private static final long serialVersionUID = -8339696405893626168L;\r
69 \r
70         /** Title node */\r
71         public TextNode titleNode;\r
72 \r
73         /** Plot node */\r
74         public Plot plot;\r
75         public HorizRuler horizRuler;\r
76         VertRuler vertRuler;   // Selected vertical ruler, null if there are no analog items\r
77         int vertRulerIndex;    // Index of the selected vertical ruler\r
78         List<VertRuler> vertRulers = new ArrayList<VertRuler>();\r
79         SelectionNode selection;\r
80         public MilestoneSpec milestones;\r
81         \r
82         /** Control bounds */\r
83         Rectangle2D bounds = new Rectangle2D.Double();\r
84         \r
85         /** Trend spec */\r
86         public TrendSpec spec;\r
87         public ViewRenderingProfile renderingProfile = new ViewRenderingProfile();\r
88         public TrendQualitySpec quality = TrendQualitySpec.DEFAULT;\r
89         public boolean printing = false;\r
90         boolean singleAxis;\r
91         \r
92         // Data nodes\r
93         List<ItemNode> analogItems = new ArrayList<ItemNode>();\r
94         List<ItemNode> binaryItems = new ArrayList<ItemNode>();\r
95         List<ItemNode> allItems = new ArrayList<ItemNode>();\r
96 \r
97         // History\r
98         static HistoryManager DUMMY_HISTORY = History.createMemoryHistory();\r
99         public HistoryManager historian = DUMMY_HISTORY;\r
100         \r
101         // Collector - set for flushing the stream, right before drawing\r
102         public Collector collector = null;\r
103         Set<String> itemIds = new HashSet<String>();\r
104         \r
105         // Signal to indicate history has changed.\r
106         public boolean datadirty = false;\r
107         // Signal to indicate the cached shapes are dirty. This will cause reloading of data.\r
108         public boolean shapedirty = false;\r
109         \r
110         public boolean autoscaletime = true;\r
111         \r
112         public TimeFormat timeFormat = TimeFormat.Time;\r
113         public ValueFormat valueFormat = ValueFormat.Default;\r
114         public boolean drawSamples = false;\r
115         \r
116         /** Time at mouse, when mouse hovers over trend. This value is set by TrendParticipant. NaN is mouse is not hovering */\r
117         public double mouseHoverTime = Double.NaN, lastMouseHoverTime = 0.0;\r
118         \r
119         /** If set, valueTip is drawn at the time */\r
120         public Double valueTipTime = null, prevValueTipTime = null;\r
121         public boolean valueTipHover = false;\r
122 \r
123         public ItemPlacement itemPlacement = ItemPlacement.Overlapping;\r
124         \r
125         @Override\r
126         public void init() {\r
127                 spec = new TrendSpec();\r
128                 spec.init();\r
129                 \r
130                 milestones = new MilestoneSpec();\r
131                 milestones.init();\r
132                 \r
133                 //// Create title node\r
134                 titleNode = addNode( "Title", TextNode.class );\r
135                 titleNode.setFont( new Font("Arial", 0, 30) );\r
136                 titleNode.setColor( Color.BLACK );\r
137                 titleNode.setText( "<title here>");\r
138                 titleNode.setSize(300, 40);\r
139                 \r
140                 plot = addNode( "Plot", Plot.class );\r
141                 \r
142                 horizRuler = addNode("HorizRuler", HorizRuler.class);\r
143                 vertRuler = addNode("VertRuler", VertRuler.class);\r
144                 vertRulers.add( vertRuler );\r
145                 \r
146                 /// Set some bounds\r
147                 horizRuler.setFromEnd(0, 100);\r
148                 vertRuler.setMinMax(0, 100);\r
149                 setSize(480, 320);\r
150         }\r
151         \r
152         /**\r
153          * Set source of data\r
154          * @param historian\r
155          * @param collector (Optional) Used for flushing collectors \r
156          */\r
157         public void setHistorian(HistoryManager historian, Collector collector) {\r
158                 this.historian = historian==null?DUMMY_HISTORY:historian;\r
159                 this.collector = collector;\r
160                 itemIds.clear();\r
161                 ItemManager allFiles = ItemManager.createUnchecked( getHistoryItems() );\r
162                 for (ItemNode item : allItems) {\r
163                         item.setTrendItem(item.item, allFiles);\r
164                         for (Bean historyItem : item.historyItems ) {\r
165                                 try {\r
166                                         itemIds.add( (String) historyItem.getField("id") );\r
167                                 } catch (BindingException e) {\r
168                                 }\r
169                         }\r
170                 }\r
171         }\r
172         \r
173         /**\r
174          * Get info of all history items.\r
175          * This util is created for polling strategy.  \r
176          * \r
177          * @return\r
178          */\r
179         Bean[] getHistoryItems() {\r
180                 Bean[] result = null;\r
181                 HistoryException e = null;\r
182                 for (int attempt=0; attempt<10; attempt++) {\r
183                         try {\r
184                                 result = historian.getItems();\r
185                                 break;\r
186                         } catch (HistoryException e2) {\r
187                                 if (e==null) e = e2;\r
188                                 try {\r
189                                         Thread.sleep(1);\r
190                                 } catch (InterruptedException e1) {\r
191                                 }\r
192                         }\r
193                 }\r
194                 if (result!=null) return result;\r
195                 //throw new RuntimeException(e);\r
196                 return new Bean[0];\r
197         }\r
198         \r
199         public void setMilestones(Bean milestones) {\r
200                 if (this.milestones.equals(milestones)) return;\r
201                 this.milestones.readFrom( milestones );         \r
202                 boolean hasBaseline = this.milestones.baseline>=0;\r
203 \r
204                 // Sort by first, put baseline on top\r
205                 final Milestone baseline = this.milestones.baseline>=0 ? this.milestones.milestones.get( this.milestones.baseline ) : null;\r
206                 Collections.sort(this.milestones.milestones, new Comparator<Milestone>() {\r
207                         public int compare(Milestone o1, Milestone o2) {\r
208                                 if (o1==baseline) return -1;\r
209                                 if (o2==baseline) return 1;\r
210                                 return Double.compare(o1.time, o1.time);\r
211                         }});\r
212                 \r
213                 this.milestones.baseline = hasBaseline ? 0 : -1;                \r
214                 double newBasetime = hasBaseline ? this.milestones.milestones.get(this.milestones.baseline).time : 0.;\r
215                 if (newBasetime != horizRuler.basetime) {                       \r
216                         horizRuler.basetime = newBasetime;\r
217                         horizRuler.layout();\r
218                 }\r
219                 shapedirty = true;\r
220         }\r
221         \r
222         public void selectVertRuler(int index) {\r
223                 vertRulerIndex = index;\r
224                 if (index<0 || index>=vertRulers.size()) {\r
225                         vertRuler = vertRulers.get(0);\r
226                 } else {\r
227                         vertRuler = vertRulers.get(index);\r
228                 }\r
229                 shapedirty = true;\r
230         }\r
231         \r
232         @Override\r
233         public void cleanup() {\r
234                 spec = new TrendSpec();\r
235                 spec.init();\r
236                 analogItems.clear();\r
237                 binaryItems.clear();\r
238                 allItems.clear();\r
239                 historian = DUMMY_HISTORY;\r
240                 super.cleanup();\r
241         }\r
242 \r
243         private static TObjectIntMap<String> itemIndexMap(List<TrendItem> items) {\r
244                 TObjectIntMap<String> map = new TObjectIntHashMap<>(items.size(), 0.5f, -1);\r
245                 for (int i = 0; i < items.size(); ++i) {\r
246                         TrendItem it = items.get(i);\r
247                         map.put(it.groupItemId, i);\r
248                 }\r
249                 return map;\r
250         }\r
251 \r
252         private static <T> TObjectIntMap<T> subtract(TObjectIntMap<T> a, TObjectIntMap<T> b) {\r
253                 TObjectIntMap<T> r = new TObjectIntHashMap<>(a);\r
254                 b.forEachKey(new TObjectProcedure<T>() {\r
255                         @Override\r
256                         public boolean execute(T key) {\r
257                                 r.remove(key);\r
258                                 return true;\r
259                         }\r
260                 });\r
261                 return r;\r
262         }\r
263 \r
264         /**\r
265          * @param newSpec\r
266          *            new trending specification, cannot not be <code>null</code>.\r
267          *            Use {@link TrendSpec#EMPTY} instead of <code>null</code>.\r
268          */\r
269         public void setTrendSpec(TrendSpec newSpec) {\r
270                 //System.out.println(newSpec);\r
271                 // Check if equal & Read spec\r
272                 if (newSpec.equals(this.spec)) return;\r
273 \r
274                 boolean timeWindowChange = !this.spec.viewProfile.timeWindow.equals( newSpec.viewProfile.timeWindow );\r
275                 boolean yaxisModeChanged = this.spec.axisMode != newSpec.axisMode;\r
276 \r
277                 TObjectIntMap<String> newItemMap = itemIndexMap(newSpec.items);\r
278                 TObjectIntMap<String> currentItemMap = itemIndexMap(spec.items);\r
279                 TObjectIntMap<String> addedItemMap = subtract(newItemMap, currentItemMap);\r
280                 TObjectIntMap<String> removedItemMap = subtract(currentItemMap, newItemMap);\r
281                 Map<String, VertRuler> existingRulers = new HashMap<>();\r
282                 if (this.spec.axisMode == YAxisMode.MultiAxis) {\r
283                         for (ItemNode item : analogItems)\r
284                                 if (item.ruler != null)\r
285                                         existingRulers.put(item.item.groupItemId, item.ruler);\r
286                 }\r
287 \r
288                 this.spec.readFrom( newSpec );\r
289                 this.spec.sortItems();\r
290                 this.renderingProfile.read(this.spec.viewProfile);\r
291 \r
292                 // Set title\r
293                 if (titleNode != null) titleNode.setText( spec.name );\r
294 \r
295                 // Setup trend item nodes\r
296                 itemIds.clear();\r
297                 for (ItemNode item : allItems) removeNode(item);\r
298                 analogItems.clear();\r
299                 binaryItems.clear();\r
300                 allItems.clear();\r
301                 \r
302                 ItemManager itemManager = ItemManager.createUnchecked( getHistoryItems() );\r
303                 for (int i = 0; i<spec.items.size(); i++) {\r
304                         TrendItem item = spec.items.get(i);\r
305                         if (item.hidden)\r
306                                 continue;\r
307                         \r
308                         ItemNode node = createItemNode(item, itemManager);\r
309                         for (Bean historyItem : node.historyItems) {\r
310                                 try {\r
311                                         itemIds.add( (String) historyItem.getField("id") );\r
312                                 } catch (BindingException e) {\r
313                                 }\r
314                         }\r
315                         \r
316                         if (item.renderer == Renderer.Analog) {\r
317                                 analogItems.add(node);\r
318                         } else {\r
319                                 binaryItems.add(node);\r
320                         }\r
321                         allItems.add(node);\r
322                 }\r
323 \r
324                 // Setup vertical ruler nodes\r
325                 singleAxis = spec.axisMode == YAxisMode.SingleAxis;\r
326                 if (singleAxis) {\r
327                         if (yaxisModeChanged || vertRulers.size() != 1 || vertRuler == null) {\r
328                                 for (VertRuler vr : vertRulers) removeNode(vr);\r
329                                 vertRulers.clear();\r
330 \r
331                                 vertRuler = addNode("VertRuler", VertRuler.class);\r
332                                 vertRulers.add( vertRuler );\r
333                         }\r
334 \r
335                         vertRuler.manualscale = true;\r
336                         for (int i=0; i<analogItems.size(); i++) {\r
337                                 ItemNode item = analogItems.get(i);\r
338                                 item.ruler = vertRuler;\r
339                                 item.trendNode = this;\r
340                                 if (item.item.scale instanceof Scale.Manual == false) vertRuler.manualscale = false;\r
341                         }\r
342                 } else {\r
343                         if (yaxisModeChanged) {\r
344                                 // Recreate all rulers\r
345                                 for (VertRuler vr : vertRulers) removeNode(vr);\r
346                                 vertRulers.clear();\r
347                                 for (int i=0; i<analogItems.size(); i++)\r
348                                         vertRulers.add( addNode(VertRuler.class) );\r
349                         } else {\r
350                                 removedItemMap.forEachKey(new TObjectProcedure<String>() {\r
351                                         @Override\r
352                                         public boolean execute(String id) {\r
353                                                 VertRuler vr = existingRulers.get(id);\r
354                                                 if (vr != null) {\r
355                                                         removeNode(vr);\r
356                                                         vertRulers.remove(vr);\r
357                                                 }\r
358                                                 return true;\r
359                                         }\r
360                                 });\r
361                                 addedItemMap.forEachValue(new TIntProcedure() {\r
362                                         @Override\r
363                                         public boolean execute(int index) {\r
364                                                 vertRulers.add( index, addNode(VertRuler.class) );\r
365                                                 return true;\r
366                                         }\r
367                                 });\r
368                         }\r
369 \r
370                         for (int i = 0; i < analogItems.size(); i++) {\r
371                                 ItemNode item = analogItems.get(i);\r
372                                 VertRuler vr = vertRulers.get(i);\r
373                                 vr.setZIndex(1000 + i);\r
374                                 vr.color = item.color;\r
375                                 vr.label = item.item.label;\r
376                                 vr.manualscale = item.item.scale instanceof Scale.Manual;\r
377                                 item.ruler = vr;\r
378                                 item.trendNode = this;\r
379                         }\r
380                         // Select vert ruler\r
381                         vertRuler = vertRulers.isEmpty() ? null : vertRulers.get( vertRulerIndex <= 0 || vertRulerIndex >= vertRulers.size() ? 0 : vertRulerIndex );\r
382                 }\r
383                 \r
384                 // Locked\r
385                 TimeWindow tw = spec.viewProfile.timeWindow;\r
386                 horizRuler.manualscale = tw.timeWindowLength!=null && tw.timeWindowStart!=null;\r
387                 \r
388                 if (timeWindowChange) {\r
389                         horizRuler.autoscale();\r
390                 }\r
391                 shapedirty = true;\r
392         }\r
393 \r
394     private ItemNode createItemNode(TrendItem item, ItemManager itemManager) {\r
395         ItemNode node = addNode( ItemNode.class );\r
396         //node.trendNode = this;\r
397         node.setTrendItem(item, itemManager);\r
398         node.color = toColor(item.customColor);\r
399         if (node.color == null)\r
400             node.color = JarisPaints.getColor( item.index );\r
401         node.stroke = item.customStrokeWidth != null\r
402                 ? withStrokeWidth(item.customStrokeWidth, Plot.TREND_LINE_STROKE)\r
403                 : Plot.TREND_LINE_STROKE;\r
404         return node;\r
405     }\r
406 \r
407     private static BasicStroke withStrokeWidth(float width, BasicStroke stroke) {\r
408         return new BasicStroke(width,\r
409                 stroke.getEndCap(), stroke.getLineJoin(), stroke.getMiterLimit(),\r
410                 stroke.getDashArray(), stroke.getDashPhase());\r
411     }\r
412 \r
413     private static Color toColor(float[] components) {\r
414         if (components == null)\r
415             return null;\r
416         switch (components.length) {\r
417         case 3: return new Color(components[0], components[1], components[2]);\r
418         case 4: return new Color(components[0], components[1], components[2], components[3]);\r
419         default: return null;\r
420         }\r
421     }\r
422 \r
423         /**\r
424          * Layout graphical nodes based on bounds\r
425          */\r
426         public void layout() {\r
427                 double w = bounds.getWidth();\r
428                 double h = bounds.getHeight();\r
429                 if ( titleNode != null ) {\r
430                         titleNode.setSize(w, h * 0.02);\r
431                         titleNode.setTranslate(0, VERT_MARGIN);\r
432                         titleNode.layout();\r
433                 }\r
434                 \r
435                 // Plot-Ruler area width \r
436                 double praw = w-HORIZ_MARGIN*2;\r
437                 \r
438                 // Plot height\r
439                 double ph = h-VERT_MARGIN*2-MILESTONE_HEIGHT-HORIZ_RULER_HEIGHT;\r
440                 if ( titleNode != null ) {\r
441                         ph -= titleNode.th;\r
442                 }\r
443                 \r
444                 // Analog & Binary area height\r
445                 double aah, bah;\r
446                 if (!analogItems.isEmpty()) {\r
447                         bah = binaryItems.size() * BINARY[3];\r
448                         aah = Math.max(0, ph-bah);\r
449                         if (aah+bah>ph) bah = ph-aah;\r
450                 } else {\r
451                         // No analog items\r
452                         aah = 0;\r
453                         bah = ph; \r
454                 }\r
455                 // Vertical ruler\r
456                 for (VertRuler vertRuler : vertRulers) {\r
457                         vertRuler.setHeight(aah);\r
458                         vertRuler.layout();\r
459                 }\r
460                 plot.analogAreaHeight = aah;\r
461                 plot.binaryAreaHeight = bah;\r
462 \r
463                 // Vertical ruler widths\r
464                 double vrws = 0;\r
465                 for (VertRuler vertRuler : vertRulers) {\r
466                         vrws += vertRuler.getWidth();\r
467                 }\r
468                 \r
469                 // Add room for Binary label\r
470                 if ( !binaryItems.isEmpty() ) {\r
471                         double maxLabelWidth = BINARY_LABEL_WIDTH;\r
472                         for (ItemNode node : binaryItems) {\r
473                                 if (node.item != null) {\r
474                                         GlyphVector glyphVector = RULER_FONT.createGlyphVector(GridUtil.frc, node.item.label);\r
475                                         double labelWidth = glyphVector.getVisualBounds().getWidth();\r
476                                         maxLabelWidth = Math.max( maxLabelWidth, labelWidth );\r
477                                 }\r
478                         }\r
479                         vrws = Math.max(maxLabelWidth, vrws);\r
480                 }\r
481                 \r
482                 // Plot Width\r
483                 double pw = praw - vrws;\r
484                 plot.setTranslate(HORIZ_MARGIN, (titleNode!=null?titleNode.th:0)+VERT_MARGIN+MILESTONE_HEIGHT);\r
485                 plot.setSize(pw, ph);\r
486                 \r
487                 horizRuler.layout();\r
488                 horizRuler.setTranslate(HORIZ_MARGIN, plot.getY()+plot.getHeight()+3);\r
489                 boolean l = horizRuler.setWidth(pw);\r
490                 l |= horizRuler.setFromEnd(horizRuler.from, horizRuler.end);\r
491                 if (l) horizRuler.layout();\r
492                 \r
493                 // Move vertical rulers\r
494                 double vrx = plot.getX() + plot.getWidth() + 3; \r
495                 for (VertRuler vertRuler : vertRulers) {\r
496                         vertRuler.setTranslate(vrx, plot.getY());\r
497                         vrx += vertRuler.getWidth() + 3;\r
498                 }\r
499                 \r
500         }\r
501 \r
502         public void setSize(double width, double height) {\r
503                 bounds.setFrame(0, 0, width, height);\r
504         }\r
505         \r
506         @Override\r
507         public void render(Graphics2D g2d) {\r
508                 // Set Quality High\r
509                 QualityHints qh = QualityHints.getQuality(g2d);\r
510         g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, quality.textQuality==LineQuality.Antialias?RenderingHints.VALUE_TEXT_ANTIALIAS_ON:RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);\r
511                 \r
512                 // Set Bounds\r
513                 Rectangle bounds = g2d.getClipBounds();         \r
514                 if (bounds.getWidth()!=this.bounds.getWidth() || bounds.getHeight()!=this.bounds.getHeight()) {\r
515                         setSize(bounds.getWidth(), bounds.getHeight());\r
516                         layout();\r
517                 }\r
518                 \r
519                 // Flush history subscriptions\r
520                 flushHistory();\r
521 \r
522                 // Render children\r
523                 super.render(g2d);\r
524                 \r
525                 // Render plot's value tip\r
526                 plot.renderValueTip(g2d);\r
527                 \r
528                 // Restore quality\r
529                 qh.setQuality(g2d);\r
530         }\r
531         \r
532         @Override\r
533         public Rectangle2D getBoundsInLocal() {\r
534                 return bounds;\r
535         }\r
536         \r
537         /**\r
538          * Return true if the viewport is not moving and shows only past values \r
539          * @return\r
540          */\r
541         public boolean allPast() {\r
542                 TimeWindow timeWindow = spec.viewProfile.timeWindow;\r
543                 boolean fixedWindow = !horizRuler.autoscroll || (timeWindow.timeWindowStart!=null && timeWindow.timeWindowLength!=null);\r
544                 if (fixedWindow) {\r
545                         for (ItemNode item : allItems) {\r
546                                 if (item.end <= horizRuler.end) return false;\r
547                         }\r
548                         return true;\r
549                 } else {\r
550                         return false;\r
551                 }\r
552         }\r
553 \r
554         public void flushHistory() {\r
555                 if (collector == null || collector instanceof CollectorImpl == false) return;\r
556                 CollectorImpl fh = (CollectorImpl) collector;\r
557                 fh.flush( itemIds );\r
558         }\r
559         \r
560         /**\r
561          * Read values of min,max,from,end to all items.\r
562          * Put iMin,iMax,iFrom,iEnd to all axes.\r
563          */\r
564         public void readMinMaxFromEnd() {\r
565                 flushHistory();\r
566                 // Read min,max,from,end from all items - Gather collective ranges\r
567                 for (ItemNode item : allItems) {\r
568                         item.readMinMaxFromEnd();\r
569                 }\r
570                 horizRuler.setKnownFromEnd();\r
571                 for (VertRuler vertRuler : vertRulers) vertRuler.setKnownMinMax();\r
572         }\r
573 \r
574         \r
575         \r
576         /**\r
577          * Flushes history, resets streams,\r
578          * reads the latest time and value ranges from disc.\r
579          * Scales the axes.\r
580          * Sets dirty true if there was change in axis.\r
581          */\r
582         public boolean autoscale(boolean timeAxis, boolean valueAxis) {\r
583                 if (!timeAxis && !valueAxis) return false;\r
584 \r
585                 readMinMaxFromEnd();\r
586                 boolean changed = false;\r
587                                 \r
588                 // Time axis\r
589                 if (timeAxis) {\r
590                         changed |= horizRuler.autoscale();\r
591                         if ( changed && !printing ) horizRuler.fireListener();\r
592                 }\r
593 \r
594                 // Y-Axis\r
595                 if (valueAxis) {\r
596                         for (VertRuler vertRuler : vertRulers) changed |= vertRuler.autoscale();\r
597                 }       \r
598 \r
599                 return changed;\r
600         }\r
601 \r
602         public boolean updateValueTipTime() {\r
603                 if (valueTipTime != null && spec.experimentIsRunning && spec.viewProfile.trackExperimentTime) {\r
604                         double endTime = horizRuler.getItemEndTime();\r
605                         if (!Double.isNaN(endTime)) {\r
606                                 boolean changed = Double.compare(valueTipTime, endTime) != 0;\r
607                                 valueTipTime = endTime;\r
608                                 return changed;\r
609                         }\r
610                 }\r
611                 return false;\r
612         }\r
613 \r
614         public void zoomIn(double x, double y, double width, double height, boolean horiz, boolean vert) {\r
615                 if (horiz) {\r
616                         horizRuler.zoomIn(x, width);\r
617                 }\r
618                 \r
619                 if (vert) {\r
620                         for (VertRuler vertRuler : vertRulers) {\r
621                                 vertRuler.zoomIn(y, height);\r
622                         }\r
623                 }\r
624                 shapedirty = true;\r
625         }\r
626         \r
627         public void zoomOut() {\r
628                 horizRuler.zoomOut();\r
629                 for (VertRuler vertRuler : vertRulers) {\r
630                         vertRuler.zoomOut();\r
631                 }\r
632                 shapedirty = true;\r
633         }\r
634         \r
635         public TrendSpec getTrendSpec() {\r
636                 return spec;\r
637         }\r
638 \r
639         public Viewport getViewport() \r
640         {\r
641                 Viewport vp = new Viewport();\r
642                 vp.init();\r
643                 vp.from = horizRuler.from;\r
644                 vp.end = horizRuler.end;\r
645                 for (VertRuler vr : vertRulers) {\r
646                         AxisViewport avp = new AxisViewport();\r
647                         avp.min = vr.min;\r
648                         avp.max = vr.max;\r
649                         vp.axesports.add( avp );\r
650                 }\r
651                 return vp;\r
652         }\r
653         \r
654         public void setViewport( Viewport vp ) \r
655         {\r
656                 horizRuler.from = vp.from;\r
657                 horizRuler.end = vp.end;\r
658                 int i=0; \r
659                 for ( AxisViewport avp : vp.axesports ) {\r
660                         if ( i>=vertRulers.size() ) break;\r
661                         VertRuler vr = vertRulers.get(i++);\r
662                         vr.min = avp.min;\r
663                         vr.max = avp.max;\r
664                 }\r
665         }\r
666         \r
667         public void setQuality(TrendQualitySpec quality)\r
668         {\r
669                 this.quality = quality;\r
670         }\r
671         \r
672         public TrendQualitySpec getQuality()\r
673         {\r
674                 return quality;\r
675         }\r
676         \r
677         public Double snapToValue(double time, double snapToleranceInTime) throws HistoryException, AccessorException\r
678         {\r
679                 double from = horizRuler.from;\r
680                 double end = horizRuler.end;\r
681                 double pixelsPerSecond = (end-from) / plot.getWidth();\r
682                 \r
683                 TreeSet<Double> values = new TreeSet<Double>(Bindings.DOUBLE);\r
684                 for (ItemNode item : allItems) {                        \r
685                         Stream s = item.openStream( pixelsPerSecond );\r
686                         if ( s==null ) continue;\r
687                         int pos = s.binarySearch(Bindings.DOUBLE, time);\r
688                         ValueBand vb = new ValueBand(s.sampleBinding, s.sampleBinding.createDefaultUnchecked());\r
689                         // Exact match\r
690                         if (pos >= 0) {\r
691                                 return time;\r
692                         }\r
693                                 \r
694                         int prev = -pos-2;\r
695                         int next = -pos-1;\r
696                         int count = s.count();          \r
697                         Double prevTime = null, nextTime = null;\r
698                                 \r
699                         if ( prev>=0 && prev<count ) {\r
700                                 s.accessor.get(prev, s.sampleBinding, vb.getSample());\r
701                                 if ( !vb.isNanSample() ) {\r
702                                         prevTime = vb.getTimeDouble();\r
703                                         if ( vb.hasEndTime() ) {\r
704                                                 Double nTime = vb.getEndTimeDouble();\r
705                                                 if (nTime!=null && nTime+snapToleranceInTime>time) nextTime = nTime;\r
706                                         }\r
707                                 }\r
708                         }\r
709                                 \r
710                         if ( nextTime==null && next>=0 && next<count ) {\r
711                                 s.accessor.get(next, s.sampleBinding, vb.getSample());\r
712                                 if ( !vb.isNanSample() ) {\r
713                                         nextTime = vb.getTimeDouble();\r
714                                 }\r
715                         }\r
716                         \r
717                         if (prevTime==null && nextTime==null) continue;\r
718                                 \r
719                         if (prevTime==null) {\r
720                                 if ( nextTime - time < snapToleranceInTime ) \r
721                                         values.add(nextTime);\r
722                         } else if (nextTime==null) {\r
723                                 if ( time - prevTime < snapToleranceInTime ) \r
724                                         values.add(prevTime);\r
725                         } else {\r
726                                 values.add(nextTime);\r
727                                 values.add(prevTime);\r
728                         }\r
729                 }\r
730                 if (values.isEmpty()) return null;\r
731                 \r
732                 Double lower = values.floor( time );\r
733                 Double higher = values.ceiling( time );\r
734                 \r
735                 if ( lower == null ) return higher;\r
736                 if ( higher == null ) return lower;\r
737                 double result = time-lower < higher-time ? lower : higher;\r
738                 \r
739                 \r
740                 return result;\r
741                 \r
742         }\r
743         \r
744         \r
745 }\r