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}