【问题标题】:How to convert JSON data into a tree image?如何将 JSON 数据转换为树形图像?
【发布时间】:2016-10-18 21:10:11
【问题描述】:

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

示例 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 数据生成树?

【问题讨论】:

  • 你看过了吗plot.ly/python/tree-plots
  • @corn3lius,不,以前没见过,你能帮我吗?我是 JSON 新手!
  • 顺便说一句,with_data=False 版本的 JSON 与您图像中的树匹配,但 with_data=True 版本不匹配。
  • 是的@PM2Ring,但with_data=False 已经足够我用了。

标签: python json tree


【解决方案1】:

对于这样的树,无需使用库:您可以直接生成 Graphviz DOT 语言语句。唯一棘手的部分是从 JSON 数据中提取树边缘。为此,我们首先将 JSON 字符串转换回 Python dict,然后递归解析 dict

如果树 dict 中的名称没有孩子,则它是一个简单的字符串,否则,它是一个 dict,我们需要扫描其 "children" 列表中的项目。我们找到的每个(父、子)对都被附加到一个全局列表edges

这条有点神秘的线:

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

treedict 获取一个密钥。这给了我们这个人的名字,因为这是treedict 中的唯一键。在 Python 2 中我们可以这样做

name = treedict.keys()[0]

但之前的代码在 Python 2 和 Python 3 中都可以使用。

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 数据打印到标准输出,以便我们可以将其捕获到文件或直接通过管道传输到 Graphviz 程序。例如,如果脚本名称为“tree_to_graph.py”,那么您可以在命令行中执行此操作,将图形保存为名为“tree.png”的 PNG 文件:

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

这是 PNG 输出:

【讨论】:

  • 这正是我所需要的,但我不断收到错误:Warning: <stdin>: syntax error in line 1 near。知道这个错误的原因是什么吗?
  • 可能是因为我的树是由整数而不是字符串组成的。因此,每当我在控制台中执行该命令时,都会出现错误:Warning: <stdin>: syntax error in line 1 near 114,其中 114 是根节点。
  • 从您的代码中测试了所有内容,它可以工作。问题是,我正在以s = tree.to_json(with_data=False) 的身份获取数据,这导致了上述错误,任何想法,我该如何解决? @PM2Ring
  • @Grimlock 我的代码可以处理整数。但是,JSON 中的所有键必须 是字符串,也许 treelib 正在生成导致 json.loads 引发错误的无效 JSON。请将您的实际数据示例添加到问题的末尾,我会看一下。
  • 我自己解决了Warning: <stdin>: syntax error in line 1 near 错误,但问题是我使用的是整数,整数可能会不时重复。这种时候我该怎么办?就像在这个例子中一样,可以有一个名为“Diane”的“Diane”的孩子。如何解决?
【解决方案2】:

根据 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)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-09
    • 2021-08-07
    • 2021-09-24
    • 1970-01-01
    • 2017-10-08
    • 2020-01-07
    相关资源
    最近更新 更多