/* Copyright 2008-2010 Gephi Authors : Mathieu Bastian <mathieu.bastian@gephi.org>, Sebastien Heymann <sebastien.heymann@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.exporter.plugin; import java.awt.Color; import java.io.Writer; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.gephi.data.attributes.api.AttributeColumn; import org.gephi.data.attributes.api.AttributeModel; import org.gephi.data.attributes.api.AttributeOrigin; import org.gephi.data.attributes.type.TimeInterval; import org.gephi.dynamic.DynamicUtilities; import org.gephi.dynamic.api.DynamicModel; import org.gephi.graph.api.Edge; import org.gephi.graph.api.EdgeIterable; import org.gephi.graph.api.Graph; import org.gephi.graph.api.GraphModel; import org.gephi.graph.api.HierarchicalGraph; import org.gephi.graph.api.Node; import org.gephi.graph.api.NodeData; import org.gephi.io.exporter.api.FileType; import org.gephi.io.exporter.spi.GraphExporter; import org.gephi.io.exporter.spi.CharacterExporter; import org.gephi.project.api.Workspace; import org.gephi.utils.longtask.spi.LongTask; import org.gephi.utils.progress.Progress; import org.gephi.utils.progress.ProgressTicket; import org.openide.util.NbBundle; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Text; /** * * @author Sebastien Heymann * @author Mathieu Bastian */ public class ExporterGraphML implements GraphExporter, CharacterExporter, LongTask { private boolean cancel = false; private ProgressTicket progressTicket; private Workspace workspace; private Writer writer; private boolean exportVisible; private GraphModel graphModel; private AttributeModel attributeModel; //Settings private boolean normalize = false; private boolean exportColors = true; private boolean exportPosition = true; private boolean exportSize = true; private boolean exportAttributes = true; private boolean exportHierarchy = false; //Settings Helper private float minSize; private float maxSize; private float minX; private float maxX; private float minY; private float maxY; private float minZ; private float maxZ; //Dynamic private TimeInterval visibleInterval; public boolean execute() { attributeModel = workspace.getLookup().lookup(AttributeModel.class); graphModel = workspace.getLookup().lookup(GraphModel.class); HierarchicalGraph graph = null; if (exportVisible) { graph = graphModel.getHierarchicalGraphVisible(); } else { graph = graphModel.getHierarchicalGraph(); } DynamicModel dynamicModel = workspace.getLookup().lookup(DynamicModel.class); visibleInterval = dynamicModel != null && exportVisible ? dynamicModel.getVisibleInterval() : new TimeInterval(); try { exportData(createDocument(), graph, attributeModel); } catch (Exception e) { graph.readUnlockAll(); throw new RuntimeException(e); } return !cancel; } public Document createDocument() throws ParserConfigurationException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = factory.newDocumentBuilder(); final Document document = documentBuilder.newDocument(); document.setXmlVersion("1.0"); document.setXmlStandalone(true); return document; } private void transform(Document document) throws TransformerConfigurationException, TransformerException { Source source = new DOMSource(document); Result result = new StreamResult(writer); Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transformer.transform(source, result); } /* public Schema getSchema() { try { SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); return sf.newSchema(new URL("http://www.gexf.net/1.1draft/gexf.xsd")); } catch (MalformedURLException ex) { Exceptions.printStackTrace(ex); } catch (SAXException ex) { Exceptions.printStackTrace(ex); } return null; } */ public boolean exportData(Document document, HierarchicalGraph graph, AttributeModel model) throws Exception { Progress.start(progressTicket); graph.readLock(); //Options calculateMinMax(graph); //Calculate progress units count int max; if (graphModel.isHierarchical()) { HierarchicalGraph hgraph = graphModel.getHierarchicalGraph(); max = hgraph.getNodeCount() + hgraph.getEdgeCount(); } else { max = graph.getNodeCount() + graph.getEdgeCount(); } Progress.switchToDeterminate(progressTicket, max); Element root = document.createElementNS("http://graphml.graphdrawing.org/xmlns", "graphml"); document.appendChild(root); createKeys(document, root); Element graphE = createGraph(document, graph); root.appendChild(graphE); graph.readUnlockAll(); if (!cancel) { transform(document); } Progress.finish(progressTicket); return !cancel; } private void createKeys(Document document, Element root) { Element nodeLabelKeyE = document.createElement("key"); nodeLabelKeyE.setAttribute("id", "label"); nodeLabelKeyE.setAttribute("attr.name", "label"); nodeLabelKeyE.setAttribute("attr.type", "string"); nodeLabelKeyE.setAttribute("for", "node"); root.appendChild(nodeLabelKeyE); Element edgeLabelKeyE = document.createElement("key"); edgeLabelKeyE.setAttribute("id", "edgelabel"); edgeLabelKeyE.setAttribute("attr.name", "Edge Label"); edgeLabelKeyE.setAttribute("attr.type", "string"); edgeLabelKeyE.setAttribute("for", "edge"); root.appendChild(edgeLabelKeyE); Element weightKeyE = document.createElement("key"); weightKeyE.setAttribute("id", "weight"); weightKeyE.setAttribute("attr.name", "weight"); weightKeyE.setAttribute("attr.type", "double"); weightKeyE.setAttribute("for", "edge"); root.appendChild(weightKeyE); Element edgeIdKeyE = document.createElement("key"); edgeIdKeyE.setAttribute("id", "edgeid"); edgeIdKeyE.setAttribute("attr.name", "Edge Id"); edgeIdKeyE.setAttribute("attr.type", "string"); edgeIdKeyE.setAttribute("for", "edge"); root.appendChild(edgeIdKeyE); if (exportColors) { Element colorRKeyE = document.createElement("key"); colorRKeyE.setAttribute("id", "r"); colorRKeyE.setAttribute("attr.name", "r"); colorRKeyE.setAttribute("attr.type", "int"); colorRKeyE.setAttribute("for", "node"); root.appendChild(colorRKeyE); Element colorGKeyE = document.createElement("key"); colorGKeyE.setAttribute("id", "g"); colorGKeyE.setAttribute("attr.name", "g"); colorGKeyE.setAttribute("attr.type", "int"); colorGKeyE.setAttribute("for", "node"); root.appendChild(colorGKeyE); Element colorBKeyE = document.createElement("key"); colorBKeyE.setAttribute("id", "b"); colorBKeyE.setAttribute("attr.name", "b"); colorBKeyE.setAttribute("attr.type", "int"); colorBKeyE.setAttribute("for", "node"); root.appendChild(colorBKeyE); } if (exportPosition) { Element positionKeyE = document.createElement("key"); positionKeyE.setAttribute("id", "x"); positionKeyE.setAttribute("attr.name", "x"); positionKeyE.setAttribute("attr.type", "float"); positionKeyE.setAttribute("for", "node"); root.appendChild(positionKeyE); Element positionKey2E = document.createElement("key"); positionKey2E.setAttribute("id", "y"); positionKey2E.setAttribute("attr.name", "y"); positionKey2E.setAttribute("attr.type", "float"); positionKey2E.setAttribute("for", "node"); root.appendChild(positionKey2E); if (minZ != 0f || maxZ != 0f) { Element positionKey3E = document.createElement("key"); positionKey3E.setAttribute("id", "z"); positionKey3E.setAttribute("attr.name", "z"); positionKey3E.setAttribute("attr.type", "float"); positionKey3E.setAttribute("for", "node"); root.appendChild(positionKey3E); } } if (exportSize) { Element sizeKeyE = document.createElement("key"); sizeKeyE.setAttribute("id", "size"); sizeKeyE.setAttribute("attr.name", "size"); sizeKeyE.setAttribute("attr.type", "float"); sizeKeyE.setAttribute("for", "node"); root.appendChild(sizeKeyE); } //Attributes if (attributeModel != null && exportAttributes) { //Node attributes for (AttributeColumn column : attributeModel.getNodeTable().getColumns()) { if (!column.getOrigin().equals(AttributeOrigin.PROPERTY)) { Element attributeE = createAttribute(document, column); attributeE.setAttribute("for", "node"); root.appendChild(attributeE); } } for (AttributeColumn column : attributeModel.getEdgeTable().getColumns()) { if (!column.getOrigin().equals(AttributeOrigin.PROPERTY)) { //Data or computed Element attributeE = createAttribute(document, column); attributeE.setAttribute("for", "edge"); root.appendChild(attributeE); } } } } private Element createGraph(Document document, Graph graph) throws Exception { Element graphE = document.createElement("graph"); if (graphModel.isDirected()) { graphE.setAttribute("edgedefault", "directed"); } else { graphE.setAttribute("edgedefault", "undirected"); // defaultValue } //Nodes createNodes(document, graphE, graph, null); //Edges createEdges(document, graphE, graph); return graphE; } private Element createAttribute(Document document, AttributeColumn column) { Element attributeE = document.createElement("key"); attributeE.setAttribute("id", column.getId()); attributeE.setAttribute("attr.name", column.getTitle()); switch (column.getType()) { case INT: attributeE.setAttribute("attr.type", "int"); break; default: attributeE.setAttribute("attr.type", column.getType().getTypeString().toLowerCase()); break; } if (column.getDefaultValue() != null) { Element defaultE = document.createElement("default"); Text defaultTextE = document.createTextNode(column.getDefaultValue().toString()); defaultE.appendChild(defaultTextE); } return attributeE; } private Element createNodeAttvalue(Document document, AttributeColumn column, Node n) throws Exception { int index = column.getIndex(); if (n.getNodeData().getAttributes().getValue(index) != null) { Object val = n.getNodeData().getAttributes().getValue(index); val = DynamicUtilities.getDynamicValue(val, visibleInterval.getLow(), visibleInterval.getHigh()); String value = val.toString(); String id = column.getId(); Element attvalueE = document.createElement("data"); attvalueE.setAttribute("key", id); attvalueE.setTextContent(value); return attvalueE; } return null; } private Element createEdgeAttvalue(Document document, AttributeColumn column, Edge e) throws Exception { int index = column.getIndex(); if (e.getEdgeData().getAttributes().getValue(index) != null) { Object val = e.getEdgeData().getAttributes().getValue(index); val = DynamicUtilities.getDynamicValue(val, visibleInterval.getLow(), visibleInterval.getHigh()); String value = val.toString(); String id = column.getId(); Element attvalueE = document.createElement("data"); attvalueE.setAttribute("key", id); attvalueE.setTextContent(value); return attvalueE; } return null; } private void createNodes(Document document, Element parentE, Graph graph, Node nodeParent) throws Exception { if (nodeParent != null) { Element graphE = document.createElement("graph"); if (graphModel.isDirected()) { graphE.setAttribute("edgedefault", "directed"); } else { graphE.setAttribute("edgedefault", "undirected"); // defaultValue } // we are inside the tree HierarchicalGraph hgraph = graphModel.getHierarchicalGraph(); for (Node n : hgraph.getChildren(nodeParent)) { Element childE = createNode(document, graph, n); graphE.appendChild(childE); } parentE.appendChild(graphE); } else if (exportHierarchy && graphModel.isHierarchical()) { // we are on the top of the tree HierarchicalGraph hgraph = graphModel.getHierarchicalGraph(); for (Node n : hgraph.getTopNodes()) { Element nodeE = createNode(document, hgraph, n); parentE.appendChild(nodeE); } } else { // there is no tree for (Node n : graph.getNodes()) { if (cancel) { break; } Element nodeE = createNode(document, graph, n); parentE.appendChild(nodeE); } } } private Element createNode(Document document, Graph graph, Node n) throws Exception { Element nodeE = document.createElement("node"); nodeE.setAttribute("id", n.getNodeData().getId()); //Label if (n.getNodeData().getLabel() != null && !n.getNodeData().getLabel().isEmpty()) { Element labelE = createNodeLabel(document, n); nodeE.appendChild(labelE); } //Attribute values if (attributeModel != null && exportAttributes) { for (AttributeColumn column : attributeModel.getNodeTable().getColumns()) { if (!column.getOrigin().equals(AttributeOrigin.PROPERTY)) { //Data or computed Element attvalueE = createNodeAttvalue(document, column, n); if (attvalueE != null) { nodeE.appendChild(attvalueE); } } } } //Viz if (exportSize) { Element sizeE = createNodeSize(document, n); nodeE.appendChild(sizeE); } if (exportColors) { Element colorE = createNodeColorR(document, n); nodeE.appendChild(colorE); colorE = createNodeColorG(document, n); nodeE.appendChild(colorE); colorE = createNodeColorB(document, n); nodeE.appendChild(colorE); } if (exportPosition) { Element positionXE = createNodePositionX(document, n); nodeE.appendChild(positionXE); Element positionYE = createNodePositionY(document, n); nodeE.appendChild(positionYE); if (minZ != 0f || maxZ != 0f) { Element positionZE = createNodePositionZ(document, n); nodeE.appendChild(positionZE); } } //Hierarchy if (exportHierarchy && graphModel.isHierarchical()) { HierarchicalGraph hgraph = graphModel.getHierarchicalGraph(); int childCount = hgraph.getChildrenCount(n); if (childCount != 0) { createNodes(document, nodeE, graph, n); } } Progress.progress(progressTicket); return nodeE; } private void createEdges(Document document, Element edgesE, Graph graph) throws Exception { EdgeIterable it; HierarchicalGraph hgraph = graphModel.getHierarchicalGraph(); if (exportHierarchy && graphModel.isHierarchical()) { it = hgraph.getEdgesTree(); } else { it = hgraph.getEdgesAndMetaEdges(); } for (Edge e : it.toArray()) { if (cancel) { break; } Element edgeE = createEdge(document, e); edgesE.appendChild(edgeE); } } private Element createEdge(Document document, Edge e) throws Exception { Element edgeE = document.createElement("edge"); edgeE.setAttribute("source", e.getSource().getNodeData().getId()); edgeE.setAttribute("target", e.getTarget().getNodeData().getId()); if (e.getEdgeData().getId() != null && !e.getEdgeData().getId().isEmpty() && !String.valueOf(e.getId()).equals(e.getEdgeData().getId())) { Element idE = createEdgeId(document, e); edgeE.appendChild(idE); } //Label if (e.getEdgeData().getLabel() != null && !e.getEdgeData().getLabel().isEmpty()) { Element labelE = createEdgeLabel(document, e); edgeE.appendChild(labelE); } Element weightE = createEdgeWeight(document, e); edgeE.appendChild(weightE); if (e.isDirected() && !graphModel.isDirected()) { edgeE.setAttribute("type", "directed"); } else if (!e.isDirected() && graphModel.isDirected()) { edgeE.setAttribute("type", "undirected"); } //Attribute values if (attributeModel != null) { for (AttributeColumn column : attributeModel.getEdgeTable().getColumns()) { if (!column.getOrigin().equals(AttributeOrigin.PROPERTY)) { //Data or computed Element attvalueE = createEdgeAttvalue(document, column, e); if (attvalueE != null) { edgeE.appendChild(attvalueE); } } } } Progress.progress(progressTicket); return edgeE; } private Element createNodeSize(Document document, Node n) throws Exception { Element sizeE = document.createElement("data"); float size = n.getNodeData().getSize(); if (normalize) { size = (size - minSize) / (maxSize - minSize); } sizeE.setAttribute("key", "size"); sizeE.setTextContent("" + size); return sizeE; } private Element createNodeColorR(Document document, Node n) throws Exception { int r = Math.round(n.getNodeData().r() * 255f); Element colorE = document.createElement("data"); colorE.setAttribute("key", "r"); colorE.setTextContent("" + r); return colorE; } private Element createNodeColorG(Document document, Node n) throws Exception { int g = Math.round(n.getNodeData().g() * 255f); Element colorE = document.createElement("data"); colorE.setAttribute("key", "g"); colorE.setTextContent("" + g); return colorE; } private Element createNodeColorB(Document document, Node n) throws Exception { int b = Math.round(n.getNodeData().b() * 255f); Element colorE = document.createElement("data"); colorE.setAttribute("key", "b"); colorE.setTextContent("" + b); return colorE; } private Element createNodePositionX(Document document, Node n) throws Exception { Element positionXE = document.createElement("data"); float x = n.getNodeData().x(); if (normalize && x != 0.0) { x = (x - minX) / (maxX - minX); } positionXE.setAttribute("key", "x"); positionXE.setTextContent("" + x); return positionXE; } private Element createNodePositionY(Document document, Node n) throws Exception { Element positionYE = document.createElement("data"); float y = n.getNodeData().y(); if (normalize && y != 0.0) { y = (y - minY) / (maxY - minY); } positionYE.setAttribute("key", "y"); positionYE.setTextContent("" + y); return positionYE; } private Element createNodePositionZ(Document document, Node n) throws Exception { Element positionZE = document.createElement("data"); float z = n.getNodeData().z(); if (normalize && z != 0.0) { z = (z - minZ) / (maxZ - minZ); } positionZE.setAttribute("key", "z"); positionZE.setTextContent("" + z); return positionZE; } private Element createNodeLabel(Document document, Node n) throws Exception { Element labelE = document.createElement("data"); labelE.setAttribute("key", "label"); labelE.setTextContent(n.getNodeData().getLabel()); return labelE; } private Element createEdgeId(Document document, Edge e) throws Exception { Element idE = document.createElement("data"); idE.setAttribute("key", "edgeid"); idE.setTextContent(e.getEdgeData().getId()); return idE; } private Element createEdgeWeight(Document document, Edge e) throws Exception { Element weightE = document.createElement("data"); weightE.setAttribute("key", "weight"); weightE.setTextContent(Float.toString(e.getWeight(visibleInterval.getLow(), visibleInterval.getHigh()))); return weightE; } private Element createEdgeLabel(Document document, Edge e) throws Exception { Element labelE = document.createElement("data"); labelE.setAttribute("key", "edgelabel"); labelE.setTextContent(e.getEdgeData().getLabel()); return labelE; } private void calculateMinMax(Graph graph) { minX = Float.POSITIVE_INFINITY; maxX = Float.NEGATIVE_INFINITY; minY = Float.POSITIVE_INFINITY; maxY = Float.NEGATIVE_INFINITY; minZ = Float.POSITIVE_INFINITY; maxZ = Float.NEGATIVE_INFINITY; minSize = Float.POSITIVE_INFINITY; maxSize = Float.NEGATIVE_INFINITY; for (Node node : graph.getNodes()) { NodeData nodeData = node.getNodeData(); minX = Math.min(minX, nodeData.x()); maxX = Math.max(maxX, nodeData.x()); minY = Math.min(minY, nodeData.y()); maxY = Math.max(maxY, nodeData.y()); minZ = Math.min(minZ, nodeData.z()); maxZ = Math.max(maxZ, nodeData.z()); minSize = Math.min(minSize, nodeData.getSize()); maxSize = Math.max(maxSize, nodeData.getSize()); } } public boolean cancel() { cancel = true; return true; } public void setProgressTicket(ProgressTicket progressTicket) { this.progressTicket = progressTicket; } public String getName() { return NbBundle.getMessage(getClass(), "ExporterGraphML_name"); } public FileType[] getFileTypes() { FileType ft = new FileType(".graphml", NbBundle.getMessage(getClass(), "fileType_GraphML_Name")); return new FileType[]{ft}; } public void setExportAttributes(boolean exportAttributes) { this.exportAttributes = exportAttributes; } public void setExportColors(boolean exportColors) { this.exportColors = exportColors; } public void setExportPosition(boolean exportPosition) { this.exportPosition = exportPosition; } public void setExportSize(boolean exportSize) { this.exportSize = exportSize; } public void setNormalize(boolean normalize) { this.normalize = normalize; } public boolean isExportAttributes() { return exportAttributes; } public boolean isExportColors() { return exportColors; } public boolean isExportPosition() { return exportPosition; } public boolean isExportSize() { return exportSize; } public boolean isNormalize() { return normalize; } public boolean isExportVisible() { return exportVisible; } public void setExportVisible(boolean exportVisible) { this.exportVisible = exportVisible; } public void setWriter(Writer writer) { this.writer = writer; } public Workspace getWorkspace() { return workspace; } public void setWorkspace(Workspace workspace) { this.workspace = workspace; } public boolean isExportHierarchy() { return exportHierarchy; } public void setExportHierarchy(boolean exportHierarchy) { this.exportHierarchy = exportHierarchy; } }