/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in 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.scenegraph.g2d.nodes; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.Rectangle2D; import java.io.Serializable; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.ArrayList; import java.util.List; import org.simantics.scenegraph.g2d.G2DNode; public class Trend2DNode extends G2DNode { /** * */ private static final long serialVersionUID = 8508750881358776559L; protected Rectangle2D bounds = null; protected transient List points = null; protected Integer x = 0; protected Integer y = 0; @SyncField({"x", "y"}) public void setPosition(int x, int y) { this.x = x; this.y = y; } @SyncField("bounds") public void setBounds(Rectangle2D bounds) { this.bounds = bounds; } @SyncField("points") protected void setPoints(List points) { this.points = points; } @ClientSide protected void appendPoints(List points) { if(this.points == null) this.points = new ArrayList(); /** * We need to have the same set of points on the both sides, so locally the points are just updated, * but on remote side we send only the new points. * In case we are running on local workbench, the point list is updated and in addition these points * would be added to the list without this check. * FIXME: find out some way to implement this without this check */ if(location.equals(Location.REMOTE)) { this.points.addAll(points); } this.repaint(); } /** * Update trend points. If * * @param newpoints */ public void updatePoints(List newpoints) { if(points == null) points = new ArrayList(); for(int n = newpoints.size()-1; n >= 0; n--) { int t = 0; for(int o = points.size()-1; o >= 0; o--) { if(!newpoints.get(n-t).equals(points.get(o))) { break; } if(o == 0 || o < points.size()-10) { // Now we have 10 matching values, so n tells where the old points ends ArrayList appendedPoints = new ArrayList(); for(int i = n; i < newpoints.size()-1; i++) { appendedPoints.add(newpoints.get(i)); } points = new ArrayList(newpoints); if(appendedPoints != null && appendedPoints.size() > 0) { appendPoints(appendedPoints); } return; } if((n-t) == 0) { setPoints(newpoints); return; } t++; } } setPoints(newpoints); } @Override public void render(Graphics2D g2d) { if(points == null || bounds == null) { return; } AffineTransform ot = g2d.getTransform(); g2d.translate(x, y); @SuppressWarnings("unused") int pointsDrawn = 0; double max_y = Double.MIN_VALUE; // for scaling double min_y = Double.MAX_VALUE; double max_x = Double.MIN_VALUE; // for scaling double min_x = Double.MAX_VALUE; for(int index = points.size()-1; index >= 0; index--) { pointsDrawn++; double y = points.get(index).getY(); double x = points.get(index).getX(); if(y > max_y) max_y = y; if(y < min_y) min_y = y; if(x > max_x) max_x = x; if(x < min_x) min_x = x; } if(min_y > 0) min_y = 0; if(max_y < 0) max_y = 0; double mid_y = 0.5 * (max_y + min_y); GeneralPath path = new GeneralPath(); double scalex = bounds.getWidth() / (max_x - min_x); // Scale to fit double scalefactor = 0; double height = bounds.getHeight()-2; if(max_y - min_y > 0) scalefactor = height / (max_y - min_y); for(int index = points.size()-1; index >= 0; index--) { double t = bounds.getMinX(); float px = (float)((points.get(index).getX()-min_x) * scalex + t); float py = (float)((mid_y-points.get(index).getY())*scalefactor + (bounds.getY() + 0.5 * height) + 1); if(path.getCurrentPoint() == null) { path.moveTo(px, py); } else { path.lineTo(px, py); } } g2d.setColor(Color.WHITE); g2d.fill(bounds); g2d.setColor(Color.RED); g2d.setStroke(new BasicStroke(0.2f)); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.draw(path); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); float w = (float)(0.2*g2d.getTransform().getScaleX() < 1 ? 1/g2d.getTransform().getScaleX() : 0.2); g2d.setStroke(new BasicStroke(w)); g2d.setColor(new Color(200, 200, 200)); g2d.draw(bounds); GeneralPath axes = new GeneralPath(); axes.moveTo((float)bounds.getMinX()-1, (float)bounds.getMinY()); // Scale line axes.lineTo((float)bounds.getMinX(), (float)bounds.getMinY()); axes.lineTo((float)bounds.getMinX(), (float)bounds.getMaxY()); axes.moveTo((float)bounds.getMinX()-1, (float)bounds.getMaxY()); // Scale line axes.lineTo((float)bounds.getMaxX(), (float)bounds.getMaxY()); axes.lineTo((float)bounds.getMaxX(), (float)bounds.getMaxY()+1); g2d.setColor(Color.BLACK); g2d.draw(axes); // Print scale NumberFormat eformat = new DecimalFormat("###E0"); NumberFormat format = new DecimalFormat("0.0#"); String max = Math.abs(max_y) > 1000 ? eformat.format(max_y) : format.format(max_y); String min = Math.abs(min_y) > 1000 ? eformat.format(min_y) : format.format(min_y); Font font = Font.decode("Arial 5"); g2d.setFont(font); FontMetrics fm = g2d.getFontMetrics(); double x1 = bounds.getMinX()-fm.getStringBounds(max, g2d).getWidth()-0.3; double x2 = bounds.getMinX()-fm.getStringBounds(min, g2d).getWidth()-0.3; double y1 = bounds.getMinY()+fm.getStringBounds(max, g2d).getHeight()/2; double y2 = bounds.getMaxY()+fm.getStringBounds(max, g2d).getHeight()/2; g2d.drawString(max, (float)x1, (float)y1); g2d.drawString(min, (float)x2, (float)y2); // Print max time NumberFormat tformat = new DecimalFormat("#.0"); String time = tformat.format(max_x); g2d.drawString(time, (float)(bounds.getMaxX()-fm.getStringBounds(time, g2d).getWidth()/2), (float)(bounds.getMaxY()+fm.getStringBounds(time, g2d).getHeight())); g2d.setTransform(ot); } /** * Same as Point2D.Double, but serializable.. (Point2D.Double is not serializable in 1.5) * @author J-P * */ public static class TrendPoint implements Serializable { private static final long serialVersionUID = -3135433049709995399L; public double x; public double y; public TrendPoint(double x, double y) { this.x = x; this.y = y; } public double getX() { return x; } public double getY() { return y; } public boolean equals(TrendPoint b) { double deltax = Math.abs(b.getX() - this.getX()); double deltay = Math.abs(b.getY() - this.getY()); return deltax < 0.0000001 && deltay < 0.0000001; // FIXME } } @Override public Rectangle2D getBoundsInLocal() { return bounds; } }