最近研究CJ Mayes博客里的一篇文章《Don’t Burst My Bubble》,里面介绍了circular packing的做法。
经过一段时间的研究,源代码只包含生成图形的部分,我对源代码进行了一些修改,增加了数据处理过程,用Tableau画圆的部分也采用目前自己最喜欢的并集+表计算的方式。
由于涉及代码和tableau画圆的知识,属于高级图形的范畴,所以本文不做过多原理的讲解。
数据处理
原文直接使用的是json格式的数据。
这种数据我们不好获取,正常情况下,我们excel里的数据是这样存储的。
所以需要通过python转成有层次的json格式,代码如下:
import pandas as pd
import json
df=pd.read_csv('test.csv',encoding='gb2312')
Level1_sum=df.groupby('Level1').sum('value').reset_index()
Level1_sum.rename(columns={'Level1':'Lebel'},inplace=True)
concat_sum=pd.concat([Level1_sum])
dic_Level1=[]
for x in range(Level1_sum.shape[0]):
Level2_sum=df[df.Level1==Level1_sum.Lebel[x]].groupby('Level2').sum('value').reset_index()
Level2_sum.rename(columns={'Level2':'Lebel'},inplace=True)
concat_sum=pd.concat([concat_sum,Level2_sum])
dic_Level2=[]
for y in range(Level2_sum.shape[0]):
Level3_sum=df[df.Level2==Level2_sum.Lebel[y]].groupby('Level3').sum('value').reset_index()
Level3_sum.rename(columns={'Level3':'Lebel'},inplace=True)
concat_sum=pd.concat([concat_sum,Level3_sum])
dic_Level3=[]
for z in range(Level3_sum.shape[0]):
dic_Level3.append(dict(id=Level3_sum.Lebel[z] ,datum=Level3_sum.value[z]))
dic_Level2.append(dict(id=Level2_sum.Lebel[y] ,datum=Level2_sum.value[y],children=dic_Level3))
dic_Level1.append(dict(id=Level1_sum.Lebel[x] ,datum=Level1_sum.value[x],children=dic_Level2))
#生成json
data=json.dumps(eval(str(dic_Level1)))
#第二部分输出没有value值,增加一个输出文件,方便tableau里添加标签
concat_sum.reset_index(drop=True).to_csv('test_label.csv',encoding='utf-8-sig')
生成图形
首先要安装circlify库(请自行百度),代码如下:(我对源代码进行了些许修改)
import csv
import circlify
import matplotlib.pyplot as plt
# 将第一部分的json数据赋值给data
data=json.loads(data)
# Compute circle positions thanks to the circlify() function
# The maximum radius is set to 1.
circles = circlify.circlify(
data,
show_enclosure=False,
target_enclosure=circlify.Circle(x=0, y=0, r=1)
)
# To ensure in Tableau the sizing works the fig size is the same x*y.
fig, ax = plt.subplots(figsize=(10,10))
# Add axes
ax.axis('on')
# Find axis boundaries, lim will be 1.0 with current settings
lim = max(
max(
abs(circle.x) + circle.r,
abs(circle.y) + circle.r,
)
for circle in circles
)
plt.xlim(-lim, lim)
plt.ylim(-lim, lim)
# Amend Radius zone, Make sure Inner pad always > than Outer pad
padding_outer = 1
padding_inner = 1
header = ['ID', 'X co-ord', 'Y-cord', 'Radius', 'Rank',"Level"]
rank = 1
with open('test_result.csv', 'w',newline="") as f:
writer = csv.writer(f)
writer.writerow(header)
# World Level
for circle in circles:
if circle.level != 1:
continue
x, y, r = circle
level =1
label = circle.ex["id"]
#print(label,x,y,r,level)
printdata = [label, x, y, r, rank,level]
rank = rank + 1
writer.writerow(printdata)
# Not needed for Tableau
ax.add_patch(plt.Circle((x, y), r*padding_outer, alpha=0.5, linewidth=2, color="lightblue"))
# Continent Level
for circle in circles:
if circle.level != 2:
continue
x, y, r = circle
level =2
label = circle.ex["id"]
#print(label,x,y,r,level)
printdata = [label, x, y, r, rank,level]
rank = rank + 1
writer.writerow(printdata)
# Not needed for Tableau
ax.add_patch(plt.Circle((x, y), r*padding_outer, alpha=0.5, linewidth=2, color="lightblue"))
# Country Level
for circle in circles:
if circle.level != 3:
continue
x, y, r = circle
level =3
label = circle.ex["id"]
#print(label,x,y,r)
printdata = [label, x, y, r, rank,level]
rank = rank + 1
writer.writerow(printdata)
# Not needed for Tableau
ax.add_patch(plt.Circle((x, y), r*padding_inner, alpha=0.5, linewidth=2, color="#69b3a2"))
plt.annotate(label, (x,y), ha='center', color="white")
# Show The Example Of The Graph We Will Be Recreating!
plt.show()
python里生成的图形如下:
同时生成了一个test_result.csv的文件。文件记录了圆形的ID,原点的x、y坐标和半径r。
制作图形
导入test_result.csv文件,做一次并集。
创建计算字段
-
新建计算字段path
IF [表名称]='test_result.csv' then 0 ELSE 360 END -
创建path数据桶,数据桶大小是1
-
新建计算字段index
index() -
创建计算字段X
SIN(2*PI()*[index]/360)*WINDOW_MAX(MAX([Radius]))
+WINDOW_MAX(MAX([X co-ord])) -
创建计算字段Y
COS(2*PI()*[index]/360)*WINDOW_MAX(MAX([Radius]))
+WINDOW_MAX(MAX([Y-cord])) -
检查path字段“显示了缺失值”是否已经勾选
-
标记选择线,将ID拖到详细信息,path数据桶拖到路径,Level放到颜色上
-
将X、Y分别放到行、列功能区,计算依据选择path数据桶
如果得到了上面的图形,就证明没有任何问题,我在利用其他数据源的时候,出现过ID字段重名的情况,用Rank字段作为区分也没有问题,不过建议还是处理一下数据,保证ID值唯一。
最终,我们使用多边形,并修改好颜色就是我们想要的结果了。如果需要增加value值的提示,可以用生成的test_label.csv文件做关联。
至于图形的其他玩法,比如嵌套更多层次的圆形,那么就需要你自己去探索了,可能需要修改代码。
另外画圆的原理,我推荐我自己在cctalk上的课程《Tableau高级图表进阶》,第三部分第一课(免费)。
此篇文章已发布到我的公众号: saodisir,有兴趣也可关注一下
网友评论