graph.el is a small library for building grid-based graphs inside
Emacs.
You provide the layout:
- nodes have
:rowand:col - edges are polylines through grid points
- renderers decide how to draw that geometry
The repository includes:
graph.elfor the core graph data structures and text renderinggraph-svg.elfor SVG renderinggraph-git.elfor Git history graphs built on top of the core library
The core API is intentionally small:
graph-creategraph-add-nodegraph-add-edgegraph-open
Rows and columns are zero-based integers.
(add-to-list 'load-path default-directory)
(require 'graph)
(let ((g (graph-create)))
(graph-add-node g
:id 'root
:row 0
:col 2
:data '(:label "root"))
(graph-add-node g
:id 'left
:row 3
:col 0
:data '(:label "left"))
(graph-add-node g
:id 'right
:row 3
:col 4
:data '(:label "right"))
(graph-add-edge g
:from 'root
:to 'left
:points '((0 . 2) (1 . 2) (2 . 1) (3 . 0)))
(graph-add-edge g
:from 'root
:to 'right
:points '((0 . 2) (1 . 2) (2 . 3) (3 . 4)))
(with-temp-buffer
(graph-view-mode)
(graph-view-set-graph g)
(princ (concat "#+begin_example\n"
(buffer-string)
"#+end_example\n"))))
That renders directly into the Org results block. If you want the same graph in
its own interactive buffer, replace the final with-temp-buffer form inside
the let block with:
(graph-open g "*Example Graph*")A node is placed at a single grid position:
(graph-add-node g :id 'a :row 3 :col 2)An edge is a list of points that forms a polyline:
(graph-add-edge g
:from 'a
:to 'b
:points '((3 . 2)
(4 . 2)
(5 . 3)
(6 . 3)))Each consecutive pair of points becomes one segment.
If you want an image instead of character cells, require graph-svg and call
graph-svg-image or graph-svg-render.
(add-to-list 'load-path default-directory)
(require 'graph)
(require 'graph-svg)
(let ((g (graph-create))
(graph-svg-edge-width 2.0))
(graph-add-node g :id 'a :row 0 :col 2)
(graph-add-node g :id 'b :row 4 :col 0)
(graph-add-node g :id 'c :row 4 :col 4)
(graph-add-edge g
:from 'a
:to 'b
:points '((0 . 2) (1 . 2) (2 . 1) (3 . 1) (4 . 0))
:face 'default)
(graph-add-edge g
:from 'a
:to 'c
:points '((0 . 2) (1 . 2) (2 . 3) (3 . 3) (4 . 4))
:face 'default)
(let* ((render (graph-svg-render g))
(svg (car render))
(file (expand-file-name
"readme-svg-example.svg"
(file-name-directory (or buffer-file-name default-directory)))))
(with-temp-file file
(svg-print svg))
file))Evaluating that block writes an SVG file and inserts it as the result.
The SVG renderer uses these main custom variables:
graph-svg-x-stepgraph-svg-y-stepgraph-svg-paddinggraph-svg-node-radiusgraph-svg-edge-width
Nothing in graph.el is Git-specific. A Git history view is just a graph where:
- each commit is a node
- rows correspond to commit order
- columns correspond to active lanes
- parent relationships become edges
The important idea is that layout happens before rendering. For example:
(add-to-list 'load-path default-directory)
(require 'graph)
(let ((g (graph-create)))
;; Newest commit in lane 0.
(graph-add-node g :id 'c3 :row 0 :col 0)
;; Side branch commit in lane 1.
(graph-add-node g :id 'c2 :row 2 :col 1)
;; Mainline parent back in lane 0.
(graph-add-node g :id 'c1 :row 4 :col 0)
;; Merge-looking edge.
(graph-add-edge g
:from 'c3
:to 'c1
:points '((0 . 0) (1 . 0) (2 . 0) (3 . 0) (4 . 0)))
;; Side branch flowing back.
(graph-add-edge g
:from 'c3
:to 'c2
:points '((0 . 0) (1 . 0) (2 . 1)))
(with-temp-buffer
(graph-view-mode)
(graph-view-set-graph g)
(princ (concat "#+begin_example\n"
(buffer-string)
"#+end_example\n"))))
graph-git.el adds a Git history browser on top of the core library.
History is loaded asynchronously and fetches more commits as you move near the end of the buffer.
