cong wang
2024-06-05

jinja & pyec...

jinja&pyechart联合构建html报告

pyechart是一个具有良好交互性和精巧图表设计的开源的数据可视化python包;Jinja 是一款快速、富有表现力且可扩展的模板引擎,模板中的特殊占位符允许编写类似于 Python 语法的代码。二者结合,能便捷有效的生成数据分析报告。

jinja&pyechart安装

1
2
3
4
5
6
7

# pyecharts
pip install pyecharts

# jinja
pip install Jinja2

直接插入html

input

html模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
h1, h2 {
color: #333;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 8px;
border: 1px solid #ddd;
text-align: left;
}
th {
background-color: #f2f2f2;
}
img {
max-width: 100%;
height: auto;
}
iframe {
border: none;
}
</style>
</head>
<body>
<h1>{{ title }}</h1>

<h2>Table Data</h2>
<table>
<tr>
<th>Product</th>
<th>Amount</th>
</tr>
{% for row in table_data %}
<tr>
<td>{{ row.Product }}</td>
<td>{{ row.Amount }}</td>
</tr>
{% endfor %}
</table>

<h2>Chart</h2>
{{ chart_html }}

</body>
</html>


process

python脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

from jinja2 import Environment, FileSystemLoader
from pyecharts import options as opts
from pyecharts.charts import Bar
from pyecharts.commons.utils import JsCode
from pyecharts.globals import ThemeType

# 准备数据
report_data = {
'title': 'Sales Report',
'table_data': [
{'Product': 'Product A', 'Amount': 1000},
{'Product': 'Product B', 'Amount': 1500},
{'Product': 'Product C', 'Amount': 2000},
],
'image_path': 'path/to/image.jpg'
}


list2 = [
{"value": 12, "percent": 12 / (12 + 3)},
{"value": 23, "percent": 23 / (23 + 21)},
{"value": 33, "percent": 33 / (33 + 5)},
{"value": 3, "percent": 3 / (3 + 52)},
{"value": 33, "percent": 33 / (33 + 43)},
]

list3 = [
{"value": 3, "percent": 3 / (12 + 3)},
{"value": 21, "percent": 21 / (23 + 21)},
{"value": 5, "percent": 5 / (33 + 5)},
{"value": 52, "percent": 52 / (3 + 52)},
{"value": 43, "percent": 43 / (33 + 43)},
]

c = (
Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT))
.add_xaxis([1, 2, 3, 4, 5])
.add_yaxis("product1", list2, stack="stack1", category_gap="50%")
.add_yaxis("product2", list3, stack="stack1", category_gap="50%")
.set_series_opts(
label_opts=opts.LabelOpts(
position="right",
formatter=JsCode(
"function(x){return Number(x.data.percent * 100).toFixed() + '%';}"
),
)
)
.render("stack_bar_percent.html") # 保存为 HTML 文件
)

# 将图表的 HTML 内容读取为字符串
with open("stack_bar_percent.html", "r") as f:
chart_html = f.read()

# 准备模板数据
template_data = {
'title': 'Sales Report',
'chart_html': chart_html,
'table_data': [
{'Product': 'Product A', 'Amount': 1000},
{'Product': 'Product B', 'Amount': 1500},
{'Product': 'Product C', 'Amount': 2000},
]
}

# 创建Jinja2环境
env = Environment(loader=FileSystemLoader('templates'))

# 加载报告模板
template = env.get_template('report_template.html')

# 渲染报告模板
report = template.render(template_data)

# 将报告保存到文件
with open('sales_report.html', 'w') as f:
f.write(report)

results

图片的html

html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Awesome-pyecharts</title>
            <script type="text/javascript" src="https://assets.pyecharts.org/assets/echarts.min.js"></script>

