环境
windows11 python-3.10.5 pyecharts-2.0.3,其他版本没试过。
需求
之前用0.5版本的pyecharts倒是没遇到离线使用的问题。换了v2版本后遇到了。
网上查到的大多数方法是开个http服务器,让浏览器访问生成图表的html时下载js脚本改为从本地服务器下载,从而解决pyecharts离线使用问题。但我的需求是:
1、不需要开服务器也能生成html图表。
2、生成的html不用再另外下载js脚本,换台电脑也能直接打开显示图表。
3、不在源码上改动,换台电脑使用一样的pyecharts也可以运行使用。
解决过程
下载js脚本
改动前,生成一份非嵌入js的图表html,找到js脚本链接,下载到本地。
浏览器访问https://assets.pyecharts.org/assets/v5/echarts.min.js,保存下js文件。
嵌入JS配置
需求2很好解决,把js代码嵌入html即可。在pyecharts手册里有提到一个渲染配置项RenderOpts,提供是否在渲染HTML时嵌入JS文件,默认False不嵌入。若想要“换台电脑也能直接打开显示图表”这个效果,则把此配置项设为True即可。
创建图表时候,初始化时候指定下render_opts参数,如下柱状图代码:
bar = Bar(init_opts=opts.InitOpts(), render_opts=opts.RenderOpts(is_embed_js=True))
加载js代码方法重写
其实到本步骤,使用本地开服务器方式+嵌入js到html方法,也能勉强满足我原始的需求,但是运行时候发现了个问题,我是用http方式开的服务,但是嵌入js脚本代码却是用https请求访问js资源后才去嵌入html的,导致了异常报错。修改源码把HTTPSConnection改为HTTPConnection发现可以,进一步可以取scheme进行http/https判断走不同逻辑请求。但是我并不想在源码做改动,这样我换台电脑还要改,显然挺麻烦的。
这里我想到了猴子补丁的方法,将源码里的load_javascript_contents方法替换为自己编写的方法。load_javascript_contents的逻辑延用源码的逻辑,然后补充2个点。1个是如果请求的js是echarts.min.js(别的同理),则读取本地echarts.min.js文件内容赋值,达成离线使用效果。1个是判断请求是http还是https,走不同的连接,后续如果扩展可能需要。替换方法的代码如下:
import http.client
from typing import Optional
from urllib.parse import urlparse
from pyecharts import options as opts
from pyecharts.charts import Bar
from pyecharts.render.display import Javascript
class ChartsLib(object):
def __init__(self):
Javascript.load_javascript_contents = self.load_javascript_contents
@staticmethod
def read_echarts_min_js():
with open("echarts.min.js", "r", encoding="utf-8") as f:
text = f.read()
f.close()
return text
@staticmethod
def load_javascript_contents(self):
for lib in self.lib:
if "echarts.min.js" in lib:
self.javascript_contents[lib] = ChartsLib.read_echarts_min_js()
continue
parsed_url = urlparse(lib)
scheme: str = parsed_url.scheme
host: str = str(parsed_url.hostname)
port: int = parsed_url.port
path: str = parsed_url.path
resp: Optional[http.client.HTTPResponse] = None
try:
if scheme == "https":
conn = http.client.HTTPSConnection(host, port)
else:
conn = http.client.HTTPConnection(host, port)
conn.request("GET", path)
resp = conn.getresponse()
if resp.status != 200:
raise RuntimeError("Cannot load JavaScript lib: %s" % lib)
self.javascript_contents[lib] = resp.read().decode("utf-8")
finally:
if resp is not None:
resp.close()
return self
@staticmethod
def demo_bar(infos, interactions, html_name="bar_interactions.html"):
bar = Bar(init_opts=opts.InitOpts(height="1000px"), render_opts=opts.RenderOpts(is_embed_js=True))
bar.add_xaxis(infos)
bar.add_yaxis("xx数据", interactions)
bar.reversal_axis()
bar.set_series_opts(label_opts=opts.LabelOpts(position="right"))
bar.set_global_opts(title_opts=opts.TitleOpts(title="示例表"),
toolbox_opts=opts.ToolboxOpts(),
datazoom_opts=[
opts.DataZoomOpts(orient="horizontal", range_start=0, range_end=100),
opts.DataZoomOpts(orient='vertical', range_start=0, range_end=100)
])
bar.render(html_name)
return bar
这样就达到了3个需求点要求了。