Skip to content
Jacob Williams edited this page Apr 23, 2017 · 4 revisions

Graphviz Example

This example shows you how to generate a directed graph of a JSON structure using Graphviz.

JSON data

Example JSON data from here:

{
  "glossary": {
    "title": "example glossary",
    "GlossDiv": {
      "title": "S",
      "GlossList": {
        "GlossEntry": {
          "ID": "SGML",
          "SortAs": "SGML",
          "GlossTerm": "Standard Generalized Markup Language",
          "Acronym": "SGML",
          "Abbrev": "ISO 8879:1986",
          "GlossDef": {
            "para": "A meta-markup language, used to create markup languages such as DocBook.",
            "GlossSeeAlso": ["GML", "XML"]
          },
          "GlossSee": "markup"
        }
      }
    }
  }
}

Code

This code reads the above JSON data from a string (note: it could also be read from a file), generates a file in Graphviz dot language, and then uses execute_command_line to call dot and generate a png of the graph.

program graphviz_test

    use json_module, lk => json_lk, rk => json_rk, ik => json_ik, ck => json_ck

    implicit none

    character(len=*),parameter :: new_line = char(10)
    character(len=*),parameter :: tab = '  '  !! for indenting

    type(json_core) :: json
    type(json_value),pointer :: p
    integer(IK) :: icount,iunit
    character(kind=CK,len=:),allocatable :: str

    !>
    !  Example JSON data from: http://json.org/example.html
    character(kind=CK,len=*),parameter :: json_example = &
        ' {'//new_line//&
        ' "glossary": {'//new_line//&
        ' "title": "example glossary",'//new_line//&
        ' "GlossDiv": {'//new_line//&
        ' "title": "S",'//new_line//&
        ' "GlossList": {'//new_line//&
        ' "GlossEntry": {'//new_line//&
        ' "ID": "SGML",'//new_line//&
        ' "SortAs": "SGML",'//new_line//&
        ' "GlossTerm": "Standard Generalized Markup Language",'//new_line//&
        ' "Acronym": "SGML",'//new_line//&
        ' "Abbrev": "ISO 8879:1986",'//new_line//&
        ' "GlossDef": {'//new_line//&
        ' "para": "A meta-markup language, used to create markup languages such as DocBook.",'//new_line//&
        ' "GlossSeeAlso": ["GML", "XML"]'//new_line//&
        ' },'//new_line//&
        ' "GlossSee": "markup"'//new_line//&
        ' }'//new_line//&
        ' }'//new_line//&
        ' }'//new_line//&
        ' }'//new_line//&
        ' }'

    ! load JSON from string:
    call json%parse(p,json_example)

    ! initialize the graphviz file:
    str = ''
    str = str // 'digraph json {' // new_line // new_line
    str = str // tab // 'graph [ dpi = 300 ]' // new_line // new_line

    ! traverse the structure:
    icount = 0
    call graphviz_func(p,icount=icount,ithis=0)

    ! cleanup:
    call json%destroy(p)

    ! close out the graphviz structure:
    str = str // new_line // '}'

    ! write it to a file:
    open(newunit=iunit,file='json.dot',status='REPLACE')
    write(iunit,'(A)') str
    close(iunit)

    ! call graphviz to make a png of the graph:
    call execute_command_line('dot -Tpng -o json.png json.dot')

    contains

        recursive subroutine graphviz_func(p,icount,ithis,iparent,iprev)

        implicit none

        type(json_value),pointer,intent(in) :: p
        integer,intent(inout)               :: icount  !! running counter of all elements
                                                       !! traversed (initialize with 0)
        integer,intent(in)                  :: ithis   !! index for this element
                                                       !! (root should be 0)
        integer,intent(in),optional         :: iparent !! index for parent
        integer,intent(in),optional         :: iprev   !! index for previous in list

        type(json_value),pointer :: child,next
        character(len=:),allocatable :: name
        integer :: itmp

        if (ithis==0) then
            !first one (root)
            str = str//tab//'0 '//&
                '[shape=circle,fillcolor="HoneyDew",style=filled,label="root"]'//&
                new_line
        end if

        itmp = ithis

        call json%get_child(p,child)
        if (associated(child)) then
            icount = icount + 1
            name = get_name(child)
            !create node and draw arrow from this to child:
            str = str//tab//int_to_str(icount)//' '//&
                    '[shape=circle,fillcolor="cornsilk",style=filled,label="'//&
                    name//'"]'//new_line
            str = str //tab// int_to_str(itmp) // '->' // &
                  int_to_str(icount)//';'//new_line
            !process child:
            call graphviz_func(child,icount=icount,&
                                ithis=icount,iparent=itmp)
        end if

        call json%get_next(p,next)
        if (associated(next)) then
            icount = icount + 1
            name = get_name(next)
            !create node and draw arrow from parent to next:
            str = str//tab//int_to_str(icount)//' '//&
                    '[shape=circle,fillcolor="cornsilk",style=filled,label="'//&
                    name//'"]'//new_line
            str = str //tab// int_to_str(iparent) // '->' //&
                         int_to_str(icount)//';'//new_line
            !process next:
            call graphviz_func(next,icount=icount,ithis=icount,&
                                iparent=iparent,iprev=itmp)
        end if

        end subroutine graphviz_func

        function int_to_str(i) result(str)

        !! integer to string

        implicit none

        integer,intent(in)           :: i
        character(len=:),allocatable :: str

        character(len=10) :: itmp

        write(itmp,'(I10)') i
        str = trim(adjustl(itmp))

        end function int_to_str

        function get_name(p) result(name)

        !! the name of the node.
        !! this is either the name of the json_value variable
        !! (if there is one), or the actual value as a string.

        implicit none

        type(json_value),pointer             :: p
        character(kind=CK,len=:),allocatable :: name

        logical(LK)       :: tf
        real(RK)          :: r
        integer(IK)       :: i
        character(len=30) :: tmp
        integer           :: var_type

        call json%info(p,name=name)
        if (name=='') then
            call json%info(p,var_type=var_type)
            select case (var_type)
            case(json_null)
                name = 'null'
            case(json_logical)
                call json%get(p,tf)
                if (tf) then
                    name = 'T'
                else
                    name = 'F'
                end if
            case(json_string)
                call json%get(p,name)
            case(json_double)
                call json%get(p,r)
                write(tmp,'(E30.16)') r
                name = trim(adjustl(tmp))
            case(json_integer)
                call json%get(p,i)
                name = int_to_str(i)
            case default
                name = ''
            end select
        end if

        end function get_name

