]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.trend/src/org/simantics/trend/impl/Plot.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.trend / src / org / simantics / trend / impl / Plot.java
1 /*******************************************************************************\r
2  * Industry THTH ry.\r
3  * All rights reserved. This program and the accompanying materials\r
4  * are made available under the terms of the Eclipse Public License v1.0\r
5  * which accompanies this distribution, and is available at\r
6  * http://www.eclipse.org/legal/epl-v10.html\r
7  *\r
8  * Contributors:\r
9  *     VTT Technical Research Centre of Finland - initial API and implementation\r
10  *******************************************************************************/\r
11 package org.simantics.trend.impl;\r
12 \r
13 import java.awt.AlphaComposite;\r
14 import java.awt.BasicStroke;\r
15 import java.awt.Color;\r
16 import java.awt.Composite;\r
17 import java.awt.Font;\r
18 import java.awt.FontMetrics;\r
19 import java.awt.GradientPaint;\r
20 import java.awt.Graphics2D;\r
21 import java.awt.Paint;\r
22 import java.awt.font.GlyphVector;\r
23 import java.awt.font.LineMetrics;\r
24 import java.awt.geom.AffineTransform;\r
25 import java.awt.geom.Line2D;\r
26 import java.awt.geom.Path2D;\r
27 import java.awt.geom.Point2D;\r
28 import java.awt.geom.Rectangle2D;\r
29 import java.text.Format;\r
30 import java.text.NumberFormat;\r
31 import java.text.ParseException;\r
32 import java.util.ArrayList;\r
33 import java.util.List;\r
34 \r
35 import org.simantics.databoard.Bindings;\r
36 import org.simantics.databoard.accessor.error.AccessorException;\r
37 import org.simantics.databoard.binding.Binding;\r
38 import org.simantics.databoard.binding.NumberBinding;\r
39 import org.simantics.databoard.binding.error.BindingException;\r
40 import org.simantics.g2d.utils.GridSpacing;\r
41 import org.simantics.g2d.utils.GridUtil;\r
42 import org.simantics.history.HistoryException;\r
43 import org.simantics.history.util.Stream;\r
44 import org.simantics.history.util.ValueBand;\r
45 import org.simantics.scenegraph.utils.ColorUtil;\r
46 import org.simantics.trend.configuration.TrendItem.Renderer;\r
47 import org.simantics.trend.configuration.TrendSpec;\r
48 import org.simantics.utils.format.TimeFormat;\r
49 \r
50 public class Plot extends TrendGraphicalNode {\r
51 \r
52     public static final double VALUE_TIP_BOX_PLOT_MARGIN = 7;\r
53     private static final double VALUE_TIP_BOX_FILM_MARGIN = 5;\r
54 \r
55     private static final long serialVersionUID = 6335497685577733932L;\r
56 \r
57     public static final BasicStroke BORDER_LINE_STROKE =\r
58             new BasicStroke(1.0f,\r
59                     BasicStroke.CAP_SQUARE,\r
60                     BasicStroke.JOIN_MITER,\r
61                     10.0f, null, 0.0f);\r
62 \r
63     public static final BasicStroke MILESTONE_LINE_STROKE =\r
64             new BasicStroke(1.0f,\r
65                     BasicStroke.CAP_SQUARE,\r
66                     BasicStroke.JOIN_MITER,\r
67                     10.0f, null, 0.0f);\r
68 \r
69     public static final BasicStroke TREND_LINE_STROKE =\r
70             new BasicStroke(1.0f,\r
71                     BasicStroke.CAP_BUTT,\r
72                     BasicStroke.JOIN_MITER,\r
73                     10.0f, null, 0.0f);\r
74 \r
75     public static final BasicStroke DASHED_LINE_STROKE =\r
76                 new BasicStroke(2.0f,              // Width\r
77                     BasicStroke.CAP_BUTT,    // End cap\r
78                     BasicStroke.JOIN_MITER,    // Join style\r
79                     5.0f,                      // Miter limit\r
80                     new float[] {5.0f,5.0f}, // Dash pattern\r
81                     0.0f);                     // Dash phase                    \r
82     public static final BasicStroke DASHED_LINE_STROKE_2 =\r
83                 new BasicStroke(2.0f,              // Width\r
84                     BasicStroke.CAP_BUTT,    // End cap\r
85                     BasicStroke.JOIN_MITER,    // Join style\r
86                     5.0f,                      // Miter limit\r
87                     new float[] {5.0f,5.0f}, // Dash pattern\r
88                     5.0f);                     // Dash phase                    \r
89     public static final BasicStroke DASHED_LINE_STROKE_INVERSE =\r
90                 new BasicStroke(1.0f,              // Width\r
91                     BasicStroke.CAP_BUTT,    // End cap\r
92                     BasicStroke.JOIN_MITER,    // Join style\r
93                     5.0f,                      // Miter limit\r
94                     new float[] {3.0f,8.0f}, // Dash pattern\r
95                     0.0f);                     // Dash phase                    \r
96 \r
97     public static final Color PLOT_AREA_BG_GRADIENT_COLOR_TOP = new Color(228, 228, 248);\r
98     public static final Color PLOT_AREA_BG_GRADIENT_COLOR_BOTTOM = new Color(250, 250, 250);\r
99 \r
100     static final Font MILESTONE_FONT, BASELINE_FONT, TOOLTIP_FONT;    \r
101     \r
102     static final GridSpacing SOME_SPACING = GridSpacing.makeGridSpacing(100, 100, 15);\r
103     \r
104     public static final Color GRID_LINE_COLOR = new Color(190, 190, 220);\r
105 \r
106     static final double DIAMOND_SIZE = 7;\r
107     static final Path2D DIAMOND;\r
108         \r
109         double analogAreaHeight;\r
110         double binaryAreaHeight;\r
111 \r
112         Rectangle2D valueTipBoxBounds = new Rectangle2D.Double();\r
113 \r
114         @SuppressWarnings("unused")\r
115     @Override\r
116         protected void doRender(Graphics2D g2d) {\r
117                 //long startTime = System.nanoTime();\r
118                 TrendNode trend = (TrendNode) getParent();\r
119                 TrendSpec ts = trend.spec;\r
120                 ViewRenderingProfile rprof = trend.renderingProfile;\r
121                 double w = bounds.getWidth();\r
122                 double h = bounds.getHeight();\r
123                 \r
124                 double from = trend.horizRuler.from;\r
125                 double end = trend.horizRuler.end;\r
126                 GridSpacing xGrid = trend.horizRuler.spacing;\r
127                 GridSpacing yGrid = trend.vertRuler != null ? trend.vertRuler.spacing : Plot.SOME_SPACING;\r
128                 \r
129                 if (w<1. || h<1.) return; \r
130                 \r
131                 // Prepare data\r
132                 if (trend.shapedirty) {\r
133                         trend.shapedirty = false;\r
134                         for (ItemNode node : trend.analogItems) prepareItem(node, 0, analogAreaHeight);\r
135                         for (ItemNode node : trend.binaryItems) prepareItem(node, 0, analogAreaHeight);\r
136                 }\r
137                 \r
138                 // Fill gradient\r
139                 Gradient: {\r
140                         Paint bgp = rprof.backgroundColor2 == null ? rprof.backgroundColor1\r
141                                         : new GradientPaint(\r
142                                                         0.f, (float) h, rprof.backgroundColor1, \r
143                                                         0.f, 0.f, rprof.backgroundColor2, false);\r
144 //                      g2d.setPaint(Color.white);\r
145                         g2d.setPaint( bgp );\r
146                         g2d.fill(bounds);\r
147                 }\r
148                         \r
149                 // Draw grid lines\r
150                 GridLines: if (ts.viewProfile.showGrid) {\r
151                         g2d.setPaint( rprof.gridColor );\r
152                         g2d.setStroke( GridUtil.GRID_LINE_STROKE );\r
153                         GridUtil.paintGridLines(\r
154                                 xGrid, yGrid, \r
155                                 g2d, \r
156                                 from - trend.horizRuler.basetime, \r
157                                 trend.vertRuler != null ? trend.vertRuler.min : 0, \r
158                                 w, \r
159                                 h,\r
160                                 analogAreaHeight);\r
161                 }\r
162                 \r
163                 Rectangle2D oldClip = g2d.getClipBounds();\r
164                 \r
165                 // Draw analog items\r
166                 AnalogItems: if ( !trend.analogItems.isEmpty() ) {\r
167                         Rectangle2D analogAreaClip = new Rectangle2D.Double(0, 0, getWidth(), analogAreaHeight);\r
168                         g2d.setClip( analogAreaClip );\r
169                         \r
170                         for (int phase=0; phase<4; phase++) {\r
171                                 for (int i=0; i<trend.analogItems.size(); i++) {\r
172                                         ItemNode data = trend.analogItems.get(i);\r
173                                         drawItem(g2d, data, 0, analogAreaHeight, phase);\r
174                                 }\r
175                         }\r
176                         g2d.setClip(oldClip);\r
177                 }\r
178 \r
179                 Separator: if ( !trend.analogItems.isEmpty() && !trend.binaryItems.isEmpty() ) {\r
180                         g2d.setColor( Color.BLACK );\r
181                         g2d.setStroke( GridUtil.GRID_LINE_STROKE );\r
182                         g2d.drawLine(0, (int) analogAreaHeight, (int) getWidth(), (int) analogAreaHeight);\r
183                 }\r
184                 \r
185                 // Draw binary items\r
186                 BinaryItems: if ( !trend.binaryItems.isEmpty() ) {\r
187                         Rectangle2D binaryAreaClip = new Rectangle2D.Double(0, analogAreaHeight, getWidth(), binaryAreaHeight);\r
188                         g2d.setClip( binaryAreaClip );\r
189                         for (int phase=0; phase<4; phase++) {\r
190                                 for (int i=0; i<trend.binaryItems.size(); i++) {\r
191                                         ItemNode data = trend.binaryItems.get(i);\r
192                                         double y = analogAreaHeight + i*BINARY[3];\r
193                                         drawItem(g2d, data, y, BINARY[2], phase);\r
194                                 }\r
195                         }\r
196                         g2d.setClip(oldClip);\r
197                         \r
198                         // Draw labels\r
199                         g2d.setFont( RULER_FONT );\r
200                         for (int i=0; i<trend.binaryItems.size(); i++)\r
201                         {\r
202                                 ItemNode data = trend.binaryItems.get(i);\r
203                                 g2d.setColor( data.color );\r
204                                 double fh = 9.; // font height\r
205                                 double y = analogAreaHeight + i*BINARY[3] + 1.f;\r
206                                 double fy = y+(BINARY[3]-fh)/2+fh;\r
207                                 g2d.drawString( data.item.label, ((float) getWidth())+7.f, (float) fy);\r
208                         }\r
209                 }\r
210                 \r
211                 // Draw milestones\r
212                 Milestone: if (ts.viewProfile.showMilestones) {\r
213                         double sx = getWidth() / ( end - from );\r
214                         MilestoneSpec mss = trend.milestones;\r
215                         List<Milestone> ls = mss.milestones;\r
216                         if ( ls.isEmpty() ) break Milestone;\r
217                         \r
218                         Line2D line = new Line2D.Double(0, 0, 0, h);\r
219                         g2d.setStroke( MILESTONE_LINE_STROKE );\r
220                         Rectangle2D diamondRegion = new Rectangle2D.Double(0, -DIAMOND_SIZE*2, w, DIAMOND_SIZE*2);\r
221                         \r
222                         for (int i=mss.milestones.size()-1; i>=0; i--) {\r
223                                 Milestone ms = mss.milestones.get( i );\r
224                                 if ( ms.hidden ) continue;\r
225                                 boolean isBaseline = i == mss.baseline; \r
226                                 double time = ms.time;\r
227                                 double x = (time-from)*sx;\r
228                                 if (x<-DIAMOND_SIZE*2 || x>w+DIAMOND_SIZE*2) continue;\r
229                                 x = Math.floor(x);\r
230 \r
231                                 // Diamond\r
232                                 g2d.setClip(diamondRegion);\r
233                                 g2d.translate( x, 0);\r
234                                 g2d.setColor( isBaseline ? Color.LIGHT_GRAY : Color.DARK_GRAY );\r
235                                 g2d.fill( DIAMOND );\r
236                                 g2d.setColor( Color.BLACK );\r
237                                 g2d.draw( DIAMOND );\r
238 \r
239                                 // Text\r
240                                 Font f = isBaseline ? BASELINE_FONT : MILESTONE_FONT;\r
241                                 g2d.setFont( f );\r
242                                 g2d.setColor( isBaseline ? Color.black : Color.ORANGE );\r
243                                 GlyphVector glyphVector = f.createGlyphVector(g2d.getFontRenderContext(), ms.label);\r
244                     double cx = glyphVector.getVisualBounds().getCenterX();\r
245                     double cy = glyphVector.getVisualBounds().getHeight();\r
246                                 g2d.drawString(ms.label, (float)(-cx), (float)(-DIAMOND_SIZE+cy/2) );\r
247                                 g2d.setClip( null );\r
248                                 \r
249                                 // Line\r
250                                 if (x>=0 && x<w) {\r
251                                         g2d.setColor( Color.BLACK );\r
252                                         g2d.draw( line );\r
253                                 }\r
254                                 \r
255                                 g2d.translate(-x, 0);                           \r
256                         }\r
257                 }\r
258                 \r
259                 // Draw hover marker\r
260                 Hoverer: {\r
261                         Double time = trend.valueTipTime;\r
262                         if ( time != null && time>=from && time<=end && !Double.isNaN(time)) {\r
263                                 double sx = getWidth() / ( end - from );\r
264                                 double x = (time-from)*sx;\r
265                                 Line2D line = new Line2D.Double(x, 0, x, h);\r
266                                 g2d.setStroke( DASHED_LINE_STROKE_2 );\r
267                                 g2d.setColor( trend.valueTipHover ? Color.GRAY : Color.WHITE );\r
268                                 g2d.draw( line );\r
269                                 \r
270                                 g2d.setStroke( DASHED_LINE_STROKE );\r
271                                 g2d.setColor( Color.BLACK );\r
272                                 g2d.draw( line );\r
273                                 \r
274 //                              g2d.setStroke( DASHED_LINE_STROKE_INVERSE );\r
275 //                              g2d.setColor( Color.white );\r
276 //                              g2d.draw( line );\r
277                         }\r
278                 }\r
279 \r
280                 // Draw border\r
281                 Border: {\r
282                         g2d.setStroke(BORDER_LINE_STROKE);\r
283                         g2d.setColor( Color.BLACK );\r
284                         Rectangle2D rect = new Rectangle2D.Double();\r
285                         rect.setFrame(0, 0, w, h);\r
286                         g2d.draw(rect);\r
287                 }\r
288 \r
289                 //long endTime = System.nanoTime();\r
290 //              System.out.println("Plot render: "+((double)(endTime-startTime)/1000000)+" ms");\r
291         }\r
292 \r
293         public void drawItem(Graphics2D g, ItemNode data, double y, double height, int phase) {\r
294                 TrendNode trend = getTrend();\r
295                 double from = trend.horizRuler.from;\r
296                 double end = trend.horizRuler.end;\r
297 \r
298 //              trend.vertRulerIndex\r
299 //              boolean selected = trend.singleAxis ? false : trend.vertRuler \r
300 //              selected &= !trend.printing;\r
301 \r
302                 VertRuler ruler = data.ruler;\r
303                 AffineTransform at = g.getTransform();\r
304                 try {\r
305                         //double pixelsPerSecond = (end-from) / getWidth();\r
306                         g.translate(0, y);\r
307                         g.setStroke(data.stroke);\r
308 \r
309                         AffineTransform ab = new AffineTransform();\r
310                         if (data.item.renderer == Renderer.Analog) {\r
311                                 ab.scale( getWidth()/(end-from), height/(ruler.min-ruler.max) );\r
312                                 ab.translate(-from, -ruler.max);\r
313 //                              if (phase == 0) data.prepareLine(from, end, pixelsPerSecond, ab);\r
314                                 data.draw(g, phase, false);\r
315                         }\r
316                         if (data.item.renderer == Renderer.Binary) {\r
317                                 ab.scale( getWidth()/(end-from), 1/*height*/ );\r
318                                 ab.translate(-from, 0);\r
319 //                              if (phase == 0) data.prepareLine(from, end, pixelsPerSecond, ab);\r
320                                 data.draw(g, phase, false);\r
321                         }\r
322 //              } catch (HistoryException e) {\r
323 //                      e.printStackTrace();\r
324 //              } catch (AccessorException e) {\r
325 //                      e.printStackTrace();\r
326                 } finally {\r
327                         g.setTransform(at);\r
328                 }\r
329         }\r
330 \r
331         /**\r
332          * Prepare item for draw.\r
333          *  \r
334          * @param data\r
335          * @param y\r
336          * @param height\r
337          */\r
338         public void prepareItem(ItemNode data, double y, double height) {\r
339                 TrendNode tn = getTrend();\r
340                 double from = tn.horizRuler.from;\r
341                 double end = tn.horizRuler.end;\r
342                 \r
343                 VertRuler ruler = data.ruler;\r
344                 try {\r
345                         double pixelsPerSecond = (end-from) / getWidth();\r
346                         \r
347                         AffineTransform ab = new AffineTransform();\r
348                         if (data.item.renderer == Renderer.Analog) {\r
349                                 ab.scale( getWidth()/(end-from), height/(ruler.min-ruler.max) );\r
350                                 ab.translate(-from, -ruler.max);\r
351                                 data.prepareLine(from, end, pixelsPerSecond, ab);\r
352                         }\r
353                         if (data.item.renderer == Renderer.Binary) {\r
354                                 ab.scale( getWidth()/(end-from), 1/*height*/ );\r
355                                 ab.translate(-from, 0);\r
356                                 data.prepareLine(from, end, pixelsPerSecond, ab);\r
357                         }\r
358                 } catch (HistoryException e) {\r
359                         e.printStackTrace();\r
360                 } catch (AccessorException e) {\r
361                         e.printStackTrace();\r
362                 }\r
363         }\r
364         \r
365         static {\r
366                 \r
367                 DIAMOND = new Path2D.Double();\r
368                 DIAMOND.moveTo(0, -DIAMOND_SIZE*2);\r
369                 DIAMOND.lineTo(DIAMOND_SIZE, -DIAMOND_SIZE);\r
370                 DIAMOND.lineTo(0, 0);\r
371                 DIAMOND.lineTo(-DIAMOND_SIZE, -DIAMOND_SIZE);\r
372                 DIAMOND.lineTo(0, -DIAMOND_SIZE*2);\r
373 \r
374                 MILESTONE_FONT = new Font("Tahoma", 0, (int) (DIAMOND_SIZE*1.2) );\r
375                 BASELINE_FONT = new Font("Tahoma", Font.BOLD, (int) (DIAMOND_SIZE*1.2) );\r
376                 TOOLTIP_FONT = new Font("Tahoma", 0, 13 );\r
377         }\r
378 \r
379         \r
380         ///  ValueTip\r
381         public static final AlphaComposite composite66 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .80f);\r
382         \r
383         void drawValuetip( Graphics2D g, double time ) throws HistoryException, BindingException {\r
384                 TrendNode trend = getTrend();\r
385                 Font font = TOOLTIP_FONT;\r
386                 FontMetrics fm = g.getFontMetrics( font );\r
387 \r
388                 //double from = trend.horizRuler.from;\r
389                 //double end = trend.horizRuler.end;\r
390                 //double pixelsPerSecond = (end-from) / trend.plot.getWidth();\r
391                 \r
392                 double marginInPlot = VALUE_TIP_BOX_PLOT_MARGIN;\r
393                 double marginOnFilm = VALUE_TIP_BOX_FILM_MARGIN;\r
394                 double marginBetweenNamesAndValues = 16;\r
395                 double quantumWidthOfValueArea = 20;\r
396                 double marginBetweenLines = 5;\r
397                 double textAreaHeight = 0;\r
398                 double textAreaWidth = 0;\r
399                 double valueAreaLeft = 0;\r
400                 double valueAreaRight = 0;\r
401                 double valueAreaWidth = 0;\r
402                 \r
403                 double x, y, w, h;\r
404                 \r
405                 List<TipLine> tipLines = new ArrayList<TipLine>( trend.allItems.size()+1 );\r
406                 \r
407                 /// Initialize\r
408                 // Add time\r
409                 {\r
410                         TipLine tl = new TipLine();\r
411                         tipLines.add(tl);\r
412                         tl.label = "Time";\r
413                         LineMetrics lm = fm.getLineMetrics(tl.label, g);\r
414                         tl.height = lm.getHeight();\r
415                         tl.labelBaseline = fm.getAscent();\r
416                         textAreaHeight += marginBetweenLines;\r
417                         textAreaHeight += tl.height; \r
418                         tl.labelWidth = fm.stringWidth( tl.label );\r
419                         textAreaWidth = Math.max(tl.labelWidth, textAreaWidth);                 \r
420                         tl.color = Color.WHITE;\r
421 \r
422                         Format f = NumberFormat.getInstance();\r
423                         if (trend.timeFormat == org.simantics.trend.configuration.TimeFormat.Time) {                    \r
424                                 f = new TimeFormat(trend.horizRuler.iEnd, 3);\r
425                         }\r
426                         double t = time - trend.horizRuler.basetime;\r
427                         String formattedTime = f.format( time - trend.horizRuler.basetime );\r
428                         double roundedTime = t;\r
429                         try {\r
430                                 roundedTime = (Double) f.parseObject(formattedTime);\r
431                         } catch (ParseException e) {\r
432                                 // Should never happen.\r
433                         }\r
434                         boolean actuallyLessThan = t < roundedTime;\r
435                         boolean actuallyMoreThan = t > roundedTime;\r
436                         tl.value = actuallyLessThan ? "< " + formattedTime\r
437                                         : actuallyMoreThan ? "> " + formattedTime\r
438                                         : formattedTime;\r
439                         tl.valueWidth = fm.stringWidth( tl.value );\r
440                         valueAreaWidth = Math.max(valueAreaWidth, tl.valueWidth);\r
441                 }\r
442                 \r
443                 // Add items\r
444                 nextItem:\r
445                 for ( ItemNode i : trend.allItems )\r
446                 {\r
447                         TipLine tl = new TipLine();\r
448                         tipLines.add(tl);\r
449                         // Get Label\r
450                         tl.label = i.item.label;\r
451                         LineMetrics lm = fm.getLineMetrics(tl.label, g);\r
452                         tl.height = lm.getHeight();\r
453                         tl.labelBaseline = fm.getAscent();\r
454                         textAreaHeight += tl.height; \r
455                         textAreaHeight += marginBetweenLines;\r
456                         tl.labelWidth = fm.stringWidth( tl.label );\r
457                         textAreaWidth = Math.max(tl.labelWidth, textAreaWidth);\r
458                         tl.color = ColorUtil.gamma( i.color, 0.55 );\r
459                         \r
460                         // Get Value\r
461                         Stream s = i.openStream( /*pixelsPerSecond*/0 );\r
462                         if ( s!=null ) {\r
463                                 \r
464                                 int index = s.binarySearch(Bindings.DOUBLE, time);\r
465                                 if (index<0) index = -index-2;\r
466                                 if ( index<0 || index>=s.count() ) continue nextItem;\r
467                                 boolean isLast = index+1>=s.count();\r
468                                 \r
469                                 \r
470                                 ValueBand vb = new ValueBand(s.sampleBinding);                          \r
471                                 try {\r
472                                         vb.setSample( s.accessor.get(index, s.sampleBinding) );\r
473                                 } catch (AccessorException e) {\r
474                                         throw new HistoryException(e);\r
475                                 }\r
476                                 \r
477                                 if ( vb.getSample()!=null ) {\r
478                                         \r
479                                         if (isLast && vb.hasEndTime() && vb.getEndTimeDouble()<time) continue nextItem;\r
480                                                                                 \r
481                                         if ( !vb.isNanSample() && !vb.isNullValue() ) {\r
482                                                 Binding b = vb.getValueBinding();\r
483                                                 if ( b instanceof NumberBinding) {\r
484                                                         double v = vb.getValueDouble();\r
485                                                         tl.value = trend.valueFormat.format.format( v );\r
486                                                         tl.number = true;\r
487                                                         \r
488                                                         int desimalPos = tl.value.indexOf('.');\r
489                                                         if (desimalPos<0) desimalPos = tl.value.indexOf(',');\r
490                                                         if ( desimalPos>=0 ) {\r
491                                                                 String beforeDesimal = tl.value.substring(0, desimalPos);\r
492                                                                 String afterDesimal = tl.value.substring(desimalPos, tl.value.length());\r
493                                                                 tl.valueLeftWidth = fm.stringWidth(beforeDesimal);\r
494                                                                 tl.valueRightWidth = fm.stringWidth(afterDesimal);\r
495                                                                 tl.valueWidth = tl.valueLeftWidth + tl.valueRightWidth;\r
496                                                         } else {\r
497                                                                 tl.valueWidth = tl.valueLeftWidth = fm.stringWidth(tl.value);\r
498                                                         }\r
499                                                         \r
500                                                         valueAreaWidth = Math.max(valueAreaWidth, tl.valueLeftWidth+tl.valueRightWidth);\r
501                                                         valueAreaLeft = Math.max(valueAreaLeft, tl.valueLeftWidth);\r
502                                                         valueAreaRight = Math.max(valueAreaRight, tl.valueRightWidth);\r
503                                                 } else {\r
504                                                         Object v = vb.getValue();\r
505                                                         tl.value = b.toString(v);\r
506                                                         tl.number = false;\r
507                                                         tl.valueLeftWidth = tl.valueRightWidth = fm.stringWidth( tl.value );\r
508                                                         valueAreaWidth = Math.max(valueAreaWidth, tl.valueLeftWidth);\r
509                                                 }\r
510                                         }\r
511                                 }\r
512                         }\r
513                 }\r
514                 \r
515                 // Layout\r
516                 double halfQuantum = quantumWidthOfValueArea/2;\r
517                 valueAreaWidth = Math.ceil( valueAreaWidth / quantumWidthOfValueArea ) * quantumWidthOfValueArea;\r
518                 valueAreaLeft = Math.ceil( valueAreaLeft / halfQuantum ) * halfQuantum;\r
519                 valueAreaRight = Math.ceil( valueAreaRight / halfQuantum ) * halfQuantum;\r
520                 double finalValueAreaWidth = Math.max(valueAreaWidth, valueAreaLeft + valueAreaRight);\r
521                 w = marginOnFilm + textAreaWidth + marginBetweenNamesAndValues + finalValueAreaWidth + marginOnFilm + 0;\r
522                 h = marginOnFilm + textAreaHeight + marginOnFilm;\r
523                 double maxX = trend.plot.getWidth() - marginInPlot - w;\r
524                 double maxY = trend.plot.getHeight() - marginInPlot - h;\r
525                 x = marginInPlot + (maxX - marginInPlot)*trend.spec.viewProfile.valueViewPositionX;\r
526                 y = marginInPlot + (maxY - marginInPlot)*trend.spec.viewProfile.valueViewPositionY;\r
527 \r
528                 if ( x < TrendLayout.VERT_MARGIN ) x = TrendLayout.VERT_MARGIN;\r
529 \r
530                 valueTipBoxBounds.setFrame(x, y, w, h);\r
531                 //System.out.println("value tip bounds: " + valueTipBounds);\r
532 \r
533                 // Draw\r
534                 Rectangle2D rect = new Rectangle2D.Double(0, 0, w, h);\r
535                 Composite oldComposite = g.getComposite();\r
536                 AffineTransform oldTransform = g.getTransform();\r
537                 try {                   \r
538                         g.setComposite(composite66);\r
539                         g.translate(x, y);\r
540                         g.setColor(Color.BLACK);\r
541                         g.fill( rect );\r
542                         g.setFont( font );\r
543                         g.setComposite(oldComposite);\r
544                         \r
545                         y = marginInPlot;\r
546                         for (TipLine tl : tipLines) {\r
547                                 g.setColor( tl.color );\r
548                                 x = marginInPlot;\r
549                                 g.drawString( tl.label, (float)x, (float)(y+tl.labelBaseline));\r
550 \r
551                                 if ( tl.value!=null ) {\r
552                                         x = marginInPlot + textAreaWidth + marginBetweenNamesAndValues;\r
553                                         if ( tl.number ) {\r
554                                                 x += valueAreaLeft - tl.valueLeftWidth;\r
555                                         } else {\r
556                                                 x += (finalValueAreaWidth - tl.valueWidth)/2;\r
557                                         }\r
558                                         g.drawString(tl.value, (float) x, (float) (y+tl.labelBaseline));\r
559                                 }\r
560                                 \r
561                                 y+=tl.height;\r
562                                 y+=marginBetweenLines;\r
563                         }\r
564                         \r
565                 } finally {\r
566                         g.setTransform( oldTransform );\r
567                         g.setComposite( oldComposite );\r
568                 }\r
569         }\r
570 \r
571         static class TipLine {\r
572                 String label, value;\r
573                 Color color;\r
574                 double labelWidth, height, valueLeftWidth, valueRightWidth, valueWidth, labelBaseline;\r
575                 boolean number;\r
576 \r
577                 @Override\r
578                 public String toString() {\r
579                         return "TipLine[label=" + label + ", value=" + value + ", color=" + color + ", labelWidth=" + labelWidth\r
580                                         + ", height=" + height + ", valueLeftWidth=" + valueLeftWidth + ", valueRightWidth="\r
581                                         + valueRightWidth + ", valueWidth=" + valueWidth + ", labelBaseline=" + labelBaseline + ", number="\r
582                                         + number + "]";\r
583                 }\r
584         }\r
585 \r
586         public void renderValueTip(Graphics2D g2d) {\r
587                 TrendNode trend = getTrend();\r
588                 if ( trend.valueTipTime != null ) {\r
589                         AffineTransform at = g2d.getTransform();                                \r
590                         try {\r
591                                 g2d.transform( getTransform() );\r
592                                 drawValuetip( g2d, trend.valueTipTime );\r
593                         } catch (HistoryException e) {\r
594                                 e.printStackTrace();\r
595                         } catch (BindingException e) {\r
596                                 e.printStackTrace();\r
597                         } finally {\r
598                                 g2d.setTransform( at );\r
599                         }                       \r
600                 }\r
601         }\r
602         \r
603         /**\r
604          * Pick item (Binary node)\r
605          * \r
606          * @param pt coordinate in trend coordinate system\r
607          * @return item node\r
608          */\r
609         public ItemNode pickItem(Point2D pt)\r
610         {\r
611                 TrendNode trend = getTrend();\r
612                 double y = pt.getY()-getY();\r
613                 double x = pt.getX()-getX();\r
614                 if (y<analogAreaHeight || y>analogAreaHeight+binaryAreaHeight) return null;\r
615                 if (x<0 || x+getX()>trend.getBounds().getWidth()) return null;\r
616                 for (int i=0; i<trend.binaryItems.size(); i++) {\r
617                         double sy = analogAreaHeight + i*BINARY[3];\r
618                         double ey = analogAreaHeight + (i+1)*BINARY[3];\r
619                         if ( y>=sy && y<ey ) return trend.binaryItems.get(i);\r
620                 }\r
621                 return null;\r
622         }\r
623                 \r
624 }\r