/* * Licensed Materials - Property of Perforce Software, Inc. * © Copyright Perforce Software, Inc. 2014, 2021 * © Copyright IBM Corp. 2009, 2014 * © Copyright ILOG 1996, 2009 * All Rights Reserved. * * Note to U.S. Government Users Restricted Rights: * The Software and Documentation were developed at private expense and * are "Commercial Items" as that term is defined at 48 CFR 2.101, * consisting of "Commercial Computer Software" and * "Commercial Computer Software Documentation", as such terms are * used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4, * as applicable. */ import java.awt.BorderLayout; import java.awt.Color; import java.awt.FlowLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.SystemColor; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRootPane; import javax.swing.JTextField; import ilog.views.diagrammer.IlvDiagrammer; import ilog.views.diagrammer.application.IlvDiagrammerViewBar; import ilog.views.graphlayout.GraphLayoutEvent; import ilog.views.graphlayout.GraphLayoutEventListener; import ilog.views.graphlayout.IlvGraphLayout; import ilog.views.graphlayout.IlvGraphLayoutReport; import ilog.views.graphlayout.random.IlvRandomLayout; import ilog.views.sdm.renderer.graphlayout.IlvGraphLayoutRenderer; import ilog.views.sdm.util.IlvSDMMutableStyleSheet; import ilog.views.util.IlvProductUtil; import ilog.views.util.swing.IlvSwingUtil; /** * This is a very simple application that uses the * <code>IlvDiagrammer</code> to perform a hierarchical layout. It loads a style * sheet containing the hierarchical layout specification. It allows to set * various hierarchical layout parameters by using the * <code>IlvSDMMutableStyleSheet</code>. */ public class HierarchicalLayoutApp extends JRootPane { { // This sample uses JViews Diagrammer features. When deploying an // application that includes this code, you need to be in possession // of a Perforce JViews Diagrammer Deployment license. IlvProductUtil.DeploymentLicenseRequired(IlvProductUtil.JViews_Diagrammer_Deployment); } /** The diagrammer */ IlvDiagrammer diagrammer = new IlvDiagrammer(); /** A graph layout event listener */ LayoutIterationListener layoutListener = new LayoutIterationListener(); /** A text field to display messages */ JTextField msgLine = new JTextField(); /** The style sheet for temporary changes */ IlvSDMMutableStyleSheet styleSheet = new IlvSDMMutableStyleSheet(diagrammer.getEngine(), true, false); /** The flow direction selector */ JComboBox<String> flowDirectionSelector = new JComboBox<String>(); /** The link style selector */ JComboBox<String> linkStyleSelector = new JComboBox<String>(); /** Text fields for the offset parameters */ JTextField horiNodeOffset = new JTextField("" + 40.0, 6); JTextField vertNodeOffset = new JTextField("" + 40.0, 6); JTextField horiLinkOffset = new JTextField("" + 15.0, 6); JTextField vertLinkOffset = new JTextField("" + 15.0, 6); JTextField horiNodeLinkOffset = new JTextField("" + 20.0, 6); JTextField vertNodeLinkOffset = new JTextField("" + 20.0, 6); /** * Initializes the application. */ public void init() { // we use the standard diagrammer toolbar IlvDiagrammerViewBar toolbar = new IlvDiagrammerViewBar(); // various settings of the diagrammer diagrammer.setSelectMode(true); diagrammer.setScrollable(true); diagrammer.setEditingAllowed(true); diagrammer.setMinimumZoom(0.02); diagrammer.setMaximumZoom(10); diagrammer.getView().setBackground(Color.white); diagrammer.getView().setForeground(SystemColor.windowText); // create the file selector JComboBox<String> fileSelector = new JComboBox<String>(); fileSelector.addItem("small"); fileSelector.addItem("medium"); fileSelector.addItem("large"); fileSelector.addItem("huge"); fileSelector.addItem("ports1"); fileSelector.addItem("ports2"); fileSelector.setToolTipText("Select a sample graph"); fileSelector.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent event) { SuppressWarnings("unchecked") JComboBox<String> comboBox = (JComboBox<String>) event.getSource(); String fileName = (String) comboBox.getSelectedItem(); loadDataFile(fileName); } }); // create the randomize button JButton randomizeButton = new JButton("Random"); randomizeButton.setToolTipText("Randomize the node positions"); randomizeButton.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent evt) { randomize(); } }); // create the layout button JButton layoutButton = new JButton("Layout"); layoutButton.setToolTipText("Perform a hierarchical layout"); layoutButton.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent evt) { // before performing layout, make sure that the values in the text // fields are transfered to the style sheet styleSheet.setAdjusting(true); try { setOffset("horizontalNodeOffset", horiNodeOffset); setOffset("verticalNodeOffset", vertNodeOffset); setOffset("horizontalLinkOffset", horiLinkOffset); setOffset("verticalLinkOffset", vertLinkOffset); setOffset("horizontalNodeLinkOffset", horiNodeLinkOffset); setOffset("verticalNodeLinkOffset", vertNodeLinkOffset); } finally { styleSheet.setAdjusting(false); } performLayout(); } }); // create the panel for the layout buttons JPanel layoutButtonPanel = new JPanel(); layoutButtonPanel.setLayout(new FlowLayout()); layoutButtonPanel.add(randomizeButton); layoutButtonPanel.add(layoutButton); // create the top panel with the toolbar and the file selector JPanel topPanel = new JPanel(); topPanel.setLayout(new FlowLayout(FlowLayout.LEFT)); topPanel.add(toolbar); topPanel.add(new JLabel("Select graph:")); topPanel.add(fileSelector); // create the bottom panel with the layout buttons and the message line JPanel bottomPanel = new JPanel(); bottomPanel.setLayout(new BorderLayout()); bottomPanel.add(layoutButtonPanel, BorderLayout.NORTH); bottomPanel.add(createParameterPanel(), BorderLayout.CENTER); bottomPanel.add(msgLine, BorderLayout.SOUTH); msgLine.setEditable(false); // put diagrammer, top panel and bottom panel together getContentPane().setLayout(new BorderLayout(0, 0)); getContentPane().add(diagrammer, BorderLayout.CENTER); getContentPane().add(topPanel, BorderLayout.NORTH); getContentPane().add(bottomPanel, BorderLayout.SOUTH); try { // load the main style file diagrammer.setStyleSheet(IlvSwingUtil.getRelativeURL(this, "data/hierarchical.css")); // load a mutable style file that we use for temporary style changes diagrammer.getEngine().setStyleSheets(1, styleSheet.toString()); } catch (Exception e) { showMessage(e.getMessage()); } // load the initial sample grapher loadDataFile("small"); // add the layout listener to the current graph layout. This must be done // here after reading the style file, because before reading the style file, // the layout instance is not yet instantiated. getGraphLayout().addGraphLayoutEventListener(layoutListener); } /** * Creates the layout parameter panel. */ private JPanel createParameterPanel() { JPanel panel = new JPanel(); GridBagLayout gridbag = new GridBagLayout(); GridBagConstraints c = new GridBagConstraints(); c.fill = GridBagConstraints.HORIZONTAL; c.anchor = GridBagConstraints.EAST; panel.setLayout(gridbag); // link style parameter c.gridx = 0; c.gridy = 1; panel.add(new JLabel("Link Style:"), c); c.gridx = 1; panel.add(linkStyleSelector, c); linkStyleSelector.addItem("polyline"); linkStyleSelector.addItem("orthogonal"); linkStyleSelector.addItem("straight"); linkStyleSelector.setSelectedIndex(0); linkStyleSelector.setToolTipText("Select the global link style"); linkStyleSelector.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent event) { SuppressWarnings("unchecked") JComboBox<String> comboBox = (JComboBox<String>) event.getSource(); String style = (String) comboBox.getSelectedItem(); setGlobalLinkStyle(style); } }); // flow direction parameter c.gridx = 0; c.gridy = 2; panel.add(new JLabel("Flow Direction:"), c); c.gridx = 1; panel.add(flowDirectionSelector, c); flowDirectionSelector.addItem("top"); flowDirectionSelector.addItem("bottom"); flowDirectionSelector.addItem("left"); flowDirectionSelector.addItem("right"); flowDirectionSelector.setSelectedIndex(3); flowDirectionSelector.setToolTipText("Select the link flow direction"); flowDirectionSelector.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent event) { SuppressWarnings("unchecked") JComboBox<String> comboBox = (JComboBox<String>) event.getSource(); String flow = (String) comboBox.getSelectedItem(); setFlowDirection(flow); } }); // hierarchical layout offset parameters c.gridx = 3; c.gridy = 0; panel.add(new JLabel("horizontal"), c); c.gridy = 1; panel.add(horiNodeOffset, c); c.gridy = 2; panel.add(horiNodeLinkOffset, c); c.gridy = 3; panel.add(horiLinkOffset, c); c.gridx = 4; c.gridy = 0; c.insets = new Insets(0, 5, 0, 0); panel.add(new JLabel("vertical"), c); c.gridy = 1; panel.add(vertNodeOffset, c); c.gridy = 2; panel.add(vertNodeLinkOffset, c); c.gridy = 3; panel.add(vertLinkOffset, c); c.gridx = 2; c.gridy = 0; c.insets = new Insets(0, 25, 0, 0); panel.add(new JLabel("Offsets"), c); c.gridy = 1; panel.add(new JLabel("bw. nodes:"), c); c.gridy = 2; panel.add(new JLabel("bw. node/link:"), c); c.gridy = 3; panel.add(new JLabel("bw. link bends:"), c); horiNodeOffset.setToolTipText("Set the minimal horizontal offset between nodes"); horiNodeOffset.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent e) { setOffset("horizontalNodeOffset", (JTextField) e.getSource()); } }); horiNodeLinkOffset.setToolTipText("Set the minimal horizontal offset between nodes and links"); horiNodeLinkOffset.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent e) { setOffset("horizontalNodeLinkOffset", (JTextField) e.getSource()); } }); horiLinkOffset.setToolTipText("Set the minimal horizontal offset between link bends"); horiLinkOffset.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent e) { setOffset("horizontalLinkOffset", (JTextField) e.getSource()); } }); vertNodeOffset.setToolTipText("Set the minimal vertical offset between nodes"); vertNodeOffset.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent e) { setOffset("verticalNodeOffset", (JTextField) e.getSource()); } }); vertNodeLinkOffset.setToolTipText("Set the minimal vertical offset between nodes and links"); vertNodeLinkOffset.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent e) { setOffset("verticalNodeLinkOffset", (JTextField) e.getSource()); } }); vertLinkOffset.setToolTipText("Set the minimal vertical offset between link bends"); vertLinkOffset.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent e) { setOffset("verticalLinkOffset", (JTextField) e.getSource()); } }); return panel; } /** * Load a sample data file. * * @param fileNameBase * The base of the filename, excluding the path prefix and the * extension suffix. */ private void loadDataFile(String fileNameBase) { try { showMessage("Reading " + fileNameBase + ".xml ..."); diagrammer.setDataFile(IlvSwingUtil.getRelativeURL(this, "data/" + fileNameBase + ".xml")); showMessage("Reading " + fileNameBase + ".xml done."); prepare(fileNameBase); } catch (Exception e) { showMessage(e.getMessage()); } } /** * Just for demo purpose: Select the orthogonal link style for the port * samples, since the look better this way. */ private void prepare(String fileNameBase) { if (fileNameBase.equals("medium") || fileNameBase.equals("ports1") || fileNameBase.equals("ports2")) { linkStyleSelector.setSelectedIndex(1); setGlobalLinkStyle("orthogonal"); } } /** * Sets the flow direction. */ private void setFlowDirection(String flow) { if (flow.equals("top")) styleSheet.setDeclaration("GraphLayout", "flowDirection", "Top"); else if (flow.equals("bottom")) styleSheet.setDeclaration("GraphLayout", "flowDirection", "Bottom"); else if (flow.equals("left")) styleSheet.setDeclaration("GraphLayout", "flowDirection", "Left"); else if (flow.equals("right")) styleSheet.setDeclaration("GraphLayout", "flowDirection", "Right"); } /** * Sets the global link style. */ private void setGlobalLinkStyle(String style) { if (style.equals("orthogonal")) styleSheet.setDeclaration("GraphLayout", "globalLinkStyle", "ORTHOGONAL_STYLE"); else if (style.equals("straight")) styleSheet.setDeclaration("GraphLayout", "globalLinkStyle", "STRAIGHT_LINE_STYLE"); else if (style.equals("polyline")) styleSheet.setDeclaration("GraphLayout", "globalLinkStyle", "POLYLINE_STYLE"); } /** * Sets the node, node/link or link offset from a textfield. */ private void setOffset(String property, JTextField valueSelector) { try { // get the value of the parameter from the valueSelector float value = Float.valueOf(valueSelector.getText().trim()).floatValue(); // write again to be sure the right value is shown valueSelector.setText("" + value); // set the new value of the layout property styleSheet.setDeclaration("GraphLayout", property, "" + value); } catch (Exception ex) { showMessage("Illegal " + property + ": " + ex.getMessage()); } } /** * Performs the hierarchical layout. The layout type is stored in the CSS * file, therefore we don't need to set the hierarchical layout explicitely. * After loading the graph and the CSS style sheet, the hierarchical layout is * already instantiated. */ private void performLayout() { showMessage("Layout started..."); // initialize the iteration listener layoutListener.initialize(getGraphLayout()); // Perform Hierarchical Layout as node layout. Hierarchical layout always // handles nodes and links at the same time, therefore a separate link // layout is not necessary. if (diagrammer.isNodeLayoutAvailable()) { // perform the layout diagrammer.layoutAllNodes(); // show the layout report information about the return code IlvGraphLayoutReport report = getGraphLayout().getLayoutReport(); if (report != null) { showMessage(report.codeToString(report.getCode()) + "."); // if layout was successfully done, fit in the diagrammer's view if (report.getCode() == IlvGraphLayoutReport.LAYOUT_DONE) { diagrammer.fitToContents(); } } } } /** * Randomize the node positions. */ private void randomize() { // clear previous message showMessage(""); // create a new random layout IlvRandomLayout randomLayout = new IlvRandomLayout(); // save the old layout instance IlvGraphLayout oldLayout = getGraphLayout(); // tell the diagrammer to temporarily use the random layout, which // disables the hierarchical layout. setGraphLayout(randomLayout); if (diagrammer.isNodeLayoutAvailable()) { // perform the layout diagrammer.layoutAllNodes(); } // restore the old layout instance setGraphLayout(oldLayout); } /** * Returns the graph layout currently used by the diagrammer. */ private IlvGraphLayout getGraphLayout() { IlvGraphLayoutRenderer renderer = (IlvGraphLayoutRenderer) diagrammer.getEngine().getNodeLayoutRenderer(); return renderer == null ? null : renderer.getGraphLayout(); } /** * Sets the graph layout currently used by the diagrammer. */ private void setGraphLayout(IlvGraphLayout layout) { IlvGraphLayoutRenderer renderer = (IlvGraphLayoutRenderer) diagrammer.getEngine().getNodeLayoutRenderer(); if (renderer == null) return; renderer.setGraphLayout(layout); } /** * Displays a message. */ void showMessage(String message) { // the message is displayed in the message line msgLine.setText(message); msgLine.paintImmediately(0, 0, msgLine.getWidth(), msgLine.getHeight()); } /** * Allows you to run the demo as a standalone application. */ public static void main(String[] args) { javax.swing.SwingUtilities.invokeLater(new Runnable() { Override public void run() { HierarchicalLayoutApp app = new HierarchicalLayoutApp(); app.init(); JFrame frame = new JFrame("Hierarchical Layout Example"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(600, 600); frame.getContentPane().add(app); frame.setVisible(true); } }); } // -------------------------------------------------------------------------- /** * A graph layout iteration listener. Implementing the interface * GraphLayoutEventListener gives you the possibility to receive during the * layout information about the behavior of the layout algorithm. This * information is contained in the graph layout event. * * In our case, we will simply print the percentage completed by the layout * each time the method layoutStepPerformed is called, that is after each * iteration of the Hierarchical Layout algorithm. */ class LayoutIterationListener implements GraphLayoutEventListener { IlvGraphLayout layout; private float oldPercentage; /** * This method is automatically called by the layout algorithm. */ Override public void layoutStepPerformed(GraphLayoutEvent event) { float percentage = event.getLayoutReport().getPercentageComplete(); if (percentage != oldPercentage) { showMessage("" + percentage + "%"); oldPercentage = percentage; } } /** * Initialize the listener. This method must be called before the layout is * started. */ void initialize(IlvGraphLayout l) { if (layout != l) { if (layout != null) layout.removeGraphLayoutEventListener(this); layout = l; if (layout != null) layout.addGraphLayoutEventListener(this); } oldPercentage = -1.0f; } } }