文章目录
- 简介
- Geo地理图绘制
- 折线图
- 区域突出显示
- 横坐标带选择展示
- add
- 地图Map
- formatter控制value显示
- 在图中显示value值
- 目标html的解析
- 自定义地图js资源
- 原生地图js的解析
- 解决省份上文字不居中的问题
- 桑基图
- 设置桑基柱的颜色
- 参考文献
简介
(这是20年的笔记,所以使用的pyecharts版本在现在看来可能有些过时了,供参考)
简单介绍一下如何用pyecharts来绘图,主要是讲地理图的绘制。
pyecharts是知名绘图工具echarts的python版本,以HTML为载体,用于绘制可交互的展示图。
多看看官网的参考文献,尤其是Gallery的部分,基本讲完了很多图的绘制。
Geo地理图绘制
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)
折线图
区域突出显示
实例代码在pyecharts官网:折线图Line/Line-Distribution_of_electricity;
横坐标带选择展示
下方的区间是可以随意选择的,实例代码在pyecharts官网:折线图Line/Line-Beijing_aqi;
add
其中有个maptype参数,可选项有很多,具体可以参考pyecharts包目录下datasets / map_filenames.json 文件
地图Map
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)
formatter控制value显示
这个跟下面一小节“在图中显示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), # 视觉映射配置项
)
参考文献:
- pyecharts如何使用formatter回调函数
- pyecharts的的label_formatter的作用
在图中显示value值
实际上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 地图显示添加数据的问题
目标html的解析
地图绘制完成后,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资源
首先需要写好一个新地图的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只有一行,推荐先以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。
参考文献
- pyecharts绘制geo地图
- pyecharts官网含API文档
- pyecharts画廊官网-很多demo
- 自定义地图 JSON 资源 这个有点意思,从这里可以下到中国各省市地图的边缘点经纬度集合。
- pyecharts自定义地图 提供了一种自定义地图的新思路,把坐标也给换了。
- pyecharts指定从本地加载js路径修改默认远程优先拉取 比较有趣,但是我没成功