001/* Plot histograms.
002
003 @Copyright (c) 1998-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.actor.lib.gui;
029
030import java.io.InputStream;
031import java.net.URL;
032
033import ptolemy.actor.TypedIOPort;
034import ptolemy.data.BooleanToken;
035import ptolemy.data.DoubleToken;
036import ptolemy.data.expr.Parameter;
037import ptolemy.data.type.BaseType;
038import ptolemy.kernel.CompositeEntity;
039import ptolemy.kernel.util.Attribute;
040import ptolemy.kernel.util.IllegalActionException;
041import ptolemy.kernel.util.NameDuplicationException;
042import ptolemy.plot.Histogram;
043import ptolemy.plot.PlotBox;
044import ptolemy.plot.plotml.HistogramMLParser;
045
046///////////////////////////////////////////////////////////////////
047//// HistogramPlotter
048
049/**
050 A histogram plotter.  This plotter contains an instance of the Histogram
051 class from the Ptolemy plot package as a public member.  A histogram
052 of data at the input port, which can consist of any number of channels,
053 is plotted on this instance. The input data type is double.
054 <p>
055 The output plot consists of a set of vertical bars, each representing
056 a histogram bin.  The height of the bar is the count of the number
057 of inputs that have been observed that fall within that bin.
058 The <i>n</i>-th bin represents values in the range
059 (<i>x</i> - <i>w</i>/2 + <i>o</i>, <i>x</i> + <i>w</i>/2 + <i>o</i>),
060 where <i>w</i> is the value of the <i>binWidth</i> parameter,
061 and <i>o</i> is the value of the <i>binOffset</i> parameter.
062 So for example, if <i>o = w/2</i>,
063 then each bin represents values from <i>nw</i> to
064 (<i>n</i> + 1)<i>w</i> for some integer <i>n</i>.
065 The default offset is 0.5, half the default bin width, which is 1.0.
066 <p>
067 This actor has a <i>legend</i> parameter,
068 which gives a comma-separated list of labels to attach to
069 each dataset.  Normally, the number of elements in this list
070 should equal the number of input channels, although this
071 is not enforced.
072
073 @see ptolemy.plot.Histogram
074
075 @author  Edward A. Lee
076 @version $Id$
077 @since Ptolemy II 1.0
078 @Pt.ProposedRating Green (eal)
079 @Pt.AcceptedRating Green (cxh)
080 */
081public class HistogramPlotter extends PlotterBase {
082    /** Construct an actor with the given container and name.
083     *  @param container The container.
084     *  @param name The name of this actor.
085     *  @exception IllegalActionException If the actor cannot be contained
086     *   by the proposed container.
087     *  @exception NameDuplicationException If the container already has an
088     *   actor with this name.
089     */
090    public HistogramPlotter(CompositeEntity container, String name)
091            throws IllegalActionException, NameDuplicationException {
092        super(container, name);
093
094        input = new TypedIOPort(this, "input", true, false);
095        input.setMultiport(true);
096        input.setTypeEquals(BaseType.DOUBLE);
097
098        binWidth = new Parameter(this, "binWidth");
099        binWidth.setExpression("1.0");
100        binWidth.setTypeEquals(BaseType.DOUBLE);
101
102        binOffset = new Parameter(this, "binOffset");
103        binOffset.setExpression("0.5");
104        binOffset.setTypeEquals(BaseType.DOUBLE);
105
106        _attachText("_iconDescription", "<svg>\n" + "<rect x=\"-20\" y=\"-20\" "
107                + "width=\"40\" height=\"40\" " + "style=\"fill:lightGrey\"/>\n"
108                + "<rect x=\"-12\" y=\"-12\" " + "width=\"24\" height=\"24\" "
109                + "style=\"fill:white\"/>\n" + "<rect x=\"2\" y=\"-18\" "
110                + "width=\"4\" height=\"4\" " + "style=\"fill:grey\"/>\n"
111                + "<rect x=\"8\" y=\"-18\" " + "width=\"4\" height=\"4\" "
112                + "style=\"fill:grey\"/>\n" + "<rect x=\"14\" y=\"-18\" "
113                + "width=\"4\" height=\"4\" " + "style=\"fill:grey\"/>\n"
114                + "<rect x=\"-8\" y=\"2\" " + "width=\"4\" height=\"10\" "
115                + "style=\"fill:red\"/>\n" + "<rect x=\"-2\" y=\"-8\" "
116                + "width=\"4\" height=\"20\" " + "style=\"fill:red\"/>\n"
117                + "<rect x=\"4\" y=\"-5\" " + "width=\"4\" height=\"17\" "
118                + "style=\"fill:red\"/>\n" + "</svg>\n");
119    }
120
121    ///////////////////////////////////////////////////////////////////
122    ////                     ports and parameters                  ////
123
124    /** The width of the bin of the histogram.
125     *  This parameter has type double, with default value 1.0.
126     */
127    public Parameter binWidth;
128
129    /** The offset for bins of the histogram.
130     *  This parameter has type double, with default value 0.5.
131     */
132    public Parameter binOffset;
133
134    /** The input port, which is a multiport. */
135    public TypedIOPort input;
136
137    ///////////////////////////////////////////////////////////////////
138    ////                         public methods                    ////
139
140    /** If the parameter is <i>binWidth</i> or <i>binOffset</i>, then
141     *  configure the histogram with the specified bin width or offset.
142     *  @param attribute The attribute that changed.
143     *  @exception IllegalActionException If the bin width is not positive.
144     */
145    @Override
146    public void attributeChanged(Attribute attribute)
147            throws IllegalActionException {
148        // NOTE: Do not react to changes in _windowProperties.
149        // Those properties are only used when originally opening a window.
150        if (attribute == binWidth) {
151            double width = ((DoubleToken) binWidth.getToken()).doubleValue();
152
153            if (width <= 0.0) {
154                throw new IllegalActionException(this,
155                        "Invalid bin width (must be positive): " + width);
156            }
157
158            if (plot instanceof Histogram) {
159                ((Histogram) plot).setBinWidth(width);
160            }
161        } else if (attribute == binOffset) {
162            double offset = ((DoubleToken) binOffset.getToken()).doubleValue();
163
164            if (plot instanceof Histogram) {
165                ((Histogram) plot).setBinOffset(offset);
166            }
167        } else {
168            super.attributeChanged(attribute);
169        }
170    }
171
172    /** Configure the plot with data from the specified input source
173     *  (a URL) and/or textual data, assumed to be in PlotML format.
174     *  If this is called before the histogram has been created
175     *  (by calling place() or initialize()), then reading of the input
176     *  stream is deferred until the histogram is created.
177     *  @param base The base relative to which references within the input
178     *   stream are found, or null if this is not known.
179     *  @param source The input source, which specifies a URL.
180     *  @param text Configuration information given as text.
181     *  @exception Exception If the configuration source cannot be read
182     *   or if the configuration information is incorrect.
183     */
184    @Override
185    public void configure(URL base, String source, String text)
186            throws Exception {
187        if (plot instanceof Histogram) {
188            _base = base;
189            _source = source;
190            _text = text;
191
192            HistogramMLParser parser = new HistogramMLParser((Histogram) plot);
193
194            if (source != null && !source.trim().equals("")) {
195                URL xmlFile = new URL(base, source);
196                InputStream stream = xmlFile.openStream();
197                parser.parse(base, stream);
198                stream.close();
199            }
200
201            if (text != null && !text.equals("")) {
202                // NOTE: Regrettably, the XML parser we are using cannot
203                // deal with having a single processing instruction at the
204                // outer level.  Thus, we have to strip it.
205                String trimmed = text.trim();
206
207                if (trimmed.startsWith("<?") && trimmed.endsWith("?>")) {
208                    trimmed = trimmed.substring(2, trimmed.length() - 2).trim();
209
210                    if (trimmed.startsWith("plotml")) {
211                        trimmed = trimmed.substring(6).trim();
212                        parser.parse(base, trimmed);
213                    }
214
215                    // If it's not a plotml processing instruction, ignore.
216                } else {
217                    // Data is not enclosed in a processing instruction.
218                    // Must have been given in a CDATA section.
219                    parser.parse(base, text);
220                }
221            }
222        } else {
223            super.configure(base, source, text);
224        }
225    }
226
227    /** Return the input source that was specified the last time the configure
228     *  method was called.
229     *  @return The string representation of the input URL.
230     */
231    @Override
232    public String getSource() {
233        return null;
234    }
235
236    /** If the histogram has not already been created, create it using
237     *  place().
238     *  If configurations specified by a call to configure() have not yet
239     *  been processed, process them.
240     *  @exception IllegalActionException If the parent class throws it.
241     */
242    @Override
243    public void initialize() throws IllegalActionException {
244        super.initialize();
245
246        if (plot == null) {
247            // Place the histogram in its own frame.
248            plot = _newPlot();
249            plot.setTitle(getName());
250            plot.setButtons(true);
251        }
252
253        if (((BooleanToken) automaticRescale.getToken()).booleanValue()) {
254            plot.setAutomaticRescale(true);
255        }
256
257        if (_getImplementation().getFrame() == null
258                && _getImplementation().getPlatformContainer() == null) {
259            // Need an effigy and a tableau so that menu ops work properly.
260            _getImplementation().initializeEffigy();
261
262            _implementDeferredConfigurations();
263
264            // The SizeAttribute property is used to specify the size
265            // of the Plot component. Unfortunately, with Swing's
266            // mysterious and undocumented handling of component sizes,
267            // there appears to be no way to control the size of the
268            // Plot from the size of the Frame, which is specified
269            // by the WindowPropertiesAttribute.
270            _getImplementation().updateSize();
271        } else {
272            // Clear the histogram without clearing the axes.
273            plot.clear(false);
274            plot.repaint();
275        }
276
277        _getImplementation().bringToFront();
278    }
279
280    /** Read at most one input token from each input channel
281     *  and update the histogram.
282     *  This is done in postfire to ensure that data has settled.
283     *  @exception IllegalActionException If there is no director.
284     */
285    @Override
286    public boolean postfire() throws IllegalActionException {
287        int width = input.getWidth();
288
289        for (int i = width - 1; i >= 0; i--) {
290            if (input.hasToken(i)) {
291                DoubleToken curToken = (DoubleToken) input.get(i);
292                double curValue = curToken.doubleValue();
293
294                // NOTE: Should we test before this cast?
295                ((Histogram) plot).addPoint(i, curValue);
296            }
297        }
298
299        return super.postfire();
300    }
301
302    ///////////////////////////////////////////////////////////////////
303    ////                         protected methods                 ////
304
305    /** If configurations have been deferred, implement them now.
306     *  Also, configure the histogram parameters, if appropriate.
307     */
308    @Override
309    protected void _implementDeferredConfigurations() {
310        super._implementDeferredConfigurations();
311
312        // Configure the new histogram with parameter values, possibly
313        // overriding those set in evaluating the deferred configure.
314        try {
315            attributeChanged(binWidth);
316            attributeChanged(binOffset);
317        } catch (IllegalActionException ex) {
318            // Safe to ignore because user would
319            // have already been alerted.
320        }
321    }
322
323    /** Create a new Histogram plot.
324     *  @return A new plot object.
325     */
326    @Override
327    protected PlotBox _newPlot() {
328        return new Histogram();
329    }
330}