/******************************************************************************* * 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.swing; import java.awt.Graphics2D; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.List; import javax.swing.JPanel; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartPanel; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.XYPlot; import org.jfree.data.xy.XYSeries; import org.jfree.data.xy.XYSeriesCollection; import org.simantics.scenegraph.ExportableWidget.RasterOutputWidget; import org.simantics.scenegraph.g2d.IdentityAffineTransform; import org.simantics.scenegraph.g2d.nodes.Trend2DNode.TrendPoint; @RasterOutputWidget public class JFreeTrendNode extends ComponentNode { /** * */ private static final long serialVersionUID = 8508750881358776559L; protected String title = null; protected String yTitle = null; protected String xTitle = null; protected transient List points = null; protected transient XYSeries series = null; @Override public void init() { series = new XYSeries("Trend"); final XYSeriesCollection dataset = new XYSeriesCollection(series); if(title == null) title = "Trend"; if(xTitle == null) xTitle = "Value"; if(yTitle == null) yTitle = "Time"; scale = true; final JFreeChart chart = ChartFactory.createXYLineChart( title, xTitle, yTitle, dataset, PlotOrientation.VERTICAL, false, true, false ); final XYPlot plot = chart.getXYPlot(); ValueAxis axis = plot.getDomainAxis(); axis.setAutoRange(true); // axis.setFixedAutoRange(60000.0); // 60 seconds component = new ChartPanel(chart, false); ((ChartPanel)component).setRefreshBuffer(false); component.setIgnoreRepaint(true); component.setDoubleBuffered(false); if(bounds != null) { component.setBounds(0, 0, 0, 0); } if(points != null) { for(TrendPoint p : points) { try { series.add(p.getX(), p.getY()); } catch(org.jfree.data.general.SeriesException e) { } } } super.init(); } @Override public void render(Graphics2D g2d) { if (component != null) { AffineTransform ot = g2d.getTransform(); double scaleX = ot.getScaleX(); double scaleY = ot.getScaleY(); if (transform == IdentityAffineTransform.INSTANCE) transform = new AffineTransform(); synchronized(transform) { transform.setToTranslation(bounds.getMinX(), bounds.getMinY()); transform.scale(1/scaleX, 1/scaleY); } g2d.transform(transform); int width = (int)(bounds.getWidth() * scaleX); int height = (int)(bounds.getHeight() * scaleY); component.setLocation((int)g2d.getTransform().getTranslateX(), (int)g2d.getTransform().getTranslateY()); if(component.getSize().getWidth() != width || component.getSize().getHeight() != height) { component.setSize(width, height); } component.paint(g2d); g2d.setTransform(ot); } } @SyncField("title") public void setTitle(String title) { this.title = title; if(component != null) ((ChartPanel)component).getChart().setTitle(title); } @SyncField("xTitle") public void setXTitle(String xTitle) { this.xTitle = xTitle; if(component != null) ((ChartPanel)component).getChart().getXYPlot().getDomainAxis().setLabel(xTitle); } @SyncField("yTitle") public void setYTitle(String yTitle) { this.yTitle = yTitle; if(component != null) ((ChartPanel)component).getChart().getXYPlot().getRangeAxis().setLabel(yTitle); } @SyncField("bounds") public void setBounds(Rectangle2D bounds) { this.bounds = bounds; // width = (int)bounds.getWidth(); // height = (int)bounds.getHeight(); } @SyncField("points") protected void setPoints(List points) { this.points = points; if(series != null) { series.clear(); for(TrendPoint p : points) { try { series.add(p.getX(), p.getY()); } catch(org.jfree.data.general.SeriesException e) { } } } } @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); } for(TrendPoint p : points) { try { series.add(p.getX(), p.getY()); } catch(org.jfree.data.general.SeriesException e) { } } } /** * 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); } }