diff --git a/modules/MultiVizPlugin/README.md b/modules/MultiVizPlugin/README.md new file mode 100644 index 0000000000..5921a28cc4 --- /dev/null +++ b/modules/MultiVizPlugin/README.md @@ -0,0 +1,4 @@ +## MultiVizPlugin + +MultiViz: A Gephi Plugin for Scalable Visualization of Multi-Layer Networks +https://arxiv.org/abs/2209.03149 diff --git a/modules/MultiVizPlugin/pom.xml b/modules/MultiVizPlugin/pom.xml new file mode 100644 index 0000000000..a39d51da08 --- /dev/null +++ b/modules/MultiVizPlugin/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + gephi-plugin-parent + org.gephi + 0.9.3 + + + amrita + multiviz + 1.0.0 + nbm + + MultiVizPlugin + + + + + org.gephi + layout-api + jar + + + org.gephi + graph-api + + + org.gephi + layout-plugin + jar + + + org.gephi + ui-utils + + + org.gephi + filters-api + jar + + + org.gephi + appearance-api + jar + + + org.gephi + filters-plugin + jar + + + org.netbeans.api + org-openide-util-lookup + jar + + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + + MIT + J + amenp2cse20006@am.students.amrita.edu + + + + + + + + + + + + + oss-sonatype + oss-sonatype + https://oss.sonatype.org/content/repositories/snapshots/ + + true + + + + diff --git a/modules/MultiVizPlugin/src/main/java/helpers/AbstractProjectionPropertyEditor.java b/modules/MultiVizPlugin/src/main/java/helpers/AbstractProjectionPropertyEditor.java new file mode 100644 index 0000000000..012e88ab13 --- /dev/null +++ b/modules/MultiVizPlugin/src/main/java/helpers/AbstractProjectionPropertyEditor.java @@ -0,0 +1,88 @@ +/* +Copyright 2008 WebAtlas +Authors : Mathieu Bastian, Alexis Jacomy, Julian Bilcke +Website : http://www.gephi.org + +This file is part of Gephi. + +Gephi is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Gephi is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Gephi. If not, see . + */ +package helpers; + +import java.beans.PropertyEditorSupport; + +/** + * + * @author J + */ +abstract class AbstractProjectionPropertyEditor extends PropertyEditorSupport { + + protected AbstractProjectionPropertyEditor() { + this.defaultColumns = new String[]{"Node Label", "Edge Type"}; + } + + private String selectedColumn; + private final String[] defaultColumns; + + @Override + public String[] getTags() { + if (multiviz.MultiLayerVisualization.selectableColumns.isEmpty()){ + return defaultColumns; + } else { + return multiviz.MultiLayerVisualization.selectableColumns.toArray(String[]::new); + } + } + + @Override + public Object getValue() { + return selectedColumn; + } + + @Override + public void setValue(Object value) { + if(multiviz.MultiLayerVisualization.selectableColumns.isEmpty()){ + for (String gColumn : defaultColumns) { + if (gColumn.equals((String)value)) { + selectedColumn = gColumn; + break; + } + } + } else { + for(int i=0;i. + */ +package helpers; + +/** + * + * @author Alexis Jacomy + */ +public class CustomComboBoxEditor extends AbstractProjectionPropertyEditor { + public CustomComboBoxEditor() { + super(); + } +} + diff --git a/modules/MultiVizPlugin/src/main/java/helpers/LayoutAlgorithmProperty.java b/modules/MultiVizPlugin/src/main/java/helpers/LayoutAlgorithmProperty.java new file mode 100644 index 0000000000..5bb573000d --- /dev/null +++ b/modules/MultiVizPlugin/src/main/java/helpers/LayoutAlgorithmProperty.java @@ -0,0 +1,68 @@ +/* +Copyright 2008 WebAtlas +Authors : Mathieu Bastian, Mathieu Jacomy, Julian Bilcke +Website : http://www.gephi.org + +This file is part of Gephi. + +Gephi is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Gephi is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Gephi. If not, see . + */ +package helpers; + +import java.beans.PropertyEditorSupport; + +/** + * + * @author J + */ +public abstract class LayoutAlgorithmProperty extends PropertyEditorSupport{ + + private final String[] listOfAlgorithms; + private String selectedAlgorithm = "Linear Layout"; + + protected LayoutAlgorithmProperty(){ + this.listOfAlgorithms = new String[]{"Circle Layout", "Grid Layout", "Linear Layout", "Random Layout", "ForceAtlas2", "Fruchterman Reingold", "Yifan Hu"}; + } + + + @Override + public String[] getTags() { + return listOfAlgorithms; + } + + @Override + public Object getValue() { + return selectedAlgorithm; + } + + @Override + public void setValue(Object value) { + for (String algorithm : listOfAlgorithms) { + if(algorithm.equals((String) value)) { + selectedAlgorithm = algorithm; + break; + } + } + } + + @Override + public String getAsText() { + return getValue().toString(); + } + + @Override + public void setAsText(String text) throws IllegalArgumentException { + setValue(text); + } +} diff --git a/modules/MultiVizPlugin/src/main/java/helpers/LayoutDropDowns.java b/modules/MultiVizPlugin/src/main/java/helpers/LayoutDropDowns.java new file mode 100644 index 0000000000..455937ea6d --- /dev/null +++ b/modules/MultiVizPlugin/src/main/java/helpers/LayoutDropDowns.java @@ -0,0 +1,33 @@ +/* +Copyright 2008 WebAtlas +Authors : Mathieu Bastian, Mathieu Jacomy, Julian Bilcke +Website : http://www.gephi.org + +This file is part of Gephi. + +Gephi is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Gephi is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Gephi. If not, see . + */ + + +package helpers; + +/** + * + * @author J + */ +public class LayoutDropDowns extends LayoutAlgorithmProperty{ + public LayoutDropDowns() { + super(); + } +} diff --git a/modules/MultiVizPlugin/src/main/java/helpers/Point.java b/modules/MultiVizPlugin/src/main/java/helpers/Point.java new file mode 100644 index 0000000000..6dd4b76b39 --- /dev/null +++ b/modules/MultiVizPlugin/src/main/java/helpers/Point.java @@ -0,0 +1,39 @@ +package helpers; + +public class Point +{ + + public Point(float x, float y) + { + this.x = x; + this.y = y; + } + public Point() + { + this(0,0); + } + public float GetX() + { + return x; + } + public float GetY() + { + return y; + } + public void SetX(float x) + { + this.x = x; + } + public void SetY(float y) + { + this.y = y; + } + + public void Print() + { + System.out.print("(" + x + "," + y + ")"); + } + + private float x; + private float y; +} \ No newline at end of file diff --git a/modules/MultiVizPlugin/src/main/java/helpers/VizUtils.java b/modules/MultiVizPlugin/src/main/java/helpers/VizUtils.java new file mode 100644 index 0000000000..bd382e706b --- /dev/null +++ b/modules/MultiVizPlugin/src/main/java/helpers/VizUtils.java @@ -0,0 +1,50 @@ +package helpers; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.stream.Collectors; +import javax.swing.JOptionPane; +import org.gephi.graph.api.Column; +import org.gephi.graph.api.Edge; +import org.gephi.graph.api.Node; +import org.gephi.graph.api.Table; + +/** + * + * @author J + */ +public class VizUtils { + + public static HashMap sortByValue(HashMap hm) { + return hm.entrySet().stream().sorted((e1, e2) -> Integer.compare(e2.getValue(), e1.getValue())).collect(Collectors.toMap(HashMap.Entry::getKey, HashMap.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); + } + + public static String getLabel(Node node, Column column) { + return node.getAttribute(column).toString(); + } + + public static Column getLabel(Table table, String column) { + Column label = table.getColumn(column); + if (label == null) { + label = table.getColumn(column.replace(" ", "_")); + } + return label; + } + + public static String getLabel(Edge edge, Column column) { + return edge.getAttribute(column).toString(); + } + + public static void nodesRandom(Node[] nodes) { + for (Node node : nodes) { + float random = (float) ((0.01 + Math.random()) * 1000) - 500; + node.setX(random); + node.setY((float) ((0.01 + Math.random()) * 1000) - 500); + } + } + + public static void alert(String title, String message){ + JOptionPane.showMessageDialog(null, message, title, JOptionPane.WARNING_MESSAGE); + } + +} diff --git a/modules/MultiVizPlugin/src/main/java/multiviz/MLVBuilder.java b/modules/MultiVizPlugin/src/main/java/multiviz/MLVBuilder.java new file mode 100644 index 0000000000..a4b0791573 --- /dev/null +++ b/modules/MultiVizPlugin/src/main/java/multiviz/MLVBuilder.java @@ -0,0 +1,95 @@ +/* +Copyright 2008-2011 Gephi +Authors : Mathieu Bastian +Website : http://www.gephi.org +This file is part of Gephi. +DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +Copyright 2011 Gephi Consortium. All rights reserved. +The contents of this file are subject to the terms of either the GNU +General Public License Version 3 only ("GPL") or the Common +Development and Distribution License("CDDL") (collectively, the +"License"). You may not use this file except in compliance with the +License. You can obtain a copy of the License at +http://gephi.org/about/legal/license-notice/ +or /cddl-1.0.txt and /gpl-3.0.txt. See the License for the +specific language governing permissions and limitations under the +License. When distributing the software, include this License Header +Notice in each file and include the License files at +/cddl-1.0.txt and /gpl-3.0.txt. If applicable, add the following below the +License Header, with the fields enclosed by brackets [] replaced by +your own identifying information: +"Portions Copyrighted [year] [name of copyright owner]" +If you wish your version of this file to be governed by only the CDDL +or only the GPL Version 3, indicate your decision by adding +"[Contributor] elects to include this software in this distribution +under the [CDDL or GPL Version 3] license." If you do not indicate a +single choice of license, a recipient has the option to distribute +your version of this file under either the CDDL, the GPL Version 3 or +to extend the choice of license to its licensees as provided above. +However, if you add GPL Version 3 code and therefore, elected the GPL +Version 3 license, then the option applies only if the new code is +made subject to such option by the copyright holder. +Contributor(s): +Portions Copyrighted 2011 Gephi Consortium. + */ + +package multiviz; + + +import javax.swing.Icon; +import javax.swing.JPanel; +import org.gephi.layout.spi.Layout; +import org.gephi.layout.spi.LayoutBuilder; +import org.gephi.layout.spi.LayoutUI; +import multiviz.MultiLayerVisualization; +import org.openide.util.lookup.ServiceProvider; + + +/** + * + * @author J + */ +@ServiceProvider(service = LayoutBuilder.class) +public class MLVBuilder implements LayoutBuilder{ + + @Override + public String getName() { + return "Multi Layer Visualization"; + } + + @Override + public LayoutUI getUI() { + return new LayoutUI() { + @Override + public String getDescription() { + return ""; + } + + @Override + public Icon getIcon() { + return null; + } + + @Override + public JPanel getSimplePanel(Layout layout) { + return null; + } + + @Override + public int getQualityRank() { + return -1; + } + + @Override + public int getSpeedRank() { + return -1; + } + }; + } + + @Override + public Layout buildLayout() { + return new MultiLayerVisualization(this); + } + +} diff --git a/modules/MultiVizPlugin/src/main/java/multiviz/MultiLayerVisualization.java b/modules/MultiVizPlugin/src/main/java/multiviz/MultiLayerVisualization.java new file mode 100644 index 0000000000..4ebbf64dcf --- /dev/null +++ b/modules/MultiVizPlugin/src/main/java/multiviz/MultiLayerVisualization.java @@ -0,0 +1,600 @@ +package multiviz; + +import helpers.CustomComboBoxEditor; +import helpers.LayoutDropDowns; +import helpers.VizUtils; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.stream.Collectors; +import org.gephi.graph.api.Column; +import org.gephi.graph.api.Graph; +import org.gephi.graph.api.GraphModel; +import org.gephi.graph.api.Node; +import org.gephi.layout.spi.Layout; +import org.gephi.layout.spi.LayoutBuilder; +import org.gephi.layout.spi.LayoutProperty; + +import org.gephi.filters.api.FilterController; +import org.gephi.filters.plugin.partition.PartitionBuilder.NodePartitionFilter; +import org.openide.util.Lookup; +import org.gephi.appearance.api.AppearanceController; +import org.gephi.appearance.api.AppearanceModel; +import org.gephi.filters.plugin.partition.PartitionBuilder.PartitionFilter; +import org.gephi.graph.api.Edge; +import org.gephi.layout.plugin.force.StepDisplacement; +import org.gephi.layout.plugin.force.yifanHu.YifanHuLayout; +import org.gephi.layout.plugin.forceAtlas2.ForceAtlas2; +import org.gephi.layout.plugin.fruchterman.FruchtermanReingold; +import org.gephi.layout.plugin.random.RandomLayout; + +/** + * + * @author J + */ + +public class MultiLayerVisualization implements Layout { + + private final LayoutBuilder builder; + private GraphModel graphModel; + private boolean executing = false; + + private int layerDistance; + private int noOfIterations; + + private String selectedColumn = "Node Label"; + private String layoutAlgorithm; + + private boolean horizontalLayout; + private boolean is3DLayout; + private boolean sortLayers = false; + + private float layerArea; + private double gravity; + private double speed; + private boolean splitAsLevel; + public static List selectableColumns = new ArrayList<>(); + + private Column label; + Graph graph; + HashMap classes; + private String algorithmType = "BasicL"; + + MultiLayerVisualization(MLVBuilder mBuilder) { + this.builder = mBuilder; + } + + @Override + public void initAlgo() { + executing = true; + } + + @Override + public void setGraphModel(GraphModel graphModel) { + this.graphModel = graphModel; + graph = this.graphModel.getGraphVisible(); + selectableColumns = new ArrayList<>(); + for (int i = 0; i < graphModel.getNodeTable().countColumns(); i++) { + selectableColumns.add("Node " + graphModel.getNodeTable().getColumn(i).getTitle()); + } + for (int i = 0; i < graphModel.getEdgeTable().countColumns(); i++) { + selectableColumns.add("Edge " + graphModel.getEdgeTable().getColumn(i).getTitle()); + } + } + + @Override + public void goAlgo() { + + if (graphModel == null) { + return; + } + graph = graphModel.getGraphVisible(); + + Node[] nodes = graph.getNodes().toArray(); + Edge[] edges = graph.getEdges().toArray(); + + label = graphModel.getNodeTable().getColumn(0); + + FilterController filterController = Lookup.getDefault().lookup(FilterController.class); + AppearanceModel appearanceModel = Lookup.getDefault().lookup(AppearanceController.class).getModel(); + NodePartitionFilter partitionFilter = null; + + classes = new HashMap<>(); + if (selectedColumn.startsWith("Node ")) { + label = VizUtils.getLabel(graph.getModel().getNodeTable(), selectedColumn.substring(5)); + if(label != null) { + for (Node node : nodes) { + Object key = node.getAttribute(label); + if (classes.containsKey(key)) { + Integer value = classes.get(key); + classes.replace(key, value, 1 + value); + } else { + classes.put(key, 1); + } + } + partitionFilter = new NodePartitionFilter(appearanceModel.getNodePartition(label)); + } + } else if (selectedColumn.startsWith("Edge ")) { + + Column newColumn = VizUtils.getLabel(graph.getModel().getNodeTable(), "mviz_edge_" + selectedColumn.substring(5)); + label = VizUtils.getLabel(graph.getModel().getEdgeTable(), selectedColumn.substring(5)); + + if (label != null) { + + if (newColumn == null) { + newColumn = graphModel.getNodeTable().addColumn("mviz_edge_" + label.getTitle(), String.class); + } + + List edgeNodes = new ArrayList<>(); + for (Edge edge : edges) { + + Object key = edge.getAttribute(label); + + if (edgeNodes.contains(edge.getSource())) { + } else { + edgeNodes.add(edge.getSource()); + edge.getSource().setAttribute(newColumn, String.valueOf(key)); + } + if (edgeNodes.contains(edge.getTarget())) { + } else { + edgeNodes.add(edge.getTarget()); + edge.getTarget().setAttribute(newColumn, String.valueOf(key)); + } + } + + /** + * If same label/nodes exists in multiple layers then node should + * only be allowed to primary layer + */ + for (Node node : nodes) { + if (edgeNodes.contains(node)) { + } else { + node.setAttribute(newColumn, "mviz_outlier_nodes"); + } + + Object key = node.getAttribute(newColumn); + if (classes.containsKey(key)) { + Integer value = classes.get(key); + classes.replace(key, value, 1 + value); + } else { + classes.put(key, 1); + } + } + partitionFilter = new NodePartitionFilter(appearanceModel.getNodePartition(newColumn)); + } + } + + if (partitionFilter != null) { + + if (sortLayers) { + classes = VizUtils.sortByValue(classes); + } + + VizUtils.nodesRandom(nodes); + + if ("ForceD".equals(getAlgorithmType())) { + if (!isSplitAsLevel()) { + Node farthestNode = null; + for (Object layer : classes.keySet()) { + Pair pair = getSubset(partitionFilter, filterController, layer); + Node[] subset = (Node[]) pair.getNodes(); + if (subset != null && subset.length > 0) { + Node biggestNode = (Node) pair.getBiggestNode(); + drawForceDirectedLayouts(graphModel); + splitLayer(subset, farthestNode, biggestNode); + farthestNode = Arrays.stream(subset).max(Comparator.comparing(v -> v.y())).get(); + } + } + graphModel.setVisibleView(null); + } else { + drawForceDirectedLayouts(graphModel); + Node farthestNode = null; + for (Object layer : classes.keySet()) { + Pair pair = getSubset(partitionFilter, filterController, layer); + Node[] subset = (Node[]) pair.getNodes(); + if(subset != null) { + splitLayer(subset, farthestNode, (Node) pair.getBiggestNode()); + farthestNode = Arrays.stream(subset).max(Comparator.comparing(v -> v.y())).get(); + graphModel.setVisibleView(null); + } + } + } + } else { + Node initialNode = nodes[0]; + Node farthestNode = null; + int iter = 0; + for (Object layer : classes.keySet()) { + Pair pair = getSubset(partitionFilter, filterController, layer); + Node[] subset = (Node[]) pair.getNodes(); + if (subset != null && subset.length > 0) { + Node biggestNode = (Node) pair.getBiggestNode(); + if (iter == 0) { + initialNode = subset[0]; + } + if ("Linear Layout".equals(getLayoutAlgorithm())) { + linearLayout(subset, initialNode); + } else if ("Grid Layout".equals(getLayoutAlgorithm())) { + gridLayout(subset, biggestNode, initialNode); + } else if ("Circle Layout".equals(getLayoutAlgorithm())) { + circleLayout(subset, initialNode); + } else if ("Random Layout".equals(getLayoutAlgorithm())) { + randomLayout(graphModel, subset, biggestNode); + } + splitLayer(subset, farthestNode, biggestNode); + farthestNode = Arrays.stream(subset).max(Comparator.comparing(v -> v.y())).get(); + } + iter += 1; + } + graphModel.setVisibleView(null); + } + } else { + } + + graph.readLock(); + if (is3DLayout()) { + for (Node node : nodes) { + if (!node.isFixed()) { + double theta = 65 * Math.PI / 180; + node.setY((float) (node.y() * Math.cos(theta) - node.z() * Math.sin(theta))); + node.setZ((float) (Math.random() * 0.01)); + } + } + } + + if (isHorizontalLayout()) { + double theta = 270 * Math.PI / 180; + float px = 0; + float py = 0; + for (Node u : nodes) { + if (!u.isFixed()) { + float dx = u.x() - px; + float dy = u.y() - py; + u.setX((float) (px + dx * Math.cos(theta) - dy * Math.sin(theta))); + u.setY((float) (py + dy * Math.cos(theta) + dx * Math.sin(theta))); + } + } + } + graph.readUnlock(); + endAlgo(); + } + + @Override + public boolean canAlgo() { + return executing; + } + + @Override + public void endAlgo() { + executing = false; + try { + graph.readLock(); + for (Node node : graph.getNodes()) { + node.setLayoutData(null); + } + } finally { + graph.readUnlockAll(); + } + } + + @Override + public LayoutProperty[] getProperties() { + List properties = new ArrayList<>(); + final String BASIC = "Basic Features"; + final String FORCED = "Force Directed Features"; + try { + properties.add(LayoutProperty.createProperty(this, Integer.class, "Iterations", FORCED, "Number of iterations", "getNoOfIterations", "setNoOfIterations")); + properties.add(LayoutProperty.createProperty(this, Float.class, "Set Area", FORCED, "Set area for a layer", "getArea", "setArea")); + properties.add(LayoutProperty.createProperty(this, Boolean.class, "Split by level", FORCED, "If selected, Layout Algorithm will be applied on the whole network before splitting in to layers, If not Algorithm will be applied on each layer.", "isSplitAsLevel", "setSplitAsLevel")); + properties.add(LayoutProperty.createProperty(this, Double.class, "Set Speed", FORCED, "Set Speed", "getSpeed", "setSpeed")); + properties.add(LayoutProperty.createProperty(this, Double.class, "Set Gravity", FORCED, "Set gravity to prevent nodes going off the screen in force directed layouts", "getGravity", "setGravity")); + properties.add(LayoutProperty.createProperty(this, Boolean.class, "Horizontal Layout", BASIC, "If selected, layers will be placed next to each other, instad of stacking top of one another", "isHorizontalLayout", "setHorizontalLayout")); + properties.add(LayoutProperty.createProperty(this, Boolean.class, "Set as 3D", BASIC, "Set nodes in 3 Dimension", "is3DLayout", "set3DLayout")); + properties.add(LayoutProperty.createProperty(this, Boolean.class, "Sort Layers", BASIC, "If selected, layers will sorted (layers with the least number of nodes will be placed at bottom/left)", "isSorted", "setSorted")); + properties.add(LayoutProperty.createProperty(this, Integer.class, "Layer Distance", BASIC, "Distance between two layers", "getLayerDistance", "setLayerDistance")); + properties.add(LayoutProperty.createProperty(this, String.class, "Select Layer", BASIC, "Select the feature which is to be considered as a layer", "getSelectedColumn", "setSelectedColumn", CustomComboBoxEditor.class)); + properties.add(LayoutProperty.createProperty(this, String.class, "Layout Algorithm", BASIC, "Select the layout algorithm which is to be applied to a layer", "getLayoutAlgorithm", "setLayoutAlgorithm", LayoutDropDowns.class)); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + + return properties.toArray(LayoutProperty[]::new); + } + + @Override + public void resetPropertiesValues() { + layerDistance = 400; + gravity = 10d; + layerArea = 10000f; + noOfIterations = 100; + horizontalLayout = false; + is3DLayout = false; + splitAsLevel = false; + speed = 1.0; + } + + @Override + public LayoutBuilder getBuilder() { + return builder; + } + + public Integer getLayerDistance() { + return layerDistance; + } + + public void setLayerDistance(Integer layerDistance) { + this.layerDistance = layerDistance; + } + + public Integer getNoOfIterations() { + return noOfIterations; + } + + public void setNoOfIterations(Integer noOfIterations) { + this.noOfIterations = noOfIterations; + } + + public String getSelectedColumn() { + return this.selectedColumn; + } + + public void setSelectedColumn(String selectedColumn) { + this.selectedColumn = selectedColumn; + } + + public String getLayoutAlgorithm() { + return layoutAlgorithm; + } + + public void setLayoutAlgorithm(String layoutAlgorithm) { + this.layoutAlgorithm = layoutAlgorithm; + if (!"ForceAtlas2".equals(layoutAlgorithm) && !"Fruchterman Reingold".equals(layoutAlgorithm) && !"Yifan Hu".equals(layoutAlgorithm)) { + setAlgorithmType("BasicL"); + } else { + setAlgorithmType("ForceD"); + } + } + + public Boolean isHorizontalLayout() { + return horizontalLayout; + } + + public void setHorizontalLayout(Boolean horizontalLayout) { + this.horizontalLayout = horizontalLayout; + } + + public Boolean is3DLayout() { + return is3DLayout; + } + + public void set3DLayout(Boolean is3DLayout) { + this.is3DLayout = is3DLayout; + } + + public Float getArea() { + return layerArea; + } + + public void setArea(Float layerArea) { + this.layerArea = layerArea; + } + + public Double getGravity() { + return gravity; + } + + public void setGravity(Double gravity) { + this.gravity = gravity; + } + + public Double getSpeed() { + return speed; + } + + public void setSpeed(Double speed) { + this.speed = speed; + } + + public Boolean isSplitAsLevel() { + return splitAsLevel; + } + + public void setSplitAsLevel(Boolean splitAsLevel) { + this.splitAsLevel = splitAsLevel; + } + + public Boolean isSorted() { + return sortLayers; + } + + public void setSorted(Boolean sortLayers) { + this.sortLayers = sortLayers; + } + + public String getAlgorithmType() { + return algorithmType; + } + + public void setAlgorithmType(String algorithmType) { + this.algorithmType = algorithmType; + } + + private void linearLayout(Node[] subset, Node initialNode) { + double distancex = 0; + int index = 0; + for (Node node : subset) { + if (index > 0) { + distancex += subset[index - 1].size() + (node.size() * 2) + (node.getTextProperties().getWidth() + 20); + double ry = Math.random() * ((subset[index - 1].size() + subset[index - 1].getTextProperties().getHeight() + node.size() + node.getTextProperties().getSize()) - 1 + 1) + 1; + node.setX(initialNode.x() + (float) distancex); + node.setY(node.y() + (float) ry); + } + index++; + } + } + + private void gridLayout(Node[] subset, Node biggestNode, Node initialNode) { + int rows = (int) Math.round(Math.sqrt(subset.length)) + 1; + int cols = (int) Math.round(Math.sqrt(subset.length)) + 1; + double layerSize = ((biggestNode.size() + biggestNode.getTextProperties().getSize()) * subset.length) * 1.2f; + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols && (i * rows + j) < subset.length; j++) { + Node node = subset[i * rows + j]; + double nx = (-layerSize / 2f) + ((float) j / cols) * layerSize; + double ny = (layerSize / 2f) - ((float) i / rows) * layerSize; + double tx = nx; + double ty = (initialNode.y() + (initialNode.y() + (ny - initialNode.y()))); + if (i == 0 && j == 0) { + tx = ((initialNode.x() + (nx - initialNode.x())) + (0)); + } else { + tx = ((initialNode.x() + (nx - initialNode.x())) + (10 * j)); + } + node.setX((float) tx); + node.setY((float) ty); + node.setZ(initialNode.z()); + } + } + } + + private void circleLayout(Node[] subset, Node initialNode) { + if (subset.length == 1) { + subset[0].setX(initialNode.x()); + subset[0].setY(initialNode.y()); + subset[0].setZ(initialNode.z()); + } else { + double circumference = 0; + for (Node node : subset) { + circumference += (node.size() * 2) + node.getTextProperties().getWidth(); + } + circumference = circumference * 1.2f; + + double diameter = circumference / Math.PI; + double theta = (2 * Math.PI) / circumference; + + double tempTheta = 0; + double nodeSize = 0; + + for (Node node : subset) { + if (!node.isFixed()) { + nodeSize = node.size() + node.getTextProperties().getWidth() / 2; + double arc = nodeSize * 1.2f * theta; + float dx = (float) (diameter * (Math.cos((tempTheta + arc) + (Math.PI / 2)))); + float dy = (float) (diameter * (Math.sin((tempTheta + arc) + (Math.PI / 2)))); + tempTheta += nodeSize * 2 * theta * 1.2f; + node.setX(initialNode.x() + dx); + node.setY(initialNode.y() + dy); + node.setZ(initialNode.z()); + } + } + } + } + + private void randomLayout(GraphModel graphModel, Node[] subset, Node biggestNode) { + float layerSpace = (biggestNode.size() + biggestNode.getTextProperties().getSize()) * subset.length; + var randomLayout = new RandomLayout(builder, layerSpace); + randomLayout.setGraphModel(graphModel); + randomLayout.initAlgo(); + randomLayout.goAlgo(); + randomLayout.endAlgo(); + } + + private void forceAtlas2(GraphModel graphModel) { + var forceAtlas2 = new ForceAtlas2(null); + forceAtlas2.setGraphModel(graphModel); + forceAtlas2.setJitterTolerance(1.0); + forceAtlas2.setBarnesHutOptimize(true); + forceAtlas2.setBarnesHutTheta(1.2); + forceAtlas2.setOutboundAttractionDistribution(true); + //forceAtlas2.setThreadsCount(1); //1 to 7 + forceAtlas2.setGravity(gravity); + forceAtlas2.setAdjustSizes(true); + forceAtlas2.initAlgo(); + for (int i = 0; i < getNoOfIterations(); i++) { + forceAtlas2.goAlgo(); + } + } + + private void fruchterman(GraphModel graphModel) { + var fruchtermanReingold = new FruchtermanReingold(getBuilder()); + fruchtermanReingold.setGraphModel(graphModel); + fruchtermanReingold.setArea(getArea()); + fruchtermanReingold.setGravity(getGravity()); + fruchtermanReingold.setSpeed(getSpeed()); + fruchtermanReingold.initAlgo(); + for (int i = 0; i < getNoOfIterations(); i++) { + fruchtermanReingold.goAlgo(); + } + } + + private void yifanHuNormal(GraphModel graphModel) { + var yifanHu = new YifanHuLayout(getBuilder(), new StepDisplacement(1f)); + yifanHu.setGraphModel(graphModel); + yifanHu.initAlgo(); + for (int i = 0; i < getNoOfIterations(); i++) { + yifanHu.goAlgo(); + } + } + + private void drawForceDirectedLayouts(GraphModel graphModel) { + if ("ForceAtlas2".equals(getLayoutAlgorithm())) { + forceAtlas2(graphModel); + } else if ("Fruchterman Reingold".equals(getLayoutAlgorithm())) { + fruchterman(graphModel); + } else if ("Yifan Hu".equals(getLayoutAlgorithm())) { + yifanHuNormal(graphModel); + } + } + + class Pair { + + private U mGraphModel; + private V mNodes; + private W mBiggestNode; + + public Pair(U mGraphModel, V mNodes, W mBiggestNode) { + this.mGraphModel = mGraphModel; + this.mNodes = mNodes; + this.mBiggestNode = mBiggestNode; + } + + public W getBiggestNode() { + return mBiggestNode; + } + + public U getGraphModel() { + return mGraphModel; + } + + public V getNodes() { + return mNodes; + } + } + + private Pair getSubset(PartitionFilter partitionFilter, FilterController filterController, Object value) { + try { + partitionFilter.unselectAll(); + partitionFilter.addPart(value); + graphModel.setVisibleView(filterController.filter(filterController.createQuery(partitionFilter))); + Node[] subset = graphModel.getGraphVisible().getNodes().toArray(); + Node biggestNode = null; + if (subset.length > 0) { + biggestNode = Arrays.stream(subset).max(Comparator.comparing(node -> node.size())).orElse(subset[0]); + } + return new Pair(graphModel, subset, biggestNode); + } catch(Exception e) { + return new Pair(graphModel, null, null); + } + } + + private void splitLayer(Node[] subset, Node farthestNode, Node biggestNode) { + for (Node node : subset) { + float y = node.y(); + if (farthestNode != null) { + y = y + (farthestNode.y() + farthestNode.size() + farthestNode.getTextProperties().getHeight()) + getLayerDistance() + biggestNode.size(); + } + node.setY(y); + } + } +} diff --git a/modules/MultiVizPlugin/src/main/nbm/manifest.mf b/modules/MultiVizPlugin/src/main/nbm/manifest.mf new file mode 100644 index 0000000000..5f33210c20 --- /dev/null +++ b/modules/MultiVizPlugin/src/main/nbm/manifest.mf @@ -0,0 +1,5 @@ +Manifest-Version: 1.0 +OpenIDE-Module-Name: MultiVizPlugin +OpenIDE-Module-Short-Description: multi layer visualization +OpenIDE-Module-Long-Description: visualizes multilayer complex networks +OpenIDE-Module-Display-Category: Layout diff --git a/pom.xml b/pom.xml index 2403d9e1f5..6643c7f785 100644 --- a/pom.xml +++ b/pom.xml @@ -12,6 +12,7 @@ + modules/MultiVizPlugin @@ -218,5 +219,4 @@ - - + \ No newline at end of file