X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.trend%2Fsrc%2Forg%2Fsimantics%2Ftrend%2Fimpl%2FPlot.java;h=d014d6556a0633d6704338bf1c565e1e9141da3c;hp=bb5de8c0dd22bb56ecaea3646cda27df927cc081;hb=e2e0b33c6a7b55c4b0f7a268dd37e545feefc5a2;hpb=969bd23cab98a79ca9101af33334000879fb60c5 diff --git a/bundles/org.simantics.trend/src/org/simantics/trend/impl/Plot.java b/bundles/org.simantics.trend/src/org/simantics/trend/impl/Plot.java index bb5de8c0d..d014d6556 100644 --- a/bundles/org.simantics.trend/src/org/simantics/trend/impl/Plot.java +++ b/bundles/org.simantics.trend/src/org/simantics/trend/impl/Plot.java @@ -1,624 +1,632 @@ -/******************************************************************************* - * Industry THTH ry. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * VTT Technical Research Centre of Finland - initial API and implementation - *******************************************************************************/ -package org.simantics.trend.impl; - -import java.awt.AlphaComposite; -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Composite; -import java.awt.Font; -import java.awt.FontMetrics; -import java.awt.GradientPaint; -import java.awt.Graphics2D; -import java.awt.Paint; -import java.awt.font.GlyphVector; -import java.awt.font.LineMetrics; -import java.awt.geom.AffineTransform; -import java.awt.geom.Line2D; -import java.awt.geom.Path2D; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import java.text.Format; -import java.text.NumberFormat; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.List; - -import org.simantics.databoard.Bindings; -import org.simantics.databoard.accessor.error.AccessorException; -import org.simantics.databoard.binding.Binding; -import org.simantics.databoard.binding.NumberBinding; -import org.simantics.databoard.binding.error.BindingException; -import org.simantics.g2d.utils.GridSpacing; -import org.simantics.g2d.utils.GridUtil; -import org.simantics.history.HistoryException; -import org.simantics.history.util.Stream; -import org.simantics.history.util.ValueBand; -import org.simantics.scenegraph.utils.ColorUtil; -import org.simantics.trend.configuration.TrendItem.Renderer; -import org.simantics.trend.configuration.TrendSpec; -import org.simantics.utils.format.TimeFormat; - -public class Plot extends TrendGraphicalNode { - - public static final double VALUE_TIP_BOX_PLOT_MARGIN = 7; - private static final double VALUE_TIP_BOX_FILM_MARGIN = 5; - - private static final long serialVersionUID = 6335497685577733932L; - - public static final BasicStroke BORDER_LINE_STROKE = - new BasicStroke(1.0f, - BasicStroke.CAP_SQUARE, - BasicStroke.JOIN_MITER, - 10.0f, null, 0.0f); - - public static final BasicStroke MILESTONE_LINE_STROKE = - new BasicStroke(1.0f, - BasicStroke.CAP_SQUARE, - BasicStroke.JOIN_MITER, - 10.0f, null, 0.0f); - - public static final BasicStroke TREND_LINE_STROKE = - new BasicStroke(1.0f, - BasicStroke.CAP_BUTT, - BasicStroke.JOIN_MITER, - 10.0f, null, 0.0f); - - public static final BasicStroke DASHED_LINE_STROKE = - new BasicStroke(2.0f, // Width - BasicStroke.CAP_BUTT, // End cap - BasicStroke.JOIN_MITER, // Join style - 5.0f, // Miter limit - new float[] {5.0f,5.0f}, // Dash pattern - 0.0f); // Dash phase - public static final BasicStroke DASHED_LINE_STROKE_2 = - new BasicStroke(2.0f, // Width - BasicStroke.CAP_BUTT, // End cap - BasicStroke.JOIN_MITER, // Join style - 5.0f, // Miter limit - new float[] {5.0f,5.0f}, // Dash pattern - 5.0f); // Dash phase - public static final BasicStroke DASHED_LINE_STROKE_INVERSE = - new BasicStroke(1.0f, // Width - BasicStroke.CAP_BUTT, // End cap - BasicStroke.JOIN_MITER, // Join style - 5.0f, // Miter limit - new float[] {3.0f,8.0f}, // Dash pattern - 0.0f); // Dash phase - - public static final Color PLOT_AREA_BG_GRADIENT_COLOR_TOP = new Color(228, 228, 248); - public static final Color PLOT_AREA_BG_GRADIENT_COLOR_BOTTOM = new Color(250, 250, 250); - - static final Font MILESTONE_FONT, BASELINE_FONT, TOOLTIP_FONT; - - static final GridSpacing SOME_SPACING = GridSpacing.makeGridSpacing(100, 100, 15); - - public static final Color GRID_LINE_COLOR = new Color(190, 190, 220); - - static final double DIAMOND_SIZE = 7; - static final Path2D DIAMOND; - - double analogAreaHeight; - double binaryAreaHeight; - - Rectangle2D valueTipBoxBounds = new Rectangle2D.Double(); - - @SuppressWarnings("unused") - @Override - protected void doRender(Graphics2D g2d) { - //long startTime = System.nanoTime(); - TrendNode trend = (TrendNode) getParent(); - TrendSpec ts = trend.spec; - ViewRenderingProfile rprof = trend.renderingProfile; - double w = bounds.getWidth(); - double h = bounds.getHeight(); - - double from = trend.horizRuler.from; - double end = trend.horizRuler.end; - GridSpacing xGrid = trend.horizRuler.spacing; - GridSpacing yGrid = trend.vertRuler != null ? trend.vertRuler.spacing : Plot.SOME_SPACING; - - if (w<1. || h<1.) return; - - // Prepare data - if (trend.shapedirty) { - trend.shapedirty = false; - for (ItemNode node : trend.analogItems) prepareItem(node, 0, analogAreaHeight); - for (ItemNode node : trend.binaryItems) prepareItem(node, 0, analogAreaHeight); - } - - // Fill gradient - Gradient: { - Paint bgp = rprof.backgroundColor2 == null ? rprof.backgroundColor1 - : new GradientPaint( - 0.f, (float) h, rprof.backgroundColor1, - 0.f, 0.f, rprof.backgroundColor2, false); -// g2d.setPaint(Color.white); - g2d.setPaint( bgp ); - g2d.fill(bounds); - } - - // Draw grid lines - GridLines: if (ts.viewProfile.showGrid) { - g2d.setPaint( rprof.gridColor ); - g2d.setStroke( GridUtil.GRID_LINE_STROKE ); - GridUtil.paintGridLines( - xGrid, yGrid, - g2d, - from - trend.horizRuler.basetime, - trend.vertRuler != null ? trend.vertRuler.min : 0, - w, - h, - analogAreaHeight); - } - - Rectangle2D oldClip = g2d.getClipBounds(); - - // Draw analog items - AnalogItems: if ( !trend.analogItems.isEmpty() ) { - Rectangle2D analogAreaClip = new Rectangle2D.Double(0, 0, getWidth(), analogAreaHeight); - g2d.setClip( analogAreaClip ); - - for (int phase=0; phase<4; phase++) { - for (int i=0; i ls = mss.milestones; - if ( ls.isEmpty() ) break Milestone; - - Line2D line = new Line2D.Double(0, 0, 0, h); - g2d.setStroke( MILESTONE_LINE_STROKE ); - Rectangle2D diamondRegion = new Rectangle2D.Double(0, -DIAMOND_SIZE*2, w, DIAMOND_SIZE*2); - - for (int i=mss.milestones.size()-1; i>=0; i--) { - Milestone ms = mss.milestones.get( i ); - if ( ms.hidden ) continue; - boolean isBaseline = i == mss.baseline; - double time = ms.time; - double x = (time-from)*sx; - if (x<-DIAMOND_SIZE*2 || x>w+DIAMOND_SIZE*2) continue; - x = Math.floor(x); - - // Diamond - g2d.setClip(diamondRegion); - g2d.translate( x, 0); - g2d.setColor( isBaseline ? Color.LIGHT_GRAY : Color.DARK_GRAY ); - g2d.fill( DIAMOND ); - g2d.setColor( Color.BLACK ); - g2d.draw( DIAMOND ); - - // Text - Font f = isBaseline ? BASELINE_FONT : MILESTONE_FONT; - g2d.setFont( f ); - g2d.setColor( isBaseline ? Color.black : Color.ORANGE ); - GlyphVector glyphVector = f.createGlyphVector(g2d.getFontRenderContext(), ms.label); - double cx = glyphVector.getVisualBounds().getCenterX(); - double cy = glyphVector.getVisualBounds().getHeight(); - g2d.drawString(ms.label, (float)(-cx), (float)(-DIAMOND_SIZE+cy/2) ); - g2d.setClip( null ); - - // Line - if (x>=0 && x=from && time<=end && !Double.isNaN(time)) { - double sx = getWidth() / ( end - from ); - double x = (time-from)*sx; - Line2D line = new Line2D.Double(x, 0, x, h); - g2d.setStroke( DASHED_LINE_STROKE_2 ); - g2d.setColor( trend.valueTipHover ? Color.GRAY : Color.WHITE ); - g2d.draw( line ); - - g2d.setStroke( DASHED_LINE_STROKE ); - g2d.setColor( Color.BLACK ); - g2d.draw( line ); - -// g2d.setStroke( DASHED_LINE_STROKE_INVERSE ); -// g2d.setColor( Color.white ); -// g2d.draw( line ); - } - } - - // Draw border - Border: { - g2d.setStroke(BORDER_LINE_STROKE); - g2d.setColor( Color.BLACK ); - Rectangle2D rect = new Rectangle2D.Double(); - rect.setFrame(0, 0, w, h); - g2d.draw(rect); - } - - //long endTime = System.nanoTime(); -// System.out.println("Plot render: "+((double)(endTime-startTime)/1000000)+" ms"); - } - - public void drawItem(Graphics2D g, ItemNode data, double y, double height, int phase) { - TrendNode trend = getTrend(); - double from = trend.horizRuler.from; - double end = trend.horizRuler.end; - -// trend.vertRulerIndex -// boolean selected = trend.singleAxis ? false : trend.vertRuler -// selected &= !trend.printing; - - VertRuler ruler = data.ruler; - AffineTransform at = g.getTransform(); - try { - //double pixelsPerSecond = (end-from) / getWidth(); - g.translate(0, y); - g.setStroke(data.stroke); - - AffineTransform ab = new AffineTransform(); - if (data.item.renderer == Renderer.Analog) { - ab.scale( getWidth()/(end-from), height/(ruler.min-ruler.max) ); - ab.translate(-from, -ruler.max); -// if (phase == 0) data.prepareLine(from, end, pixelsPerSecond, ab); - data.draw(g, phase, false); - } - if (data.item.renderer == Renderer.Binary) { - ab.scale( getWidth()/(end-from), 1/*height*/ ); - ab.translate(-from, 0); -// if (phase == 0) data.prepareLine(from, end, pixelsPerSecond, ab); - data.draw(g, phase, false); - } -// } catch (HistoryException e) { -// e.printStackTrace(); -// } catch (AccessorException e) { -// e.printStackTrace(); - } finally { - g.setTransform(at); - } - } - - /** - * Prepare item for draw. - * - * @param data - * @param y - * @param height - */ - public void prepareItem(ItemNode data, double y, double height) { - TrendNode tn = getTrend(); - double from = tn.horizRuler.from; - double end = tn.horizRuler.end; - - VertRuler ruler = data.ruler; - try { - double pixelsPerSecond = (end-from) / getWidth(); - - AffineTransform ab = new AffineTransform(); - if (data.item.renderer == Renderer.Analog) { - ab.scale( getWidth()/(end-from), height/(ruler.min-ruler.max) ); - ab.translate(-from, -ruler.max); - data.prepareLine(from, end, pixelsPerSecond, ab); - } - if (data.item.renderer == Renderer.Binary) { - ab.scale( getWidth()/(end-from), 1/*height*/ ); - ab.translate(-from, 0); - data.prepareLine(from, end, pixelsPerSecond, ab); - } - } catch (HistoryException e) { - e.printStackTrace(); - } catch (AccessorException e) { - e.printStackTrace(); - } - } - - static { - - DIAMOND = new Path2D.Double(); - DIAMOND.moveTo(0, -DIAMOND_SIZE*2); - DIAMOND.lineTo(DIAMOND_SIZE, -DIAMOND_SIZE); - DIAMOND.lineTo(0, 0); - DIAMOND.lineTo(-DIAMOND_SIZE, -DIAMOND_SIZE); - DIAMOND.lineTo(0, -DIAMOND_SIZE*2); - - MILESTONE_FONT = new Font("Tahoma", 0, (int) (DIAMOND_SIZE*1.2) ); - BASELINE_FONT = new Font("Tahoma", Font.BOLD, (int) (DIAMOND_SIZE*1.2) ); - TOOLTIP_FONT = new Font("Tahoma", 0, 13 ); - } - - - /// ValueTip - public static final AlphaComposite composite66 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .80f); - - void drawValuetip( Graphics2D g, double time ) throws HistoryException, BindingException { - TrendNode trend = getTrend(); - Font font = TOOLTIP_FONT; - FontMetrics fm = g.getFontMetrics( font ); - - //double from = trend.horizRuler.from; - //double end = trend.horizRuler.end; - //double pixelsPerSecond = (end-from) / trend.plot.getWidth(); - - double marginInPlot = VALUE_TIP_BOX_PLOT_MARGIN; - double marginOnFilm = VALUE_TIP_BOX_FILM_MARGIN; - double marginBetweenNamesAndValues = 16; - double quantumWidthOfValueArea = 20; - double marginBetweenLines = 5; - double textAreaHeight = 0; - double textAreaWidth = 0; - double valueAreaLeft = 0; - double valueAreaRight = 0; - double valueAreaWidth = 0; - - double x, y, w, h; - - List tipLines = new ArrayList( trend.allItems.size()+1 ); - - /// Initialize - // Add time - { - TipLine tl = new TipLine(); - tipLines.add(tl); - tl.label = "Time"; - LineMetrics lm = fm.getLineMetrics(tl.label, g); - tl.height = lm.getHeight(); - tl.labelBaseline = fm.getAscent(); - textAreaHeight += marginBetweenLines; - textAreaHeight += tl.height; - tl.labelWidth = fm.stringWidth( tl.label ); - textAreaWidth = Math.max(tl.labelWidth, textAreaWidth); - tl.color = Color.WHITE; - - Format f = NumberFormat.getInstance(); - if (trend.timeFormat == org.simantics.trend.configuration.TimeFormat.Time) { - f = new TimeFormat(trend.horizRuler.iEnd, 3); - } - double t = time - trend.horizRuler.basetime; - String formattedTime = f.format( time - trend.horizRuler.basetime ); - double roundedTime = t; - try { - roundedTime = (Double) f.parseObject(formattedTime); - } catch (ParseException e) { - // Should never happen. - } - boolean actuallyLessThan = t < roundedTime; - boolean actuallyMoreThan = t > roundedTime; - tl.value = actuallyLessThan ? "< " + formattedTime - : actuallyMoreThan ? "> " + formattedTime - : formattedTime; - tl.valueWidth = fm.stringWidth( tl.value ); - valueAreaWidth = Math.max(valueAreaWidth, tl.valueWidth); - } - - // Add items - nextItem: - for ( ItemNode i : trend.allItems ) - { - TipLine tl = new TipLine(); - tipLines.add(tl); - // Get Label - tl.label = i.item.label; - LineMetrics lm = fm.getLineMetrics(tl.label, g); - tl.height = lm.getHeight(); - tl.labelBaseline = fm.getAscent(); - textAreaHeight += tl.height; - textAreaHeight += marginBetweenLines; - tl.labelWidth = fm.stringWidth( tl.label ); - textAreaWidth = Math.max(tl.labelWidth, textAreaWidth); - tl.color = ColorUtil.gamma( i.color, 0.55 ); - - // Get Value - Stream s = i.openStream( /*pixelsPerSecond*/0 ); - if ( s!=null ) { - - int index = s.binarySearch(Bindings.DOUBLE, time); - if (index<0) index = -index-2; - if ( index<0 || index>=s.count() ) continue nextItem; - boolean isLast = index+1>=s.count(); - - - ValueBand vb = new ValueBand(s.sampleBinding); - try { - vb.setSample( s.accessor.get(index, s.sampleBinding) ); - } catch (AccessorException e) { - throw new HistoryException(e); - } - - if ( vb.getSample()!=null ) { - - if (isLast && vb.hasEndTime() && vb.getEndTimeDouble()=0 ) { - String beforeDesimal = tl.value.substring(0, desimalPos); - String afterDesimal = tl.value.substring(desimalPos, tl.value.length()); - tl.valueLeftWidth = fm.stringWidth(beforeDesimal); - tl.valueRightWidth = fm.stringWidth(afterDesimal); - tl.valueWidth = tl.valueLeftWidth + tl.valueRightWidth; - } else { - tl.valueWidth = tl.valueLeftWidth = fm.stringWidth(tl.value); - } - - valueAreaWidth = Math.max(valueAreaWidth, tl.valueLeftWidth+tl.valueRightWidth); - valueAreaLeft = Math.max(valueAreaLeft, tl.valueLeftWidth); - valueAreaRight = Math.max(valueAreaRight, tl.valueRightWidth); - } else { - Object v = vb.getValue(); - tl.value = b.toString(v); - tl.number = false; - tl.valueLeftWidth = tl.valueRightWidth = fm.stringWidth( tl.value ); - valueAreaWidth = Math.max(valueAreaWidth, tl.valueLeftWidth); - } - } - } - } - } - - // Layout - double halfQuantum = quantumWidthOfValueArea/2; - valueAreaWidth = Math.ceil( valueAreaWidth / quantumWidthOfValueArea ) * quantumWidthOfValueArea; - valueAreaLeft = Math.ceil( valueAreaLeft / halfQuantum ) * halfQuantum; - valueAreaRight = Math.ceil( valueAreaRight / halfQuantum ) * halfQuantum; - double finalValueAreaWidth = Math.max(valueAreaWidth, valueAreaLeft + valueAreaRight); - w = marginOnFilm + textAreaWidth + marginBetweenNamesAndValues + finalValueAreaWidth + marginOnFilm + 0; - h = marginOnFilm + textAreaHeight + marginOnFilm; - double maxX = trend.plot.getWidth() - marginInPlot - w; - double maxY = trend.plot.getHeight() - marginInPlot - h; - x = marginInPlot + (maxX - marginInPlot)*trend.spec.viewProfile.valueViewPositionX; - y = marginInPlot + (maxY - marginInPlot)*trend.spec.viewProfile.valueViewPositionY; - - if ( x < TrendLayout.VERT_MARGIN ) x = TrendLayout.VERT_MARGIN; - - valueTipBoxBounds.setFrame(x, y, w, h); - //System.out.println("value tip bounds: " + valueTipBounds); - - // Draw - Rectangle2D rect = new Rectangle2D.Double(0, 0, w, h); - Composite oldComposite = g.getComposite(); - AffineTransform oldTransform = g.getTransform(); - try { - g.setComposite(composite66); - g.translate(x, y); - g.setColor(Color.BLACK); - g.fill( rect ); - g.setFont( font ); - g.setComposite(oldComposite); - - y = marginInPlot; - for (TipLine tl : tipLines) { - g.setColor( tl.color ); - x = marginInPlot; - g.drawString( tl.label, (float)x, (float)(y+tl.labelBaseline)); - - if ( tl.value!=null ) { - x = marginInPlot + textAreaWidth + marginBetweenNamesAndValues; - if ( tl.number ) { - x += valueAreaLeft - tl.valueLeftWidth; - } else { - x += (finalValueAreaWidth - tl.valueWidth)/2; - } - g.drawString(tl.value, (float) x, (float) (y+tl.labelBaseline)); - } - - y+=tl.height; - y+=marginBetweenLines; - } - - } finally { - g.setTransform( oldTransform ); - g.setComposite( oldComposite ); - } - } - - static class TipLine { - String label, value; - Color color; - double labelWidth, height, valueLeftWidth, valueRightWidth, valueWidth, labelBaseline; - boolean number; - - @Override - public String toString() { - return "TipLine[label=" + label + ", value=" + value + ", color=" + color + ", labelWidth=" + labelWidth - + ", height=" + height + ", valueLeftWidth=" + valueLeftWidth + ", valueRightWidth=" - + valueRightWidth + ", valueWidth=" + valueWidth + ", labelBaseline=" + labelBaseline + ", number=" - + number + "]"; - } - } - - public void renderValueTip(Graphics2D g2d) { - TrendNode trend = getTrend(); - if ( trend.valueTipTime != null ) { - AffineTransform at = g2d.getTransform(); - try { - g2d.transform( getTransform() ); - drawValuetip( g2d, trend.valueTipTime ); - } catch (HistoryException e) { - e.printStackTrace(); - } catch (BindingException e) { - e.printStackTrace(); - } finally { - g2d.setTransform( at ); - } - } - } - - /** - * Pick item (Binary node) - * - * @param pt coordinate in trend coordinate system - * @return item node - */ - public ItemNode pickItem(Point2D pt) - { - TrendNode trend = getTrend(); - double y = pt.getY()-getY(); - double x = pt.getX()-getX(); - if (yanalogAreaHeight+binaryAreaHeight) return null; - if (x<0 || x+getX()>trend.getBounds().getWidth()) return null; - for (int i=0; i=sy && y ls = mss.milestones; + if ( ls.isEmpty() ) break Milestone; + + Line2D line = new Line2D.Double(0, 0, 0, h); + g2d.setStroke( MILESTONE_LINE_STROKE ); + Rectangle2D diamondRegion = new Rectangle2D.Double(0, -DIAMOND_SIZE*2, w, DIAMOND_SIZE*2); + + for (int i=mss.milestones.size()-1; i>=0; i--) { + Milestone ms = mss.milestones.get( i ); + if ( ms.hidden ) continue; + boolean isBaseline = i == mss.baseline; + double time = ms.time; + double x = (time-from)*sx; + if (x<-DIAMOND_SIZE*2 || x>w+DIAMOND_SIZE*2) continue; + x = Math.floor(x); + + // Diamond + g2d.setClip(diamondRegion); + g2d.translate( x, 0); + g2d.setColor( isBaseline ? Color.LIGHT_GRAY : Color.DARK_GRAY ); + g2d.fill( DIAMOND ); + g2d.setColor( Color.BLACK ); + g2d.draw( DIAMOND ); + + // Text + Font f = isBaseline ? BASELINE_FONT : MILESTONE_FONT; + g2d.setFont( f ); + g2d.setColor( isBaseline ? Color.black : Color.ORANGE ); + GlyphVector glyphVector = f.createGlyphVector(g2d.getFontRenderContext(), ms.label); + double cx = glyphVector.getVisualBounds().getCenterX(); + double cy = glyphVector.getVisualBounds().getHeight(); + g2d.drawString(ms.label, (float)(-cx), (float)(-DIAMOND_SIZE+cy/2) ); + g2d.setClip( null ); + + // Line + if (x>=0 && x=from && time<=end && !Double.isNaN(time)) { + double sx = getWidth() / ( end - from ); + double x = (time-from)*sx; + Line2D line = new Line2D.Double(x, 0, x, h); + g2d.setStroke( DASHED_LINE_STROKE_2 ); + g2d.setColor( trend.valueTipHover ? Color.GRAY : Color.WHITE ); + g2d.draw( line ); + + g2d.setStroke( DASHED_LINE_STROKE ); + g2d.setColor( Color.BLACK ); + g2d.draw( line ); + +// g2d.setStroke( DASHED_LINE_STROKE_INVERSE ); +// g2d.setColor( Color.white ); +// g2d.draw( line ); + } + } + + // Draw border + Border: { + g2d.setStroke(BORDER_LINE_STROKE); + g2d.setColor( Color.BLACK ); + Rectangle2D rect = new Rectangle2D.Double(); + rect.setFrame(0, 0, w, h); + g2d.draw(rect); + } + + //long endTime = System.nanoTime(); +// System.out.println("Plot render: "+((double)(endTime-startTime)/1000000)+" ms"); + } + + public void drawItem(Graphics2D g, ItemNode data, double y, double height, int phase) { + TrendNode trend = getTrend(); + double from = trend.horizRuler.from; + double end = trend.horizRuler.end; + +// trend.vertRulerIndex +// boolean selected = trend.singleAxis ? false : trend.vertRuler +// selected &= !trend.printing; + + VertRuler ruler = data.ruler; + AffineTransform at = g.getTransform(); + try { + //double pixelsPerSecond = (end-from) / getWidth(); + g.translate(0, y); + g.setStroke(data.stroke); + + AffineTransform ab = new AffineTransform(); + if (data.item.renderer == Renderer.Analog) { + ab.scale( getWidth()/(end-from), height/(ruler.min-ruler.max) ); + ab.translate(-from, -ruler.max); +// if (phase == 0) data.prepareLine(from, end, pixelsPerSecond, ab); + data.draw(g, phase, false); + } + if (data.item.renderer == Renderer.Binary) { + ab.scale( getWidth()/(end-from), 1/*height*/ ); + ab.translate(-from, 0); +// if (phase == 0) data.prepareLine(from, end, pixelsPerSecond, ab); + data.draw(g, phase, false); + } +// } catch (HistoryException e) { +// e.printStackTrace(); +// } catch (AccessorException e) { +// e.printStackTrace(); + } finally { + g.setTransform(at); + } + } + + /** + * Prepare item for draw. + * + * @param data + * @param y + * @param height + */ + public void prepareItem(ItemNode data, double y, double height) { + TrendNode tn = getTrend(); + double from = tn.horizRuler.from; + double end = tn.horizRuler.end; + + VertRuler ruler = data.ruler; + try { + double pixelsPerSecond = (end-from) / getWidth(); + + AffineTransform ab = new AffineTransform(); + if (data.item.renderer == Renderer.Analog) { + ab.scale( getWidth()/(end-from), height/(ruler.min-ruler.max) ); + ab.translate(-from, -ruler.max); + data.prepareLine(from, end, pixelsPerSecond, ab); + } + if (data.item.renderer == Renderer.Binary) { + ab.scale( getWidth()/(end-from), 1/*height*/ ); + ab.translate(-from, 0); + data.prepareLine(from, end, pixelsPerSecond, ab); + } + } catch (HistoryException e) { + e.printStackTrace(); + } catch (AccessorException e) { + e.printStackTrace(); + } + } + + static { + + DIAMOND = new Path2D.Double(); + DIAMOND.moveTo(0, -DIAMOND_SIZE*2); + DIAMOND.lineTo(DIAMOND_SIZE, -DIAMOND_SIZE); + DIAMOND.lineTo(0, 0); + DIAMOND.lineTo(-DIAMOND_SIZE, -DIAMOND_SIZE); + DIAMOND.lineTo(0, -DIAMOND_SIZE*2); + + MILESTONE_FONT = new Font("Tahoma", 0, (int) (DIAMOND_SIZE*1.2) ); + BASELINE_FONT = new Font("Tahoma", Font.BOLD, (int) (DIAMOND_SIZE*1.2) ); + TOOLTIP_FONT = new Font("Tahoma", 0, 13 ); + } + + + /// ValueTip + public static final AlphaComposite composite66 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .80f); + + void drawValuetip( Graphics2D g, double time ) throws HistoryException, BindingException { + TrendNode trend = getTrend(); + Font font = TOOLTIP_FONT; + FontMetrics fm = g.getFontMetrics( font ); + + //double from = trend.horizRuler.from; + //double end = trend.horizRuler.end; + //double pixelsPerSecond = (end-from) / trend.plot.getWidth(); + + double marginInPlot = VALUE_TIP_BOX_PLOT_MARGIN; + double marginOnFilm = VALUE_TIP_BOX_FILM_MARGIN; + double marginBetweenNamesAndValues = 16; + double quantumWidthOfValueArea = 20; + double marginBetweenLines = 5; + double textAreaHeight = 0; + double textAreaWidth = 0; + double valueAreaLeft = 0; + double valueAreaRight = 0; + double valueAreaWidth = 0; + + double x, y, w, h; + + List tipLines = new ArrayList( trend.allItems.size()+1 ); + + /// Initialize + // Add time + { + TipLine tl = new TipLine(); + tipLines.add(tl); + tl.label = "Time"; + LineMetrics lm = fm.getLineMetrics(tl.label, g); + tl.height = lm.getHeight(); + tl.labelBaseline = fm.getAscent(); + textAreaHeight += marginBetweenLines; + textAreaHeight += tl.height; + tl.labelWidth = fm.stringWidth( tl.label ); + textAreaWidth = Math.max(tl.labelWidth, textAreaWidth); + tl.color = Color.WHITE; + + Format f = NumberFormat.getInstance(); + if (trend.timeFormat == org.simantics.trend.configuration.TimeFormat.Time) { + f = new TimeFormat(trend.horizRuler.iEnd, 3); + } + double t = time - trend.horizRuler.basetime; + String formattedTime = f.format( time - trend.horizRuler.basetime ); + double roundedTime = t; + try { + roundedTime = (Double) f.parseObject(formattedTime); + } catch (ParseException e) { + // Should never happen. + } + boolean actuallyLessThan = t < roundedTime; + boolean actuallyMoreThan = t > roundedTime; + tl.value = actuallyLessThan ? "< " + formattedTime + : actuallyMoreThan ? "> " + formattedTime + : formattedTime; + tl.valueWidth = fm.stringWidth( tl.value ); + valueAreaWidth = Math.max(valueAreaWidth, tl.valueWidth); + } + + // Add items + nextItem: + for ( ItemNode i : trend.allItems ) + { + TipLine tl = new TipLine(); + tipLines.add(tl); + // Get Label + tl.label = i.item.label; + LineMetrics lm = fm.getLineMetrics(tl.label, g); + tl.height = lm.getHeight(); + tl.labelBaseline = fm.getAscent(); + textAreaHeight += tl.height; + textAreaHeight += marginBetweenLines; + tl.labelWidth = fm.stringWidth( tl.label ); + textAreaWidth = Math.max(tl.labelWidth, textAreaWidth); + tl.color = ColorUtil.gamma( i.color, 0.55 ); + + // Get Value + Stream s = i.openStream( /*pixelsPerSecond*/0 ); + if ( s!=null ) { + + int index = s.binarySearch(Bindings.DOUBLE, time); + if (index<0) index = -index-2; + if ( index<0 || index>=s.count() ) continue nextItem; + boolean isLast = index+1>=s.count(); + + + ValueBand vb = new ValueBand(s.sampleBinding); + try { + vb.setSample( s.accessor.get(index, s.sampleBinding) ); + } catch (AccessorException e) { + throw new HistoryException(e); + } + + if ( vb.getSample()!=null ) { + + // gitlab #54: this can cause the value tip to not render values at all + // for items that have not been flushed at the point in time when this drawing + // is done. These circumstances are always temporary and later refreshes will + // remedy the situation. Having this logic can cause item values to randomly + // not be shown which is actually even more confusing to the user than rendering + // the latest flushed sample value. + // + // For this reason, the if below has been commented out. + //if (isLast && vb.hasEndTime() && vb.getEndTimeDouble()=0 ) { + String beforeDesimal = tl.value.substring(0, desimalPos); + String afterDesimal = tl.value.substring(desimalPos, tl.value.length()); + tl.valueLeftWidth = fm.stringWidth(beforeDesimal); + tl.valueRightWidth = fm.stringWidth(afterDesimal); + tl.valueWidth = tl.valueLeftWidth + tl.valueRightWidth; + } else { + tl.valueWidth = tl.valueLeftWidth = fm.stringWidth(tl.value); + } + + valueAreaWidth = Math.max(valueAreaWidth, tl.valueLeftWidth+tl.valueRightWidth); + valueAreaLeft = Math.max(valueAreaLeft, tl.valueLeftWidth); + valueAreaRight = Math.max(valueAreaRight, tl.valueRightWidth); + } else { + Object v = vb.getValue(); + tl.value = b.toString(v); + tl.number = false; + tl.valueLeftWidth = tl.valueRightWidth = fm.stringWidth( tl.value ); + valueAreaWidth = Math.max(valueAreaWidth, tl.valueLeftWidth); + } + } + } + } + } + + // Layout + double halfQuantum = quantumWidthOfValueArea/2; + valueAreaWidth = Math.ceil( valueAreaWidth / quantumWidthOfValueArea ) * quantumWidthOfValueArea; + valueAreaLeft = Math.ceil( valueAreaLeft / halfQuantum ) * halfQuantum; + valueAreaRight = Math.ceil( valueAreaRight / halfQuantum ) * halfQuantum; + double finalValueAreaWidth = Math.max(valueAreaWidth, valueAreaLeft + valueAreaRight); + w = marginOnFilm + textAreaWidth + marginBetweenNamesAndValues + finalValueAreaWidth + marginOnFilm + 0; + h = marginOnFilm + textAreaHeight + marginOnFilm; + double maxX = trend.plot.getWidth() - marginInPlot - w; + double maxY = trend.plot.getHeight() - marginInPlot - h; + x = marginInPlot + (maxX - marginInPlot)*trend.spec.viewProfile.valueViewPositionX; + y = marginInPlot + (maxY - marginInPlot)*trend.spec.viewProfile.valueViewPositionY; + + if ( x < TrendLayout.VERT_MARGIN ) x = TrendLayout.VERT_MARGIN; + + valueTipBoxBounds.setFrame(x, y, w, h); + //System.out.println("value tip bounds: " + valueTipBounds); + + // Draw + Rectangle2D rect = new Rectangle2D.Double(0, 0, w, h); + Composite oldComposite = g.getComposite(); + AffineTransform oldTransform = g.getTransform(); + try { + g.setComposite(composite66); + g.translate(x, y); + g.setColor(Color.BLACK); + g.fill( rect ); + g.setFont( font ); + g.setComposite(oldComposite); + + y = marginInPlot; + for (TipLine tl : tipLines) { + g.setColor( tl.color ); + x = marginInPlot; + g.drawString( tl.label, (float)x, (float)(y+tl.labelBaseline)); + + if ( tl.value!=null ) { + x = marginInPlot + textAreaWidth + marginBetweenNamesAndValues; + if ( tl.number ) { + x += valueAreaLeft - tl.valueLeftWidth; + } else { + x += (finalValueAreaWidth - tl.valueWidth)/2; + } + g.drawString(tl.value, (float) x, (float) (y+tl.labelBaseline)); + } + + y+=tl.height; + y+=marginBetweenLines; + } + + } finally { + g.setTransform( oldTransform ); + g.setComposite( oldComposite ); + } + } + + static class TipLine { + String label, value; + Color color; + double labelWidth, height, valueLeftWidth, valueRightWidth, valueWidth, labelBaseline; + boolean number; + + @Override + public String toString() { + return "TipLine[label=" + label + ", value=" + value + ", color=" + color + ", labelWidth=" + labelWidth + + ", height=" + height + ", valueLeftWidth=" + valueLeftWidth + ", valueRightWidth=" + + valueRightWidth + ", valueWidth=" + valueWidth + ", labelBaseline=" + labelBaseline + ", number=" + + number + "]"; + } + } + + public void renderValueTip(Graphics2D g2d) { + TrendNode trend = getTrend(); + if ( trend.valueTipTime != null ) { + AffineTransform at = g2d.getTransform(); + try { + g2d.transform( getTransform() ); + drawValuetip( g2d, trend.valueTipTime ); + } catch (HistoryException e) { + e.printStackTrace(); + } catch (BindingException e) { + e.printStackTrace(); + } finally { + g2d.setTransform( at ); + } + } + } + + /** + * Pick item (Binary node) + * + * @param pt coordinate in trend coordinate system + * @return item node + */ + public ItemNode pickItem(Point2D pt) + { + TrendNode trend = getTrend(); + double y = pt.getY()-getY(); + double x = pt.getX()-getX(); + if (yanalogAreaHeight+binaryAreaHeight) return null; + if (x<0 || x+getX()>trend.getBounds().getWidth()) return null; + for (int i=0; i=sy && y