</head>
<body>
    <div id="b36e75289fd94ccfa574eb707867f493" style="width:900px; height:500px;"></div>
    <script>
        var chart_b36e75289fd94ccfa574eb707867f493 = echarts.init(
            document.getElementById('b36e75289fd94ccfa574eb707867f493'), 'light', {renderer: 'canvas'});
        var option_b36e75289fd94ccfa574eb707867f493 = {
    "series": [
        {
            "type": "bar",
            "name": "product1",
            "data": [
                {
                    "value": 12,
                    "percent": 0.8
                },
                {
                    "value": 23,
                    "percent": 0.5227272727272727
                },
                {
                    "value": 33,
                    "percent": 0.868421052631579
                },
                {
                    "value": 3,
                    "percent": 0.05454545454545454
                },
                {
                    "value": 33,
                    "percent": 0.4342105263157895
                }
            ],
            "stack": "stack1",
            "barCategoryGap": "50%",
            "label": {
                "show": true,
                "position": "right",
                "margin": 8,
                "formatter": function(x){return Number(x.data.percent * 100).toFixed() + '%';}
            },
            "rippleEffect": {
                "show": true,
                "brushType": "stroke",
                "scale": 2.5,
                "period": 4
            }
        },
        {
            "type": "bar",
            "name": "product2",
            "data": [
                {
                    "value": 3,
                    "percent": 0.2
                },
                {
                    "value": 21,
                    "percent": 0.4772727272727273
                },
                {
                    "value": 5,
                    "percent": 0.13157894736842105
                },
                {
                    "value": 52,
                    "percent": 0.9454545454545454
                },
                {
                    "value": 43,
                    "percent": 0.5657894736842105
                }
            ],
            "stack": "stack1",
            "barCategoryGap": "50%",
            "label": {
                "show": true,
                "position": "right",
                "margin": 8,
                "formatter": function(x){return Number(x.data.percent * 100).toFixed() + '%';}
            },
            "rippleEffect": {
                "show": true,
                "brushType": "stroke",
                "scale": 2.5,
                "period": 4
            }
        }
    ],
    "legend": [
        {
            "data": [
                "product1",
                "product2"
            ],
            "selected": {
                "product1": true,
                "product2": true
            }
        }
    ],
    "tooltip": {
        "show": true,
        "trigger": "item",
        "triggerOn": "mousemove|click",
        "axisPointer": {
            "type": "line"
        },
        "textStyle": {
            "fontSize": 14
        },
        "borderWidth": 0
    },
    "xAxis": [
        {
            "show": true,
            "scale": false,
            "nameLocation": "end",
            "nameGap": 15,
            "gridIndex": 0,
            "inverse": false,
            "offset": 0,
            "splitNumber": 5,
            "minInterval": 0,
            "splitLine": {
                "show": false,
                "lineStyle": {
                    "width": 1,
                    "opacity": 1,
                    "curveness": 0,
                    "type": "solid"
                }
            },
            "data": [
                1,
                2,
                3,
                4,
                5
            ]
        }
    ],
    "yAxis": [
        {
            "show": true,
            "scale": false,
            "nameLocation": "end",
            "nameGap": 15,
            "gridIndex": 0,
            "inverse": false,
            "offset": 0,
            "splitNumber": 5,
            "minInterval": 0,
            "splitLine": {
                "show": false,
                "lineStyle": {
                    "width": 1,
                    "opacity": 1,
                    "curveness": 0,
                    "type": "solid"
                }
            }
        }
    ]
};
        chart_b36e75289fd94ccfa574eb707867f493.setOption(option_b36e75289fd94ccfa574eb707867f493);
    </script>
</body>
</html>

报告的html

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Sales Report</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
        }
        h1, h2 {
            color: #333;
        }
        table {
            width: 100%;
            border-collapse: collapse;
        }
        th, td {
            padding: 8px;
            border: 1px solid #ddd;
            text-align: left;
        }
        th {
            background-color: #f2f2f2;
        }
        img {
            max-width: 100%;
            height: auto;
        }
        iframe {
            border: none;
        }
    </style>
</head>
<body>
    <h1>Sales Report</h1>

    <h2>Table Data</h2>
    <table>
        <tr>
            <th>Product</th>
            <th>Amount</th>
        </tr>
        
        <tr>
            <td>Product A</td>
            <td>1000</td>
        </tr>
        
        <tr>
            <td>Product B</td>
            <td>1500</td>
        </tr>
        
        <tr>
            <td>Product C</td>
            <td>2000</td>
        </tr>
        
    </table>

    <h2>Chart</h2>
    <!-- 使用 <iframe> 标签插入 HTML 图表 -->
    <!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Awesome-pyecharts</title>
            <script type="text/javascript" src="https://assets.pyecharts.org/assets/echarts.min.js"></script>

