001/* A live signal plotter. 002 003 @Copyright (c) 1997-2014 The Regents of the University of California. 004 All rights reserved. 005 006 Permission is hereby granted, without written agreement and without 007 license or royalty fees, to use, copy, modify, and distribute this 008 software and its documentation for any purpose, provided that the 009 above copyright notice and the following two paragraphs appear in all 010 copies of this software. 011 012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 013 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 014 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 015 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 016 SUCH DAMAGE. 017 018 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 019 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 020 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 021 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 022 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 023 ENHANCEMENTS, OR MODIFICATIONS. 024 025 PT_COPYRIGHT_VERSION_2 026 COPYRIGHTENDKEY 027 */ 028package ptolemy.plot; 029 030import java.awt.event.ActionEvent; 031import java.awt.event.ActionListener; 032 033import javax.swing.JButton; 034 035/////////////////////////////////////////////////////////////////// 036//// PlotLive 037 038/** 039 Plot signals dynamically, where points can be added at any time 040 and the display will be updated. This should be normally used 041 with some finite persistence so that old points are erased as new 042 points are added. Unfortunately, the most efficient way to erase 043 old points is to draw graphics using the "exclusive or" mode, which 044 introduces quite a number of artifacts. When lines are drawn 045 between points, where they overlap the points the line becomes 046 white. Moreover, if two lines or points overlap completely, they 047 disappear. 048 <p> 049 This class is abstract, so it must be used by creating a derived 050 class. To use it, create a derived class with an 051 addPoints() method. Your class may also set graph parameters like 052 titles and axis labels in the constructor by calling 053 methods in the Plot or PlotBox classes (both of which are base classes). 054 The addPoints() method should call addPoint() of the Plot base 055 class to dynamically add points to the plot. This method is called 056 within a thread separate from the applet thread, so the zooming 057 mechanism and buttons remain live. 058 059 @author Edward A. Lee, Christopher Brooks, Contributor: Jeff Lane 060 @version $Id$ 061 @since Ptolemy II 0.2 062 @Pt.ProposedRating Yellow (cxh) 063 @Pt.AcceptedRating Yellow (cxh) 064 */ 065@SuppressWarnings("serial") 066public abstract class PlotLive extends Plot implements Runnable { 067 /////////////////////////////////////////////////////////////////// 068 //// public methods //// 069 070 /** Redefine in derived classes to add points to the plot. 071 * Adding many points at once will make the plot somewhat faster 072 * because the thread yields between calls to this method. 073 * However, the plot will also be somewhat less responsive to user 074 * inputs such as zooming, filling, or stopping. In the derived-class 075 * implementation, this method should probably be synchronized. 076 * 077 * <p>Jeff Lane points out that if the derived class version of 078 * addPoints() does not return quickly, and it seems like then you 079 * may want to experiment with addPoints() to not being synchronized. 080 */ 081 public abstract void addPoints(); 082 083 /** Make start and stop buttons. 084 * This method is deprecated. Use setButtons() instead. 085 * @deprecated 086 */ 087 @Deprecated 088 public void makeButtons() { 089 if (_startButton == null) { 090 _startButton = new JButton("start"); 091 _startButton.addActionListener(new StartButtonListener()); 092 add(_startButton); 093 } 094 095 _startButton.setVisible(true); 096 097 if (_stopButton == null) { 098 _stopButton = new JButton("stop"); 099 _stopButton.addActionListener(new StopButtonListener()); 100 add(_stopButton); 101 } 102 103 _stopButton.setVisible(true); 104 _stopButton.setEnabled(false); 105 _startButton.setEnabled(true); 106 } 107 108 /** Pause the plot. To resume, call start(). 109 */ 110 public void pause() { 111 _paused = true; 112 _plotting = false; 113 _stopButton.setEnabled(false); 114 _startButton.setEnabled(true); 115 } 116 117 /** This is the body of a thread that repeatedly calls addPoints() 118 * if the plot is active. To make the plot active, call start(). 119 * To pause the plot, call pause(). To stop the plot and destroy 120 * the thread, call stop(). The next time start() is called, a new 121 * thread will be started. Between calls to addPoints(), this method calls 122 * Thread.yield() so that the thread does not hog all 123 * the resources. This somewhat slows down execution, so derived 124 * classes may wish to plot quite a few points in their 125 * addPoints() method, if possible. However, 126 * plotting more points at once may also decrease the 127 * responsiveness of the user interface. 128 */ 129 @Override 130 public void run() { 131 while (_plotting || _paused) { 132 if (_plotting) { 133 addPoints(); 134 135 // Give the event thread a chance. 136 Thread.yield(); 137 } else if (_paused) { 138 // NOTE: Cannot synchronize this entire method because then 139 // the Thread.yield() call above does not yield to any 140 // synchronized methods (like _drawPlot()). 141 synchronized (this) { 142 try { 143 wait(); 144 } catch (InterruptedException e) { 145 } 146 } 147 } 148 } 149 } 150 151 /** If the argument is true, make a start, stop, and fill button 152 * visible at the upper right. Otherwise, make the buttons invisible. 153 * NOTE: The buttons may infringe on the title space, 154 * if the title is long. In an application, it is preferable to provide 155 * a menu with the commands. This way, when printing the plot, 156 * the printed plot will not have spurious buttons. Thus, this method 157 * should be used only by applets, which normally do not have menus. 158 */ 159 @Override 160 public void setButtons(boolean visible) { 161 super.setButtons(visible); 162 163 if (_startButton == null) { 164 _startButton = new JButton("start"); 165 _startButton.addActionListener(new StartButtonListener()); 166 add(_startButton); 167 } 168 169 _startButton.setVisible(visible); 170 171 if (_stopButton == null) { 172 _stopButton = new JButton("stop"); 173 _stopButton.addActionListener(new StopButtonListener()); 174 add(_stopButton); 175 } 176 177 _stopButton.setVisible(visible); 178 179 if (visible) { 180 _stopButton.setEnabled(false); 181 _startButton.setEnabled(true); 182 } 183 } 184 185 /** Make the plot active. Start a new thread if necessary. 186 */ 187 public synchronized void start() { 188 _plotting = true; 189 _paused = false; 190 if (_stopButton != null) { 191 _stopButton.setEnabled(true); 192 } 193 if (_startButton != null) { 194 _startButton.setEnabled(false); 195 } 196 if (_plotLiveThread == null) { 197 _plotLiveThread = new Thread(this, "PlotLive Thread"); 198 _plotLiveThread.start(); 199 } else { 200 // synchronized (this) { is useless since the method is already 201 // synchronized. 202 // synchronized (this) { 203 notifyAll(); 204 //} 205 } 206 } 207 208 /** Stop the plot. The plot thread exits. This should be called by 209 * an applet's stop() method. 210 */ 211 public void stop() { 212 _plotting = false; 213 _paused = false; 214 215 // FindBugs: [M M IS] Inconsistent synchronization [IS2_INCONSISTENT_SYNC] 216 // Actually this is not an issue, since once the thread has been created this 217 // member is not accessed until its destruction. 218 _plotLiveThread = null; 219 } 220 221 /////////////////////////////////////////////////////////////////// 222 //// private variables //// 223 224 /** @serial Thread of this plotter */ 225 private Thread _plotLiveThread = null; 226 227 /** @serial True if we are actually plotting. */ 228 private boolean _plotting = false; 229 230 /** @serial True if we are paused. */ 231 private boolean _paused = false; 232 233 /** @serial Start and Stop Buttons. */ 234 private JButton _startButton; 235 236 /** @serial Start and Stop Buttons. */ 237 private JButton _stopButton; 238 239 /////////////////////////////////////////////////////////////////// 240 //// inner classes //// 241 class StartButtonListener implements ActionListener { 242 @Override 243 public void actionPerformed(ActionEvent event) { 244 start(); 245 } 246 } 247 248 // Despite the name, the stop button calls pause. 249 class StopButtonListener implements ActionListener { 250 @Override 251 public void actionPerformed(ActionEvent event) { 252 pause(); 253 } 254 } 255}