需要
Python
环境+
Pygame
库,点击即可运行。效果见B站视频: 中国古代历史人物生卒时间可视化。
动画代码
from msilib.schema import Font
import pygame,sys,random,datetime,os,json
from time import sleep,time
from random import randint
from pygame.locals import *
from heapq import heappop,heappush,heapreplace
from math import inf,ceil,sqrt
from win32.win32api import GetSystemMetrics
import pyautogui
soundDir="D:\\Music\\"
songs=[
"杨洪基 - 滚滚长江东逝水.mp3",
"朱桦 - 意中人.mp3",
]
backgroundColor=(250,250,250)
color_WHITE=(255,255,255)
color_RED=(255,0,0)
color_GRAY=(200,200,200)
color_DarkGRAY=(100,100,100)
windowSize=[int(1*GetSystemMetrics(0)),int(1*GetSystemMetrics(1))]
topMargin,bottomMargin=[min(windowSize)//18,max(windowSize)//60]
mainHeight=windowSize[1]-topMargin-bottomMargin
midXLine=windowSize[0]//2
textSize=max(windowSize)//60
titleTextSize=2*textSize
titleTopMargin=0
FPS=30
animationTime=FPS
FPSCLOCK=pygame.time.Clock()
BACKGROUND=pygame.display.set_mode(windowSize)
SCREEN=pygame.display.set_mode(windowSize)
def MinkowskiDis(v1:list[int],v2:list[int])->int:
return sum(abs(v1[i]-v2[i]) for i in range(len(v1)))
def loadHistoryData():
with open("全部历史人物生卒时间_含热度_备注_json版.txt",'r',encoding='utf-8') as f:
peopleData=json.load(f)
print(f"从文件中加载完毕,{
len(peopleData)}项")
f.close()
allNum=sum(len(i[0].split(",")) for i in peopleData)
print(f"文件读到的总人数:{
allNum}")
return peopleData
class HistoryVisualization:
def __init__(self,peopleData=[]):
self.pauseState=True
self.timeWindowWidth=110
self.timeGridWidth=10
self.curLeftTime=-2070-self.timeWindowWidth
self.timeStep0=0.51
self.timeStep=self.timeStep0
if len(peopleData):
self.peopleData=peopleData
else:
self.peopleData=loadHistoryData()
self.historyDataInitial(isNew=(len(peopleData)==0))
self.curPlayState=0
self.stateTime=[2.9*FPS,180*FPS,10*FPS,inf]
self.marginRate=0.5
self.defaultRowNum=20
self.rectRowNum=self.rectRowNum0=20
self.rowsFreeTime=[self.curLeftTime]*self.rectRowNum
self.rowsFreeTimeHeap=[[self.curLeftTime,i] for i in range(self.rectRowNum)]
self.rectHeight=self.targetRectHeight=mainHeight/((1+self.marginRate)*self.rectRowNum+self.marginRate)
def historyDataInitial(self,isNew=True):
self.startYear=min(-2070,min(i[1] for i in self.peopleData)-40)
self.endYear=max(1910,max(i[2] for i in self.peopleData)+40)
self.curLeftTime=min(self.curLeftTime,self.startYear)
self.dataIdx=0
self.hp=[]
tp=[]
if isNew:
self.peopleData+=tp+[["空心表示时间不确定,仅供参考",self.startYear+self.timeWindowWidth//2-20,self.startYear+self.timeWindowWidth//2+20,0],['今',2024,2025,1]]
self.peopleData.sort(key=lambda x:(x[1],x[2]))
n=len(self.peopleData)
self.dataRowIdx=[0]*n
importantPeopleDc=set(["黄帝","炎帝","大禹","老子","孔子","屈原","嬴政","秦始皇","刘彻","卫青","霍去病","诸葛亮","李世民","武则天","李白","杜甫","苏轼","辛弃疾","朱元璋","王阳明","孙中山","毛泽东",'今'])
self.peopleDataColors=[[randint(0,255) for j in range(3)] for i in range(n)]
for i in range(n):
if any(name in importantPeopleDc for name in self.peopleData[i][0].split(",")):
self.peopleDataColors[i]=color_RED
else:
while min(MinkowskiDis(self.peopleDataColors[i],backgroundColor),MinkowskiDis(self.peopleDataColors[i],color_RED))<60:
self.peopleDataColors[i]=[randint(0,255) for j in range(3)]
self.dataNameLen=[0]*n
for i in range(n):
sm=0
for c in self.peopleData[i][0]:
if c in "+-":
sm+=0.6
elif c in "()" or c.isdigit():
sm+=0.5
else:
sm+=1
self.dataNameLen[i]=sm
self.eraData=[['夏朝', -2070, -1600], ['商朝', -1600, -1046], ['西周', -1046, -771],['春秋',-770,-476],['战国',-475,-221], ['秦朝', -221, -207], ['西汉', -206, 8], ['新朝', 8, 23],['玄汉',23,25], ['东汉', 25,220],['三国',220,265] ,['西晋', 265,316],['东晋',317,420],['南北朝',420,581], ['隋朝', 581,618], ['唐朝', 618,690], ['武周', 690,705], ['唐朝', 705,907],['五代十国',907,960], ['北宋', 960,1127],['南宋',1127,1279], ['元朝', 1279,1368], ['明朝', 1368,1644], ['清朝', 1644,1911]]
self.eraLeftIdx=0
self.eraRightIdx=0
n=len(self.eraData)
self.eraDataColors=[[randint(0,255) for j in range(3)] for i in range(n)]
for i in range(n):
while MinkowskiDis(self.eraDataColors[i],backgroundColor)<60:
self.eraDataColors[i]=[randint(0,255) for j in range(3)]
idx=self.eraData.index(['武周', 690,705])
if idx!=-1:
self.eraDataColors[idx]=self.eraDataColors[idx-1]
self.eraDataColors[idx+1]=self.eraDataColors[idx-1]
def plotText(self,pos,label,color=[0,0,0],textSize=30,fontName='simhei'):
"""绘制文本,不支持传入Font"""
FONT=pygame.font.Font(pygame.font.match_font(fontName),int(textSize))
textInfo=FONT.render(label,True,color)
SCREEN.blit(textInfo,pos)
def getXPosFromTime(self,t):
return windowSize[0]*(t-self.curLeftTime)/self.timeWindowWidth
def plotBackground(self):
"""
静态背景,预想中只用绘制一次
"""
SCREEN.fill(backgroundColor)
if self.curPlayState<2:
FONT=pygame.font.Font(pygame.font.match_font('simhei'),titleTextSize)
textInfo=FONT.render('时代:',True,color_DarkGRAY)
SCREEN.blit(textInfo,(0,titleTopMargin))
if self.curPlayState<3:
FONT=pygame.font.Font(pygame.font.match_font('simhei'),textSize)
textInfo=FONT.render('公元',True,color_DarkGRAY)
SCREEN.blit(textInfo,(0,windowSize[1]-textSize))
def plotScreenBackground(self):
"""
运动的背景,背景线等
"""
FONT=pygame.font.Font(pygame.font.match_font('simhei'),textSize)
t=self.curLeftTime%self.timeGridWidth
startTime=int(self.curLef