开源一个 topn 词竞赛动画项目 topn_race:
- GitCode 仓库:https://gitcode.net/csdn/topn_race
核心功能:
- 输入:按月统计的topN词频数据
- 输出:topN词频竞赛动画(可带音效)
本项目基于开源项目:https://github.com/dexplo/bar_chart_race 定制,src/bar_chart_race 从 bar_chart_race 项目的源代码修改以适配需求。
依赖库:
progress==1.5 matplotlib==3.4.2 pandas==1.2.4 numpy==1.19.5 moviepy==1.0.3
源码结构:
. ├── LICENSE ├── README.md ├── data │ ├── csdn_ask_top10_month │ │ ├── 2008-05-01.json │ │ ├── 2008-06-01.json │ │ ├── ... │ └── csdn_trends_top10_month │ └── csdn_index_top_10.csv ├── demo │ ├── csdn_ask_top10_month.gif │ ├── csdn_trends_top10_month.gif │ └── demo.md ├── main.py ├── pub │ ├── ... ├── requirements.txt └── src ├── bar_chart_race │ ├── __init__.py │ ├── chart.py │ └── colormaps.py ├── common │ ├── __init__.py │ ├── error.py │ ├── gif.py │ ├── json.py │ ├── path.py │ ├── random.py │ └── utils.py └── top.py
其中:
- main.py 是测试程序入口
- src/top.py 是 topN 竞赛动画的逻辑组织控制层
- src/common 提供了一些基本的utils
- src/bar_chart_race 从 bar_chart_race 项目的源代码修改以适配需求
- 基本样式的内部调整
- 使用漫画风格
基本用法如下:
def test_build_csdn_trend_top10_tag_race(): input = Inputmeta( type='csv', path='data/csdn_trends_top10_month/csdn_index_top_10.csv', month_field='date', name_field='tag_name', count_field='index_value', audio='pub/mali.mp3' ) output = Outputmeta( path='pub/csdn_trends_top10_month', ext='gif', title='CSDN topN指数月排行榜', x_label='csdn.net/trends', y_label='指数', month_count=None ) top = Top(input, output) top.build()
Top 类的构造函数传入两个参数:input: Inputmeta 和output: Outputmeta。很多Python代码的参数能有几十个参数,通过Inputmeta 和 Outputmeta 两个dataclass可以让使用更友好:
@dataclass class Inputmeta: ''' type: 指定类型,如果是 "json_str" 表示一个JSON文件夹,如果是"csv"表示一个csv文件 JSON 文件夹: 约定每个文件的文件名是月份,每个JSON文件是一个数组,数组元素是标签统计信息 name_field: 指定标签名字的字段名 count_field: 指定标签月份统计信息的字段名 CSV 文件 month_field: 指定月份字段 name_field: 指定标签名字的字段名 count_field: 指定标签月份统计信息的字段名 audio: 音频 ''' type: str path: str month_field: str name_field: str count_field: str audio: str @dataclass class Outputmeta: ''' 输出配置 path: 输出路径 title: 标题 x_label: X轴名字 y_label: Y轴名字 month_count: 绘制月份,用来调试,使用较少的月份快速查看输出效果 ''' path: str ext: str title: str x_label: str y_label: str month_count: int
Top 类的 build 里的处理流程包括:
- 转换输入数据到每月一行的 Dataframe
- 每12个月数据生成一个竞赛动图 GIF
- 原因之一:太大的GIF文件生成会有内存占用问题,分片处理。
- 原因之二:分片后,规避出错时要从头再来的问题。
- 合并多个GIF,生成一个MP4文件
- 如果输入指定了音频文件,使用音频源采用repeat方式与MP4合成轨道并输出带音效的文件
输出目录pub下的文件不提交到git仓库,需要注意的是,构建过程中不同平台上的中文字体会有差异,目前适配了Mac和Linux的字体,其他平台待测试。
实例:CSDN topN 指数月排行榜竞赛动图CSDN指数
CSDN 指数是基于自 2000 年以来 CSDN 平台产生的海量内容数据、用户行为计算而来,作为中国最大专业 IT 技术社区,CSDN 指数具备高度权威性,您可通过查询关键字,用以进行技术领域趋势分析、技术选型变迁历史探索、技术内容消费特征洞察、开发者岗位需求预测等。
我们用 CSDN 指数的数据做了一个topN 指数月排行榜竞赛动图
竞赛动画部分片段GIF:
完整版本请看:
- CSDN topN指数 月排行榜竞赛动画(2000.1-2021.12)独立视频
- CSDN topN指数 月排行榜竞赛动画(2000.1-2021.12)社区帖子
数据经过可视化处理后,可以发现数据间的规律,欢迎对项目提交贡献。开箱即用的漫画风格topN竞赛动图:https://gitcode.net/csdn/topn_race
这个项目在6月份的时候做过一个版本,对问答的历年标签月排行榜做了一次渲染。当时一次性跑数据渲染比较久,这次再做的时候想到了一个原因应该是同一个GIF整体渲染可能会导致性能越来越慢。于是第一个改进的思路就是分片渲染,再做合成。
分片 *** 作的过程中,也会顺便产生满足多种需要的输出考虑,例如最后一片的最后一帧会增加停留时长,避免动图到最后一帧一闪而过;例如最后一片也会生成一个小于5M的摘要GIF,用来写博客的时候上传片段GIF使用:
class Top: ... def build(self): ... max_rows = self.df.shape[0] i = 0 j = 0 df = self.df gifs = [] os.makedirs(self.output, exist_ok=True) while i < max_rows: end = i+12 if end >= max_rows: end = max_rows+1 step = end-i filename = os.path.join(self.output, f'{j}.{self.ext}') if i+step >= max_rows: # 最后一个 last_df = df[i:end] # 生成一个短摘要 min_half = 5 if min_half > last_df.shape[0]: min_half = 0 self.df = last_df[min_half:] filename_abstracts = os.path.join( self.output, f'{j}_abstracts.{self.ext}') self.__build_race(filename_abstracts) # 加强最后一帧 self.df = last_df for k in range(0, 12): self.df = self.df.append(df[end-2:end]) self.__build_race(filename) else: self.df = df[i:end] self.__build_race(filename) gifs.append(filename) i += step j += 1
其次很多这样的库包含一堆的参数,例如 topn_race 下层使用的原始库bar_chart_race的代码就是这样的。实际上这里有一个经典的设计模式是可以解决此类代码的组织问题:Builder模式。我觉的后续改进是可以改造下它的代码。不要用一堆的构造函数参数让使用者很难用,通过Builder模式是可以轻易对同一个库的不同使用情景做模块化接口设计。这块后面可以用来进一步改造bar_chart_race的代码。Python 代码越是灵活,越是要在写的过程中注意简洁的基础上有好的设计。
一个多道程序的内部会有很多重要的实际干活的重型关节代码,如果没有一些控制逻辑,多次运行不能保持轻量,会让人害怕。举个例子,渲染的多个关键环节,都应该加入一些规避不必要的重复 *** 作的判定逻辑:
例如,判定文件已存在,是否需要覆盖,这样你就可以放心的多次 *** 作
class Top: ... def build(self): ... # 合并 gif 生成mp4 all = f'{self.output}.mp4' if os.path.exists(all): ret = input(f"文件:{all}已存在,是否覆盖?[y/n]:") if ret == 'y': concat_gif_list(gifs, all) else: concat_gif_list(gifs, all)潜在需求
- 完备的全平台字体支持
- 支持为条形图增加关联的「d幕文本」
- 增加片头和片尾渲染(保持很短),让它接近代码微电影
- 进一步解决性能问题
- 使用Flask支持服务化,支持在线部署和调用
–end–
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)