如何像下圖一樣將關系型數據庫的上下級關系畫成樹形圖
測試數據准備
為了程序的通用性,也方便進行驗證,本例采用最通用的sql寫法,數據庫采用SQLiter3, 如果你的數據庫是ORACLE, MS-SQL, MYSQL,不用修改任何代碼,只需要在調用的時候傳入相應的db連接即可
def sampledata(): db=sqlite3.connect('dbname.db') cur=db.cursor() cur.execute("create table if not exists relation(mother, child)"); cur.execute("INSERT INTO relation(mother, child) VALUES('1000', '1100')"); cur.execute("INSERT INTO relation(mother, child) VALUES('1000', '1200')"); cur.execute("INSERT INTO relation(mother, child) VALUES('1000', '1300')"); cur.execute("INSERT INTO relation(mother, child) VALUES('1000', '1400')"); cur.execute("INSERT INTO relation(mother, child) VALUES('1200', '1210')"); cur.execute("INSERT INTO relation(mother, child) VALUES('1200', '1220')"); cur.execute("INSERT INTO relation(mother, child) VALUES('1200', '1230')"); cur.execute("INSERT INTO relation(mother, child) VALUES('1220', '1221')"); cur.execute("INSERT INTO relation(mother, child) VALUES('1220', '1222')"); db.commit();
看起來是這樣的
程序編寫
節點信息類(相當於C/C++里面的結構體)
value就是當前節點的值
isleaf記錄的是當前節點是否是葉子節點
leafcounts記錄的是當前節點下面有多少個葉子節點,這個主要是為了在排版的時候知道占用多寬,
maxlevel記錄的是當前節點下面最深的深度是多少,這個主要是為了在排版的時候知道生成多長的圖片
class decisionnode: def __init__(self, value, isleaf=False, leafcounts=0, maxlevel=1): self.childs=[] self.value=value self.isleaf=isleaf self.leafcounts=leafcounts self.maxlevel=maxlevel def addchild(self, child): self.childs.append(child)
關系樹類編寫
gentree 方法用於生成整棵樹,需要傳值:db:數據庫連接,mathervalue:根節點的數值 tablename:數據庫表名 childcol:子節點在表中的對應的字段名稱 mothercol:上級節點在表中的字段名稱
draweachnode 方法用於化每個節點的值和兩個節點之間的連線
drawTree 方法用於畫整棵樹,它會調用draweachnode然后利用draweachnode的遞歸畫完整棵樹
class RelationTree: def __init__(self, basewidth=100, basedepth=100): self.basewidth = basewidth self.basedepth = basedepth self.root=None def gentree(self, db, mothervalue, tablename, childcol, mothercol): self.root=decisionnode(mothervalue) cur=db.cursor() def swap_gentree(node): cur.execute("select %s from %s where %s = '%s'" % \ (childcol, tablename, mothercol, node.value)); results=cur.fetchall() #如果是葉子節點,則直接返回 if not results: return decisionnode(node.value, isleaf=True, maxlevel=1) #程序運行到這里,說明是非葉子節點 #對非葉子節點進行其下包含的葉子節點進行統計(leafcounts) #該節點之下最深的深度maxlevel收集 maxlevel=1 for each in results: entrynode=swap_gentree(decisionnode(each[0])) if(entrynode.isleaf): node.leafcounts += 1 else: node.leafcounts += entrynode.leafcounts if (entrynode.maxlevel > maxlevel): maxlevel = entrynode.maxlevel node.addchild(entrynode) node.maxlevel = maxlevel+1 return node swap_gentree(self.root) def draweachnode(self, tree, draw, x, y): draw.text((x,y), tree.value, (0,0,0)) if not tree.childs: return childs_leafcounts=[child.leafcounts if child.leafcounts else 1 for child in tree.childs] leafpoint=x-sum(childs_leafcounts)*self.basewidth/2 cumpoint=0 for childtree, point in zip(tree.childs, childs_leafcounts): centerpoint=leafpoint+self.basewidth*cumpoint+self.basewidth*point/2 cumpoint += point draw.line((x,y, centerpoint, y+self.basedepth), (255,0,0)) self.draweachnode(childtree, draw, centerpoint, y+self.basedepth) def drawTree(self, filename='tree.jpg'): width=self.root.leafcounts * self.basewidth + self.basewidth depth=self.root.maxlevel * self.basedepth + self.basedepth img=Image.new(mode="RGB", size=(width, depth), color=(255,255,255)) draw=ImageDraw.Draw(img) self.draweachnode(self.root, draw, width/2, 20) img.save(filename)
測試驗收
確認運行代碼的環境是否已經安裝以下包
pillow (如果沒有請pip install pillow (這個模塊實際上就是PIL))
sqlite3 (如果沒有請pip install sqlite3)
新建一個文件 drawtree.py
在文件開頭導入需要的模塊
import sqlite3
from PIL import Image, ImageDraw
然后將上面的代碼復制進去
然后根據下面操作
到這里,就會在工作目錄下生成tree.jpg了,效果就像文章開頭那樣,
為了程序的通用性,本例的程序已經寫成很通用的代碼了,如果你用的是其他數據庫
則不要用我的樣本數據,也就是不要運行drawtree.sampledata這個函數
然后將db=sqlite3.connect('daname,db')換成你數據庫對應的連接,例如你的數據庫是oracle,則用db=cx_Oracle.connect('usernae/password@tnsname')