</head>
<body>
    <div id="b36e75289fd94ccfa574eb707867f493" style="width:900px; height:500px;"></div>
    <script>
        var chart_b36e75289fd94ccfa574eb707867f493 = echarts.init(
            document.getElementById('b36e75289fd94ccfa574eb707867f493'), 'light', {renderer: 'canvas'});
        var option_b36e75289fd94ccfa574eb707867f493 = {
    "series": [
        {
            "type": "bar",
            "name": "product1",
            "data": [
                {
                    "value": 12,
                    "percent": 0.8
                },
                {
                    "value": 23,
                    "percent": 0.5227272727272727
                },
                {
                    "value": 33,
                    "percent": 0.868421052631579
                },
                {
                    "value": 3,
                    "percent": 0.05454545454545454
                },
                {
                    "value": 33,
                    "percent": 0.4342105263157895
                }
            ],
            "stack": "stack1",
            "barCategoryGap": "50%",
            "label": {
                "show": true,
                "position": "right",
                "margin": 8,
                "formatter": function(x){return Number(x.data.percent * 100).toFixed() + '%';}
            },
            "rippleEffect": {
                "show": true,
                "brushType": "stroke",
                "scale": 2.5,
                "period": 4
            }
        },
        {
            "type": "bar",
            "name": "product2",
            "data": [
                {
                    "value": 3,
                    "percent": 0.2
                },
                {
                    "value": 21,
                    "percent": 0.4772727272727273
                },
                {
                    "value": 5,
                    "percent": 0.13157894736842105
                },
                {
                    "value": 52,
                    "percent": 0.9454545454545454
                },
                {
                    "value": 43,
                    "percent": 0.5657894736842105
                }
            ],
            "stack": "stack1",
            "barCategoryGap": "50%",
            "label": {
                "show": true,
                "position": "right",
                "margin": 8,
                "formatter": function(x){return Number(x.data.percent * 100).toFixed() + '%';}
            },
            "rippleEffect": {
                "show": true,
                "brushType": "stroke",
                "scale": 2.5,
                "period": 4
            }
        }
    ],
    "legend": [
        {
            "data": [
                "product1",
                "product2"
            ],
            "selected": {
                "product1": true,
                "product2": true
            }
        }
    ],
    "tooltip": {
        "show": true,
        "trigger": "item",
        "triggerOn": "mousemove|click",
        "axisPointer": {
            "type": "line"
        },
        "textStyle": {
            "fontSize": 14
        },
        "borderWidth": 0
    },
    "xAxis": [
        {
            "show": true,
            "scale": false,
            "nameLocation": "end",
            "nameGap": 15,
            "gridIndex": 0,
            "inverse": false,
            "offset": 0,
            "splitNumber": 5,
            "minInterval": 0,
            "splitLine": {
                "show": false,
                "lineStyle": {
                    "width": 1,
                    "opacity": 1,
                    "curveness": 0,
                    "type": "solid"
                }
            },
            "data": [
                1,
                2,
                3,
                4,
                5
            ]
        }
    ],
    "yAxis": [
        {
            "show": true,
            "scale": false,
            "nameLocation": "end",
            "nameGap": 15,
            "gridIndex": 0,
            "inverse": false,
            "offset": 0,
            "splitNumber": 5,
            "minInterval": 0,
            "splitLine": {
                "show": false,
                "lineStyle": {
                    "width": 1,
                    "opacity": 1,
                    "curveness": 0,
                    "type": "solid"
                }
            }
        }
    ]
};
        chart_b36e75289fd94ccfa574eb707867f493.setOption(option_b36e75289fd94ccfa574eb707867f493);
    </script>
</body>
</html>


</body>
</html>

使用iframe插入html

input

