--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+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;\r
+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<TrendPoint> 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<TrendPoint> points) {
+ this.points = points;
+ }
+
+ @ClientSide
+ protected void appendPoints(List<TrendPoint> points) {
+ if(this.points == null) this.points = new ArrayList<TrendPoint>();
+ /**
+ * 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<TrendPoint> newpoints) {
+ if(points == null) points = new ArrayList<TrendPoint>();
+ 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<TrendPoint> appendedPoints = new ArrayList<TrendPoint>();
+ for(int i = n; i < newpoints.size()-1; i++) {
+ appendedPoints.add(newpoints.get(i));
+ }
+ points = new ArrayList<TrendPoint>(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;
+ }\r
+ AffineTransform ot = g2d.getTransform();\r
+
+ g2d.translate(x, y);
+ @SuppressWarnings("unused")\r
+ 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()));\r
+ \r
+ 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;
+ }
+}