需求

近日几个高中朋友都上岸研究生,某人提议做一个求学路线图,这种技术活儿当然就交给我了。

  • 一共是两幅图,本科城市分布图和研究生城市分布图
  • 背景是中国地图
  • 在地图中标记大学所在城市
  • 在标记点上显示每个人的头像
  • 附录显示大学名称
  • 每个人本科和研究生之间添加一条线
  • 导出为图片

环境

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' # 设置地图类型为china
    			 zoom=1.5, # 缩放比例为1.5
    			 center=[105.95, 34.27], # 设置视角中心点为[105.95, 34.27]
               )

其中zoom center都是为后面导出图像设置的,效果如下
zoom为1.5
zoom为1

zoom=1,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_coordinate

make_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' # 设置地图类型为china
			 zoom=1.5, # 缩放比例为1.5
			 center=[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

  1. 城市不能自己命名,否则会无法正常显示
  2. 如果在渲染图片的时候遇到“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分钟之多。
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