(这是20年的笔记,所以使用的pyecharts版本在现在看来可能有些过时了,供参考)
简单介绍一下如何用pyecharts来绘图,主要是讲地理图的绘制。
pyecharts是知名绘图工具echarts的python版本,以输出的HTML为载体,用于绘制可交互的展示图。
我在20年看的时候,当时还是分为v0.5和v1.0两系版本,1.0版本是对0.5的颠覆式更新。然后我现在看了看,已经更新到v2.x版本了。。
网上关于v1.0版本的使用介绍比较少,这方面讲解的最好的是pyecharts 的官方文档,链接:官方文档,写的很全面,而且每个讲解后面都带了个Demo。
多看看官网的参考文献,尤其是Gallery的部分,基本讲完了很多图的绘制。
from pyecharts import options as opts
from pyecharts.charts import Geo
from pyecharts.faker import Faker
from pyecharts.globals import ChartType
import pandas as pd
from pyecharts.charts import Map
from pyecharts.render import make_snapshot
from snapshot_selenium import snapshot
def draw(zz_cors, custom_cors, agent_cors):
g = Geo(init_opts=opts.InitOpts(width="1200px", height="950px")) # 设置HTML中画布的大小
coors = {} # 待加入的所有坐标,key=每个坐标的name,value是坐标的组合,在这里就是'[经度,纬度]'
value_pairs = [] # 为所有坐标分段,即为每个坐标加入一个段标识,这样以后可以对相同段的数据做一些集中处理,比如说采用同一种颜色标注等。
i = 0
for j in range(len(custom_cors)):
coors[str(i)] = custom_cors[j]
value_pairs.append([str(i), 3])
i += 1
for j in range(len(zz_cors)): #
coors[str(i)] = zz_cors[j]
value_pairs.append([str(i), 4])
i+=1
coors[str(i)] = [114.2265222, 30.52285061]
value_pairs.append([str(i), 2])
i+=1
coors[str(i)] = [114.4879442, 30.56922194] #
value_pairs.append([str(i), 1])
for key, value in coors.items():
g.add_coordinate(key, value[0], value[1]) # 将所有自定义坐标加入Geo中
# ECharts 提供的标记类型包括
# # 'circle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow', 'none'
# # 可以通过 'image://url' 设置为图片,其中 URL 为图片的链接,或者 dataURI。
# # 可以通过 'path://' 将图标设置为任意的矢量路径。
pieces = [ # 对不同的分段进行具体的设置
{'value': '1', 'label': '名字_1', 'color': '#8A2BE2', 'symbolSize': 20, 'symbol':'pin', 'colorAlpha':1},
{'value': '2', 'label': '名字_2', 'color': 'red', 'symbolSize': 20, 'symbol': 'pin', 'colorAlpha':1},
{'value': '3', 'label': '名字_3', 'color': 'cyan', 'symbolSize': 2, 'symbol': 'circle'},
{'value': '4', 'label': '名字_4', 'color': '#002CFF', 'symbolSize': 5, 'symbol': 'roundRect'},
]
c = (
# g.add_schema(maptype="china")
g.add_schema(maptype="湖北")
# g.add_schema(maptype="武汉")
.add(
"",
# [list(z) for z in zip(names, values)],
value_pairs,
type_=ChartType.SCATTER,
symbol_size=5,
color='#00FFF3',
is_large=True,
)
.set_series_opts(label_opts=opts.LabelOpts(is_show=False)) # is_show=FALSE,默认不显示每个点对应的value
.set_global_opts(
visualmap_opts=opts.VisualMapOpts(is_piecewise=True, pieces=pieces),
# legend_opts = opts.LegendOpts(pos_top='top'),
title_opts=opts.TitleOpts(title=""),
)
)
c.render('test.html')
def read_zhongzhi():
file = '../data/机构地址规范版本.csv'
data = pd.read_csv(file, encoding='gbk')
cors = []
values = []
for idx, row in data.iterrows():
if row['PROVINCE'] == '湖北省':
cors.append([row[11], row[12]])
return cors
def read_custom():
file = '../data/20201102_202011031827.csv'
data = pd.read_csv(file, encoding='utf8')
cors = []
values = []
for idx, row in data.iterrows():
cors.append([row['GIS_LNG'], row['GIS_LAT']])
return cors
def read_agent():
file = '../data/query-hive-48766.csv'
data = pd.read_csv(file, encoding='gbk')
cors = []
values = []
for idx, row in data.iterrows():
cors.append([row['tmp_tsales_agnt_loc_all_last.gis_lng'], row['tmp_tsales_agnt_loc_all_last.gis_lat']])
return cors
if __name__ == '__main__':
zz_cors = read_zhongzhi()
custom_cors = read_custom()
agent_cors = read_agent()
draw(zz_cors, custom_cors, agent_cors)
需要注意的是,自从 v0.3.2 开始,为了缩减项目本身的体积以及维持 pyecharts 项目的轻量化运行,pyecharts 将不再自带地图 js 文件。如用户需要用到地图图表,可自行安装对应的地图文件包。下面介绍如何安装。
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple echarts-countries-pypkg
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple echarts-china-provinces-pypkg
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple echarts-china-cities-pypkg
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple echarts-china-counties-pypkg
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple echarts-china-misc-pypkg
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple echarts-united-kingdom-pypkg#如果提示缺少这个就安装一下
pip install pyecharts_snapshot
实例代码在pyecharts官网:折线图Line/Line-Distribution_of_electricity;
下方的区间是可以随意选择的,实例代码在pyecharts官网:折线图Line/Line-Beijing_aqi;
其中有个maptype参数,可选项有很多,具体可以参考pyecharts包目录下datasets / map_filenames.json 文件
map类型跟Geo类型不同,
先来个小实例:
from pyecharts import options as opts
from pyecharts.charts import Map
from pyecharts.faker import Faker
import pandas as pd
from pyecharts.globals import CurrentConfig
def draw(datas):
canvas = Map(init_opts=opts.InitOpts(width="1200px", height="950px"))
# 分段图例,数据小于该值会自动归入某段
pieces = [
{'min': 0, 'max': 1000, 'label': '0 - 1000', 'color': '#FFFACD'},
{'min': 1000, 'max': 2000, 'label': '1000 - 2000', 'color': '#FFD700'},
{'min': 2000, 'max': 5000, 'label': '2000 - 5000', 'color': '#FFA54F'},
{'min': 5000, 'max': 10000, 'label': '5000 - 10000', 'color': '#FF6347'},
{'min': 10000, 'max': 1000000, 'label': '> 10000', 'color': '#A52A2A'},
]
canvas.add(
"",
datas,
'china', # 定义映射的地图文件,省级,
#'china-cities', # 定义映射的地图文件,市级,
label_opts=opts.LabelOpts(is_show=True),
is_map_symbol_show=False, # 是否显示标记图形
)
canvas.set_global_opts(
title_opts=opts.TitleOpts(title="各省分布"),
visualmap_opts=opts.VisualMapOpts(min_=0,
max_=100000,
# range_color=['#FFFFFF', '#FFEC8B', '#FFA500', '#FF3030'],
is_piecewise=True,
pieces=pieces,
textstyle_opts=opts.TextStyleOpts(font_size=20) # 设置分段图例的文本大小
), # 视觉映射配置项
)
# 配置系列项
canvas.set_series_opts(label_opts=opts.LabelOpts(
position='Bottom',
is_show= True # 是否显示系列值
),
)
canvas.render("province_kehu.html")
def read_gaoke_data():
file = '../data/客户-省级分布.xlsx'
data = pd.read_excel(file, )
values = []
for idx, row in data.iterrows():
values.append([row['FIL_NAME'], row['N_TOTAL']])
return values
if __name__ == '__main__':
v_gaoke = read_gaoke_data()
draw(v_gaoke)
这个跟下面一小节“在图中显示value值”的目的是基本相同的,写下面那个小节时,我对图label的formatter参数并没有什么了解,所以解决方案比较曲折且局限,然今天突然有个需求,用下面那个小节并无法解决问题,于是拓展探索到了formatter的作用。
这个需求是这样的:
还是画城市地图,但是数据不止城市名和城市内用户数量这两列,还有一列“城市内用户占比”,要求用“用户数量”来绘制map,但是标记点附近要show出“城市内用户占比”,类似
用下面小节无法完成,于是开始偶然探索到了formatter。
具体参见本小节的参考文献1
pyecharts中formatter参数支持字符串模板和回调函数两种形式。
字符串模板其实就是下一小节那种方式,就是abcd那几个选项,
让我先放一下官网上对formatter的解释:
# 标签内容格式器,支持字符串模板和回调函数两种形式,字符串模板与回调函数返回的字符串均支持用 \n 换行。
# 模板变量有 {a}, {b},{c},{d},{e},分别表示系列名,数据名,数据值等。
# 在 trigger 为 'axis' 的时候,会有多个系列的数据,此时可以通过 {a0}, {a1}, {a2} 这种后面加索引的方式表示系列的索引。
# 不同图表类型下的 {a},{b},{c},{d} 含义不一样。 其中变量{a}, {b}, {c}, {d}在不同图表类型下代表数据含义为:
# 折线(区域)图、柱状(条形)图、K线图 : {a}(系列名称),{b}(类目值),{c}(数值), {d}(无)
# 散点图(气泡)图 : {a}(系列名称),{b}(数据名称),{c}(数值数组), {d}(无)
# 地图 : {a}(系列名称),{b}(区域名称),{c}(合并数值), {d}(无)
# 饼图、仪表盘、漏斗图: {a}(系列名称),{b}(数据项名称),{c}(数值), {d}(百分比)
# 示例:formatter: '{b}: {@score}'
#
# 回调函数,回调函数格式:
# (params: Object|Array) => string
# 参数 params 是 formatter 需要的单个数据集。格式如下:
# {
# componentType: 'series',
# // 系列类型
# seriesType: string,
# // 系列在传入的 option.series 中的 index
# seriesIndex: number,
# // 系列名称
# seriesName: string,
# // 数据名,类目名
# name: string,
# // 数据在传入的 data 数组中的 index
# dataIndex: number,
# // 传入的原始数据项
# data: Object,
# // 传入的数据值
# value: number|Array,
# // 数据图形的颜色
# color: string,
# }
formatter: Optional[str] = None,
但是字符串形式太生硬,可扩展性不高,所以也可以选择回调函数,即:
from pyecharts.commons.utils import JsCode
# then
.add(
type_="effectScatter",
series_name="",
data_pair=data,
symbol_size=10,
effect_opts=opts.EffectOpts(),
label_opts=opts.LabelOpts(
position="top",
is_show=True, #is_show是否显示标签,点上面的内容
formatter=JsCode( #formatter为标签内容格式器{a}:系列名;{b}:数据名;{c}:数值数组也可以是回调函数
"""function(params) {
if ('value' in params.data) {
return params.data.value[2];
}
}"""
),#显示数据,可以去掉经纬度只显示数值return params.data.value[2] + ': ' + params.data.value[0]+': ' + params.data.value[1];
),
itemstyle_opts=opts.ItemStyleOpts(),
is_selected=True, #选中图例
)
通过params.data.value[i]可以显示数组对应的值,value[0]表示第一个值。
在上面的需求中,我的数据集格式是:
[
['北京', [20, '3%']],
['武汉', [56, '2%']],
....
]
所以用params.data.value[0]可以调用“用户数量”, value[1]调用“用户占比”。
所以formatter可以写成:
canvas.set_series_opts(label_opts=opts.LabelOpts(
position='Bottom',
is_show= True, # 是否显示系列值
formatter=JsCode(
'''function(params){
if('value' in params.data){
return params.name + '\n'+ params.data.value[1]
}
}'''
)
)
然后,然后指定绘图用的数据维度,一般情况下,value是一维,所以默认绘图就可;但是在上面的例子中,value是二维的,所以我们需要显式指定绘图所用数据维度,即在visualmap_opts中设置dimension参数,经过测试,0是value中的第一列,2及以上都是value中的第二列,1不知道是什么鬼,暂时不清楚;
canvas.set_global_opts(
title_opts=opts.TitleOpts(title="各省分布"),
visualmap_opts=opts.VisualMapOpts(min_=0,
max_=10000,
# range_color=['#FFFFFF', '#FFEC8B', '#FFA500', '#FF3030'],
series_index=0, # 指定取哪个系列的数据
dimension=0, # 组件映射维度, 控制用x,y哪个维度来画图(做分段值)
is_piecewise=True,
pieces=pieces), # 视觉映射配置项
)
参考文献:
实际上map应该是有这个功能的,map.add()中可以设置参数:
label_opts=opts.LabelOpts(is_show=True)
来启用显示,但是显示的是城市名,因为我们为画板传入的数据是
[
[city_1,num_1],
[city_2,num_2],
....,
]
如果我们需要将num也加入显示,似乎光靠提供的API接口是实现不了的。
因此我们需要进行下面的处理:
在目标html生成后,修改body下面的js脚本,即在series的label下新增一个“normal”节点,
"series": [
{
"type": "map",
"label": {
"show": true,
"position": "Bottom",
"margin": 8,
"normal": {
"show": true,
"formatter":'{b}\n{c}', //有理由相信,这个类似形参,是按a、b、c...等的顺序依次排列的(猜错了,好像不是)
"textStyle": {
"fontSize": 12
},
"position": "bottom"
}
},
"mapType": "china",
"data": [
{
"name": "\u897f\u85cf",
"value": 77
},
.....
]
参考文献:
关于pyecharts 地图显示添加数据的问题
地图绘制完成后,render成一个html文件,这里简单介绍一下这个html的内容。
首先是header头,这里会定义绘制所需的地图资源,假设我在Map对象中声明的maptype是“china”,那header头引入的js地图资源形如:
<script type="text/javascript" src="https://assets.pyecharts.org/assets/echarts.min.js">script>
<script type="text/javascript" src="https://assets.pyecharts.org/assets/maps/china.js">script>
意思是从远程库拉指定地图资源,第一个是布局资源,第二个是地图本身。
需要知道的是,这些地图资源在本地实际上是存在的(我是说第二个,第一个没找到),比如说第二个js资源,在你python安装目录下\Lib\site-packages\echarts_countries_pypkg\resources\echarts-countries-js\里,但是从html的声明中来看,pyecharts还是优先选择从云端拉仓库资源,可能如果不联网的话,才会选择拉本地地图资源。
需要提一句,python安装目录下Lib\site-packages\pyecharts\datasets目录下有一个map_filename.json里,里面内容类似:
放的是maptype在云端的映射路径;
理论上,接下来是理论上,如果想换成本地的js资源的话,只需要把script中的src路径换成本地js的路径,就可以实现用本地js绘图,可借此实现调用本地的自定义地图资源。想法很好,(但是实现上稍微有问题,有时候不一定成功,我不清楚为什么),另外,在更换本地路径时,要记得用相对路径,不能使用绝对路径,在绝对路径下,浏览器会报not allow to load local resource。
在html中,还有一个比较重要的组成成分,就是body中的script,里面有一个var,var里面有一个叫做series的数组,这个数组中有个data数组,里面放的都是你在绘点时加入的数据,形如:
"series": [
{
"type": "map",
"label": {
"show": false,
"position": "Bottom",
"margin": 8
},
"mapType": "china-cities",
"data": [
{
"name": "\u594e\u5c6f",
"value": 2
},
{
"name": "\u4e09\u6c99",
"value": 5
},
{
"name": "\u5de2\u6e56",
"value": 13
},
这里我画的是中国的城市分布,所以name是城市名,value是城市对应的值,这个value将配合我设定的pieces分段函数来控制每个城市对应的颜色。这里的name是Unicode码格式,可以在网上随便搜一个Unicode在线转换网站,比如说,转换,把它转成中文,如果想加新数据的话,也可以把它转成Unicode码,设定好value,塞进data数组中即可;
首先需要写好一个新地图的js,然后在生成的html中header的script,显式调用新js的路径即可;
第二个步骤参考上一小节“目标html的解析”;
关于新建地图js,可以直接依托老js,比如说,我想生成中国的省份地图,这一点,通过设置Map的maptype=‘china’就可以实现,但是如果我想在这个基础上再加工一下,比如说我想在省份的基础上再显示出几个特殊的城市,比如说大连,形如:
那这样该怎么办呢?
方法很简单,首先找到china.js,复制一个副本出来,命名为new_china.js,然后找到存在本地的辽宁省地图js,把里面的大连市数据(包含边缘经纬度集合)复制出来放到new_china.js对应位置(提一句,原始的js文件格式都很乱,推荐找个在线网站,格式化一下js再使用)。
这里需要提一下,推荐先以原生的china.js生成html,之后在手动修改html的script来引入新地图;(因为之前试过以新地图.js来生成html,失败)
参考文献:pyecharts 自定义地图之添加js文件 思路参考这个文献
原生地图的js只有一行,推荐先以js格式化工具将其格式化后再查看,另外,不推荐以vscode配合插件来格式化js,因为我整了半天没整好,太费劲了,推荐找个在线js格式化网站来做。
下面是格式化好的china.js
每个geometry里coordinates属性里含着的应该是每个省的边缘经纬度,显示成上面乱码的样子不是因为编码格式不对,似乎是因为pyecharts对这些经纬度点进行了压缩,目前不可逆(如果你实在想知道每个省的边缘经纬度集合,可以去第4个参考文献那里去下
)。
虽然肉眼看不出来,但是不影响使用。在自定义地图时,直接粘贴就可以,不影响使用,程序内部可以自解;
默认设置下,每个省上所展示文件的位置,是在该省的省会位置,并不是该省居中位置,所以展示的文字可能看的比较奇怪,比如:
文字靠的太近,影响观看的效果。
修改的方法也很简单,把地图js里每个省存的中心坐标改成你想要的经纬度坐标就可以了,“properties”下的“cp”属性存储的是默认的省会文字位置的经纬度,把这个调了就可以了;
参考文献:解决Echarts 中国地图省份上文字不居中的问题
关于如何设置桑基图的顺序,找了很久,并找不到方式。
echarts桑基图 设置每个节点的不同颜色 虽然说的是echarts,但是pyecharts可以用,根据这里面说得改html,或许有可以直接代码设置的方式,但是目前还没找到。
现在暂时还没找到在代码中的设置方式,但是确认了怎么在最终生成的html中修改:
"series": [
{
"type": "sankey",
"data": [
{
"name": "2018\u5e74"
,
"itemStyle": {
"color":"#EE7700"
}
},
{
"name": "2019\u5e74\u7559\u5b58"
,
"itemStyle": {
"color":"#EE7700"
}
},
就是在series中添加itemStyle。