1   /****************************************************************************
2    **
3    ** This file is part of the yFiles extension package ySVG-2.3.
4    ** 
5    ** yWorks proprietary/confidential. Use is subject to license terms.
6    **
7    ** Redistribution of this file or of an unauthorized byte-code version
8    ** of this file is strictly forbidden.
9    **
10   ** Copyright (c) 2002-2010 by yWorks GmbH, Vor dem Kreuzberg 28,
11   ** 72070 Tuebingen, Germany. All rights reserved.
12   **
13   ***************************************************************************/
14  package demo.yext.svg;
15  
16  import yext.svg.io.SVGNodeRealizerSerializer;
17  import yext.svg.view.SVGModel;
18  import yext.svg.view.SVGNodeRealizer;
19  
20  import y.io.GraphMLIOHandler;
21  import y.option.ComponentOptionItem;
22  import y.option.DefaultEditorFactory;
23  import y.option.Editor;
24  import y.option.EditorFactory;
25  import y.option.ItemEditor;
26  import y.option.OptionGroup;
27  import y.option.OptionHandler;
28  import y.option.OptionItem;
29  import y.util.D;
30  import y.view.EditMode;
31  import y.view.Graph2D;
32  import y.view.NodeRealizer;
33  import y.base.Node;
34  import y.base.NodeCursor;
35  
36  import java.awt.BorderLayout;
37  import java.awt.Color;
38  import java.awt.Dimension;
39  import java.awt.EventQueue;
40  import java.awt.GridBagConstraints;
41  import java.awt.GridBagLayout;
42  import java.awt.event.ComponentAdapter;
43  import java.awt.event.ComponentEvent;
44  import java.beans.PropertyChangeEvent;
45  import java.beans.PropertyChangeListener;
46  import java.util.HashSet;
47  import java.util.Iterator;
48  import java.util.LinkedHashMap;
49  import java.util.Map;
50  import java.util.Set;
51  import java.net.URL;
52  import java.io.IOException;
53  import javax.swing.JFrame;
54  import javax.swing.JLabel;
55  import javax.swing.JMenu;
56  import javax.swing.JMenuBar;
57  import javax.swing.JPanel;
58  import javax.swing.JScrollPane;
59  import javax.swing.JSplitPane;
60  import javax.swing.JTextArea;
61  
62  /**
63   * Demonstrates how caching intermediate images improves performance of the
64   * graph viewer component when rendering <em>Scalable Vector Graphics</em>.
65   * To experience performance changes, use the mouse to move preselected
66   * graph elements around before and after changing cache settings.
67   * The following cache settings are available:
68   * <ul>
69   *   <li><b>Image Cache Enabled</b><br>
70   *       allows for activation/deactivation of the caching mechanism</li>
71   *   <li><b>Image Cache Size</b><br>
72   *       determines the number of images to be cached</li>
73   *   <li><b>Maximum Image Size</b><br>
74   *       determines the maximum size (in bytes) of images to be cached</li>
75   *   <li><b>Minimum Relative Image Size</b><br>
76   *       determines the minimum relative size for which precached images
77   *       may be reused when rendering duplicates of smaller sizes</li>
78   * </ul>
79   *
80   * @see yext.svg.view.SVGModel#setImageCacheEnabled(boolean)
81   * @see yext.svg.view.SVGModel#setImageCacheSize(int)
82   * @see yext.svg.view.SVGModel#setMaximumImageSize(int)
83   * @see yext.svg.view.SVGModel#setMinimumRelativeImageSize(double)
84   */
85  public class RenderingPerformanceDemo extends SVGExportDemo {
86    private static final String GRAPH =
87            "Graph";
88    private static final String IMAGE_CACHE_ENABLED =
89            "Image Cache Enabled";
90    private static final String IMAGE_CACHE_SIZE =
91            "Image Cache Size";
92    private static final String MAXIMUM_IMAGE_SIZE =
93            "Maximum Image Size (in bytes)";
94    private static final String MINIMUM_RELATIVE_IMAGE_SIZE =
95            "Minimum Relative Image Size";
96  
97    private Map nameToUrl;
98  
99    public RenderingPerformanceDemo() {
100     createNameToUrlMap();
101 
102     if (nameToUrl != null && !nameToUrl.isEmpty()) {
103       readGraph((URL)nameToUrl.values().iterator().next());
104     }
105 
106     createContent();
107     createListeners();
108     updateView();
109   }
110 
111   private void createNameToUrlMap() {
112     nameToUrl = new LinkedHashMap();
113 
114     final String[] names = {
115             "100Earths",
116             "mimeTypes",
117             "mixedSizes"
118     };
119     URL url;
120     for (int i = 0; i < names.length; ++i) {
121       final String resource = "resource/" + names[i] + ".graphml";
122       url = RenderingPerformanceDemo.class.getResource(resource);
123       if (url != null) {
124         nameToUrl.put(names[i], url);
125       } else {
126         System.err.println("Could not resolve " + resource + "!");
127       }
128     }
129   }
130 
131   private void createListeners() {
132     final Dimension oldSize = view.getPreferredSize();
133     view.addComponentListener(new ComponentAdapter() {
134       public void componentResized( final ComponentEvent e ) {
135         final Dimension newSize = e.getComponent().getSize();
136         if (oldSize.width < newSize.width || oldSize.height < newSize.height) {
137           updateView();
138           view.removeComponentListener(this);
139         }
140       }
141     });
142   }
143 
144   /**
145    * Creates the GUI components.
146    */
147   private void createContent() {
148     final String[] names;
149     if (nameToUrl != null && !nameToUrl.isEmpty()) {
150       names = new String[nameToUrl.size()];
151       int i = 0;
152       for (Iterator it = nameToUrl.keySet().iterator(); it.hasNext(); ++i) {
153         names[i] = (String)it.next();
154       }
155     } else {
156       names = new String[0];
157     }
158 
159     OptionGroup group;
160 
161     final OptionHandler oh = new OptionHandler("CacheOptions");
162     OptionItem item;
163 
164     if (names.length > 0) {
165       group = new OptionGroup();
166       group.setAttribute(OptionGroup.ATTRIBUTE_TITLE, "Sample SVG Graph");
167       item = oh.addEnum(GRAPH, names, 0);
168       item.setAttribute(ItemEditor.ATTRIBUTE_AUTO_COMMIT, Boolean.TRUE);
169       group.addItem(item);
170     } else {
171       group = new OptionGroup();
172       group.setAttribute(OptionGroup.ATTRIBUTE_TITLE, "Sample SVG Graph");
173 
174       final JLabel label = new JLabel("No graphs available.");
175       label.setForeground(Color.RED.darker());
176       item = new ComponentOptionItem("_COMPONENT", label) {
177         public boolean wantsVisibleName() {
178           return false;
179         }
180       };
181       oh.addItem(item);
182       group.addItem(item);
183     }
184 
185     group = new OptionGroup();
186     group.setAttribute(OptionGroup.ATTRIBUTE_TITLE, "SVG Image Cache Options");
187 
188     item = oh.addBool(IMAGE_CACHE_ENABLED, SVGModel.isImageCacheEnabled());
189     item.setTipText(
190             "<html><body><table width=\"240\"><tr><td>" +
191             "Enables or disables image caching. " +
192             "Image caching is a performance optimization that tries to " +
193             "minimize expensive vector graphics rendering to a cached " +
194             "intermediate image that will be displayed instead. To achieve " +
195             "nice rendering quality images are created and cached for " +
196             "multiple zoom levels." +
197             "</td></tr></table></body></html>"
198     );
199     item.setAttribute(ItemEditor.ATTRIBUTE_AUTO_COMMIT, Boolean.TRUE);
200     group.addItem(item);
201     item = oh.addInt(IMAGE_CACHE_SIZE, SVGModel.getImageCacheSize());
202     item.setTipText(
203             "<html><body><table width=\"240\"><tr><td>" +
204             "The maximum number of images that will be cached." +
205             "</td></tr></table></body></html>"
206     );
207     item.setAttribute(ItemEditor.ATTRIBUTE_AUTO_COMMIT, Boolean.TRUE);
208     group.addItem(item);
209     item = oh.addInt(MAXIMUM_IMAGE_SIZE, SVGModel.getMaximumImageSize());
210     item.setTipText(
211             "<html><body><table width=\"240\"><tr><td>" +
212             "The maximum size of images that will be cached. " +
213             "Image size is measured in bytes necessary to store it. " +
214             "For an image with size <code>W x H</code> the image size " +
215             "is <code>W * H * 4</code>. " +
216             "The width and height of an image is calulated by " +
217             "multiplying the width and height of the SVG by the scale " +
218             "factor of the graphics context it is rendered on." +
219             "</td></tr></table></body></html>"
220     );
221     item.setAttribute(ItemEditor.ATTRIBUTE_AUTO_COMMIT, Boolean.TRUE);
222     group.addItem(item);
223     item = oh.addDouble(MINIMUM_RELATIVE_IMAGE_SIZE, SVGModel.getMinimumRelativeImageSize());
224     item.setTipText(
225             "<html><body><table width=\"240\"><tr><td>" +
226             "The minimum relative image size for which precached image " +
227             "duplicates are reused. I.e. if the image cache is queried for " +
228             "an intermediate image for a given SVG definition and image " +
229             "size, a cache miss is avoided if there is a cached image for " +
230             "the given SVG definition and <code>query width/cached width " +
231             "&gt;= minimumRelativeImageSize</code> as well as " +
232             "<code>query height/cached height &gt;= minimumRelativeImageSize</code> " +
233             "hold." +
234             "</td></tr></table></body></html>"
235     );
236     item.setAttribute(ItemEditor.ATTRIBUTE_AUTO_COMMIT, Boolean.TRUE);
237     group.addItem(item);
238 
239 
240     final JTextArea jta = new JTextArea();
241     jta.setText(info());
242     jta.setEditable(false);
243     jta.setLineWrap(true);
244     jta.setWrapStyleWord(true);
245     final JScrollPane jsp = new JScrollPane(jta);
246     jsp.setPreferredSize(new Dimension(200, 100));
247     item = new ComponentOptionItem("Comment", jsp) {
248       public boolean wantsVisibleName() {
249         return false;
250       }
251     };
252     oh.addItem(item);
253 
254     oh.addChildPropertyChangeListener(new PropertyChangeListener() {
255       public void propertyChange( PropertyChangeEvent evt ) {
256         if (!OptionItem.PROPERTY_VALUE.equals(evt.getPropertyName())) {
257           return;
258         }
259 
260         final OptionItem src = (OptionItem)evt.getSource();
261         final String name = src.getName();
262         if (GRAPH.equals(name)) {
263           SVGModel.clearImageCache();
264           readGraph((URL)nameToUrl.get(evt.getNewValue()));
265           jta.setText(info());
266         } else if (IMAGE_CACHE_ENABLED.equals(name)) {
267           SVGModel.setImageCacheEnabled(((Boolean)evt.getNewValue()).booleanValue());
268         } else if (IMAGE_CACHE_SIZE.equals(name)) {
269           SVGModel.setImageCacheSize(((Number)evt.getNewValue()).intValue());
270         } else if (MAXIMUM_IMAGE_SIZE.equals(name)) {
271           SVGModel.setMaximumImageSize(((Number)evt.getNewValue()).intValue());
272         } else if (MINIMUM_RELATIVE_IMAGE_SIZE.equals(name)) {
273           SVGModel.setMinimumRelativeImageSize(((Number)evt.getNewValue()).doubleValue());
274           view.repaint();
275         }
276       }
277     });
278 
279 
280     final EditorFactory ef = new DefaultEditorFactory();//new TableEditorFactory();
281     final Editor editor = ef.createEditor(oh);
282 
283     final GridBagConstraints gbc = new GridBagConstraints();
284     final JPanel editorPane = new JPanel(new GridBagLayout());
285     gbc.fill = GridBagConstraints.HORIZONTAL;
286     gbc.gridx = 0;
287     gbc.gridy = 0;
288     gbc.weightx = 1.0;
289     gbc.weighty = 0.0;
290     editorPane.add(editor.getComponent(), gbc);
291     gbc.fill = GridBagConstraints.BOTH;
292     ++gbc.gridy;
293     gbc.weighty = 1.0;
294     editorPane.add(new JPanel(), gbc);
295 
296     remove(view);
297     add(new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true,
298             editorPane, view),
299             BorderLayout.CENTER);
300   }
301 
302 
303   protected JMenuBar createMenuBar() {
304     JMenuBar jmb = new JMenuBar();
305     JMenu menu = new JMenu("File");
306     menu.add(new ExitAction());
307     jmb.add(menu);
308     return jmb;
309   }
310 
311   protected EditMode createEditMode() {
312     final EditMode em = new EditMode() {
313       protected String getNodeTip( final Node v ) {
314         final NodeRealizer nr = getGraph2D().getRealizer(v);
315         final double zoom = view.getZoom();
316         final int bytes = (int)Math.ceil(nr.getWidth() *zoom) *
317                           (int)Math.ceil(nr.getHeight()*zoom) * 4;
318         return "Corresponding image size: " + bytes + " bytes";
319       }
320     };
321     em.showNodeTips(true);
322     em.allowBendCreation(false);
323     em.allowEdgeCreation(false);
324     em.allowNodeCreation(false);
325     return em;
326   }
327 
328   public void start(final String title) {
329     JFrame frame = new JFrame(title);
330     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
331     addContentTo(frame.getRootPane());
332     frame.pack();
333     frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
334     frame.setLocationRelativeTo(null);
335     frame.setVisible(true);
336   }
337 
338   private void readGraph( final URL url ) {
339     if (url == null) {
340       return;
341     }
342 
343     final Graph2D graph = view.getGraph2D();
344 
345     final GraphMLIOHandler ioh = new GraphMLIOHandler();
346     ioh.addNodeRealizerSerializer(new SVGNodeRealizerSerializer());
347     try {
348       graph.clear();
349       ioh.read(view.getGraph2D(), url);
350     } catch (IOException ioe) {
351       D.show(ioe);
352     }
353 
354     graph.setSelected(graph.nodes(), true);
355     graph.setBendsSelected(graph.edges(), true);
356 
357     updateView();
358   }
359 
360   private void updateView() {
361     view.fitContent();
362     view.setPaintDetailThreshold(0);
363     view.updateView();
364   }
365 
366   private String info() {
367     final Graph2D graph = view.getGraph2D();
368 
369     final StringBuffer sb = new StringBuffer();
370     sb.append(graph.N()).append(" node(s),\n");
371     sb.append(graph.E()).append(" edge(s),\n");
372 
373     final Set urls = new HashSet();
374     for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
375       final NodeRealizer nr = graph.getRealizer(nc.node());
376       if (nr instanceof SVGNodeRealizer) {
377         urls.add(((SVGNodeRealizer)nr).getSVGURL());
378       }
379     }
380     sb.append(urls.size()).append(" SVG image(s)");
381     return sb.toString();
382   }
383 
384 
385   /**
386    * Launches this demo.
387    * @param args ignored.
388    */
389   public static void main( final String[] args ) {
390     EventQueue.invokeLater(new Runnable() {
391       public void run() {
392         initLnF();
393         (new RenderingPerformanceDemo()).start();
394       }
395     });
396   }
397 }
398