Powered by AppSignal & Oban Pro

How-To: Importing & Exporting Graphs

livebooks/how_to/import_export.livemd

How-To: Importing & Exporting Graphs

Mix.install([
  {:yog_ex, "~> 0.98"},
  {:jason, "~> 1.4"}, # Optional, but needed for JSON serialization
  {:libgraph, "~> 0.16.0"},
  {:kino_vizjs, "~> 0.8.0"} # Needed to render Graphviz DOT diagrams
])

Introduction

Yog is built to be a highly interoperable participant in the data ecosystem. It supports a wide range of industry-standard serialization and graph exchange formats. This makes it seamless to move graph datasets between Yog and external toolkits (such as Gephi, NetworkX, Cytoscape, or mathematical database archives).

This guide showcases all of Yog‘s import/export engines.


1. Structured XML Formats

GraphML (The Enterprise Standard)

GraphML is the de-facto XML standard for graph interchange. It supports comprehensive node and edge properties.

g = Yog.Generator.Classic.petersen()

# Export to GraphML string
graphml_xml = Yog.IO.GraphML.serialize(g)
IO.puts(String.slice(graphml_xml, 0, 250) <> "...\n")

# Import back from GraphML
{:ok, imported_g} = Yog.IO.GraphML.deserialize(graphml_xml)
IO.puts("Imported GraphML: #{Yog.node_count(imported_g)} nodes, #{Yog.edge_count(imported_g)} edges")

# Render imported graph
Kino.VizJS.render(Yog.Render.DOT.to_dot(imported_g))

GEXF (Gephi Exchange Format)

GEXF is the native format for Gephi. It supports structural components, hierarchical structures, and visual styling metadata.

g = Yog.directed()
  |> Yog.add_node(1, %{label: "A"})
  |> Yog.add_node(2, %{label: "B"})
  |> Yog.add_edge_ensure(1, 2, 5.5)

# Export to GEXF
gexf_xml = Yog.IO.GEXF.serialize(g)
IO.puts(String.slice(gexf_xml, 0, 250) <> "...\n")

# Import back from GEXF
{:ok, imported_g} = Yog.IO.GEXF.deserialize(gexf_xml)
IO.puts("Imported GEXF: #{Yog.node_count(imported_g)} nodes")

# Render imported graph
Kino.VizJS.render(Yog.Render.DOT.to_dot(imported_g))

2. Web Serialization

JSON (Cytoscape & D3.js)

JSON is the standard format for pushing graph databases to frontend clients using visualization libraries like Cytoscape.js or D3.js.

g = Yog.undirected()
  |> Yog.add_node(1, %{name: "Alice"})
  |> Yog.add_node(2, %{name: "Bob"})
  |> Yog.add_edge!(1, 2, %{type: "friends"})

# Export to JSON (returns the JSON string directly)
json = Yog.IO.JSON.to_json(g)
IO.puts(json)

# Import back from JSON
{:ok, imported_g} = Yog.IO.JSON.from_json(json)
IO.puts("\nImported JSON: #{Yog.node_count(imported_g)} nodes")

# Render imported graph
Kino.VizJS.render(Yog.Render.DOT.to_dot(imported_g))

3. Compact Mathematical Representations

Graph6 (For Small Graphs)

Graph6 is an extremely compact ASCII format for storing small, undirected graphs. It is widely used in graph databases, graph theory contests, and academic databases.

g = Yog.Generator.Classic.petersen()

# Export to Graph6
{:ok, g6_str} = Yog.IO.Graph6.serialize(g)
IO.puts("Petersen in Graph6: #{g6_str}")

# Import back from Graph6
{:ok, imported} = Yog.IO.Graph6.parse(g6_str)
IO.puts("Imported Graph6: #{Yog.node_count(imported)} nodes")

# Render imported graph
Kino.VizJS.render(Yog.Render.DOT.to_dot(imported))

Sparse6 (For Large Sparse Graphs)

Sparse6 is a companion format to Graph6 designed for much larger, sparse graphs where Graph6 becomes inefficient.

g = Yog.Generator.Classic.cycle(20)

# Export to Sparse6
{:ok, s6_str} = Yog.IO.Sparse6.serialize(g)
IO.puts("Cycle(20) in Sparse6: #{s6_str}")

# Import back from Sparse6
{:ok, imported} = Yog.IO.Sparse6.parse(s6_str)
IO.puts("Imported Sparse6: #{Yog.node_count(imported)} nodes")