html模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
h1, h2 {
color: #333;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 8px;
border: 1px solid #ddd;
text-align: left;
}
th {
background-color: #f2f2f2;
}
img {
max-width: 100%;
height: auto;
}
iframe {
border: none;
width: 100%; /* 使 iframe 充满整个容器 */
height: 500px; /* 设置 iframe 高度 */
}
</style>
</head>
<body>
<h1>{{ title }}</h1>

<h2>Table Data</h2>
<table>
<tr>
<th>Product</th>
<th>Amount</th>
</tr>
{% for row in table_data %}
<tr>
<td>{{ row.Product }}</td>
<td>{{ row.Amount }}</td>
</tr>
{% endfor %}
</table>

<h2>Chart</h2>
<!-- 使用 <iframe> 标签插入 HTML 图表 -->
<iframe src="{{ chart_html }}"></iframe>

</body>
</html>


process

python脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

from jinja2 import Environment, FileSystemLoader
from pyecharts import options as opts
from pyecharts.charts import Bar
from pyecharts.commons.utils import JsCode
from pyecharts.globals import ThemeType

# 准备数据
report_data = {
'title': 'Sales Report',
'table_data': [
{'Product': 'Product A', 'Amount': 1000},
{'Product': 'Product B', 'Amount': 1500},
{'Product': 'Product C', 'Amount': 2000},
],
'image_path': 'path/to/image.jpg'
}


list2 = [
{"value": 12, "percent": 12 / (12 + 3)},
{"value": 23, "percent": 23 / (23 + 21)},
{"value": 33, "percent": 33 / (33 + 5)},
{"value": 3, "percent": 3 / (3 + 52)},
{"value": 33, "percent": 33 / (33 + 43)},
]

list3 = [
{"value": 3, "percent": 3 / (12 + 3)},
{"value": 21, "percent": 21 / (23 + 21)},
{"value": 5, "percent": 5 / (33 + 5)},
{"value": 52, "percent": 52 / (3 + 52)},
{"value": 43, "percent": 43 / (33 + 43)},
]

c = (
Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT))
.add_xaxis([1, 2, 3, 4, 5])
.add_yaxis("product1", list2, stack="stack1", category_gap="50%")
.add_yaxis("product2", list3, stack="stack1", category_gap="50%")
.set_series_opts(
label_opts=opts.LabelOpts(
position="right",
formatter=JsCode(
"function(x){return Number(x.data.percent * 100).toFixed() + '%';}"
),
)
)
.render("stack_bar_percent_iframe.html") # 保存为 HTML 文件
)

# 将图表的 HTML 内容读取为字符串
#with open("stack_bar_percent_iframe.html", "r") as f:
# chart_html = f.read()

# 准备模板数据
template_data = {
'title': 'Sales Report',
'chart_html': "stack_bar_percent_iframe.html",
'table_data': [
{'Product': 'Product A', 'Amount': 1000},
{'Product': 'Product B', 'Amount': 1500},
{'Product': 'Product C', 'Amount': 2000},
]
}

# 创建Jinja2环境
env = Environment(loader=FileSystemLoader('templates'))

# 加载报告模板
template = env.get_template('report_template_iframe.html')

# 渲染报告模板
report = template.render(template_data)

# 将报告保存到文件
with open('sales_report_iframe.html', 'w') as f:
f.write(report)


results

图片的html

html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Awesome-pyecharts</title>
            <script type="text/javascript" src="https://assets.pyecharts.org/assets/echarts.min.js"></script>

