如何将JSON数据转换为树图像?

c3frrgcw  于 2022-11-26  发布在  其他
关注(0)|答案(3)|浏览(469)

我正在使用树库来生成树,现在我需要一个易于阅读的版本的树,所以我想把它们转换成图像。例如:

以下树的示例JSON数据:

带数据:

>>> print(tree.to_json(with_data=True))
{"Harry": {"data": null, "children": [{"Bill": {"data": null}}, {"Jane": {"data": null, "children": [{"Diane": {"data": null}}, {"Mark": {"data": null}}]}}, {"Mary": {"data": null}}]}}

无数据:

>>> print(tree.to_json(with_data=False))
{"Harry": {"children": ["Bill", {"Jane": {"children": [{"Diane": {"children": ["Mary"]}}, "Mark"]}}]}}

是否可以使用graphvizd3.js或其他一些python库来使用此JSON数据生成树?

nfzehxib

nfzehxib1#

对于这样的树,不需要使用库:您可以直接生成GraphvizDOT语言语句。唯一棘手的部分是从JSON数据中提取树边缘。为此,我们首先将JSON字符串转换回Python dict,然后递归解析dict
如果dict树中的一个名字没有子项,那么它就是一个简单的字符串,否则,它就是一个dict,我们需要扫描它的"children"列表中的项。我们找到的每一个(父,子)对都会被追加到一个全局列表edges中。
这句有点隐晦的台词:

name = next(iter(treedict.keys()))

treedict中得到一个键。这会给我们这个人的名字,因为这是treedict中唯一的键。在Python 2中,我们可以

name = treedict.keys()[0]

但是前面的代码在Python2和Python3中都可以使用。

from __future__ import print_function
import json
import sys

# Tree in JSON format
s = '{"Harry": {"children": ["Bill", {"Jane": {"children": [{"Diane": {"children": ["Mary"]}}, "Mark"]}}]}}'

# Convert JSON tree to a Python dict
data = json.loads(s)

# Convert back to JSON & print to stderr so we can verify that the tree is correct.
print(json.dumps(data, indent=4), file=sys.stderr)

# Extract tree edges from the dict
edges = []

def get_edges(treedict, parent=None):
    name = next(iter(treedict.keys()))
    if parent is not None:
        edges.append((parent, name))
    for item in treedict[name]["children"]:
        if isinstance(item, dict):
            get_edges(item, parent=name)
        else:
            edges.append((name, item))

get_edges(data)

# Dump edge list in Graphviz DOT format
print('strict digraph tree {')
for row in edges:
    print('    {0} -> {1};'.format(*row))
print('}')

标准错误输出

{
    "Harry": {
        "children": [
            "Bill",
            {
                "Jane": {
                    "children": [
                        {
                            "Diane": {
                                "children": [
                                    "Mary"
                                ]
                            }
                        },
                        "Mark"
                    ]
                }
            }
        ]
    }
}

标准输出

strict digraph tree {
    Harry -> Bill;
    Harry -> Jane;
    Jane -> Diane;
    Diane -> Mary;
    Jane -> Mark;
}

上面的代码运行在Python 2和Python 3上。它将JSON数据打印到stderr,这样我们就可以验证它是否正确。然后它将Graphviz数据打印到stdout,这样我们就可以将它捕获到一个文件中,或者直接将它通过管道传输到Graphviz程序中。例如,如果脚本的名称是“tree_to_graph.py”,那么你可以在命令行中这样做,将图形保存为一个名为“tree.png”的PNG文件:

python tree_to_graph.py | dot -Tpng -otree.png

下面是PNG输出:

flvlnr44

flvlnr442#

