treemap 适合展现具有层级关系的数据 能够直观的体现同级之间的比较。例如下列原始的Android设备手机占有率用原始的分叉树实现效果不是很好 看不出比例关系。
使用下面的treemap来展示效果好得多 可以清楚的看到每个品牌占有的份额 也能看见同一品牌下各个型号的份额 份额信息通过矩形面积表示。
1.2 historyTreemap由马里兰大学教授Ben Shneiderman于上个世纪90年代提出 起初是为了找到一种有效了解磁盘空间使用情况的方法
Treemap最早用的是Slice and Dice算法 在上图中出现了非常狭长的矩形带 对人眼观察不太友好。后来提出了矩形正方化(squarified)的思想使得可视化出来的矩形长宽比尽量接近1 以正方形展示。大致如下
采用此种布局方法有效减少了狭长矩形的数量。
所以squarified rectangles有以下好处
可以更加充分的利用空间。因为采用正方形布局 矩形周长和可达到最小 因为代表所有文件大小的最终面积是固定的 边界长度即矩形周长 squarify可以较少边界长度狭长的矩形容易产生混叠错误 正方形容易观察矩形长宽比趋近1时人眼可以方便比较它们的面积squarify可以提升可视化精度 2 python实现 2.1 代码说明利用python的matplotlib编程实现squarified treemap算法。
程序指定窗口大小 将给定数据根据窗口大小进行缩放并按面积降序排序。各部分函数功能如下
**normal_sort_size(size_list) **返回经过规范化且按面积降序排序的原始数据list**worst_ratio(size_list,w) **计算并返回当前绘图区域内矩形的最大纵横比 其中w是当前绘图矩形区域较小的一边的长度layout(render_list, layout_side, start_point) 以start_point为基点 绘制render_list内的所有矩形squarify(current_fixed_rec_list, rec_to_layout, layout_side, start_point, screen_width, screen_height) current_fixed_rec_list保存当前绘制区域已选定绘制的矩形 rec_to_layout保存等待绘制的矩形 layout_side为元组tuple 保存当前区域矩形绘制的叠加方向以及区域在该方向上的长度 start_point, screen_width, screen_height分别保存当前绘制区域的左下角对应的坐标 区域的宽和长。该函数是算法的核心函数 判断下一个矩形是绘制在当前子区域 还是绘制在另一个子区域。render(list_to_layout) 接受原始的待可视化的数据 函数内调用squarify()函数import matplotlib.pyplot as plt import numpy as np #规定展示区域的窗口大小 Width 6 Height 4 data [6,4,6,3,2,2,1] ratio np.sum(data)/(Width*Height) color_alpha 1 #将原始的大小数组通过窗口正则化 def normal_sort_size(size_list): new_size_list [] target_area Width*Height original_area np.sum(size_list) for size in size_list: new_size_list.append(size*(target_area/original_area)) #降序排序 new_size_list sorted(new_size_list,reverse True) return new_size_list #w is the length of the side along which the latest rectangle is layouted #size_list 当前渲染区域的矩形 def worst_ratio(size_list,w): sum_area np.sum(size_list) buttom_length sum_area / float(w) #计算最大长宽比 min_area min(size_list) ratio1 max(min_area/pow(buttom_length,2), pow(buttom_length,2)/min_area) max_area max(size_list) ratio2 max(max_area/pow(buttom_length,2), pow(buttom_length,2)/max_area) return max(ratio1,ratio2) #定义绘图区域 fig1 plt.figure() ax1 fig1.add_subplot(111, aspect equal ) #绘制图形 #layout_direction 为矩形叠加方向 即 def layout(render_list, layout_side, start_point): global color_alpha sum_area np.sum(render_list) buttom_length sum_area / layout_side[0] if layout_side[1] y :#沿着y轴增长 for rec in render_list: print( y: ,start_point,buttom_length, rec/buttom_length) ax1.add_patch( plt.Rectangle(start_point, buttom_length, rec/buttom_length, color steelblue , alpha color_alpha) ) color_alpha color_alpha/1.2 #添加文字注释 plt.text(start_point[0] buttom_length/2, start_point[1] rec/(2*buttom_length), str(rec*ratio)) start_point (start_point[0], start_point[1] rec/buttom_length) else:#沿着x轴增长 for rec in render_list: print( x: ,start_point,buttom_length, rec/buttom_length) ax1.add_patch( plt.Rectangle(start_point, rec/buttom_length, buttom_length, color steelblue , alpha color_alpha) ) color_alpha color_alpha/1.2 #添加文字注释 plt.text(start_point[0] rec/(2*buttom_length), start_point[1] buttom_length/2, str(rec*ratio)) start_point (start_point[0] rec/buttom_length, start_point[1]) #布局所有矩形 #start_point 当前绘制区域的左下角坐标 #layout_side是一个元组 (width_of_layout_side,direction_of_layout_side) def squarify(current_fixed_rec_list, rec_to_layout, layout_side, start_point, screen_width, screen_height): #取出下一个待放置矩形并从未防止矩形列表中删除 if rec_to_layout []: layout(current_fixed_rec_list, layout_side, start_point) return next_rec rec_to_layout[0] tmp current_fixed_rec_list.copy() tmp.append(next_rec) if current_fixed_rec_list []: rec_to_layout.pop(0) squarify(tmp, rec_to_layout, layout_side, start_point, screen_width, screen_height) else: #判断当前区域渲染 buttom_length np.sum(current_fixed_rec_list) / layout_side[0] #当前区域不能再放置矩形 寻找下一个区域 if worst_ratio(current_fixed_rec_list, layout_side[0]) worst_ratio(tmp, layout_side[0]): layout(current_fixed_rec_list, layout_side, start_point) if layout_side[1] y :#原先沿着y轴生长 screen_width - buttom_length#新窗口的width new_start_point (start_point[0] buttom_length, start_point[1]) else:#layout[1] x 原来是沿着x轴生长 screen_height - buttom_length#新窗口的height new_start_point (start_point[0], start_point[1] buttom_length) #判断当前区域走向 if screen_width screen_height: #沿着x轴生长 squarify([], rec_to_layout, [screen_width, x ], new_start_point,screen_width,screen_height) else:#smaller_side(screen_width, screen_height) screen_height squarify([], rec_to_layout, [screen_height, y ], new_start_point,screen_width,screen_height) #当前区域继续放置矩形 else: rec_to_layout.pop(0) squarify(tmp, rec_to_layout, layout_side, start_point, screen_width, screen_height) #调用API def render(list_to_layout): to_layout_list normal_sort_size(list_to_layout) if Width Height: squarify([], to_layout_list, [Height, y ], (0,0), Width, Height) else: squarify([], to_layout_list, [Width, x ], (0,0), Width, Height) if __name__ __main__ : render(data) plt.xlim(0, Width) plt.ylim(0, Height) plt.show() plt.axis( off ) fig1.savefig( rect1.png , dpi 90, bbox_inches tight )