</head>
<body>
    <div id="852d5339878f4f1bafa2e93fa1cd616a" style="width:900px; height:500px;"></div>
    <script>
        var chart_852d5339878f4f1bafa2e93fa1cd616a = echarts.init(
            document.getElementById('852d5339878f4f1bafa2e93fa1cd616a'), 'light', {renderer: 'canvas'});
        var option_852d5339878f4f1bafa2e93fa1cd616a = {
    "series": [
        {
            "type": "bar",
            "name": "product1",
            "data": [
                {
                    "value": 12,
                    "percent": 0.8
                },
                {
                    "value": 23,
                    "percent": 0.5227272727272727
                },
                {
                    "value": 33,
                    "percent": 0.868421052631579
                },
                {
                    "value": 3,
                    "percent": 0.05454545454545454
                },
                {
                    "value": 33,
                    "percent": 0.4342105263157895
                }
            ],
            "stack": "stack1",
            "barCategoryGap": "50%",
            "label": {
                "show": true,
                "position": "right",
                "margin": 8,
                "formatter": function(x){return Number(x.data.percent * 100).toFixed() + '%';}
            },
            "rippleEffect": {
                "show": true,
                "brushType": "stroke",
                "scale": 2.5,
                "period": 4
            }
        },
        {
            "type": "bar",
            "name": "product2",
            "data": [
                {
                    "value": 3,
                    "percent": 0.2
                },
                {
                    "value": 21,
                    "percent": 0.4772727272727273
                },
                {
                    "value": 5,
                    "percent": 0.13157894736842105
                },
                {
                    "value": 52,
                    "percent": 0.9454545454545454
                },
                {
                    "value": 43,
                    "percent": 0.5657894736842105
                }
            ],
            "stack": "stack1",
            "barCategoryGap": "50%",
            "label": {
                "show": true,
                "position": "right",
                "margin": 8,
                "formatter": function(x){return Number(x.data.percent * 100).toFixed() + '%';}
            },
            "rippleEffect": {
                "show": true,
                "brushType": "stroke",
                "scale": 2.5,
                "period": 4
            }
        }
    ],
    "legend": [
        {
            "data": [
                "product1",
                "product2"
            ],
            "selected": {
                "product1": true,
                "product2": true
            }
        }
    ],
    "tooltip": {
        "show": true,
        "trigger": "item",
        "triggerOn": "mousemove|click",
        "axisPointer": {
            "type": "line"
        },
        "textStyle": {
            "fontSize": 14
        },
        "borderWidth": 0
    },
    "xAxis": [
        {
            "show": true,
            "scale": false,
            "nameLocation": "end",
            "nameGap": 15,
            "gridIndex": 0,
            "inverse": false,
            "offset": 0,
            "splitNumber": 5,
            "minInterval": 0,
            "splitLine": {
                "show": false,
                "lineStyle": {
                    "width": 1,
                    "opacity": 1,
                    "curveness": 0,
                    "type": "solid"
                }
            },
            "data": [
                1,
                2,
                3,
                4,
                5
            ]
        }
    ],
    "yAxis": [
        {
            "show": true,
            "scale": false,
            "nameLocation": "end",
            "nameGap": 15,
            "gridIndex": 0,
            "inverse": false,
            "offset": 0,
            "splitNumber": 5,
            "minInterval": 0,
            "splitLine": {
                "show": false,
                "lineStyle": {
                    "width": 1,
                    "opacity": 1,
                    "curveness": 0,
                    "type": "solid"
                }
            }
        }
    ]
};
        chart_852d5339878f4f1bafa2e93fa1cd616a.setOption(option_852d5339878f4f1bafa2e93fa1cd616a);
    </script>
</body>
</html>

报告的html

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Sales Report</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
        }
        h1, h2 {
            color: #333;
        }
        table {
            width: 100%;
            border-collapse: collapse;
        }
        th, td {
            padding: 8px;
            border: 1px solid #ddd;
            text-align: left;
        }
        th {
            background-color: #f2f2f2;
        }
        img {
            max-width: 100%;
            height: auto;
        }
        iframe {
            border: none;
            width: 100%; /* 使 iframe 充满整个容器 */
            height: 500px; /* 设置 iframe 高度 */
        }
    </style>
</head>
<body>
    <h1>Sales Report</h1>

    <h2>Table Data</h2>
    <table>
        <tr>
            <th>Product</th>
            <th>Amount</th>
        </tr>
        
        <tr>
            <td>Product A</td>
            <td>1000</td>
        </tr>
        
        <tr>
            <td>Product B</td>
            <td>1500</td>
        </tr>
        
        <tr>
            <td>Product C</td>
            <td>2000</td>
        </tr>
        
    </table>

    <h2>Chart</h2>
    <!-- 使用 <iframe> 标签插入 HTML 图表 -->
    <iframe src="stack_bar_percent_iframe.html"></iframe>

</body>
</html>