/* Copyright 2008-2010 Gephi Authors : Mathieu Bastian <mathieu.bastian@gephi.org> 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 Affero 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with Gephi. If not, see <http://www.gnu.org/licenses/>. */ package org.gephi.io.processor.plugin; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.gephi.data.attributes.api.AttributeColumn; import org.gephi.data.attributes.api.AttributeController; import org.gephi.data.attributes.api.AttributeOrigin; import org.gephi.data.attributes.api.AttributeRow; import org.gephi.data.attributes.api.AttributeTable; import org.gephi.data.attributes.api.AttributeType; import org.gephi.data.attributes.api.AttributeValue; import org.gephi.data.attributes.type.DynamicType; import org.gephi.data.attributes.type.Interval; import org.gephi.data.attributes.type.TimeInterval; import org.gephi.data.attributes.type.TypeConvertor; import org.gephi.data.properties.PropertiesColumn; import org.gephi.dynamic.DynamicUtilities; import org.gephi.dynamic.api.DynamicController; import org.gephi.dynamic.api.DynamicModel; import org.gephi.graph.api.Edge; import org.gephi.graph.api.GraphController; import org.gephi.graph.api.GraphFactory; import org.gephi.graph.api.GraphModel; import org.gephi.graph.api.HierarchicalGraph; import org.gephi.graph.api.Node; import org.gephi.io.importer.api.EdgeDraft.EdgeType; import org.gephi.io.importer.api.EdgeDraftGetter; import org.gephi.io.importer.api.NodeDraftGetter; import org.gephi.io.processor.spi.Processor; import org.gephi.project.api.ProjectController; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.lookup.ServiceProvider; /** * Processor 'Time Frame' that uses its 'date' parameter to associate the content * of the container to a particular point in time. It locates and modify existing * elements by appending new time intervals. It also create dynamic attribute * values, as well as a dynamic weight. * @author Mathieu Bastian */ @ServiceProvider(service = Processor.class) public class DynamicProcessor extends AbstractProcessor implements Processor { //Settings private boolean dateMode = true; private String date = ""; private boolean labelmatching = true; //Variable private double point; public void process() { ProjectController pc = Lookup.getDefault().lookup(ProjectController.class); //Workspace if (workspace == null) { workspace = pc.getCurrentWorkspace(); if (workspace == null) { //Append mode but no workspace workspace = pc.newWorkspace(pc.getCurrentProject()); pc.openWorkspace(workspace); } } if (container.getSource() != null) { pc.setSource(workspace, container.getSource()); } //Architecture GraphModel graphModel = Lookup.getDefault().lookup(GraphController.class).getModel(); HierarchicalGraph graph = null; switch (container.getEdgeDefault()) { case DIRECTED: graph = graphModel.getHierarchicalDirectedGraph(); break; case UNDIRECTED: graph = graphModel.getHierarchicalUndirectedGraph(); break; case MIXED: graph = graphModel.getHierarchicalMixedGraph(); break; default: graph = graphModel.getHierarchicalMixedGraph(); break; } GraphFactory factory = graphModel.factory(); //Attributes - Manually merge models with new dynamic cols attributeModel = Lookup.getDefault().lookup(AttributeController.class).getModel(); AttributeTable nodeTable = container.getAttributeModel().getNodeTable(); AttributeTable edgeTable = container.getAttributeModel().getEdgeTable(); for (AttributeColumn column : nodeTable.getColumns()) { AttributeColumn existingCol = attributeModel.getNodeTable().getColumn(column.getTitle()); if (existingCol == null) { if (!column.getOrigin().equals(AttributeOrigin.PROPERTY)) { AttributeType dynamicType = TypeConvertor.getDynamicType(column.getType()); if (dynamicType != null && !column.getType().isDynamicType()) { attributeModel.getNodeTable().addColumn(column.getId(), column.getTitle(), dynamicType, column.getOrigin(), null); } else { attributeModel.getNodeTable().addColumn(column.getId(), column.getTitle(), column.getType(), column.getOrigin(), column.getDefaultValue()); } } } } for (AttributeColumn column : edgeTable.getColumns()) { AttributeColumn existingCol = attributeModel.getEdgeTable().getColumn(column.getTitle()); if (existingCol == null) { if (!column.getOrigin().equals(AttributeOrigin.PROPERTY)) { AttributeType dynamicType = TypeConvertor.getDynamicType(column.getType()); if (dynamicType != null && !column.getType().isDynamicType()) { attributeModel.getEdgeTable().addColumn(column.getId(), column.getTitle(), dynamicType, column.getOrigin(), null); } else { attributeModel.getEdgeTable().addColumn(column.getId(), column.getTitle(), column.getType(), column.getOrigin(), column.getDefaultValue()); } } } else if (PropertiesColumn.EDGE_WEIGHT.getId().equals(column.getId()) && !existingCol.getType().isDynamicType()) { attributeModel.getEdgeTable().replaceColumn(attributeModel.getEdgeTable().getColumn(PropertiesColumn.EDGE_WEIGHT.getIndex()), PropertiesColumn.EDGE_WEIGHT.getId(), PropertiesColumn.EDGE_WEIGHT.getTitle(), AttributeType.DYNAMIC_FLOAT, AttributeOrigin.PROPERTY, null); } } //Get Time Interval Column AttributeColumn nodeDynamicColumn = attributeModel.getNodeTable().getColumn(DynamicModel.TIMEINTERVAL_COLUMN); AttributeColumn edgeDynamicColumn = attributeModel.getEdgeTable().getColumn(DynamicModel.TIMEINTERVAL_COLUMN); if (nodeDynamicColumn == null) { nodeDynamicColumn = attributeModel.getNodeTable().addColumn(DynamicModel.TIMEINTERVAL_COLUMN, "Time Interval", AttributeType.TIME_INTERVAL, AttributeOrigin.PROPERTY, null); } if (edgeDynamicColumn == null) { edgeDynamicColumn = attributeModel.getEdgeTable().addColumn(DynamicModel.TIMEINTERVAL_COLUMN, "Time Interval", AttributeType.TIME_INTERVAL, AttributeOrigin.PROPERTY, null); } //Get Time stamp if (dateMode) { try { point = DynamicUtilities.getDoubleFromXMLDateString(date); } catch (Exception e) { throw new RuntimeException("The entered date can't be parsed"); } } else { point = Double.parseDouble(date); } DynamicController dynamicController = Lookup.getDefault().lookup(DynamicController.class); dynamicController.setTimeFormat(dateMode ? DynamicModel.TimeFormat.DATE : DynamicModel.TimeFormat.DOUBLE); //Index existing graph Map<String, Node> map = new HashMap<String, Node>(); for (Node n : graph.getNodes()) { String id = n.getNodeData().getId(); if (id != null && !labelmatching && !id.equalsIgnoreCase(String.valueOf(n.getId()))) { map.put(id, n); } if (n.getNodeData().getLabel() != null && !n.getNodeData().getLabel().isEmpty() && labelmatching) { map.put(n.getNodeData().getLabel(), n); } } //Create all nodes Set<Node> nodesInDraft = new HashSet<Node>(); int newNodeCount = 0; for (NodeDraftGetter draftNode : container.getNodes()) { Node node = null; String id = draftNode.getId(); String label = draftNode.getLabel(); if (!draftNode.isAutoId() && id != null && map.get(id) != null && !labelmatching) { node = map.get(id); } else if (label != null && map.get(label) != null && labelmatching) { node = map.get(label); } TimeInterval timeInterval = null; if (node == null) { //Node is new node = factory.newNode(draftNode.isAutoId() ? null : draftNode.getId()); flushToNode(draftNode, node); draftNode.setNode(node); newNodeCount++; } else { timeInterval = (TimeInterval) node.getNodeData().getAttributes().getValue(nodeDynamicColumn.getIndex()); flushToNodeAttributes(draftNode, node); draftNode.setNode(node); } nodesInDraft.add(node); //Add Point node.getNodeData().getAttributes().setValue(nodeDynamicColumn.getIndex(), addPoint(timeInterval, point)); } //Push nodes in data structure for (NodeDraftGetter draftNode : container.getNodes()) { Node n = draftNode.getNode(); NodeDraftGetter[] parents = draftNode.getParents(); if (parents != null) { for (int i = 0; i < parents.length; i++) { Node parent = parents[i].getNode(); graph.addNode(n, parent); } } else { graph.addNode(n); } } //Remove point from all nodes not in draft for (Node node : graph.getNodes()) { if (!nodesInDraft.contains(node)) { TimeInterval timeInterval = (TimeInterval) node.getNodeData().getAttributes().getValue(nodeDynamicColumn.getIndex()); node.getNodeData().getAttributes().setValue(nodeDynamicColumn.getIndex(), removePoint(timeInterval, point)); } } //Create all edges and push to data structure Set<Edge> edgesInDraft = new HashSet<Edge>(); int newEdgeCount = 0; for (EdgeDraftGetter draftEdge : container.getEdges()) { Node source = draftEdge.getSource().getNode(); Node target = draftEdge.getTarget().getNode(); Edge edge = graph.getEdge(source, target); TimeInterval timeInterval = null; if (edge == null) { //Edge is new switch (container.getEdgeDefault()) { case DIRECTED: edge = factory.newEdge(draftEdge.isAutoId() ? null : draftEdge.getId(), source, target, draftEdge.getWeight(), true); break; case UNDIRECTED: edge = factory.newEdge(draftEdge.isAutoId() ? null : draftEdge.getId(), source, target, draftEdge.getWeight(), false); break; case MIXED: edge = factory.newEdge(draftEdge.isAutoId() ? null : draftEdge.getId(), source, target, draftEdge.getWeight(), draftEdge.getType().equals(EdgeType.UNDIRECTED) ? false : true); break; } newEdgeCount++; graph.addEdge(edge); flushToEdge(draftEdge, edge); } else { timeInterval = (TimeInterval) edge.getEdgeData().getAttributes().getValue(edgeDynamicColumn.getIndex()); flushToEdgeAttributes(draftEdge, edge); } edgesInDraft.add(edge); //Add Point edge.getEdgeData().getAttributes().setValue(edgeDynamicColumn.getIndex(), addPoint(timeInterval, point)); } //Remove point from all edges not in draft for (Edge edge : graph.getEdges()) { if (!edgesInDraft.contains(edge)) { TimeInterval timeInterval = (TimeInterval) edge.getEdgeData().getAttributes().getValue(edgeDynamicColumn.getIndex()); edge.getEdgeData().getAttributes().setValue(edgeDynamicColumn.getIndex(), removePoint(timeInterval, point)); } } System.out.println("# New Nodes loaded: " + newNodeCount + "\n# New Edges loaded: " + newEdgeCount); workspace = null; } @Override protected void flushToNodeAttributes(NodeDraftGetter nodeDraft, Node node) { if (node.getNodeData().getAttributes() != null) { AttributeRow row = (AttributeRow) node.getNodeData().getAttributes(); for (int i = 0; i < row.countValues(); i++) { Object val = row.getValue(i); AttributeColumn col = row.getColumnAt(i); Object draftValue = nodeDraft.getAttributeRow().getValue(col.getId()); if (col.getType().isDynamicType()) { if (draftValue == null && val != null) { removePoint(col.getType(), (DynamicType) val, point); } else if (draftValue != null) { DynamicType dynamicValue = addPoint(col.getType(), (DynamicType) val, draftValue, point); row.setValue(col.getIndex(), dynamicValue); } } else if (draftValue != null && !col.getOrigin().equals(AttributeOrigin.PROPERTY)) { row.setValue(col.getIndex(), draftValue); } } } } @Override protected void flushToEdgeAttributes(EdgeDraftGetter edgeDraft, Edge edge) { if (edge.getEdgeData().getAttributes() != null) { AttributeRow row = (AttributeRow) edge.getEdgeData().getAttributes(); for (int i = 0; i < row.countValues(); i++) { Object val = row.getValue(i); AttributeColumn col = row.getColumnAt(i); Object draftValue = edgeDraft.getAttributeRow().getValue(col); if (col.getId().equals(PropertiesColumn.EDGE_WEIGHT.getId())) { draftValue = new Float(edgeDraft.getWeight()); } if (col.getType().isDynamicType()) { if (draftValue == null && val != null) { removePoint(col.getType(), (DynamicType) val, point); } else if (draftValue != null) { DynamicType dynamicValue = addPoint(col.getType(), (DynamicType) val, draftValue, point); row.setValue(col.getIndex(), dynamicValue); } } else if (draftValue != null && !col.getOrigin().equals(AttributeOrigin.PROPERTY)) { row.setValue(col.getIndex(), draftValue); } } } } private TimeInterval addPoint(TimeInterval source, double point) { if (source == null) { return new TimeInterval(point, Double.POSITIVE_INFINITY); } List<Interval<Double[]>> intervals = source.getIntervals(point, point); if (intervals.isEmpty()) { return new TimeInterval(source, point, Double.POSITIVE_INFINITY); } return source; } private DynamicType addPoint(AttributeType type, DynamicType source, Object value, double point) { if (source == null) { return DynamicUtilities.createDynamicObject(type, new Interval(point, Double.POSITIVE_INFINITY, value)); } List<Interval<?>> intervals = source.getIntervals(point, point); if (intervals.isEmpty()) { return DynamicUtilities.createDynamicObject(type, source, new Interval(point, Double.POSITIVE_INFINITY, value)); } else if (intervals.size() > 1) { throw new RuntimeException("DynamicProcessor doesn't support overlapping intervals."); } else { Interval<?> toRemove = intervals.get(0); if (!toRemove.getValue().equals(value)) { Interval toAdd = new Interval(toRemove.getLow(), point, toRemove.isLowExcluded(), true, toRemove.getValue()); DynamicType updated = DynamicUtilities.createDynamicObject(type, source, toAdd, toRemove); toAdd = new Interval(point, Double.POSITIVE_INFINITY, value); updated = DynamicUtilities.createDynamicObject(type, updated, toAdd); return updated; } } return source; } private TimeInterval removePoint(TimeInterval source, double point) { if (source == null) { return null; } List<Interval<Double[]>> intervals = source.getIntervals(point, point); if (intervals.size() > 1) { throw new RuntimeException("DynamicProcessor doesn't support overlapping intervals."); } else if (!intervals.isEmpty()) { Interval<Double[]> toRemove = intervals.get(0); if (toRemove.getLow() >= point) { return source; } Double[] toAdd = new Double[]{toRemove.getLow(), point}; return new TimeInterval(source, toAdd[0], toAdd[1], toRemove.isLowExcluded(), true, toRemove.getLow(), toRemove.getHigh(), toRemove.isLowExcluded(), toRemove.isHighExcluded()); } return source; } private DynamicType removePoint(AttributeType type, DynamicType source, double point) { if (source == null) { return null; } List<Interval<?>> intervals = source.getIntervals(point, point); if (intervals.size() > 1) { throw new RuntimeException("DynamicProcessor doesn't support overlapping intervals."); } else if (!intervals.isEmpty()) { Interval<?> toRemove = intervals.get(0); if (toRemove.getLow() >= point) { return source; } Interval toAdd = new Interval(toRemove.getLow(), point, toRemove.isLowExcluded(), true, toRemove.getValue()); return DynamicUtilities.createDynamicObject(type, source, toAdd, toRemove); } return source; } public String getDisplayName() { return NbBundle.getMessage(DynamicProcessor.class, "DynamicProcessor.displayName"); } public String getDate() { return date; } public void setDate(String date) { this.date = date; } public boolean isDateMode() { return dateMode; } public void setDateMode(boolean dateMode) { this.dateMode = dateMode; } public boolean isLabelmatching() { return labelmatching; } public void setLabelmatching(boolean labelmatching) { this.labelmatching = labelmatching; } }