001/* Plot functions of time.
002
003 @Copyright (c) 1998-2018 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.actor.lib.gui;
029
030import java.util.ArrayList;
031
032import ptolemy.actor.TimedActor;
033import ptolemy.actor.TypedIOPort;
034import ptolemy.data.BooleanToken;
035import ptolemy.data.DoubleToken;
036import ptolemy.data.IntToken;
037import ptolemy.data.SmoothToken;
038import ptolemy.data.expr.Parameter;
039import ptolemy.data.type.BaseType;
040import ptolemy.kernel.CompositeEntity;
041import ptolemy.kernel.util.IllegalActionException;
042import ptolemy.kernel.util.NameDuplicationException;
043import ptolemy.kernel.util.Workspace;
044import ptolemy.plot.PlotInterface;
045
046///////////////////////////////////////////////////////////////////
047//// TimedPlotter
048
049/**
050 A signal plotter.  This plotter contains an instance of the Plot class
051 from the Ptolemy plot package as a public member.  Data at the input, which
052 can consist of any number of channels, are plotted on this instance.
053 Each channel is plotted as a separate data set.
054 The horizontal axis represents time, which by default is the global
055 time of the model (the model time of the top-level director).
056 Setting <i>useLocalTime</i> to true changes this to use the
057 local time of the input port, which is (in most domains) the model
058 time of the local director.
059
060 @author  Edward A. Lee, Contributor: Bert Rodiers
061 @version $Id$
062 @since Ptolemy II 1.0
063 @Pt.ProposedRating Green (eal)
064 @Pt.AcceptedRating Green (cxh)
065 */
066public class TimedPlotter extends Plotter implements TimedActor {
067    /** Construct an actor with the given container and name.
068     *  @param container The container.
069     *  @param name The name of this actor.
070     *  @exception IllegalActionException If the actor cannot be contained
071     *   by the proposed container.
072     *  @exception NameDuplicationException If the container already has an
073     *   actor with this name.
074     */
075    public TimedPlotter(CompositeEntity container, String name)
076            throws IllegalActionException, NameDuplicationException {
077        super(container, name);
078        disconnectGraphOnAbsentValue = new Parameter(this,
079                "disconnectGraphOnAbsentValue", new BooleanToken(false));
080        disconnectGraphOnAbsentValue.setTypeEquals(BaseType.BOOLEAN);
081
082        // Create the input port and make it a multiport.
083        input = new TypedIOPort(this, "input", true, false);
084        input.setMultiport(true);
085        input.setTypeEquals(BaseType.DOUBLE);
086
087        useLocalTime = new Parameter(this, "useLocalTime");
088        useLocalTime.setTypeEquals(BaseType.BOOLEAN);
089        useLocalTime.setExpression("false");
090    }
091
092    ///////////////////////////////////////////////////////////////////
093    ////                     ports and parameters                  ////
094
095    /** When disconnectGraphOnAbsentValue is True there will be a gap
096     *  in the graph each time a the actor is fired, but the value
097     *  is absent for a certain channel. Especially in the continuous
098     *  domain this options is useful. By default this parameter is
099     *  False.
100     */
101    public Parameter disconnectGraphOnAbsentValue;
102
103    /** Input port, which has type DoubleToken. */
104    public TypedIOPort input;
105
106    /** If true, use the model time reported by the input port,
107     *  which is normally the model time of the local director.
108     *  If false (the default), use the model time reported by
109     *  the top-level director. Local time may differ
110     *  from global time inside modal models and certain domains
111     *  that manipulate time.
112     */
113    public Parameter useLocalTime;
114
115    ///////////////////////////////////////////////////////////////////
116    ////                         public methods                    ////
117
118    /** Clone the actor into the specified workspace. This calls the
119     *  base class and then creates new ports and parameters.
120     *  @param workspace The workspace for the new object.
121     *  @return A new actor.
122     *  @exception CloneNotSupportedException If a derived class has an
123     *   attribute that cannot be cloned.
124     */
125    @Override
126    public Object clone(Workspace workspace) throws CloneNotSupportedException {
127        TimedPlotter newObject = (TimedPlotter) super.clone(workspace);
128        newObject._connected = new ArrayList<Boolean>();
129        return newObject;
130    }
131
132    /** Pre-fire this actor. Recompute _connected is needed
133     *  @exception IllegalActionException If a derived class throws it.
134     */
135    @Override
136    public boolean prefire() throws IllegalActionException {
137        super.prefire();
138
139        int width = input.getWidth();
140        if (width != _connected.size()) {
141            _connected.clear();
142            for (int i = 0; i < width; i++) {
143                _connected.add(true);
144            }
145        }
146        return true;
147    }
148
149    /** Initialize this actor.  Derived classes override this method
150     *  to perform actions that should occur once at the beginning of
151     *  an execution, but after type resolution.  Derived classes can
152     *  produce output data and schedule events.
153     *
154     *  @exception IllegalActionException If a derived class throws it.
155     */
156    @Override
157    public void initialize() throws IllegalActionException {
158        super.initialize();
159        int width = input.getWidth();
160        _connected.clear();
161        for (int i = 0; i < width; i++) {
162            _connected.add(true);
163        }
164        ((PlotInterface) plot).markDisconnections(true);
165    }
166
167    /** Read at most one input from each channel and plot it as a
168     *  function of time.
169     *  This is done in postfire to ensure that data has settled.
170     *  @exception IllegalActionException If there is no director, or
171     *   if the base class throws it.
172     *  @return True if it is OK to continue.
173     */
174    @Override
175    public boolean postfire() throws IllegalActionException {
176        double currentTimeValue;
177        int width = input.getWidth();
178
179        boolean disconnectOnAbsent = ((BooleanToken) disconnectGraphOnAbsentValue
180                .getToken()).booleanValue();
181        int offset = ((IntToken) startingDataset.getToken()).intValue();
182
183        // NOTE: We assume the superclass ensures this cast is safe.
184        PlotInterface plot = (PlotInterface) this.plot;
185        for (int i = width - 1; i >= 0; i--) {
186            if (input.hasToken(i)) {
187                boolean localTime = ((BooleanToken) useLocalTime.getToken())
188                        .booleanValue();
189                if (localTime) {
190                    currentTimeValue = input.getModelTime(i).getDoubleValue();
191                } else {
192                    currentTimeValue = getDirector().getGlobalTime()
193                            .getDoubleValue();
194                }
195
196                DoubleToken currentToken = (DoubleToken) input.get(i);
197                SmoothToken smoothToken = currentToken instanceof SmoothToken
198                        ? (SmoothToken) currentToken
199                        : null;
200                double[] derivatives;
201                if (smoothToken != null) {
202                    derivatives = smoothToken.derivativeValues();
203                    if (derivatives == null) {
204                        derivatives = new double[0];
205                    }
206                } else {
207                    derivatives = null;
208                }
209                plot.addPoint(i + offset, currentTimeValue,
210                        currentToken.doubleValue(), derivatives,
211                        _connected.get(i));
212                if (disconnectOnAbsent) {
213                    _connected.set(i, true);
214                }
215            } else if (disconnectOnAbsent) {
216                // We have not token, and hence we want to create a gap
217                // in the graph.
218                _connected.set(i, false);
219            }
220        }
221
222        return super.postfire();
223    }
224
225    ///////////////////////////////////////////////////////////////////
226    ////                         private variables                 ////
227
228    private ArrayList<Boolean> _connected = new ArrayList<Boolean>();
229}