根据PM 2Ring的答案,我创建了一个可通过命令行使用的脚本:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Convert a JSON to a graph."""

from __future__ import print_function
import json
import sys

def tree2graph(data, verbose=True):
    """
    Convert a JSON to a graph.

    Run `dot -Tpng -otree.png`

    Parameters
    ----------
    json_filepath : str
        Path to a JSON file
    out_dot_path : str
        Path where the output dot file will be stored

    Examples
    --------
    >>> s = {"Harry": [ "Bill", \
                       {"Jane": [{"Diane": ["Mary", "Mark"]}]}]}
    >>> tree2graph(s)
    [('Harry', 'Bill'), ('Harry', 'Jane'), ('Jane', 'Diane'), ('Diane', 'Mary'), ('Diane', 'Mark')]
    """
    # Extract tree edges from the dict
    edges = []

    def get_edges(treedict, parent=None):
        name = next(iter(treedict.keys()))
        if parent is not None:
            edges.append((parent, name))
        for item in treedict[name]:
            if isinstance(item, dict):
                get_edges(item, parent=name)
            elif isinstance(item, list):
                for el in item:
                    if isinstance(item, dict):
                        edges.append((parent, item.keys()[0]))
                        get_edges(item[item.keys()[0]])
                    else:
                        edges.append((parent, el))
            else:
                edges.append((name, item))
    get_edges(data)
    return edges

def main(json_filepath, out_dot_path, lr=False, verbose=True):
    """IO."""
    # Read JSON
    with open(json_filepath) as data_file:
        data = json.load(data_file)

    if verbose:
        # Convert back to JSON & print to stderr so we can verfiy that the tree
        # is correct.
        print(json.dumps(data, indent=4), file=sys.stderr)

    # Get edges
    edges = tree2graph(data, verbose)

    # Dump edge list in Graphviz DOT format
    with open(out_dot_path, 'w') as f:
        f.write('strict digraph tree {\n')
        if lr:
            f.write('rankdir="LR";\n')
        for row in edges:
            f.write('    "{0}" -> "{1}";\n'.format(*row))
        f.write('}\n')

def get_parser():
    """Get parser object for tree2graph.py."""
    from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
    parser = ArgumentParser(description=__doc__,
                            formatter_class=ArgumentDefaultsHelpFormatter)
    parser.add_argument("-i", "--input",
                        dest="json_filepath",
                        help="JSON FILE to read",
                        metavar="FILE",
                        required=True)
    parser.add_argument("-o", "--output",
                        dest="out_dot_path",
                        help="DOT FILE to write",
                        metavar="FILE",
                        required=True)
    return parser

if __name__ == "__main__":
    import doctest
    doctest.testmod()
    args = get_parser().parse_args()
    main(args.json_filepath, args.out_dot_path, verbose=False)
uqdfh47h

uqdfh47h3#

下面是使用graphviz将snwflk的json数据直接转换为树的解决方案:
您输入的数据:

json_data = {"Harry": {"data": None, "children": [{"Bill": {"data": None}}, {"Jane": {"data": None, "children": [{"Diane": {"data": None}}, {"Mark": {"data": None}}]}}, {"Mary": {"data": None}}]}}

我们可以使用广度优先搜索遍历树,这样我们就可以确保遍历所有边,这是使用graphviz构建树的要求:

import graphviz

def get_node_info(node):
    node_name = list(node.keys())[0]
    node_data = node[node_name]['data']
    node_children = node[node_name].get('children', None)
    return node_name, node_data, node_children

traversed_nodes = [json_data] # start with root node

# initialize the graph 
f = graphviz.Digraph('finite_state_machine', filename='fsm.gv')
f.attr(rankdir='LR', size='8,5')
f.attr('node', shape='rectangle')

while (len(traversed_nodes) > 0):
    cur_node = traversed_nodes.pop(0)
    cur_node_name, cur_node_data, cur_node_children = get_node_info(cur_node)
    if (cur_node_children is not None): # check if the cur_node has a child
        for next_node in cur_node_children: 
            traversed_nodes.append(next_node)
            next_node_name = get_node_info(next_node)[0]
            f.edge(cur_node_name, next_node_name, label='') # add edge to the graph

f.view()

输出图如下所示(我认为示例树图像中玛丽的节点位置错误)

相关问题