1 /*******************************************************************************
\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
9 * VTT Technical Research Centre of Finland - initial API and implementation
\r
10 *******************************************************************************/
\r
11 package org.simantics.trend.impl;
\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
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
50 public class Plot extends TrendGraphicalNode {
\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
55 private static final long serialVersionUID = 6335497685577733932L;
\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
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
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
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
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
100 static final Font MILESTONE_FONT, BASELINE_FONT, TOOLTIP_FONT;
\r
102 static final GridSpacing SOME_SPACING = GridSpacing.makeGridSpacing(100, 100, 15);
\r
104 public static final Color GRID_LINE_COLOR = new Color(190, 190, 220);
\r
106 static final double DIAMOND_SIZE = 7;
\r
107 static final Path2D DIAMOND;
\r
109 double analogAreaHeight;
\r
110 double binaryAreaHeight;
\r
112 Rectangle2D valueTipBoxBounds = new Rectangle2D.Double();
\r
114 @SuppressWarnings("unused")
\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
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
129 if (w<1. || h<1.) return;
\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
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
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
156 from - trend.horizRuler.basetime,
\r
157 trend.vertRuler != null ? trend.vertRuler.min : 0,
\r
163 Rectangle2D oldClip = g2d.getClipBounds();
\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
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
176 g2d.setClip(oldClip);
\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
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
196 g2d.setClip(oldClip);
\r
199 g2d.setFont( RULER_FONT );
\r
200 for (int i=0; i<trend.binaryItems.size(); i++)
\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
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
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
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
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
240 Font f = isBaseline ? BASELINE_FONT : MILESTONE_FONT;
\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
251 g2d.setColor( Color.BLACK );
\r
255 g2d.translate(-x, 0);
\r
259 // Draw hover marker
\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
270 g2d.setStroke( DASHED_LINE_STROKE );
\r
271 g2d.setColor( Color.BLACK );
\r
274 // g2d.setStroke( DASHED_LINE_STROKE_INVERSE );
\r
275 // g2d.setColor( Color.white );
\r
276 // g2d.draw( line );
\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
289 //long endTime = System.nanoTime();
\r
290 // System.out.println("Plot render: "+((double)(endTime-startTime)/1000000)+" ms");
\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
298 // trend.vertRulerIndex
\r
299 // boolean selected = trend.singleAxis ? false : trend.vertRuler
\r
300 // selected &= !trend.printing;
\r
302 VertRuler ruler = data.ruler;
\r
303 AffineTransform at = g.getTransform();
\r
305 //double pixelsPerSecond = (end-from) / getWidth();
\r
307 g.setStroke(data.stroke);
\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
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
322 // } catch (HistoryException e) {
\r
323 // e.printStackTrace();
\r
324 // } catch (AccessorException e) {
\r
325 // e.printStackTrace();
\r
327 g.setTransform(at);
\r
332 * Prepare item for draw.
\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
343 VertRuler ruler = data.ruler;
\r
345 double pixelsPerSecond = (end-from) / getWidth();
\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
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
358 } catch (HistoryException e) {
\r
359 e.printStackTrace();
\r
360 } catch (AccessorException e) {
\r
361 e.printStackTrace();
\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
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
381 public static final AlphaComposite composite66 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .80f);
\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
388 //double from = trend.horizRuler.from;
\r
389 //double end = trend.horizRuler.end;
\r
390 //double pixelsPerSecond = (end-from) / trend.plot.getWidth();
\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
405 List<TipLine> tipLines = new ArrayList<TipLine>( trend.allItems.size()+1 );
\r
410 TipLine tl = new TipLine();
\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
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
426 double t = time - trend.horizRuler.basetime;
\r
427 String formattedTime = f.format( time - trend.horizRuler.basetime );
\r
428 double roundedTime = t;
\r
430 roundedTime = (Double) f.parseObject(formattedTime);
\r
431 } catch (ParseException e) {
\r
432 // Should never happen.
\r
434 boolean actuallyLessThan = t < roundedTime;
\r
435 boolean actuallyMoreThan = t > roundedTime;
\r
436 tl.value = actuallyLessThan ? "< " + formattedTime
\r
437 : actuallyMoreThan ? "> " + formattedTime
\r
439 tl.valueWidth = fm.stringWidth( tl.value );
\r
440 valueAreaWidth = Math.max(valueAreaWidth, tl.valueWidth);
\r
445 for ( ItemNode i : trend.allItems )
\r
447 TipLine tl = new TipLine();
\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
461 Stream s = i.openStream( /*pixelsPerSecond*/0 );
\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
470 ValueBand vb = new ValueBand(s.sampleBinding);
\r
472 vb.setSample( s.accessor.get(index, s.sampleBinding) );
\r
473 } catch (AccessorException e) {
\r
474 throw new HistoryException(e);
\r
477 if ( vb.getSample()!=null ) {
\r
479 if (isLast && vb.hasEndTime() && vb.getEndTimeDouble()<time) continue nextItem;
\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
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
497 tl.valueWidth = tl.valueLeftWidth = fm.stringWidth(tl.value);
\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
504 Object v = vb.getValue();
\r
505 tl.value = b.toString(v);
\r
507 tl.valueLeftWidth = tl.valueRightWidth = fm.stringWidth( tl.value );
\r
508 valueAreaWidth = Math.max(valueAreaWidth, tl.valueLeftWidth);
\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
528 if ( x < TrendLayout.VERT_MARGIN ) x = TrendLayout.VERT_MARGIN;
\r
530 valueTipBoxBounds.setFrame(x, y, w, h);
\r
531 //System.out.println("value tip bounds: " + valueTipBounds);
\r
534 Rectangle2D rect = new Rectangle2D.Double(0, 0, w, h);
\r
535 Composite oldComposite = g.getComposite();
\r
536 AffineTransform oldTransform = g.getTransform();
\r
538 g.setComposite(composite66);
\r
540 g.setColor(Color.BLACK);
\r
543 g.setComposite(oldComposite);
\r
546 for (TipLine tl : tipLines) {
\r
547 g.setColor( tl.color );
\r
549 g.drawString( tl.label, (float)x, (float)(y+tl.labelBaseline));
\r
551 if ( tl.value!=null ) {
\r
552 x = marginInPlot + textAreaWidth + marginBetweenNamesAndValues;
\r
554 x += valueAreaLeft - tl.valueLeftWidth;
\r
556 x += (finalValueAreaWidth - tl.valueWidth)/2;
\r
558 g.drawString(tl.value, (float) x, (float) (y+tl.labelBaseline));
\r
562 y+=marginBetweenLines;
\r
566 g.setTransform( oldTransform );
\r
567 g.setComposite( oldComposite );
\r
571 static class TipLine {
\r
572 String label, value;
\r
574 double labelWidth, height, valueLeftWidth, valueRightWidth, valueWidth, labelBaseline;
\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
586 public void renderValueTip(Graphics2D g2d) {
\r
587 TrendNode trend = getTrend();
\r
588 if ( trend.valueTipTime != null ) {
\r
589 AffineTransform at = g2d.getTransform();
\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
598 g2d.setTransform( at );
\r
604 * Pick item (Binary node)
\r
606 * @param pt coordinate in trend coordinate system
\r
607 * @return item node
\r
609 public ItemNode pickItem(Point2D pt)
\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