end program graphviz_test

Dot file

digraph json {

  graph [ dpi = 300 ]

  0 [shape=circle,fillcolor="HoneyDew",style=filled,label="root"]
  1 [shape=circle,fillcolor="cornsilk",style=filled,label="glossary"]
  0->1;
  2 [shape=circle,fillcolor="cornsilk",style=filled,label="title"]
  1->2;
  3 [shape=circle,fillcolor="cornsilk",style=filled,label="GlossDiv"]
  1->3;
  4 [shape=circle,fillcolor="cornsilk",style=filled,label="title"]
  3->4;
  5 [shape=circle,fillcolor="cornsilk",style=filled,label="GlossList"]
  3->5;
  6 [shape=circle,fillcolor="cornsilk",style=filled,label="GlossEntry"]
  5->6;
  7 [shape=circle,fillcolor="cornsilk",style=filled,label="ID"]
  6->7;
  8 [shape=circle,fillcolor="cornsilk",style=filled,label="SortAs"]
  6->8;
  9 [shape=circle,fillcolor="cornsilk",style=filled,label="GlossTerm"]
  6->9;
  10 [shape=circle,fillcolor="cornsilk",style=filled,label="Acronym"]
  6->10;
  11 [shape=circle,fillcolor="cornsilk",style=filled,label="Abbrev"]
  6->11;
  12 [shape=circle,fillcolor="cornsilk",style=filled,label="GlossDef"]
  6->12;
  13 [shape=circle,fillcolor="cornsilk",style=filled,label="para"]
  12->13;
  14 [shape=circle,fillcolor="cornsilk",style=filled,label="GlossSeeAlso"]
  12->14;
  15 [shape=circle,fillcolor="cornsilk",style=filled,label="GML"]
  14->15;
  16 [shape=circle,fillcolor="cornsilk",style=filled,label="XML"]
  14->16;
  17 [shape=circle,fillcolor="cornsilk",style=filled,label="GlossSee"]
  6->17;

}

Figure

See also