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