需求
近日几个高中朋友都上岸研究生,某人提议做一个求学路线图,这种技术活儿当然就交给我了。
- 一共是两幅图,本科城市分布图和研究生城市分布图
- 背景是中国地图
- 在地图中标记大学所在城市
- 在标记点上显示每个人的头像
- 附录显示大学名称
- 每个人本科和研究生之间添加一条线
- 导出为图片
环境
python 3.11.2
pyecharts 2.0.3
selenium 4.8.3
pyecharts-snapshot 0.2.0
pyecharts
官方文档
是一个基于Echarts的python工具包,用于数据可视化,可以用来画日历图、饼图、雷达图、词云图、涟漪特效图、热力图、散点图、数图、地图、3D图等
安装pyecharts
pip install pyecharts
pyecharts-snapshot
将 pyecharts/echarts.js 的输出呈现为 png、jpeg、gif、eps、svg 图像、原始 base64 编码或 pdf 文件
安装pyecharts-snapshot
pip install pyecharts-snapshot
使用
pyecharts-snapshot用来将地图渲染成图片。
要使用pyecharts-snapshot,确保电脑正确安装Phantomjs,否则会报错,Phantomjs官网下载链接,选择符合自己电脑的版本下载。
下载完成之后,将bin加入到系统变量中。
开始绘图
使用pyecharts的过程,会学到链式调用和万物皆可对象化!
一、初始化画布
第一步,我们要画中国地图,创建一个Geo对象(地理图标)
geo = Geo()
在Geo()
中初始化地图,设置一些基本的参数
一般写法
geo = Geo(width="1000px",height="600px",theme=ThemeType.ESSOS, # 地图主题)
但是在pyecharts中有一个重要的思想就是
标准的写法是
geo = Geo(init_opts=opts.InitOpts(width="1000px", # 画布宽度height="600px", # 画布高度theme=ThemeType.ESSOS, # 地图主题))
在这里使用init_opts
进行初始化,init_opts
是一个options
,值由opts.InitOpts
实例化得到,而width="1000px"...
等params
则作为opts.InitOpts
的参数。
个人认为万物皆options有以下几点好处:
- 统一化:所有的图标对象都可以使用同一个参数
- 方便查阅文档:需要哪个options就在文档中查找哪个,很直观。
比如这个InitOpts
,经查阅文档后共有这么多属性
class InitOpts(# 图表画布宽度,css 长度单位。width: str = "900px",# 图表画布高度,css 长度单位。height: str = "500px",# 图表 ID,图表唯一标识,用于在多图表时区分。chart_id: Optional[str] = None,# 渲染风格,可选 "canvas", "svg"# # 参考 `全局变量` 章节renderer: str = RenderType.CANVAS,# 网页标题page_title: str = "Awesome-pyecharts",# 图表主题theme: str = "white",# 图表背景颜色bg_color: Optional[str] = None,# 远程 js host,如不设置默认为 https://assets.pyecharts.org/assets/"# 参考 `全局变量` 章节js_host: str = "",# 画图动画初始化配置,参考 `global_options.AnimationOpts`animation_opts: Union[AnimationOpts, dict] = AnimationOpts(),
)
再加上一些必要的参数,完整版初始化代码如下:
geo = Geo(init_opts=opts.InitOpts(width="1000px", # 画布宽度height="600px", # 画布高度theme=ThemeType.ESSOS, # 地图主题),is_ignore_nonexistent_coord=True)
二、设置地图类型
调用.add_schema()
方法设置Geo
对象的地图类型、缩放、视角中心点等
.add_schema(maptype='china' # 设置地图类型为chinazoom=1.5, # 缩放比例为1.5center=[105.95, 34.27], # 设置视角中心点为[105.95, 34.27])
其中zoom
center
都是为后面导出图像设置的,效果如下
三、添加点
调用geo
对象的.add
方法,添加需要显示的点,并设置其样式
这里最坑的是,文档中没有给出点实例,导致调试data废了很大功夫
以北京举例:应该是("北京“, [116.407526, 39.90403])
,数据项为元组类型,第一个元素为坐标点名称,第二个元素为坐标点的经度纬度,如果多个点,则data_pair
为列表形式
这里有个坑,名称和经纬度必须一一对应才能正确显示,如果把北京改成北京市区,就不行了,估摸着他应该内置了一个对应关系
.add(series_name='研究生', # 这一系列点的名称symbol_size=18, # 标志(圆圈)的尺寸data_pair=city_graduate, # 数据项type_="scatter", # 数据类型 label_opts=opts.LabelOpts( #标签(就是文字)is_show=True, # 是否显示标签formatter='{b}', # 标签显示的格式font_size=20, # 标签的文字大小font_style="normal", # 标签的样式))
四、渲染HTML
调用geo.render
参数为渲染后的html文件路径
.render('html/graduate.html')
五、渲染图片
第一个参数为渲染引擎,这里选择phantomjs
;第二个参数是html文件路径;第三个参数是渲染后的图片文件路径
from snapshot_phantomjs import snapshot
from generate_coordinate import generate_coordinatemake_snapshot(snapshot, 'html/graduate.html', 'image/graduate.png')
六、整合
这里不得不提python的链式调用,十分直观,简约
geo = (Geo(init_opts=opts.InitOpts(width="1000px", # 画布宽度height="600px", # 画布高度theme=ThemeType.ESSOS, # 地图主题),is_ignore_nonexistent_coord=True).add_schema(maptype='china' # 设置地图类型为chinazoom=1.5, # 缩放比例为1.5center=[105.95, 34.27], # 设置视角中心点为[105.95, 34.27]).add(series_name='研究生', # 这一系列点的名称symbol_size=18, # 标志(圆圈)的尺寸data_pair=city_graduate, # 数据项type_="scatter", # 数据类型 label_opts=opts.LabelOpts( #标签(就是文字)is_show=True, # 是否显示标签formatter='{b}', # 标签显示的格式font_size=20, # 标签的文字大小font_style="normal", # 标签的样式)).render('html/graduate.html'))
把所有有关这个对象的操作链成一个链条执行,将每个操作的结果组合,返回给geo对象。
遇到的BUG
- 城市不能自己命名,否则会无法正常显示
- 如果在渲染图片的时候遇到
“ReferenceError: Can‘t find variable: echarts\n\n undefined:1\nnull\n“
错误,考虑项目目录中是否有中文,将其移动到纯英文目录中,问题就解决了。
这还是我google来的,仔细思考一下,
can't find variable
问题出在找不到echarts变量,但是echarts变量是存在的,否则画图就会报错,这样变量存在却找不到的情况,多半是路径出现问题,考虑到中文路径,以后这类Bug都可以往这方面想。
完整项目代码
github链接
成品展示
总结
至此,城市分布图地图画好了!!短短的几行代码花了我六小时琢磨,反思一下:
- 代码版本 确认用pyecharts之后,就在直接找线程的代码,以期用最短的事件完成这个任务,关键是完全没考虑到版本问题,代码粘贴下来就是一顿报错,按照我的习惯,遇上bug就找chatgpt,谁知道chatgpt给出的代码也是版本不一致,就这样带着一次成功的功利目的,花费了大量的时间内耗。这一次真切感受到了复制代码前先确定版本号的重要性。
- 官方文档 这些用户比较少的工具,社区、博客往往不活跃,这时候文档就是救命的东西,文档永远是最新的,pip安装的也永远是最新的,所以这俩是永远适配的。一般文档都会配有快速上手,经验之谈,5分钟的快速上手可以帮助节省50分钟之多。