Once again I’d like to welcome guest blogger Malcolm Lidierth of King’s College London. Last week Malcolm wrote about integrating the Waterloo graphing library in Matlab figures. Today, Malcolm discusses advanced features in this library.
Readers who are interested in high-quality plotting in Matlab may also be interested in the JFreeChart and MTEX open-source packages. JFreeChart provides chart types that are unavailable in Matlab, while MTEX builds upon existing Matlab plots using sophisticated data analysis and visualization. Unlike JFreeChart, I haven’t mentioned MTEX here before. MTEX is definitely worth a good mention, perhaps I’ll write about it someday. If you are using any other good charting packages for Matlab, please add a comment below.
A feature request received from several of Yair’s readers for the Waterloo graphics project was for fast updating and animation of plots. This week, I’ll describe features supporting this. As those need to be illustrated here on a web page, I’ll also describe Waterloo’s new deploy-to-web feature.
Updating Waterloo plots
Recall that Waterloo provides a Matlab OOP class called GXPlot
, which wraps the underlying Waterloo plot type and can be retrieved using the getObject() method. For a GXPlot
named p
, the data can easily be updated from Matlab by passing it a new data array of values:
p.getObject().getXData().setDataBufferData(XData); p.getObject().getYData().setDataBufferData(YData); |
This will be fast enough for most purposes, although the exchange of arrays between Matlab and Java is done by copying the data rather than by reference (a limitation imposed by the Java Native Interface rather than Matlab).
If only a few data points are to be changed at any one time, an alternative approach is available of setting individual data points (referenced by their index, 0 being the first):
p.getObject().getXData().setEntry(index, value); p.getObject().getYData().setEntry(index, value); |
These methods will update the plot p
, as well as all other plots that share its underlying data buffer.
All that is then needed is to update the display. This means:
- Optionally, checking and rescaling the axes as needed
- Calling repaint on the graph or its container
The best way will often be to make the container a listener on the relevant data object (the container houses the axes, so these will then be repainted if the scale changes). For example, for the YData:
% get a reference to the plot's graph container container = p.getObject().getParentGraph().getGraphContainer(); % attach the graph as a listener to the ydata buffer dataObject = p.getObject().getYData(); dataObject.addPropertyChangeListener(p.getObject().getParentGraph()); |
Now any change to the YData object will fire a property change event causing the display to update.
A simple example
f = GXFigure(); set(gcf, 'Units','normalized', 'Position',[0.3 0.3 0.4 0.4], 'Name','TestUpdate1'); % Create some data and plot a bar chart Y = hist(randn(1,100), 50); container = gxgca(); p = bar(container, -1:0.0408:1, Y, 'BarWidth',0.0408, 'Fill','LIGHTCORAL'); container.getView().setAxesBounds(-1,0,2,225); container.getView().setBackground(kcl.waterloo.defaults.Colors.getColor('WHEAT')); container.getView().setBackgroundPainted(true); refresh(); % Attach the graph as a listener to the ydata buffer p.getObject().getYData().addPropertyChangeListener(p.getObject().getParentGraph()); % Now update the ydata in a loop - pausing for 0.1s on each iteration. % The listener attached above will cause a repaint for k = 1 : 50 Y = hist(randn(1,100),50); Y = Y + p.getObject().getYData().getRawDataValues().'; p.getObject().getYData().setDataBufferData(Y); pause(0.1); end |
Note that the repaints will be done on the Event Dispatch Thread (EDT). The call to the Matlab pause above causes EDT to be flushed so a full repaint is done for each iteration of the loop. Using a timer, as in the example below, can avoid this and allows the Swing repaint mechanisms to collapse multiple repaint calls into a single rendering operation. This will almost always be faster: use drawnow and pause sparingly with Waterloo code.
Improving speed
Refreshing the display as above requires that
- the graph container is repainted together with the axes – using internal axes that are rendered as part of step [2] will speed this up
- the graph is repainted including grids and internal axes – speed this up by turning off, or reducing the number of, grid lines. Switch off background painting – the container’s background will suffice (this is the default).
- the plot displays are updated – to speed this up:
- Use objects that are easily translated into a square pixel-grid on screen. e.g. use squares instead of circles in a scatter plot
- Use solid lines, not dashed or dotted ones
- Turn off anti-aliasing of the plot
As painting is done on the EDT it will not block the Matlab thread. The time taken to update a plot will be variable, but it typically takes 5-20 msecs.
Fast plot updates
In the beta release, new methods have been added to the plots: plotRedraw() and plotUpdate() both redraw a plot without refreshing the background, grids or axes.
In the default implementation, plotUpdate simply calls plotRedraw: there actions are identical. plotUpdate is intended to be overridden in custom user subclasses to paint only points that have been added to end of a plot since the last update and the logic to do that needs to be added to the plot methods in those subclasses.
This logic can also be synthesized on-the-fly (e.g. in Matlab, as in the example below) by adding NaNs in the data objects – Waterloo ignores these values. In the example below, a timer is used to update the plot.
function update2(thisDelay) if nargin == 0 DELAY = 0.01; else DELAY = thisDelay; end % Set up and create some data f = GXFigure(); set(gcf, 'Units','normalized', 'Position',[0.3 0.3 0.4 0.4], 'Name','TestAnimation'); x = linspace(-pi*4, pi*4, 50); y = cos(x); % Now the plot gr1 = subplot(f, 1,1, 1); p1 = line(gxgca, [], [], 'LineSpec', '-og'); gr1.getObject().getView().setAxesBounds(-pi*4,-2.5,pi*8,5); % We'll draw 2 points only in each timer call below (2 points needed for interconnecting line) % This plot will therefore show only these points when the normal paint mechanism is % used unless all the data are added at the end: which the timer callback below does p1.getObject().setXData(x(1:2)); p1.getObject().setYData(y(1:2)*2); p1.getObject().getParentGraph().setLeftAxisPainted(false); p1.getObject().getParentGraph().setRightAxisPainted(false); p1.getObject().getParentGraph().setTopAxisPainted(false); p1.getObject().getParentGraph().setBottomAxisPainted(false); p1.getObject().getParentGraph().setInnerAxisPainted(true); p1.getObject().getParentGraph().setInnerAxisLabelled(true); p1.getObject().getParentGraph().getGraphContainer().repaint(); drawnow(); t = timer('ExecutionMode','fixedSpacing', 'Period',DELAY, 'TimerFcn', {@localTimer, p1.getObject(), x, y}); start(t); function localTimer(t, EventData, p1, x, y) k = get(t,'TasksExecuted'); if k > numel(x) % Finished stop(t); p1.setXData(x); p1.setYData(y*2); p1.plotRedraw(); elseif k > 1 % Add 2 new data points to the plot p1.setXData(x(k-1:k)); p1.setYData(y(k-1:k)*2); p1.plotRedraw(); end end % localTimer end % update2 |
Calls to plotRedraw and plotUpdate are extremely fast, typically 1-2 msecs. Each call to plotRedraw() adds two new data points to the existing plot. The result is the animated plot shown at the very top of this article.
Notes:
- this example used the same timer callback to update both data and display; it will often be best to do this is separate callbacks – the refresh rates for data update and display animation can then be set independently.
- readers who need plot animation may find interest in the related Matlab functions comet and comet3, or this week’s POTW multicomet.
- plot animation in standard Matlab typically relies on the EraseMode property. In the upcoming HG2, this mechanism will no longer work, at least as seen in the HG2 prequel that is available today. So if your code uses any plot animation, you should expect it to break when HG2 is released (2014?), and you will need to convert your code to use HG2’s new animation functionality. Waterloo graphs and animation, being independent Java-based, are not expected to be affected, and will run the same way in both HG1 and HG2.
Deploying graphics to the web
Waterloo enables export of static graphics to a variety of vector and bit-mapped formats (PNG, PDF, SVG etc.) that can be included in web pages. In the beta version, deployment to web is also supported directly, with automatic generation of the graphics files together with accompanying HTML files and optional CSS styling sheets and supporting Javascript. These are available from the Graph Editor, which is activated by double-clicking a graph and selecting the world () button.
Two formats are presently supported:
- Scalable Vector Graphics (SVG). By default, the generated files provide conversion of the SVG to HTML5 canvas commands when loaded into a browser via the canvg.js JavaScript by Gabe Lerner. Use of canvg provides better cross-browser consistency in the rendering of the graphics, particularly for text. Note that only static graphics are presently supported with SVG.
- Through the Processing script language for visual arts and processing.js JavaScript.
Processing script output supports animations using a new class developed for Waterloo (PDEGraphics2D
), which also supports an AWT/Swing container. The generated script files can be loaded and customized using the Process.app file available at the web site above.
In addition, experimental (and presently quirky) support for generating animated GIFs is included. The animations for this blog were generated using this. Animated GIFs are to be preferred when vector graphics are not required because GIFs
- are universally and consistently supported in browsers
- are smaller and less computationally intensive. They therefore consume less power and are environmentally friendlier. Visitors to a website, especially those using battery-powered devices, are likely to stay longer on the site.
For the present, only static graphics are supported through the GUIs, so animations need to be created programmatically. A “record” button will be added to the GUIs in future Waterloo releases.
Users can edit the default settings for deployment using the preferences editor, now available from the Graph Editor using the key () button. For SVG, the options are shown below: allowing a choice of whether SVG is to be embedded in the generated HTML file; whether canvg.js should be used and selection of a styling sheet. Customize the file addresses relative to the target folder for the HTML file or use a URL. The “httpd.py” file here is a Python 2.7 script that will set up a local server and display the HTML file in your system browser – it is needed only if the security rules for your browser prevent the files being loaded directly from the local file system.
By default, the deploy tool uses a template index.html file that is embedded in the distribution. You can specify your own instead, although I have not yet added that to the Preferences GUI.
Interesting, but all of your examples (here and on your sourceforge site) seem to emphasize the extraneous plot elements more than the data itself. The lines are thick, the grids and axes visually compete with the data being displayed. There is no subtlety and I while neat I can’t say that I found a single one of your examples beautiful. This is one of the things I dislike about Matlab as well, where it requires an enormous amount of work to create the simplest attractive (and effective) plot. The fact that defaults are so poorly chosen does not help. More fiddly options is not always the answer, however – the key is a designing in good defaults (and even removing some options) from the outset. It looks like you have some good engineering under the hood (love the SVG and processing.js capabilities), but what I’d really like to see is some work on the graphic design and visual display of information end of things. Cheers.
@Andrew – I think it would be constructive if you could let Malcolm know which defaults you think should be modified and how. Same with MathWork’s new HG2 (which has lots of new defaults).
When reflecting on Malcolm’s work, please remember that this entire set of library (a huge development effort) was created by a single individual, and posted as Open Source for the benefit of the community with nothing but good intentions at heart. So if you think that something should be changed, do chip in and help in a constructive manner. I suggest that you take this discussion offline directly with Malcolm (and for HG2 you can comment on the HG2 page).
@Andrew
The defaults are user-settable using the preferences dialog above for plots and axes. Colors, line thicknesses etc can all be set and saved for use across MATLAB sessions. All the setLeftAxisPainted(false) etc calls above could have been avoided if this were the defualt setting.
The settings are saved to XML in the $(java.home)/waterlooSettings folder and so kept across Waterloo updates too. That XML file can of course be shared across a team – so imposing a “corporate” theme. I would be happy to include a “theme” selector if users wanted to contribute their settings to the project.
One note of caution – the defaults table is referenced in the constructors and is a singleton instance. It is swapped during serialisation/deserialization of Waterloo files to allow the standard bean XML coder/decoder to be used. This results in graphs always using the defaults chosen by the user who created the file rather than the user opening it – The values can be edited – but the keys should not be, to ensure portability.
Eventually this may all be styled via CSS, but that is some way off.
You point is well taken and the beta version includes some preliminary work on colour schemes in the Colors class included in the kcl.waterloo.defaults package. There is a substantial literature on these issues notably in designing aircraft control GUIs (where the emphasis is on guiding the eye away from extraneous elements) and mapping (where the emphasis is on avoiding false impressions because of poorly chosen colours). Add to that the requirement to address the needs of those with colour-vision impairments and produce good output for both print and web media and you see that it is not a simple issue.
It occurs to me that some of last week’s illustrations are useful here. The Lincoln Penny contours created a set of colors from the Colors class. These used the BlueDarkRed18 color scheme. The color contrasts in the figures will be visible to those with all 3 common forms of colour-blindness. For those with full colour vision, you can check it out using the app from http://colororacle.org.
As per Yair’s comment above: general comments/suggestions about Waterloo are always welcome at
http://sourceforge.net/p/waterloo/discussion/general/
Malcolm,
Congratulations for this very impressive library which looks very attractive…
Because you seem to be an EDT expert, I was wondering if you knew a way to synchronize graphics update of java components (integrated with javacomponent function) with drawnow mechanism ?
Because I am not sure to be clear, here is an example :
When we run this code, JLabel’s string is updated at each iteration. To have a similar behavior with uicontrol’s implementation, a drawnow has to be added in the loop.
So, do you know how, in the example above, we can prevent java component’s graphics from being updated at each iteration ?
@Julien – the easiest (and unsafe!) way is simply NOT to create the
JLabel
on the EDT in the first place, and only issue a drawnow and/orjLab.repaint
when you really want to update the GUI:See http://undocumentedmatlab.com/blog/matlab-and-the-event-dispatch-thread-edt/ for more details on using Matlab with EDT
Thanks for your fast answer. I really carefully read all of your posts, especially those related to the integration of java components inside matlab figures. In particular, the EDT’s one is extremely usefull and interesting, but some points are still unclear to me.
In the code snippet above, I forgot one line which corresponds to the rendering of the component with javacomponent.
Actually, as you mention in your post, javacomponent’s function puts component on the EDT, even if it was initially created on MT. That’s why with your example, unfortunately, it does not work better…
@Julien
There are two problems:
1. Updating the text property has to be done on the EDT to ensure that calls to getText on the EDT will see the changes. That is the point of a single-threaded model for AWT/Swing. For JLabel, this is not thread-safe (it is for Swing text components like JTextField).
The simplest way to avoid updating the text property 2000 times is not to write code that does that!
However, the overhead is simply setting the property is not enormous. The problem is that for a JLabel, the setText method calls repaint().
Here’s the code, which I have edited for clarity. Note that there is no check that we are on the EDT and no code to repost to the EDT (JLabel’s setText is not thread-safe). As you point out, MATLAB will ensure the code is called on the EDT when javacomponent has been used. Note that text is a bound property – if the property change listeners call repaint, mutiple repaint calls will be issued for each call to setText.
2. The repaint will run on the EDT – whether or not javacomponent has been used – as repaint is a thread-safe method (N.B. for standard Swing components – always check when using custom subclasses).
Rendering the JLabel to screen is managed by the Swing repaint manager. That does not run on the EDT but analyses the repaint queue and collapses multiple calls to repaint the same screen area into a single call. When it is done, it calls the relevant Swing repaint methods which refresh the screen (on the EDT with the collapsed data). So despite the multiple calls to repaint, the repaint manager should reduce the work needed.
Flushing the event queue with pause or drawnow will disrupt that, forcing more repaints than needed – which I assume is why Yair put those outside the loop in the code above.
How you solve the problem depends on the use-case but a JLabel, or any other Swing component, will not always be the right tool for the job.
Thanks Malcolm for all these explanations which are very useful.
I chose this very simple example because I thought it reflected well my problem, which is actually different. A JLabel appears to be a bad candidate, your explanations are very clear on this. However, if I replace the JLabel by a JTextField or a JEditorPane for example, the behavior is unfortunately the same.
The problem which inspired my initial comment is actually le following :
I have a JTabbedPane with potentially more than 10 tabs, which can be added/removed interactively according to user’s needs. When I want to close several tabs at a time, I loop on tab indexes and invoke jTabbedPane.remove(tabIndex). However, with this MATLAB-side approach, I see the tabs which disappear one by one onscreen, and I want to avoid this. I would prefer to see all the tabs that disapper or appear at a time.
A solution could be to write a java util class with static methos that would be responsible for adding or removing several tabs (a simple loop). These methods would be invoked from MATLAB and executed on the EDT (using javaMethodEDT), in order to ensure that synchronous repaints will be done only after the method’s execution is completed (which is not the case when you do the equivalent loop MATLAB-side). I already tried this with JTable selections updates and it works well. However, in this specific case, I would like to avoid this (if you are interested, I could tell you more offline about this).
@Julien
Understood. jTabbedPane.remove(tabIndex) calls removeTabAt(tabIndex) and that calls repaint() which I suspect explains what you see. [JTable is rather different: probably the most complex of all swing components, and mixes data and visual elements in way that makes to impossible to comment on what you see without details.]
Google “grep JTabbedPane remove” to see the Java source code – Java is all open source.
A solution might be to subclass JTabbedPane and provide an additional method that allows removal of multiple tabs then calls repaint() only once. I do not see a way to do that MATLAB-side – neither could you rely simply on calling the JTabbedPane super class removeTab method in Java as that would still call repaint each time. It is not a huge task as the Oracle/Sun code shows most of what is needed to make it a Java subclass robust – so I suspect a professional programmer/consultant would quote a reasonable price if you are not happy doing it yourself (a man called Yair springs immediately to mind).
Thanks again Malcolm…
Actually, even if I am not a Java expert, I do not need help to subclass a swing component and override one method. I do not have much funds to this kind of consulting work, so I prefer to reserve Yair more tricky and exciting tasks!
To test your approach, I simply wanted to create a custom myJLabel class, which extends JLabel and overrides setText() method. In this method, I copied the setText() code from your link (thanks for the tip) and simply commented the repaint() line. However, because text property is private, I cannot modify it from the subclass without calling super.setText(), which calls repaint…
On the other hand, I have not tried yet, but it should work for JTabbedPane.removeTabAt(int) because no private properties are modified.
@Julien
One way for JLabel might be to create a myText property in your subclass. Compare the contents of that with the result returned by getText and override other methods (e.g. revalidate and paint) to call super.setText() as appropriate. Note though, there might be issues if the L&F UI delegate calls into superclass methods during construction (usually an issue with Synth-based L&Fs like Nimbus) and there could be synchronization issues. That may be why text is private to begin with.
Alternatively, copy the whole of JLabel. The GJGraph in Waterloo began as the JXGraph from the SwingX project (see the 1st blog about it). Rather than try overriding single methods I copied the whole class (it’s LPGL) and started from there, in part because I kept hitting issues with private fields.