# Render imported graph
Kino.VizJS.render(Yog.Render.DOT.to_dot(imported))

4. Trivial & Legacy Formats

TGF (Trivial Graph Format)

TGF is the simplest possible text format: nodes on top, a separator line #, and edges on the bottom. It’s excellent for rapid debugging.

g = Yog.directed()
  |> Yog.add_node(1, "Alice")
  |> Yog.add_node(2, "Bob")
  |> Yog.add_edge_ensure(1, 2, "follows")

# Export to TGF
tgf = Yog.IO.TGF.serialize(g)
IO.puts(tgf)

# Import back from TGF
{:ok, {:tgf_result, imported, _warnings}} = Yog.IO.TGF.parse(tgf, :directed)
IO.puts("Imported TGF: #{Yog.node_count(imported)} nodes")

# Render imported graph
Kino.VizJS.render(Yog.Render.DOT.to_dot(imported))

GDF (GUESS Format)

GDF is a CSV-like flat structure used by the GUESS visualization tool.

g = Yog.Generator.Classic.cycle(4)

# Export to GDF
gdf = Yog.IO.GDF.serialize(g)
IO.puts(gdf)

# Import back from GDF
{:ok, imported} = Yog.IO.GDF.deserialize(gdf)
IO.puts("Imported GDF: #{Yog.node_count(imported)} nodes")

# Render imported graph
Kino.VizJS.render(Yog.Render.DOT.to_dot(imported))

Pajek (.net format)

Pajek format is a popular format used by the Pajek social network analysis package.

g = Yog.from_edges(:directed, [{1, 2, 10}, {2, 3, 5}])

# Export to Pajek format
pajek = Yog.IO.Pajek.serialize(g)
IO.puts(pajek)

# Import back from Pajek
{:ok, {_, imported, _}} = Yog.IO.Pajek.parse(pajek)
IO.puts("Imported Pajek: #{Yog.Model.node_count(imported)} nodes")

# Render imported graph
Kino.VizJS.render(Yog.Render.DOT.to_dot(imported))

Matrix Market (Scientific / Sparse Matrix)

The Matrix Market coordinate format represents a graph as a sparse matrix, used heavily in scientific and numerical computing packages.

g = Yog.from_edges(:undirected, [{1, 2, 5.0}, {2, 3, 12.5}])

# Export to Matrix Market Format
matrix_market = Yog.IO.MatrixMarket.serialize(g)
IO.puts(matrix_market)

# Import back from Matrix Market
{:ok, {_, imported, _}} = Yog.IO.MatrixMarket.parse(matrix_market)
IO.puts("Imported Matrix Market: #{Yog.node_count(imported)} nodes")

# Render imported graph
Kino.VizJS.render(Yog.Render.DOT.to_dot(imported))

5. Elixir Ecosystem Interoperability

libgraph Bridge

If you are using Elixir’s classic libgraph package elsewhere in your architecture, Yog provides an adapter to convert graphs back and forth seamlessly.

# Create a libgraph graph
lg = Graph.new() |> Graph.add_edge(1, 2)

# Convert libgraph -> Yog
{:ok, yg} = Yog.IO.Libgraph.from_libgraph(lg)
IO.puts("Yog graph nodes: #{Yog.DAG.to_graph(yg) |> Yog.node_count()}")

# Convert Yog -> libgraph
lg_back = Yog.IO.Libgraph.to_libgraph(yg)
IO.puts("libgraph vertices back: #{length(Graph.vertices(lg_back))}")

# Render the Yog equivalent
Kino.VizJS.render(Yog.Render.DOT.to_dot(Yog.DAG.to_graph(yg)))

I/O Modules Summary

Format Module Typical File Ext Description
GraphML Yog.IO.GraphML .graphml XML standard, retains attributes
GEXF Yog.IO.GEXF .gexf Gephi XML format, retains styles/hierarchies
JSON Yog.IO.JSON .json Web frontend/interop (requires jason)
Graph6 Yog.IO.Graph6 .g6 Compact mathematical format for small graphs
Sparse6 Yog.IO.Sparse6 .s6 Compact mathematical format for large sparse graphs
TGF Yog.IO.TGF .tgf Simple space-separated text layout
GDF Yog.IO.GDF .gdf CSV-like layout for GUESS / Gephi
Pajek Yog.IO.Pajek .net / .paj Pajek social network representation
Matrix Market Yog.IO.MatrixMarket .mtx Scientific coordinate sparse matrix format
libgraph Yog.IO.Libgraph N/A Convert between Yog and legacy libgraph structs