├── .gitignore ├── .settings ├── org.eclipse.wst.jsdt.ui.superType.name ├── org.eclipse.wst.jsdt.ui.superType.container ├── org.eclipse.m2e.core.prefs ├── org.eclipse.core.resources.prefs ├── .jsdtscope ├── com.vaadin.integration.eclipse.prefs ├── org.eclipse.jdt.core.prefs └── org.eclipse.wst.common.project.facet.core.xml ├── assembly ├── MANIFEST.MF └── assembly.xml ├── src └── main │ ├── java │ └── com │ │ └── vaadin │ │ └── graph │ │ ├── shared │ │ ├── GraphExplorerServerRpc.java │ │ ├── GraphExplorerState.java │ │ ├── IndexedElement.java │ │ ├── ArcProxy.java │ │ └── NodeProxy.java │ │ ├── NodeSelector.java │ │ ├── LayoutEngineModel.java │ │ ├── LayoutEngine.java │ │ ├── GraphElement.java │ │ ├── layout │ │ ├── JungFRLayoutEngine.java │ │ ├── JungISOMLayoutEngine.java │ │ ├── JungCircleLayoutEngine.java │ │ ├── JungLayoutEngine.java │ │ └── JungLayoutEngineModel.java │ │ ├── Arc.java │ │ ├── client │ │ ├── Controller.java │ │ ├── VGraphExplorer.java │ │ ├── GraphProxy.java │ │ ├── GraphExplorerConnector.java │ │ ├── ArcPresenter.java │ │ └── NodePresenter.java │ │ ├── Node.java │ │ ├── GraphRepository.java │ │ ├── GraphExplorer.java │ │ └── GraphController.java │ └── resources │ ├── com │ └── vaadin │ │ └── graph │ │ └── GraphExplorerWidgetset.gwt.xml │ └── VAADIN │ └── themes │ └── graph-explorer │ └── graph-explorer.scss ├── README.md ├── pom.xml └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .classpath 3 | /.project 4 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.ui.superType.name: -------------------------------------------------------------------------------- 1 | Window -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.ui.superType.container: -------------------------------------------------------------------------------- 1 | org.eclipse.wst.jsdt.launching.baseBrowserLibrary -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/main/java=UTF-8 3 | encoding//src/main/resources=UTF-8 4 | encoding/=UTF-8 5 | -------------------------------------------------------------------------------- /assembly/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Vaadin-Package-Version: 1 3 | Vaadin-Addon: ${Vaadin-Addon} 4 | Vaadin-License-Title: ${Vaadin-License-Title} 5 | Implementation-Vendor: ${Implementation-Vendor} 6 | Implementation-Title: ${Implementation-Title} 7 | Implementation-Version: ${Implementation-Version} 8 | -------------------------------------------------------------------------------- /.settings/.jsdtscope: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.settings/com.vaadin.integration.eclipse.prefs: -------------------------------------------------------------------------------- 1 | #Wed May 30 10:01:22 EEST 2012 2 | com.vaadin.integration.eclipse.useLatestNightly=false 3 | com.vaadin.integration.eclipse.widgetsetBuildsSuspended=true 4 | com.vaadin.integration.eclipse.widgetsetCompilationEta=46004 5 | com.vaadin.integration.eclipse.widgetsetDirty=true 6 | com.vaadin.integration.eclipse.widgetsetStyle=DRAFT 7 | com.vaadin.integration.eclipse.widgetsetVerbose=true 8 | eclipse.preferences.version=1 9 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 4 | org.eclipse.jdt.core.compiler.compliance=1.8 5 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 6 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 7 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 8 | org.eclipse.jdt.core.compiler.source=1.8 9 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/shared/GraphExplorerServerRpc.java: -------------------------------------------------------------------------------- 1 | package com.vaadin.graph.shared; 2 | 3 | import com.vaadin.graph.shared.NodeProxy.NodeState; 4 | import com.vaadin.shared.communication.ServerRpc; 5 | 6 | public interface GraphExplorerServerRpc extends ServerRpc { 7 | 8 | void toggleNode(String nodeId); 9 | 10 | void updateNode(String nodeId, NodeState state, int x, int y); 11 | 12 | void clientResized(int clientWidth, int clientHeight); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.common.project.facet.core.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/NodeSelector.java: -------------------------------------------------------------------------------- 1 | package com.vaadin.graph; 2 | 3 | import java.util.Collection; 4 | 5 | import com.vaadin.ui.Component; 6 | 7 | /** 8 | * Interface for UI component (popup) used to select visualized/expanded graph 9 | * nodes 10 | * 11 | */ 12 | public interface NodeSelector extends Component { 13 | 14 | /** 15 | * Answer a collection of node IDs selected by user 16 | * 17 | * @return collection of selected node IDs 18 | */ 19 | public Collection getSelectedNodeIds(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/shared/GraphExplorerState.java: -------------------------------------------------------------------------------- 1 | package com.vaadin.graph.shared; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.vaadin.shared.AbstractComponentState; 7 | 8 | public class GraphExplorerState extends AbstractComponentState { 9 | 10 | private static final long serialVersionUID = 1L; 11 | 12 | public List nodes = new ArrayList(); 13 | public List arcs = new ArrayList(); 14 | public String removedId; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/LayoutEngineModel.java: -------------------------------------------------------------------------------- 1 | package com.vaadin.graph; 2 | 3 | import java.util.Collection; 4 | 5 | import com.vaadin.graph.shared.ArcProxy; 6 | import com.vaadin.graph.shared.NodeProxy; 7 | 8 | public interface LayoutEngineModel { 9 | 10 | public abstract boolean removeNode(NodeProxy v); 11 | 12 | public abstract Collection getNodes(); 13 | 14 | public abstract NodeProxy getNode(String id); 15 | 16 | public abstract Collection getNeighbors(NodeProxy node); 17 | 18 | public abstract Collection getArcs(); 19 | 20 | public abstract ArcProxy getArc(String id); 21 | 22 | public abstract int degree(NodeProxy v); 23 | 24 | public abstract boolean addNode(NodeProxy v); 25 | 26 | public abstract void addArc(ArcProxy arc); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/LayoutEngine.java: -------------------------------------------------------------------------------- 1 | package com.vaadin.graph; 2 | 3 | import java.io.Serializable; 4 | import java.util.Collection; 5 | 6 | import com.vaadin.graph.shared.NodeProxy; 7 | 8 | /** 9 | * An interface representing graph layout engine 10 | * 11 | */ 12 | public interface LayoutEngine extends Serializable { 13 | 14 | public LayoutEngineModel getModel(); 15 | 16 | /** 17 | * Perform the graph layout calculation. 18 | * Elements of the model are expected to contain proper positions/dimensions after layout is invoked. 19 | * 20 | * @param width graph canvas width 21 | * @param height graph canvas height 22 | * @param lockedNodes collection of nodes which should not be moved by layout calculation 23 | */ 24 | public void layout(final int width, final int height, Collection lockedNodes); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/GraphElement.java: -------------------------------------------------------------------------------- 1 | package com.vaadin.graph; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * An interface representing a graph element (Node/Vertex ot Arc/Edge) 7 | * 8 | */ 9 | public interface GraphElement { 10 | 11 | /** Constant for element property name specifying element's CSS style value */ 12 | public static final String PROPERTY_NAME_STYLE = "_css_style_"; 13 | 14 | /** 15 | * Answer an element ID 16 | * 17 | * @return element ID 18 | */ 19 | public String getId(); 20 | 21 | /** 22 | * Answer an element label (type) 23 | * 24 | * @return element label 25 | */ 26 | public String getLabel(); 27 | 28 | /** 29 | * Answer element additional properties (May be used e.g. by UI rendering) 30 | * 31 | * @return element additional properties 32 | */ 33 | public Map getProperties(); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/resources/com/vaadin/graph/GraphExplorerWidgetset.gwt.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/layout/JungFRLayoutEngine.java: -------------------------------------------------------------------------------- 1 | package com.vaadin.graph.layout; 2 | 3 | import java.awt.Dimension; 4 | 5 | import com.vaadin.graph.shared.ArcProxy; 6 | import com.vaadin.graph.shared.NodeProxy; 7 | 8 | import edu.uci.ics.jung.algorithms.layout.AbstractLayout; 9 | import edu.uci.ics.jung.algorithms.layout.FRLayout; 10 | import edu.uci.ics.jung.graph.Graph; 11 | 12 | public class JungFRLayoutEngine extends JungLayoutEngine { 13 | private static final long serialVersionUID = 1L; 14 | 15 | public JungFRLayoutEngine() { 16 | this(new JungLayoutEngineModel()); 17 | } 18 | 19 | public JungFRLayoutEngine(JungLayoutEngineModel model) { 20 | super(model); 21 | } 22 | 23 | protected AbstractLayout createLayout(Graph graph, Dimension size) { 24 | return new FRLayout(graph, size); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/Arc.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2013 Vaadin Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0.html 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package com.vaadin.graph; 17 | 18 | /** 19 | * An interface representing graphs Arc/Edge 20 | * 21 | */ 22 | public interface Arc extends GraphElement { 23 | 24 | /** 25 | * Arc direction 26 | * 27 | */ 28 | public static enum Direction { 29 | INCOMING, OUTGOING; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/layout/JungISOMLayoutEngine.java: -------------------------------------------------------------------------------- 1 | package com.vaadin.graph.layout; 2 | 3 | import java.awt.Dimension; 4 | 5 | import com.vaadin.graph.shared.ArcProxy; 6 | import com.vaadin.graph.shared.NodeProxy; 7 | 8 | import edu.uci.ics.jung.algorithms.layout.AbstractLayout; 9 | import edu.uci.ics.jung.algorithms.layout.ISOMLayout; 10 | import edu.uci.ics.jung.graph.Graph; 11 | 12 | public class JungISOMLayoutEngine extends JungLayoutEngine { 13 | private static final long serialVersionUID = 1L; 14 | 15 | public JungISOMLayoutEngine() { 16 | this(new JungLayoutEngineModel()); 17 | } 18 | 19 | public JungISOMLayoutEngine(JungLayoutEngineModel model) { 20 | super(model); 21 | } 22 | 23 | @Override 24 | protected AbstractLayout createLayout(Graph graph, Dimension size) { 25 | ISOMLayout layout = new ISOMLayout(graph); 26 | layout.setSize(size); 27 | return layout; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/layout/JungCircleLayoutEngine.java: -------------------------------------------------------------------------------- 1 | package com.vaadin.graph.layout; 2 | 3 | import java.awt.Dimension; 4 | 5 | import com.vaadin.graph.shared.ArcProxy; 6 | import com.vaadin.graph.shared.NodeProxy; 7 | 8 | import edu.uci.ics.jung.algorithms.layout.AbstractLayout; 9 | import edu.uci.ics.jung.algorithms.layout.CircleLayout; 10 | import edu.uci.ics.jung.graph.Graph; 11 | 12 | public class JungCircleLayoutEngine extends JungLayoutEngine { 13 | private static final long serialVersionUID = 1L; 14 | 15 | public JungCircleLayoutEngine() { 16 | this(new JungLayoutEngineModel()); 17 | } 18 | 19 | public JungCircleLayoutEngine(JungLayoutEngineModel model) { 20 | super(model); 21 | } 22 | 23 | @Override 24 | protected AbstractLayout createLayout(Graph graph, Dimension size) { 25 | CircleLayout layout = new CircleLayout(graph); 26 | layout.setSize(size); 27 | return layout; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/client/Controller.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2013 Vaadin Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0.html 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package com.vaadin.graph.client; 17 | 18 | /** 19 | * An event handler and presenter/controller. 20 | * 21 | * @author Marlon Richert @ Vaadin 22 | */ 23 | interface Controller { 24 | 25 | void onUpdateInModel(); 26 | 27 | void onRemoveFromModel(); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/Node.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2013 Vaadin Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0.html 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package com.vaadin.graph; 17 | 18 | import com.vaadin.server.Resource; 19 | 20 | /** 21 | * An interface representing a graphs Node/Vertex 22 | * 23 | */ 24 | public interface Node extends GraphElement { 25 | 26 | /** 27 | * Answer a UI resource used to visualize the node 28 | * 29 | * @return an icon resource or null 30 | */ 31 | public Resource getIcon(); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /assembly/assembly.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | addon 7 | 8 | 10 | 11 | 12 | zip 13 | 14 | 15 | 16 | false 17 | 18 | 19 | 20 | .. 21 | 22 | LICENSE.txt 23 | README.md 24 | 25 | 26 | 27 | target 28 | 29 | 30 | *.jar 31 | *.pdf 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | assembly/MANIFEST.MF 40 | META-INF 41 | true 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/client/VGraphExplorer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2013 Vaadin Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0.html 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package com.vaadin.graph.client; 17 | 18 | import org.vaadin.gwtgraphics.client.DrawingArea; 19 | import org.vaadin.gwtgraphics.client.VectorObject; 20 | 21 | import com.google.gwt.dom.client.Style.Position; 22 | import com.google.gwt.user.client.ui.AbsolutePanel; 23 | import com.google.gwt.user.client.ui.Composite; 24 | import com.google.gwt.user.client.ui.HTML; 25 | import com.google.gwt.user.client.ui.Panel; 26 | 27 | public class VGraphExplorer extends Composite { 28 | 29 | /** Set the CSS class name to allow styling. */ 30 | public static final String CSS_CLASSNAME = "v-graph-explorer"; 31 | private static final String STYLE_CANVAS = "canvas"; 32 | 33 | private final Panel root = new AbsolutePanel(); 34 | protected final DrawingArea canvas = new DrawingArea(0, 0); 35 | 36 | public VGraphExplorer() { 37 | initWidget(root); 38 | canvas.getElement().getStyle().setPosition(Position.ABSOLUTE); 39 | canvas.setStyleName(STYLE_CANVAS); 40 | root.add(canvas); 41 | setStyleName(CSS_CLASSNAME); 42 | } 43 | 44 | void add(HTML widget) { 45 | root.add(widget); 46 | } 47 | 48 | void add(VectorObject widget) { 49 | canvas.add(widget); 50 | } 51 | 52 | public void remove(HTML widget) { 53 | widget.removeFromParent(); 54 | } 55 | 56 | public void remove(VectorObject widget) { 57 | canvas.remove(widget); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/shared/IndexedElement.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2013 Vaadin Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0.html 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package com.vaadin.graph.shared; 17 | 18 | import java.io.Serializable; 19 | 20 | /** 21 | * A graph element with a unique ID. 22 | * 23 | * @author Marlon Richert @ Vaadin 24 | */ 25 | public abstract class IndexedElement implements Serializable { 26 | private static final long serialVersionUID = 1L; 27 | 28 | private String id; 29 | 30 | protected IndexedElement() { 31 | super(); 32 | } 33 | 34 | protected IndexedElement(String id) { 35 | this.id = id; 36 | } 37 | 38 | public String getId() { 39 | return id; 40 | } 41 | 42 | public void setId(String id) { 43 | this.id = id; 44 | } 45 | 46 | @Override 47 | public final boolean equals(Object obj) { 48 | if (this == obj) { 49 | return true; 50 | } 51 | if (obj == null) { 52 | return false; 53 | } 54 | if (!(obj instanceof IndexedElement)) { 55 | return false; 56 | } 57 | IndexedElement other = (IndexedElement) obj; 58 | if (id == null) { 59 | if (other.id != null) { 60 | return false; 61 | } 62 | } else if (!id.equals(other.id)) { 63 | return false; 64 | } 65 | return true; 66 | } 67 | 68 | @Override 69 | public final int hashCode() { 70 | final int prime = 31; 71 | int result = 1; 72 | result = prime * result + (id == null ? 0 : id.hashCode()); 73 | return result; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/layout/JungLayoutEngine.java: -------------------------------------------------------------------------------- 1 | package com.vaadin.graph.layout; 2 | 3 | import java.awt.Dimension; 4 | import java.awt.geom.Point2D; 5 | import java.util.Collection; 6 | import java.util.Random; 7 | 8 | import com.google.common.base.Function; 9 | 10 | import com.vaadin.graph.LayoutEngine; 11 | import com.vaadin.graph.shared.ArcProxy; 12 | import com.vaadin.graph.shared.NodeProxy; 13 | 14 | import edu.uci.ics.jung.algorithms.layout.AbstractLayout; 15 | import edu.uci.ics.jung.algorithms.util.IterativeContext; 16 | import edu.uci.ics.jung.graph.Graph; 17 | 18 | /** 19 | * LayoutEngine implementation using the JUNG library 20 | * 21 | */ 22 | public abstract class JungLayoutEngine implements LayoutEngine { 23 | 24 | private static final long serialVersionUID = 1L; 25 | 26 | private final JungLayoutEngineModel model; 27 | 28 | protected JungLayoutEngine(JungLayoutEngineModel model) { 29 | super(); 30 | this.model = model; 31 | } 32 | 33 | public JungLayoutEngineModel getModel() { 34 | return model; 35 | } 36 | 37 | public void layout(final int width, final int height, Collection lockedNodes) { 38 | AbstractLayout layout = createLayout(model.getGraph(), new Dimension(width, height)); 39 | layout.lock(false); 40 | for (NodeProxy v : lockedNodes) { 41 | layout.lock(v, true); 42 | } 43 | 44 | layout.setInitializer(new Function() { 45 | public Point2D apply(NodeProxy input) { 46 | int x = input.getX(); 47 | int y = input.getY(); 48 | return new Point2D.Double(x == -1 ? new Random().nextInt(width) : x, 49 | y == -1 ? new Random().nextInt(height) : y); 50 | } 51 | }); 52 | 53 | layout.initialize(); 54 | if (layout instanceof IterativeContext) { 55 | while (!((IterativeContext)layout).done()) { 56 | ((IterativeContext)layout).step(); 57 | } 58 | } 59 | for (NodeProxy v : model.getGraph().getVertices()) { 60 | Point2D location = layout.apply(v); 61 | v.setX((int) location.getX()); 62 | v.setY((int) location.getY()); 63 | } 64 | } 65 | 66 | protected abstract AbstractLayout createLayout(Graph graph, Dimension size); 67 | } 68 | -------------------------------------------------------------------------------- /src/main/resources/VAADIN/themes/graph-explorer/graph-explorer.scss: -------------------------------------------------------------------------------- 1 | @mixin graph-explorer { 2 | 3 | .v-graph-explorer .canvas { 4 | background-color: white; 5 | } 6 | 7 | .v-graph-explorer .node { 8 | position: absolute; 9 | min-width: 1em; 10 | max-width: 20em; 11 | min-height: 1em; 12 | font-size: 100%; 13 | background-color: #8BC760; 14 | text-align: center; 15 | display: table-row; 16 | border-radius: 1em; 17 | padding: .9ex; 18 | } 19 | 20 | .v-graph-explorer .expanded { 21 | border-style: solid; 22 | border-width: 1px; 23 | border-color: #000 #333 #333 #000; 24 | } 25 | 26 | .v-graph-explorer .node.empty { 27 | 28 | } 29 | 30 | .v-graph-explorer .node.group { 31 | font-size: 1.25em; 32 | padding: 1ex; 33 | border-radius: 50%; 34 | } 35 | 36 | .v-graph-explorer .node .label { 37 | display: table-cell; 38 | vertical-align: middle; 39 | text-align: center; 40 | } 41 | 42 | .v-graph-explorer .arc { 43 | position: absolute; 44 | background-color: white; 45 | font-size: 80%; 46 | text-align: center; 47 | white-space: nowrap; 48 | } 49 | 50 | /* Ordering */ 51 | .v-graph-explorer .arc { 52 | z-index: 0; 53 | } 54 | 55 | .v-graph-explorer .collapsed { 56 | z-index: 1; 57 | } 58 | 59 | .v-graph-explorer .group { 60 | z-index: 2; 61 | } 62 | 63 | .v-graph-explorer .expanded { 64 | z-index: 3; 65 | } 66 | 67 | .v-graph-explorer .expanding { 68 | z-index: 4; 69 | } 70 | 71 | /* Shadows */ 72 | .v-graph-explorer .node, 73 | .v-graph-explorer .node.down, 74 | .v-graph-explorer .node.group.down { 75 | box-shadow: .25ex .5ex .5ex rgba(0, 0, 0, 0.5) inset, -.25ex -.5ex .5ex 76 | rgba(255, 255, 255, 0.5) inset; 77 | -webkit-box-shadow: .25ex .5ex .5ex rgba(0, 0, 0, 0.5) inset, -.25ex -.5ex .5ex 78 | rgba(255, 255, 255, 0.5) inset; 79 | text-shadow: 1px 1px 1px rgba(255, 255, 255, .38); 80 | } 81 | 82 | .v-graph-explorer .collapsed { 83 | box-shadow: .5ex 1ex 1ex rgba(255, 255, 255, 0.8) inset, -.5ex -1ex 1ex 84 | rgba(0, 0, 0, 0.2) inset, 2px 2px 4px rgba(0, 0, 0, .62); 85 | -webkit-box-shadow: .5ex 1ex 1ex rgba(255, 255, 255, 0.8) inset, -.5ex -1ex 1ex 86 | rgba(0, 0, 0, 0.2) inset, 1px 2px 4px rgba(0, 0, 0, .62); 87 | } 88 | 89 | .v-graph-explorer .node.group { 90 | box-shadow: .75ex 1.5ex 1.5ex rgba(255, 255, 255, 0.8) inset, -.75ex -1.5ex 91 | 1.5ex rgba(0, 0, 0, 0.2) inset, 3px 3px 6px rgba(0, 0, 0, 0.62); 92 | -webkit-box-shadow: .75ex 1.5ex 1.5ex rgba(255, 255, 255, 0.8) inset, -.75ex 93 | -1.5ex 1.5ex rgba(0, 0, 0, 0.2) inset, 2px 4px 4px rgba(0, 0, 0, 0.62); 94 | } 95 | 96 | } -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/shared/ArcProxy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2013 Vaadin Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0.html 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package com.vaadin.graph.shared; 17 | 18 | /** 19 | * Client-side proxy for a server-side graph arc between two nodes. 20 | * 21 | * @author Marlon Richert @ Vaadin 22 | */ 23 | public class ArcProxy extends IndexedElement { 24 | private static final long serialVersionUID = 1L; 25 | 26 | private String label = ""; 27 | private boolean group = false; 28 | private String fromNode; 29 | private String toNode; 30 | private String style = null; 31 | 32 | public ArcProxy() { 33 | super(); 34 | } 35 | 36 | public ArcProxy(String id, String fromNode, String toNode) { 37 | super(id); 38 | this.fromNode = fromNode; 39 | this.toNode = toNode; 40 | } 41 | 42 | public String getLabel() { 43 | return label; 44 | } 45 | 46 | public void setLabel(String label) { 47 | this.label = label; 48 | } 49 | 50 | public void setGroup(boolean group) { 51 | this.group = group; 52 | } 53 | 54 | public boolean isGroup() { 55 | return group; 56 | } 57 | 58 | public String getFromNode() { 59 | return fromNode; 60 | } 61 | 62 | public void setFromNode(String fromNode) { 63 | this.fromNode = fromNode; 64 | } 65 | 66 | public String getToNode() { 67 | return toNode; 68 | } 69 | 70 | public void setToNode(String toNode) { 71 | this.toNode = toNode; 72 | } 73 | 74 | public String getStyle() { 75 | return style; 76 | } 77 | 78 | public void setStyle(String style) { 79 | this.style = style; 80 | } 81 | 82 | @Override 83 | public String toString() { 84 | StringBuilder sb = new StringBuilder(); 85 | sb.append("Arc[").append(getId()).append("] "); 86 | sb.append('"').append(getLabel()).append('"'); 87 | sb.append(getFromNode()).append(" -> ").append(getToNode()); 88 | return sb.toString(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/client/GraphProxy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2013 Vaadin Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0.html 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package com.vaadin.graph.client; 17 | 18 | import java.util.Collection; 19 | import java.util.Collections; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | 23 | /** 24 | * Client-side proxy of the server-side graph model. 25 | * 26 | * @author Marlon Richert @ Vaadin 27 | */ 28 | public class GraphProxy { 29 | private final Map nodes = new HashMap(); 30 | private final Map arcs = new HashMap(); 31 | 32 | /** 33 | * Adds a new arc from the given tail to the given head. 34 | * 35 | * @param arc arc presenter 36 | * @return true, if successful; false, otherwise 37 | */ 38 | public boolean addArc(ArcPresenter arc) { 39 | String id = arc.getModel().getId(); 40 | if (arcs.containsKey(id)) { 41 | return false; 42 | } 43 | arcs.put(id, arc); 44 | return true; 45 | } 46 | 47 | public boolean addNode(NodePresenter node) { 48 | String id = node.getModel().getId(); 49 | if (nodes.containsKey(id)) { 50 | return false; 51 | } 52 | nodes.put(id, node); 53 | return true; 54 | } 55 | 56 | public void removeNode(String id) { 57 | NodePresenter node = nodes.remove(id); 58 | if (node != null) { 59 | node.onRemoveFromModel(); 60 | } 61 | } 62 | 63 | public void removeArc(String id) { 64 | ArcPresenter arc = arcs.remove(id); 65 | if (arc != null) { 66 | arc.onRemoveFromModel(); 67 | } 68 | } 69 | 70 | public ArcPresenter getArc(String id) { 71 | return arcs.get(id); 72 | } 73 | 74 | public NodePresenter getNode(String id) { 75 | return nodes.get(id); 76 | } 77 | 78 | public Collection getNodes() { 79 | return Collections.unmodifiableCollection(nodes.values()); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/GraphRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2013 Vaadin Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0.html 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package com.vaadin.graph; 17 | 18 | import java.util.Collection; 19 | 20 | /** 21 | * An interface for graph elements repository/provider 22 | * 23 | * @param 24 | * type of node elements 25 | * @param 26 | * type of arc elements 27 | */ 28 | public interface GraphRepository { 29 | 30 | /** 31 | * Gets the node that the given arc points away from. 32 | * 33 | * @param arc graph arc to get tail of 34 | * @return tail node 35 | */ 36 | public N getTail(A arc); 37 | 38 | /** 39 | * Gets the node that the given arc points to. 40 | * 41 | * @param arc graph arc to get head of 42 | * @return head node 43 | */ 44 | public N getHead(A arc); 45 | 46 | /** 47 | * Returns a list of all possible arc labels in this graph. 48 | * 49 | * @return list of labels of all graph's arcs 50 | */ 51 | public Iterable getArcLabels(); 52 | 53 | /** 54 | * Gets all arcs connected to the given node, with the given label, in the 55 | * given direction. 56 | * 57 | * @param node 58 | * return only arcs connected to this node 59 | * @param label 60 | * return only arcs with this label 61 | * @param dir 62 | * INCOMING for arcs pointing towards the given node, OUTGOING 63 | * for arcs pointing away from the given node 64 | * @return collection of arcs connector to the given node 65 | */ 66 | public Collection getArcs(N node, String label, Arc.Direction dir); 67 | 68 | /** 69 | * Gets the root/origin of the graph 70 | * 71 | * @return grpah's root/home node 72 | */ 73 | public N getHomeNode(); 74 | 75 | /** 76 | * Gets the node at the other end of the given arc. 77 | * 78 | * @param node graph node to get the opposite arc 79 | * @param arc graph arc to get the opposite end 80 | * @return at the other end of the given arc. 81 | */ 82 | public N getOpposite(N node, A arc); 83 | 84 | /** 85 | * Returns the node with the given ID. 86 | * 87 | * @param id id of the node 88 | * @return node with the given ID or null if there's no such node 89 | */ 90 | public N getNodeById(String id); 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/shared/NodeProxy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2013 Vaadin Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0.html 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package com.vaadin.graph.shared; 17 | 18 | 19 | /** 20 | * A node in a graph. 21 | * 22 | * @author Marlon Richert @ Vaadin 23 | */ 24 | public class NodeProxy extends IndexedElement { 25 | private static final long serialVersionUID = 1L; 26 | 27 | private String content = ""; 28 | private String iconUrl = null; 29 | private int x = -1; 30 | private int y = -1; 31 | private NodeKind kind = NodeKind.NORMAL; 32 | private NodeState state = NodeState.COLLAPSED; 33 | private String style = null; 34 | 35 | public NodeProxy() { 36 | super(); 37 | } 38 | 39 | public NodeProxy(String id) { 40 | super(id); 41 | } 42 | 43 | public NodeKind getKind() { 44 | return kind; 45 | } 46 | 47 | public String getContent() { 48 | return content; 49 | } 50 | 51 | public String getIconUrl() { 52 | return iconUrl; 53 | } 54 | 55 | public NodeState getState() { 56 | return state; 57 | } 58 | 59 | public int getX() { 60 | return x; 61 | } 62 | 63 | public int getY() { 64 | return y; 65 | } 66 | 67 | public String getStyle() { 68 | return style; 69 | } 70 | 71 | public void setKind(NodeKind kind) { 72 | this.kind = kind; 73 | } 74 | 75 | public void setContent(String content) { 76 | this.content = content; 77 | } 78 | 79 | public void setIconUrl(String iconUrl) { 80 | this.iconUrl = iconUrl; 81 | } 82 | 83 | public void setState(NodeState state) { 84 | this.state = state; 85 | } 86 | 87 | public void setX(int x) { 88 | this.x = x; 89 | } 90 | 91 | public void setY(int y) { 92 | this.y = y; 93 | } 94 | 95 | public void setStyle(String style) { 96 | this.style = style; 97 | } 98 | 99 | @Override 100 | public String toString() { 101 | StringBuilder sb = new StringBuilder(); 102 | sb.append("Node[").append(getId()).append("] "); 103 | sb.append('"').append(getContent()).append('"'); 104 | return sb.toString(); 105 | } 106 | 107 | public enum NodeKind { 108 | NORMAL, 109 | GROUP, 110 | EMPTY; 111 | } 112 | 113 | public enum NodeState { 114 | COLLAPSED, 115 | EXPANDED; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/client/GraphExplorerConnector.java: -------------------------------------------------------------------------------- 1 | package com.vaadin.graph.client; 2 | 3 | import com.google.gwt.user.client.Random; 4 | import com.vaadin.client.communication.RpcProxy; 5 | import com.vaadin.client.communication.StateChangeEvent; 6 | import com.vaadin.client.ui.AbstractComponentConnector; 7 | import com.vaadin.client.ui.SimpleManagedLayout; 8 | import com.vaadin.graph.GraphExplorer; 9 | import com.vaadin.graph.shared.ArcProxy; 10 | import com.vaadin.graph.shared.GraphExplorerServerRpc; 11 | import com.vaadin.graph.shared.GraphExplorerState; 12 | import com.vaadin.graph.shared.NodeProxy; 13 | import com.vaadin.shared.ui.Connect; 14 | 15 | @Connect(GraphExplorer.class) 16 | public class GraphExplorerConnector extends AbstractComponentConnector implements SimpleManagedLayout { 17 | 18 | private static final long serialVersionUID = 1L; 19 | 20 | private GraphExplorerServerRpc rpc = RpcProxy.create(GraphExplorerServerRpc.class, this); 21 | 22 | private int oldHeight = 0; 23 | private int oldWidth = 0; 24 | 25 | private final GraphProxy graph = new GraphProxy(); 26 | private NodePresenter current; 27 | 28 | @Override 29 | public GraphExplorerState getState() { 30 | return (GraphExplorerState) super.getState(); 31 | } 32 | 33 | @Override 34 | public VGraphExplorer getWidget() { 35 | return (VGraphExplorer) super.getWidget(); 36 | } 37 | 38 | @Override 39 | public void layout() { 40 | int width = getLayoutManager().getInnerWidth(getWidget().getElement()); 41 | int height = getLayoutManager().getInnerHeight(getWidget().getElement()); 42 | 43 | if ((width > 0 && height > 0) && (width != oldWidth || height != oldHeight)) { 44 | getWidget().canvas.setWidth(width); 45 | getWidget().canvas.setHeight(height); 46 | rpc.clientResized(width, height); 47 | } 48 | oldHeight = height; 49 | oldWidth = width; 50 | } 51 | 52 | @Override 53 | public void onStateChanged(StateChangeEvent stateChangeEvent) { 54 | super.onStateChanged(stateChangeEvent); 55 | 56 | reloadNodes(); 57 | reloadArcs(); 58 | 59 | if (getState().removedId != null) { 60 | graph.removeNode(getState().removedId); 61 | } 62 | 63 | for (NodePresenter node : graph.getNodes()) { 64 | node.onUpdateInModel(); 65 | } 66 | } 67 | 68 | GraphProxy getGraph() { 69 | return graph; 70 | } 71 | 72 | void updateNode(NodeProxy node, int x, int y) { 73 | rpc.updateNode(node.getId(), node.getState(), x, y); 74 | } 75 | 76 | void toggle(NodePresenter node) { 77 | current = node; 78 | if (node != null) { 79 | rpc.toggleNode(node.getModel().getId()); 80 | } 81 | } 82 | 83 | private void reloadNodes() { 84 | if (getState().nodes == null) { 85 | return; 86 | } 87 | for (NodeProxy node : getState().nodes) { 88 | NodePresenter presenter = graph.getNode(node.getId()); 89 | if (presenter == null) { 90 | presenter = new NodePresenter(this, node); 91 | if (current == null) { 92 | presenter.setX(Random.nextInt(getWidget().getOffsetWidth())); 93 | presenter.setY(Random.nextInt(getWidget().getOffsetHeight())); 94 | } else { 95 | presenter.setX(current.getX()); 96 | presenter.setY(current.getY()); 97 | } 98 | graph.addNode(presenter); 99 | } else { 100 | presenter.setModel(node); 101 | } 102 | presenter.move(node.getX(), node.getY()); 103 | } 104 | } 105 | 106 | private void reloadArcs() { 107 | if (getState().arcs == null) { 108 | return; 109 | } 110 | for (ArcProxy arc : getState().arcs) { 111 | ArcPresenter presenter = graph.getArc(arc.getId()); 112 | if (presenter == null) { 113 | graph.addArc(new ArcPresenter(this, arc)); 114 | graph.getNode(arc.getFromNode()).addOutArc(arc.getId()); 115 | graph.getNode(arc.getToNode()).addInArc(arc.getId()); 116 | } 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/layout/JungLayoutEngineModel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2013 Vaadin Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0.html 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package com.vaadin.graph.layout; 17 | 18 | import java.util.Collection; 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | 22 | import com.vaadin.graph.LayoutEngineModel; 23 | import com.vaadin.graph.shared.ArcProxy; 24 | import com.vaadin.graph.shared.NodeProxy; 25 | 26 | import edu.uci.ics.jung.graph.DirectedSparseMultigraph; 27 | import edu.uci.ics.jung.graph.Graph; 28 | import edu.uci.ics.jung.graph.util.EdgeType; 29 | import edu.uci.ics.jung.graph.util.Pair; 30 | 31 | /** 32 | * Data structure consisting of nodes with arcs between them. 33 | * 34 | * @author Marlon Richert @ Vaadin 35 | */ 36 | public class JungLayoutEngineModel implements LayoutEngineModel { 37 | 38 | private final Map nodes = new HashMap(); 39 | private final Map arcs = new HashMap(); 40 | 41 | private final DirectedSparseMultigraph graph = new DirectedSparseMultigraph() { 42 | private static final long serialVersionUID = 1L; 43 | 44 | @Override 45 | public boolean addEdge(ArcProxy arc, 46 | Pair endpoints, 47 | EdgeType arcType) { 48 | boolean success = super.addEdge(arc, endpoints, arcType); 49 | if (success) { 50 | arcs.put(arc.getId(), arc); 51 | } 52 | return success; 53 | } 54 | 55 | @Override 56 | public boolean addVertex(NodeProxy node) { 57 | boolean success = super.addVertex(node); 58 | if (success) { 59 | nodes.put(node.getId(), node); 60 | } 61 | return success; 62 | } 63 | 64 | @Override 65 | public boolean removeEdge(ArcProxy arc) { 66 | boolean success = super.removeEdge(arc); 67 | if (success) { 68 | arcs.remove(arc.getId()); 69 | } 70 | return success; 71 | } 72 | 73 | @Override 74 | public boolean removeVertex(NodeProxy node) { 75 | boolean success = super.removeVertex(node); 76 | if (success) { 77 | nodes.remove(node.getId()); 78 | } 79 | return success; 80 | } 81 | }; 82 | 83 | @Override 84 | public void addArc(ArcProxy arc) { 85 | NodeProxy from = getNode(arc.getFromNode()); 86 | NodeProxy to = getNode(arc.getToNode()); 87 | graph.addEdge(arc, new Pair(from, to), EdgeType.DIRECTED); 88 | } 89 | 90 | @Override 91 | public boolean addNode(NodeProxy v) { 92 | return graph.addVertex(v); 93 | } 94 | 95 | @Override 96 | public int degree(NodeProxy v) { 97 | return graph.degree(v); 98 | } 99 | 100 | @Override 101 | public ArcProxy getArc(String id) { 102 | return arcs.get(id); 103 | } 104 | 105 | @Override 106 | public Collection getArcs() { 107 | return arcs.values(); 108 | } 109 | 110 | @Override 111 | public Collection getNeighbors(NodeProxy node) { 112 | return graph.getNeighbors(node); 113 | } 114 | 115 | @Override 116 | public NodeProxy getNode(String id) { 117 | return nodes.get(id); 118 | } 119 | 120 | @Override 121 | public Collection getNodes() { 122 | return nodes.values(); 123 | } 124 | 125 | @Override 126 | public boolean removeNode(NodeProxy v) { 127 | return graph.removeVertex(v); 128 | } 129 | 130 | public Graph getGraph() { 131 | return graph; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Graph Explorer (Vaadin 7) 2 | 3 | An interactive graph data visualization component 4 | 5 | ## Download release 6 | 7 | Official releases of this add-on are available at Vaadin Directory. For Maven instructions, download and reviews, go to http://vaadin.com/addon/graph-explorer-vaadin-7 8 | 9 | ## Building and running demo 10 | 11 | git clone https://github.com/mletenay/Graph-Explorer-demo 12 | mvn clean install 13 | cd demo 14 | mvn jetty:run 15 | 16 | To see the demo, navigate to http://localhost:8080/ 17 | 18 | ## Development with Eclipse IDE 19 | 20 | For further development of this add-on, the following tool-chain is recommended: 21 | - Eclipse IDE 22 | - m2e wtp plug-in (install it from Eclipse Marketplace) 23 | - Vaadin Eclipse plug-in (install it from Eclipse Marketplace) 24 | - JRebel Eclipse plug-in (install it from Eclipse Marketplace) 25 | - Chrome browser 26 | 27 | ### Importing project 28 | 29 | Choose File > Import... > Existing Maven Projects 30 | 31 | Note that Eclipse may give "Plugin execution not covered by lifecycle configuration" errors for pom.xml. Use "Permanently mark goal resources in pom.xml as ignored in Eclipse build" quick-fix to mark these errors as permanently ignored in your project. Do not worry, the project still works fine. 32 | 33 | ### Debugging server-side 34 | 35 | If you have not already compiled the widgetset, do it now by running vaadin:install Maven target for graph-explorer-root project. 36 | 37 | If you have a JRebel license, it makes on the fly code changes faster. Just add JRebel nature to your graph-explorer-demo project by clicking project with right mouse button and choosing JRebel > Add JRebel Nature 38 | 39 | To debug project and make code modifications on the fly in the server-side, right-click the graph-explorer-demo project and choose Debug As > Debug on Server. Navigate to http://localhost:8080/graph-explorer-demo/ to see the application. 40 | 41 | ### Debugging client-side 42 | 43 | The most common way of debugging and making changes to the client-side code is dev-mode. To create debug configuration for it, open graph-explorer-demo project properties and click "Create Development Mode Launch" button on the Vaadin tab. Right-click newly added "GWT development mode for graph-explorer-demo.launch" and choose Debug As > Debug Configurations... Open up Classpath tab for the development mode configuration and choose User Entries. Click Advanced... and select Add Folders. Choose Java and Resources under graph-explorer/src/main and click ok. Now you are ready to start debugging the client-side code by clicking debug. Click Launch Default Browser button in the GWT Development Mode in the launched application. Now you can modify and breakpoints to client-side classes and see changes by reloading the web page. 44 | 45 | Another way of debugging client-side is superdev mode. To enable it, uncomment devModeRedirectEnabled line from the end of DemoWidgetSet.gwt.xml located under graph-explorer-demo resources folder and compile the widgetset once by running vaadin:compile Maven target for graph-explorer-demo. Refresh graph-explorer-demo project resources by right clicking the project and choosing Refresh. Click "Create SuperDevMode Launch" button on the Vaadin tab of the graph-explorer-demo project properties panel to create superder mode code server launch configuration and modify the class path as instructed above. After starting the code server by running SuperDevMode launch as Java application, you can navigate to http://localhost:8080/graph-explorer-demo/?superdevmode. Now all code changes you do to your client side will get compiled as soon as you reload the web page. You can also access Java-sources and set breakpoints inside Chrome if you enable source maps from inspector settings. 46 | 47 | 48 | ## Release notes 49 | 50 | ### Version 0.8.0-SNAPSHOT 51 | 52 | ## Issue tracking 53 | 54 | The issues for this add-on are tracked on its github.com page. All bug reports and feature requests are appreciated. 55 | 56 | ## Contributions 57 | 58 | Contributions are welcome, but there are no guarantees that they are accepted as such. Process for contributing is the following: 59 | - Fork this project 60 | - Create an issue to this project about the contribution (bug or feature) if there is no such issue about it already. Try to keep the scope minimal. 61 | - Develop and test the fix or functionality carefully. Only include minimum amount of code needed to fix the issue. 62 | - Refer to the fixed issue in commit 63 | - Send a pull request for the original project 64 | - Comment on the original issue that you have implemented a fix for it 65 | 66 | ## License & Author 67 | 68 | Add-on is distributed under Apache License 2.0. For license terms, see LICENSE.txt. 69 | 70 | Original Vaadin 6 version of Graph-explorer written by Marlon Richert. 71 | Vaadin 7 port and further development by Martin Letenay. 72 | 73 | # Developer Guide 74 | 75 | ## Getting started 76 | 77 | Here is a simple example on how to try out the add-on component: 78 | 79 | <...> 80 | 81 | For a more comprehensive example, see src/test/java/org/vaadin/template/demo/DemoUI.java 82 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.vaadin.addons 7 | graph-explorer 8 | bundle 9 | 0.8.0 10 | Graph Explorer (Vaadin 7) 11 | 12 | 13 | UTF-8 14 | 8.0.0 15 | ${vaadin.version} 16 | 17 | 1.0.0 18 | 2.1.1 19 | 20 | 21 | ${project.version} 22 | 23 | ${project.name} 24 | Vaadin Community 25 | Apache License 2.0 26 | ${project.artifactId}-${project.version}.jar 27 | 28 | 29 | 30 | git://github.com//mletenay/Graph-Explorer.git 31 | scm:git:git://github.com//mletenay/Graph-Explorer.git 32 | Graph Explorer (Vaadin 7) 33 | 34 | 35 | 36 | GitHub 37 | https://github.com/mletenay/Graph-Explorer/issues 38 | 39 | 40 | 41 | 42 | Apache 2 43 | http://www.apache.org/licenses/LICENSE-2.0.txt 44 | repo 45 | 46 | 47 | 48 | 49 | 50 | vaadin-addons 51 | https://maven.vaadin.com/vaadin-addons 52 | 53 | 54 | vaadin-snapshots 55 | https://oss.sonatype.org/content/repositories/vaadin-snapshots/ 56 | 57 | false 58 | 59 | 60 | true 61 | 62 | 63 | 64 | 65 | 66 | 67 | vaadin-snapshots 68 | https://oss.sonatype.org/content/repositories/vaadin-snapshots/ 69 | 70 | false 71 | 72 | 73 | true 74 | 75 | 76 | 77 | 78 | 79 | 80 | com.vaadin 81 | vaadin-server 82 | ${vaadin.version} 83 | 84 | 85 | com.vaadin 86 | vaadin-client 87 | ${vaadin.version} 88 | provided 89 | 90 | 91 | 92 | org.vaadin.addons 93 | gwt-graphics 94 | ${gwt-graphics.version} 95 | 96 | 97 | 98 | net.sf.jung 99 | jung-graph-impl 100 | ${jung.version} 101 | 102 | 103 | net.sf.jung 104 | jung-algorithms 105 | ${jung.version} 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | org.apache.maven.plugins 114 | maven-compiler-plugin 115 | 3.0 116 | 117 | 1.8 118 | 1.8 119 | 120 | 121 | 122 | 123 | org.apache.maven.plugins 124 | maven-jar-plugin 125 | 2.3.1 126 | 127 | 128 | true 129 | 130 | false 131 | true 132 | 133 | 134 | 135 | 1 136 | ${Vaadin-License-Title} 137 | com.vaadin.graph.GraphExplorerWidgetset 138 | VAADIN/themes/graph-explorer/graph-explorer.scss 139 | 140 | 141 | 142 | 143 | 144 | 145 | org.apache.maven.plugins 146 | maven-javadoc-plugin 147 | 2.9 148 | 149 | 150 | attach-javadoc 151 | 152 | jar 153 | 154 | 155 | 156 | 157 | 158 | 159 | org.apache.maven.plugins 160 | maven-source-plugin 161 | 2.2.1 162 | 163 | 164 | attach-sources 165 | 166 | jar 167 | 168 | 169 | 170 | 171 | 172 | 173 | org.apache.felix 174 | maven-bundle-plugin 175 | 2.5.3 176 | true 177 | 178 | 179 | !com.vaadin.graph.client,com.vaadin.graph.* 180 | !com.google.gwt.*,!com.vaadin.client.*,!org.vaadin.gwtgraphics.*,* 181 | 182 | 183 | 184 | 185 | 186 | org.apache.maven.plugins 187 | maven-assembly-plugin 188 | 2.2.1 189 | 190 | false 191 | 192 | assembly/assembly.xml 193 | 194 | 195 | 196 | 197 | 198 | single 199 | 200 | install 201 | 202 | 203 | 204 | 205 | 206 | 207 | 209 | 210 | 211 | src/main/java 212 | 213 | rebel.xml 214 | 215 | 216 | 217 | src/main/resources 218 | 219 | 220 | 221 | 222 | 223 | 224 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/client/ArcPresenter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2013 Vaadin Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0.html 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package com.vaadin.graph.client; 17 | 18 | import org.vaadin.gwtgraphics.client.Line; 19 | 20 | import com.google.gwt.dom.client.Style; 21 | import com.google.gwt.dom.client.Style.Unit; 22 | import com.google.gwt.user.client.ui.HTML; 23 | import com.vaadin.graph.shared.ArcProxy; 24 | 25 | /** 26 | * Presenter/controller for an arc in a graph. 27 | * 28 | * @author Marlon Richert @ Vaadin 29 | */ 30 | class ArcPresenter implements Controller { 31 | 32 | /** Set the CSS class name to allow styling. */ 33 | public static final String CSS_CLASSNAME = "arc"; 34 | 35 | private static final int ARROWHEAD_LENGTH = 10; 36 | private static final int ARROWHEAD_WIDTH = ARROWHEAD_LENGTH / 2; 37 | 38 | private final GraphExplorerConnector connector; 39 | private final ArcProxy model; 40 | 41 | private final Line viewBody = new Line(0, 0, 0, 0); 42 | private final HTML viewLabel; 43 | private final Line viewHeadLeft = new Line(0, 0, 0, 0); 44 | private final Line viewHeadRight = new Line(0, 0, 0, 0); 45 | 46 | private double headX; 47 | private double headY; 48 | 49 | ArcPresenter(GraphExplorerConnector connector, ArcProxy model) { 50 | this.connector = connector; 51 | this.model = model; 52 | 53 | connector.getWidget().add(viewBody); 54 | connector.getWidget().add(viewHeadLeft); 55 | connector.getWidget().add(viewHeadRight); 56 | 57 | viewLabel = new HTML(model.getLabel()); 58 | viewLabel.getElement().setClassName(CSS_CLASSNAME); 59 | if (!model.isGroup()) { 60 | viewLabel.setTitle(model.getId()); 61 | } 62 | connector.getWidget().add(viewLabel); 63 | 64 | onUpdateInModel(); 65 | } 66 | 67 | private static double distance(double fromX, double fromY, double toX, 68 | double toY) { 69 | return Math.abs(toX - fromX) + Math.abs(toY - fromY); 70 | } 71 | 72 | public void onRemoveFromModel() { 73 | NodePresenter node = getFromNode(); 74 | if (node != null) { 75 | node.removeArc(getModel().getId()); 76 | } 77 | node = getToNode(); 78 | if (node != null) { 79 | node.removeArc(getModel().getId()); 80 | } 81 | connector.getWidget().remove(viewBody); 82 | connector.getWidget().remove(viewLabel); 83 | connector.getWidget().remove(viewHeadLeft); 84 | connector.getWidget().remove(viewHeadRight); 85 | } 86 | 87 | public void onUpdateInModel() { 88 | NodePresenter from = getFromNode(); 89 | NodePresenter to = getToNode(); 90 | updateLine(from, to); 91 | updateLabel(from); 92 | updateArrowhead(from, to); 93 | if (model.getStyle() != null) { 94 | viewBody.setStyleName(model.getStyle()); 95 | viewHeadLeft.setStyleName(model.getStyle()); 96 | viewHeadRight.setStyleName(model.getStyle()); 97 | viewLabel.getElement().addClassName(model.getStyle()); 98 | } 99 | } 100 | 101 | ArcProxy getModel() { 102 | return model; 103 | } 104 | 105 | NodePresenter getFromNode() { 106 | return connector.getGraph().getNode(model.getFromNode()); 107 | } 108 | 109 | NodePresenter getToNode() { 110 | return connector.getGraph().getNode(model.getToNode()); 111 | } 112 | 113 | private void updateArrowhead(NodePresenter from, NodePresenter to) { 114 | double fromX = from.getX(); 115 | double fromY = from.getY(); 116 | double toX = to.getX(); 117 | double toY = to.getY(); 118 | double dX = toX - fromX; 119 | double dY = toY - fromY; 120 | headX = toX; 121 | headY = toY; 122 | double distance = distance(fromX, fromY, toX, toY); 123 | double newX; 124 | double newY; 125 | 126 | double halfWidth = to.getWidth() / 2.0; 127 | double left = toX - halfWidth; 128 | double right = toX + halfWidth; 129 | newX = fromX < left ? left : fromX > right ? right : fromX; 130 | newY = fromY + dY * (newX - fromX) / dX; 131 | double newDistance = distance(newX, newY, toX, toY); 132 | if (newDistance < distance) { 133 | distance = newDistance; 134 | headX = newX; 135 | headY = newY; 136 | } 137 | 138 | double halfHeight = to.getHeight() / 2.0; 139 | double top = toY - halfHeight; 140 | double bottom = toY + halfHeight; 141 | newY = fromY < top ? top : fromY > bottom ? bottom : fromY; 142 | newX = fromX + dX * (newY - fromY) / dY; 143 | if (distance(newX, newY, toX, toY) < distance) { 144 | headX = newX; 145 | headY = newY; 146 | } 147 | 148 | double angle = Math.atan2(dY, dX); 149 | double leftX = headX 150 | + rotateX(-ARROWHEAD_LENGTH, -ARROWHEAD_WIDTH, angle); 151 | double leftY = headY 152 | + rotateY(-ARROWHEAD_LENGTH, -ARROWHEAD_WIDTH, angle); 153 | updateLine(viewHeadLeft, headX, headY, leftX, leftY); 154 | 155 | double rightX = headX 156 | + rotateX(-ARROWHEAD_LENGTH, ARROWHEAD_WIDTH, angle); 157 | double rightY = headY 158 | + rotateY(-ARROWHEAD_LENGTH, ARROWHEAD_WIDTH, angle); 159 | updateLine(viewHeadRight, headX, headY, rightX, rightY); 160 | } 161 | 162 | private void updateLine(NodePresenter from, NodePresenter to) { 163 | updateLine(viewBody, from.getX(), from.getY(), to.getX(), to.getY()); 164 | } 165 | 166 | private Style updateLabel(NodePresenter from) { 167 | Style style = viewLabel.getElement().getStyle(); 168 | double x = getLabelCenter(from.getX(), headX) 169 | - viewLabel.getOffsetWidth() / 2.0; 170 | style.setLeft(x, Unit.PX); 171 | double y = getLabelCenter(from.getY(), headY) 172 | - viewLabel.getOffsetHeight() / 2.0; 173 | style.setTop(y, Unit.PX); 174 | 175 | return style; 176 | } 177 | 178 | private static void updateLine(Line line, double x1, double y1, double x2, 179 | double y2) { 180 | updateLine(line, (int) Math.round(x1), (int) Math.round(y1), 181 | (int) Math.round(x2), (int) Math.round(y2)); 182 | } 183 | 184 | private static void updateLine(Line line, int x1, int y1, int x2, int y2) { 185 | line.setX1(x1); 186 | line.setY1(y1); 187 | line.setX2(x2); 188 | line.setY2(y2); 189 | } 190 | 191 | private static double getLabelCenter(double from, double to) { 192 | return .2 * from + .8 * to; 193 | } 194 | 195 | private static double rotateX(double x, double y, double angle) { 196 | return x * Math.cos(angle) - y * Math.sin(angle); 197 | } 198 | 199 | private static double rotateY(double x, double y, double angle) { 200 | return x * Math.sin(angle) + y * Math.cos(angle); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/client/NodePresenter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2013 Vaadin Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0.html 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package com.vaadin.graph.client; 17 | 18 | import java.util.Collection; 19 | import java.util.HashSet; 20 | import java.util.Set; 21 | 22 | import com.google.gwt.animation.client.Animation; 23 | import com.google.gwt.dom.client.Element; 24 | import com.google.gwt.dom.client.Style; 25 | import com.google.gwt.dom.client.Style.Unit; 26 | import com.google.gwt.event.dom.client.MouseDownEvent; 27 | import com.google.gwt.event.dom.client.MouseDownHandler; 28 | import com.google.gwt.event.dom.client.MouseMoveEvent; 29 | import com.google.gwt.event.dom.client.MouseMoveHandler; 30 | import com.google.gwt.event.dom.client.MouseUpEvent; 31 | import com.google.gwt.event.dom.client.MouseUpHandler; 32 | import com.google.gwt.user.client.DOM; 33 | import com.google.gwt.user.client.Window; 34 | import com.google.gwt.user.client.ui.HTML; 35 | import com.vaadin.graph.shared.NodeProxy; 36 | import com.vaadin.graph.shared.NodeProxy.NodeState; 37 | 38 | /** 39 | * Presenter/controller for a node in a graph. 40 | * 41 | * @author Marlon Richert @ Vaadin 42 | */ 43 | class NodePresenter implements Controller, MouseDownHandler, MouseMoveHandler, MouseUpHandler { 44 | 45 | /** Set the CSS class name to allow styling. */ 46 | public static final String CSS_CLASSNAME = "node"; 47 | 48 | private final GraphExplorerConnector connector; 49 | private final Set inArcSets = new HashSet(); 50 | private final Set outArcSets = new HashSet(); 51 | 52 | private final HTML view = new HTML(); 53 | private final NodeAnimation animation = new NodeAnimation(); 54 | 55 | private NodeProxy model; 56 | protected int x; 57 | protected int y; 58 | private int width; 59 | private int height; 60 | 61 | private int dragStartX; 62 | private int dragStartY; 63 | private boolean mouseDown; 64 | private boolean dragging; 65 | 66 | NodePresenter(GraphExplorerConnector connector, NodeProxy model) { 67 | this.connector = connector; 68 | this.model = model; 69 | this.x = model.getX(); 70 | this.y = model.getY(); 71 | 72 | view.setTitle(model.getId()); 73 | Style style = view.getElement().getStyle(); 74 | style.setLeft(x, Unit.PX); 75 | style.setTop(y, Unit.PX); 76 | 77 | view.addDomHandler(this, MouseDownEvent.getType()); 78 | view.addDomHandler(this, MouseMoveEvent.getType()); 79 | view.addDomHandler(this, MouseUpEvent.getType()); 80 | 81 | connector.getWidget().add(view); 82 | } 83 | 84 | public void onMouseDown(MouseDownEvent event) { 85 | setMouseDown(true); 86 | updateCSS(); 87 | DOM.setCapture(view.getElement()); 88 | dragStartX = event.getX(); 89 | dragStartY = event.getY(); 90 | event.preventDefault(); 91 | } 92 | 93 | public void onMouseMove(MouseMoveEvent event) { 94 | if (isMouseDown()) { 95 | setDragging(true); 96 | updateCSS(); 97 | setX(event.getX() + getX() - dragStartX); 98 | setY(event.getY() + getY() - dragStartY); 99 | onUpdateInModel(); 100 | int clientX = event.getClientX(); 101 | int clientY = event.getClientY(); 102 | boolean outsideWindow = clientX < 0 || clientY < 0 103 | || clientX > Window.getClientWidth() 104 | || clientY > Window.getClientHeight(); 105 | if (outsideWindow) { 106 | connector.updateNode(model, getX(), getY()); 107 | setDragging(false); 108 | } 109 | } 110 | event.preventDefault(); 111 | } 112 | 113 | public void onMouseUp(MouseUpEvent event) { 114 | Element element = view.getElement(); 115 | if (!isDragging()) { 116 | updateCSS(); 117 | limitToBoundingBox(); 118 | if (NodeState.EXPANDED.equals(model.getState())) { 119 | model.setState(NodeState.COLLAPSED); 120 | for (NodePresenter neighbor : getNeighbors()) { 121 | boolean collapsed = NodeState.COLLAPSED.equals(neighbor.getModel().getState()); 122 | boolean leafNode = neighbor.degree() == 1; 123 | if (collapsed && leafNode) { 124 | connector.getGraph().removeNode(neighbor.getModel().getId()); 125 | } 126 | } 127 | } 128 | connector.toggle(this); 129 | } else { 130 | connector.updateNode(model, getX(), getY()); 131 | setDragging(false); 132 | } 133 | setMouseDown(false); 134 | DOM.releaseCapture(element); 135 | event.preventDefault(); 136 | } 137 | 138 | public void onRemoveFromModel() { 139 | for (String each : inArcSets) { 140 | connector.getGraph().removeArc(each); 141 | } 142 | for (String each : outArcSets) { 143 | connector.getGraph().removeArc(each); 144 | } 145 | view.removeFromParent(); 146 | } 147 | 148 | private void limitToBoundingBox() { 149 | Element element = view.getElement(); 150 | Style style = element.getStyle(); 151 | 152 | width = element.getOffsetWidth(); 153 | int xRadius = width / 2; 154 | int leftEdge = getX() - xRadius; 155 | leftEdge = limit(0, leftEdge, connector.getWidget().getOffsetWidth() - width); 156 | setX(leftEdge + xRadius); 157 | style.setLeft(leftEdge, Unit.PX); 158 | 159 | height = element.getOffsetHeight(); 160 | int yRadius = height / 2; 161 | int topEdge = getY() - yRadius; 162 | topEdge = limit(0, topEdge, connector.getWidget().getOffsetHeight() - height); 163 | setY(topEdge + yRadius); 164 | style.setTop(topEdge, Unit.PX); 165 | } 166 | 167 | public void onUpdateInModel() { 168 | StringBuilder html = new StringBuilder(); 169 | if ((model.getIconUrl() != null) && !model.getIconUrl().isEmpty()) { 170 | html.append("
"); 171 | html.append(connector.getConnection().getIcon(model.getIconUrl()).getElement().getString()); 172 | html.append("
").append(model.getContent()).append("
"); 173 | html.append("
"); 174 | } else { 175 | html.append("
").append(model.getContent()).append("
"); 176 | } 177 | view.setHTML(html.toString()); 178 | limitToBoundingBox(); 179 | updateCSS(); 180 | updateArcs(); 181 | } 182 | 183 | NodeProxy getModel() { 184 | return model; 185 | } 186 | 187 | void setModel(NodeProxy model) { 188 | this.model = model; 189 | } 190 | 191 | int getX() { 192 | return x; 193 | } 194 | 195 | void setX(int x) { 196 | this.x = x; 197 | } 198 | 199 | int getY() { 200 | return y; 201 | } 202 | 203 | void setY(int y) { 204 | this.y = y; 205 | } 206 | 207 | int getWidth() { 208 | return width; 209 | } 210 | 211 | int getHeight() { 212 | return height; 213 | } 214 | 215 | void addInArc(String arc) { 216 | inArcSets.add(arc); 217 | } 218 | 219 | void addOutArc(String arc) { 220 | outArcSets.add(arc); 221 | } 222 | 223 | private void updateCSS() { 224 | Element element = view.getElement(); 225 | element.setClassName(CSS_CLASSNAME); 226 | element.addClassName(model.getState().name().toLowerCase()); 227 | element.addClassName(model.getKind().name().toLowerCase()); 228 | if (model.getStyle() != null) { 229 | element.addClassName(model.getStyle()); 230 | } 231 | if (isMouseDown()) { 232 | element.addClassName("down"); 233 | } 234 | } 235 | 236 | private void updateArcs() { 237 | for (String each : inArcSets) { 238 | ArcPresenter arc = connector.getGraph().getArc(each); 239 | if (arc != null) { 240 | arc.onUpdateInModel(); 241 | } 242 | } 243 | for (String each : outArcSets) { 244 | ArcPresenter arc = connector.getGraph().getArc(each); 245 | if (arc != null) { 246 | arc.onUpdateInModel(); 247 | } 248 | } 249 | } 250 | 251 | private Collection getNeighbors() { 252 | Set neighbors = new HashSet(); 253 | for (String each : inArcSets) { 254 | ArcPresenter arc = connector.getGraph().getArc(each); 255 | if (arc != null) { 256 | NodePresenter node = arc.getFromNode(); 257 | if (node != null) { 258 | neighbors.add(node); 259 | } 260 | } 261 | } 262 | for (String each : outArcSets) { 263 | ArcPresenter arc = connector.getGraph().getArc(each); 264 | if (arc != null) { 265 | NodePresenter node = arc.getToNode(); 266 | if (node != null) { 267 | neighbors.add(node); 268 | } 269 | } 270 | } 271 | return neighbors; 272 | } 273 | 274 | private int degree() { 275 | int degree = 0; 276 | degree += inArcSets.size(); 277 | degree += outArcSets.size(); 278 | return degree; 279 | } 280 | 281 | void removeArc(String arc) { 282 | inArcSets.remove(arc); 283 | outArcSets.remove(arc); 284 | } 285 | 286 | /** Limits value to [min, max], so that min <= value <= max. */ 287 | private static int limit(int min, int value, int max) { 288 | return Math.min(Math.max(min, value), max); 289 | } 290 | 291 | void move(int x, int y) { 292 | animation.targetX = x; 293 | animation.targetY = y; 294 | animation.run(500); 295 | } 296 | 297 | private boolean isDragging() { 298 | return dragging; 299 | } 300 | 301 | private void setDragging(boolean dragging) { 302 | this.dragging = dragging; 303 | } 304 | 305 | private boolean isMouseDown() { 306 | return mouseDown; 307 | } 308 | 309 | private void setMouseDown(boolean mouseDown) { 310 | this.mouseDown = mouseDown; 311 | } 312 | 313 | private class NodeAnimation extends Animation { 314 | private int targetX = 0; 315 | private int targetY = 0; 316 | 317 | @Override 318 | protected void onUpdate(double progress) { 319 | if (progress > 1) { 320 | progress = 1; 321 | } 322 | setX((int) Math.round(progress * targetX + (1 - progress) * getX())); 323 | setY((int) Math.round(progress * targetY + (1 - progress) * getY())); 324 | onUpdateInModel(); 325 | } 326 | 327 | @Override 328 | protected void onCancel() { 329 | // do nothing 330 | } 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/GraphExplorer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2013 Vaadin Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0.html 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package com.vaadin.graph; 17 | 18 | import java.util.ArrayList; 19 | import java.util.HashSet; 20 | import java.util.Set; 21 | 22 | import com.vaadin.graph.layout.JungFRLayoutEngine; 23 | import com.vaadin.graph.shared.ArcProxy; 24 | import com.vaadin.graph.shared.GraphExplorerServerRpc; 25 | import com.vaadin.graph.shared.GraphExplorerState; 26 | import com.vaadin.graph.shared.NodeProxy; 27 | import com.vaadin.graph.shared.NodeProxy.NodeKind; 28 | import com.vaadin.graph.shared.NodeProxy.NodeState; 29 | import com.vaadin.ui.AbstractComponent; 30 | import com.vaadin.ui.Alignment; 31 | import com.vaadin.ui.Button; 32 | import com.vaadin.ui.Button.ClickEvent; 33 | import com.vaadin.ui.Button.ClickListener; 34 | import com.vaadin.ui.HorizontalLayout; 35 | import com.vaadin.ui.VerticalLayout; 36 | import com.vaadin.ui.Window; 37 | 38 | /** 39 | * UI component displaying graph elements 40 | * 41 | * @param type of node elements 42 | * @param type of arc elements 43 | */ 44 | public class GraphExplorer extends AbstractComponent implements GraphExplorerServerRpc { 45 | private static final long serialVersionUID = 1L; 46 | 47 | private static final String STYLE_MEMBER_SELECTOR = "member-selector"; 48 | 49 | private int clientHeight = 0; 50 | private int clientWidth = 0; 51 | private transient final GraphController controller; 52 | 53 | private final GraphRepository repository; 54 | private LayoutEngine layoutEngine; 55 | 56 | /** 57 | * Constructor (using JUNG library FR layout engine) 58 | * 59 | * @param repository provider of the graph data 60 | */ 61 | public GraphExplorer(GraphRepository repository) { 62 | this(repository, new GraphController(), new JungFRLayoutEngine()); 63 | } 64 | 65 | /** 66 | * Constructor 67 | * 68 | * @param repository provider of the graph data 69 | * @param controller controller component used to customize behavior 70 | * @param layoutEngine graph layout engine implementation 71 | */ 72 | public GraphExplorer(GraphRepository repository, GraphController controller, LayoutEngine layoutEngine) { 73 | super(); 74 | registerRpc(this, GraphExplorerServerRpc.class); 75 | 76 | this.repository = repository; 77 | this.controller = controller; 78 | this.layoutEngine = layoutEngine; 79 | 80 | NodeProxy homeNode = controller.load(repository.getHomeNode(), layoutEngine.getModel()); 81 | expand(homeNode); 82 | 83 | getState().nodes = new ArrayList(layoutEngine.getModel().getNodes()); 84 | getState().arcs = new ArrayList(layoutEngine.getModel().getArcs()); 85 | 86 | setSizeFull(); 87 | } 88 | 89 | public LayoutEngine getLayoutEngine() { 90 | return layoutEngine; 91 | } 92 | 93 | public void setLayoutEngine(LayoutEngine layoutEngine) { 94 | this.layoutEngine = layoutEngine; 95 | refreshLayout(new HashSet(), true, null); 96 | } 97 | 98 | protected GraphController getController() { 99 | return controller; 100 | } 101 | 102 | public GraphRepository getRepository() { 103 | return repository; 104 | } 105 | 106 | @Override 107 | protected GraphExplorerState getState() { 108 | return (GraphExplorerState) super.getState(); 109 | } 110 | 111 | protected void refreshLayout(Set lockedNodes, boolean lockExpanded, String removedId) { 112 | if (clientWidth > 0 && clientHeight > 0) { 113 | if (lockExpanded) { 114 | for (NodeProxy v : layoutEngine.getModel().getNodes()) { 115 | if (NodeState.EXPANDED.equals(v.getState())) { 116 | lockedNodes.add(v); 117 | } 118 | } 119 | } 120 | layoutEngine.layout(clientWidth, clientHeight, lockedNodes); 121 | getState().nodes = new ArrayList(layoutEngine.getModel().getNodes()); 122 | getState().arcs = new ArrayList(layoutEngine.getModel().getArcs()); 123 | getState().removedId = removedId; 124 | } 125 | } 126 | 127 | @Override 128 | public void updateNode(String nodeId, NodeState state, int x, int y) { 129 | NodeProxy node = layoutEngine.getModel().getNode(nodeId); 130 | if (node != null) { 131 | node.setState(state); 132 | node.setX(x); 133 | node.setY(y); 134 | Set lockedNodes = new HashSet(); 135 | lockedNodes.add(node); 136 | refreshLayout(lockedNodes, true, null); 137 | } 138 | } 139 | 140 | @Override 141 | public void clientResized(int clientWidth, int clientHeight) { 142 | if ((this.clientWidth == 0) && (this.clientHeight == 0)) { 143 | //initial layout - center the home node 144 | NodeProxy homeNode = layoutEngine.getModel().getNode(repository.getHomeNode().getId()); 145 | if (homeNode != null) { 146 | homeNode.setX(clientWidth / 2); 147 | homeNode.setY(clientHeight / 2); 148 | } 149 | } 150 | this.clientWidth = clientWidth; 151 | this.clientHeight = clientHeight; 152 | refreshLayout(new HashSet(), true, null); 153 | } 154 | 155 | @Override 156 | public void toggleNode(String nodeId) { 157 | Set lockedNodes = new HashSet(); 158 | boolean lockExpanded = true; 159 | NodeProxy toggledNode = layoutEngine.getModel().getNode(nodeId); 160 | if (toggledNode != null) { 161 | if (NodeKind.GROUP.equals(toggledNode.getKind())) { 162 | openMemberSelector(nodeId); 163 | } else { 164 | if (NodeState.COLLAPSED.equals(toggledNode.getState())) { 165 | expand(toggledNode); 166 | lockedNodes.add(toggledNode); 167 | lockExpanded = false; 168 | } else { 169 | collapse(toggledNode); 170 | } 171 | } 172 | } 173 | refreshLayout(lockedNodes, lockExpanded, null); 174 | } 175 | 176 | protected void expand(NodeProxy node) { 177 | controller.loadNeighbors(node, repository, layoutEngine.getModel()); 178 | node.setState(NodeState.EXPANDED); 179 | if ((clientWidth > 0) && (clientHeight > 0)) { 180 | node.setX(clientWidth / 2); 181 | node.setY(clientHeight / 2); 182 | } 183 | } 184 | 185 | protected void collapse(NodeProxy node) { 186 | node.setState(NodeState.COLLAPSED); 187 | for (NodeProxy neighbor : layoutEngine.getModel().getNeighbors(node)) { 188 | boolean collapsed = NodeState.COLLAPSED.equals(neighbor.getState()); 189 | boolean leafNode = layoutEngine.getModel().degree(neighbor) == 1; 190 | if (collapsed && leafNode) { 191 | layoutEngine.getModel().removeNode(neighbor); 192 | } 193 | } 194 | } 195 | 196 | protected void openMemberSelector(final String groupId) { 197 | 198 | VerticalLayout layout = new VerticalLayout(); 199 | layout.setMargin(true); 200 | layout.setSpacing(true); 201 | layout.setSizeFull(); 202 | 203 | final NodeSelector selector = controller.getMemberSelector(groupId, repository); 204 | layout.addComponent(selector); 205 | layout.setExpandRatio(selector, 1.0f); 206 | 207 | HorizontalLayout buttons = new HorizontalLayout(); 208 | Button showButton = new Button(getShowButtonCaption()); 209 | Button cancelButton = new Button(getCancelButtonCaption()); 210 | buttons.addComponent(cancelButton); 211 | buttons.addComponent(showButton); 212 | buttons.setWidth(100, Unit.PERCENTAGE); 213 | layout.addComponent(buttons); 214 | layout.setComponentAlignment(buttons, Alignment.BOTTOM_RIGHT); 215 | 216 | final Window dialog = createMemberSelectorWindow(); 217 | dialog.setContent(layout); 218 | getUI().addWindow(dialog); 219 | 220 | cancelButton.addClickListener(new ClickListener() { 221 | private static final long serialVersionUID = 1L; 222 | 223 | public void buttonClick(ClickEvent event) { 224 | getUI().removeWindow(dialog); 225 | } 226 | }); 227 | 228 | showButton.addClickListener(new ClickListener() { 229 | private static final long serialVersionUID = 1L; 230 | 231 | public void buttonClick(ClickEvent event) { 232 | getUI().removeWindow(dialog); 233 | controller.loadMembers(groupId, selector.getSelectedNodeIds(), repository, layoutEngine.getModel()); 234 | Set lockedNodes = new HashSet(); 235 | NodeProxy groupNode = layoutEngine.getModel().getNode(groupId); 236 | if (groupNode == null) { 237 | refreshLayout(lockedNodes, true, groupId); 238 | } else { 239 | lockedNodes.add(groupNode); 240 | refreshLayout(lockedNodes, true, null); 241 | } 242 | } 243 | }); 244 | } 245 | 246 | protected Window createMemberSelectorWindow() { 247 | final Window dialog = new Window(getMemberSelectorTitle()); 248 | dialog.setModal(true); 249 | dialog.setStyleName(STYLE_MEMBER_SELECTOR); 250 | dialog.setWidth(300, Unit.PIXELS); 251 | dialog.setHeight(400, Unit.PIXELS); 252 | return dialog; 253 | } 254 | 255 | protected String getMemberSelectorTitle() { 256 | return "Select nodes to show"; 257 | } 258 | 259 | protected String getShowButtonCaption() { 260 | return "Show"; 261 | } 262 | 263 | protected String getCancelButtonCaption() { 264 | return "Cancel"; 265 | } 266 | 267 | } 268 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /src/main/java/com/vaadin/graph/GraphController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2013 Vaadin Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0.html 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package com.vaadin.graph; 17 | 18 | import java.util.Collection; 19 | import java.util.HashMap; 20 | import java.util.HashSet; 21 | import java.util.Map; 22 | import java.util.Set; 23 | import java.util.StringTokenizer; 24 | import java.util.stream.Collectors; 25 | 26 | import com.vaadin.data.ValueProvider; 27 | import com.vaadin.data.provider.ListDataProvider; 28 | import com.vaadin.graph.shared.ArcProxy; 29 | import com.vaadin.graph.shared.NodeProxy; 30 | import com.vaadin.graph.shared.NodeProxy.NodeKind; 31 | import com.vaadin.graph.shared.NodeProxy.NodeState; 32 | import com.vaadin.server.ResourceReference; 33 | import com.vaadin.ui.CustomComponent; 34 | import com.vaadin.ui.Grid; 35 | import com.vaadin.ui.Grid.Column; 36 | import com.vaadin.ui.Grid.SelectionMode; 37 | import com.vaadin.ui.TextField; 38 | import com.vaadin.ui.components.grid.HeaderRow; 39 | 40 | /** 41 | * Graph visualization controller component used to control/override graph construction and visualization. 42 | * 43 | * Addon consumers are expected to subclass this controller to provide customized visualization. 44 | */ 45 | public class GraphController { 46 | 47 | private final Map> groups = new HashMap>(); 48 | 49 | protected ArcProxy createGroupRel(String arcId, String arcType, String fromId, String toId) { 50 | ArcProxy arc = new ArcProxy(arcId, fromId, toId); 51 | arc.setGroup(true); 52 | arc.setLabel(getGroupArcLabel(arcType)); 53 | return arc; 54 | } 55 | 56 | protected ArcProxy createArc(A arc, N tail, N head) { 57 | ArcProxy p = new ArcProxy(arc.getId(), tail.getId(), head.getId()); 58 | p.setLabel(getArcLabel(arc)); 59 | p.setStyle(getArcStyle(arc)); 60 | return p; 61 | } 62 | 63 | /** 64 | * @param node a graph node to get conent for 65 | * @return content (html snippet) to be displayed in the node 66 | */ 67 | protected String getNodeContent(Node node) { 68 | StringBuilder builder = new StringBuilder("" + node.getLabel() + ""); 69 | for (Map.Entry property : node.getProperties().entrySet()) { 70 | if (!GraphElement.PROPERTY_NAME_STYLE.equals(property.getKey())) { 71 | builder.append("
").append("").append(property.getKey()).append(": ").append(property.getValue()); 72 | } 73 | } 74 | return builder.toString(); 75 | } 76 | 77 | /** 78 | * @param node a graph node to get icon for 79 | * @return image url to be displayed in the node 80 | */ 81 | protected String getNodeIconUrl(Node node) { 82 | if (node.getIcon() != null) { 83 | return ResourceReference.create(node.getIcon(), null, null).getURL(); 84 | } else { 85 | return null; 86 | } 87 | } 88 | 89 | /** 90 | * @param node a graph node to get CSS style for 91 | * @return CSS style of the node 92 | */ 93 | protected String getNodeStyle(Node node) { 94 | Map props = node.getProperties(); 95 | if (props != null) { 96 | return (String) props.get(GraphElement.PROPERTY_NAME_STYLE); 97 | } 98 | return null; 99 | } 100 | 101 | /** 102 | * @param nrArcs number of arcs 103 | * @return content (html snippet) to be displayed in the "group" node 104 | */ 105 | protected String getGroupNodeContent(int nrArcs) { 106 | return nrArcs + "
nodes"; 107 | } 108 | 109 | /** 110 | * @param nrArcs number of arcs 111 | * @return image url to be displayed in the node 112 | */ 113 | protected String getGroupNodeIconUrl(int nrArcs) { 114 | return null; 115 | } 116 | 117 | /** 118 | * @param node a graph node to get label for 119 | * @return label (text) to represent node in node selector 120 | */ 121 | protected String getNodeLabel(Node node) { 122 | StringBuilder builder = new StringBuilder(node.getLabel() + "; "); 123 | for (Map.Entry property : node.getProperties().entrySet()) { 124 | if (!GraphElement.PROPERTY_NAME_STYLE.equals(property.getKey())) { 125 | builder.append(property.getKey()).append(": ").append(property.getValue()).append(", "); 126 | } 127 | } 128 | return builder.toString(); 129 | } 130 | 131 | /** 132 | * @param arc a graph arc to get label for 133 | * @return label (html snippet) to be displayed in the arc 134 | */ 135 | protected String getArcLabel(Arc arc) { 136 | StringBuilder builder = new StringBuilder("" + arc.getLabel() + ""); 137 | for (Map.Entry property : arc.getProperties().entrySet()) { 138 | if (!GraphElement.PROPERTY_NAME_STYLE.equals(property.getKey())) { 139 | builder.append("
").append("").append(property.getKey()).append(": ").append(property.getValue()); 140 | } 141 | } 142 | return builder.toString(); 143 | } 144 | 145 | /** 146 | * @param arc a graph arc to get CSS style for 147 | * @return CSS style of the arc 148 | */ 149 | protected String getArcStyle(Arc arc) { 150 | Map props = arc.getProperties(); 151 | if (props != null) { 152 | return (String) props.get(GraphElement.PROPERTY_NAME_STYLE); 153 | } 154 | return null; 155 | } 156 | 157 | protected String getGroupArcLabel(String arcType) { 158 | return "" + arcType.toLowerCase().replace('_', ' ') + ""; 159 | } 160 | 161 | public NodeSelector getMemberSelector(final String groupId, final GraphRepository repository) { 162 | 163 | @SuppressWarnings("serial") 164 | class SelectorUI extends CustomComponent implements NodeSelector { 165 | 166 | protected final Grid matchList; 167 | protected final ListDataProvider members; 168 | 169 | public SelectorUI() { 170 | matchList = new Grid<>(); 171 | matchList.setSizeFull(); 172 | matchList.setSelectionMode(SelectionMode.MULTI); 173 | Column column = matchList.addColumn((node) -> getNodeLabel(node)).setCaption(""); 174 | 175 | TextField stringMatcher = new TextField(); 176 | stringMatcher.setWidth(100, Unit.PERCENTAGE); 177 | 178 | HeaderRow header = matchList.getHeaderRow(0); 179 | header.getCell(column).setComponent(stringMatcher); 180 | 181 | StringTokenizer tokenizer = new StringTokenizer(groupId); 182 | String parentId = tokenizer.nextToken(); 183 | final N parent = repository.getNodeById(parentId); 184 | members = new ListDataProvider( 185 | groups.get(groupId).values().stream().map((arc) -> repository.getOpposite(parent, arc)).collect(Collectors.toList())); 186 | matchList.setDataProvider(members); 187 | 188 | stringMatcher.addValueChangeListener(event -> { 189 | members.setFilter(ValueProvider.identity(), node -> { 190 | if (node == null) { 191 | return false; 192 | } 193 | String nodeLabel = getNodeLabel(node).toLowerCase().replaceAll("\\s", ""); 194 | String filter = event.getValue().toLowerCase().replaceAll("\\s", ""); 195 | return nodeLabel.contains(filter); 196 | }); 197 | }); 198 | setCompositionRoot(matchList); 199 | setSizeFull(); 200 | } 201 | 202 | public Collection getSelectedNodeIds() { 203 | return matchList.getSelectedItems().stream().map((node) -> node.getId()).collect(Collectors.toList()); 204 | } 205 | 206 | } 207 | 208 | return new SelectorUI(); 209 | } 210 | 211 | protected NodeProxy load(Node node, LayoutEngineModel model) { 212 | String id = node.getId(); 213 | NodeProxy p = new NodeProxy(id); 214 | if (!model.addNode(p)) { 215 | p = model.getNode(id); 216 | } 217 | p.setContent(getNodeContent(node)); 218 | p.setIconUrl(getNodeIconUrl(node)); 219 | p.setStyle(getNodeStyle(node)); 220 | if ((p.getContent() == null) || p.getContent().isEmpty()) { 221 | p.setKind(NodeKind.EMPTY); 222 | } 223 | return p; 224 | } 225 | 226 | public Collection loadMembers(String groupId, Iterable memberIds, GraphRepository repository, LayoutEngineModel model) { 227 | StringTokenizer tokenizer = new StringTokenizer(groupId); 228 | final String parentId = tokenizer.nextToken(); 229 | final N parent = repository.getNodeById(parentId); 230 | Map groupArcs = groups.get(groupId); 231 | Collection loaded = new HashSet(); 232 | for (String id : memberIds) { 233 | A arc = groupArcs.remove(id); 234 | loaded.add(load(repository.getOpposite(parent, arc), model)); 235 | model.addArc(createArc(arc, repository.getTail(arc), repository.getHead(arc))); 236 | } 237 | NodeProxy group = model.getNode(groupId); 238 | if (groupArcs.size() > 0) { 239 | group.setContent(getGroupNodeContent(groupArcs.size())); 240 | group.setIconUrl(getGroupNodeIconUrl(groupArcs.size())); 241 | } else { 242 | model.removeNode(group); 243 | groups.remove(groupId); 244 | } 245 | return loaded; 246 | } 247 | 248 | public Collection loadNeighbors(NodeProxy n, GraphRepository repository, LayoutEngineModel model) { 249 | Set neighbors = new HashSet(); 250 | if (NodeState.EXPANDED.equals(n.getState())) { 251 | return neighbors; 252 | } 253 | n.setState(NodeState.EXPANDED); 254 | N node = repository.getNodeById(n.getId()); 255 | for (Arc.Direction dir : Arc.Direction.values()) { 256 | for (String label : repository.getArcLabels()) { 257 | Map arcs = new HashMap(); 258 | for (A arc : repository.getArcs(node, label, dir)) { 259 | arcs.put(repository.getOpposite(node, arc).getId(), arc); 260 | } 261 | int nrArcs = arcs.size(); 262 | if (nrArcs > getGroupThreshold()) { 263 | String groupId = node.getId() + ' ' + dir + ' ' + label; 264 | NodeProxy groupNode = new NodeProxy(groupId); 265 | if (!model.addNode(groupNode)) { 266 | groupNode = model.getNode(groupId); 267 | } 268 | groupNode.setKind(NodeKind.GROUP); 269 | groupNode.setContent(getGroupNodeContent(nrArcs)); 270 | groupNode.setIconUrl(getGroupNodeIconUrl(nrArcs)); 271 | switch (dir) { 272 | case INCOMING: 273 | model.addArc(createGroupRel(groupId, label, groupId, node.getId())); 274 | break; 275 | case OUTGOING: 276 | model.addArc(createGroupRel(groupId, label, node.getId(), groupId)); 277 | break; 278 | default: 279 | throw new AssertionError("unexpected direction " + dir); 280 | } 281 | neighbors.add(groupNode); 282 | groups.put(groupId, arcs); 283 | } else { 284 | for (A arc : arcs.values()) { 285 | String id = arc.getId(); 286 | if (model.getArc(id) == null) { 287 | Node other = repository.getOpposite(node, arc); 288 | NodeProxy vOther = load(other, model); 289 | model.addArc(createArc(arc, repository.getTail(arc), repository.getHead(arc))); 290 | neighbors.add(vOther); 291 | } 292 | } 293 | } 294 | } 295 | } 296 | return neighbors; 297 | } 298 | 299 | /** 300 | * @return number of arcs after which node will become a "group" node 301 | */ 302 | protected int getGroupThreshold() { 303 | return 10; 304 | } 305 | } 306 | --------------------------------------------------------------------------------