├── .github └── workflows │ └── test.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── docs ├── articles │ ├── graph │ │ ├── 折线图 │ │ │ └── index.html │ │ └── 柱状图 │ │ │ └── index.html │ └── md-docs │ │ ├── README │ │ └── index.html │ │ ├── 使用方法 │ │ └── index.html │ │ ├── 使用示例 │ │ └── index.html │ │ └── 配色选择 │ │ └── index.html ├── css │ ├── diff.css │ ├── index.css │ ├── python.css │ └── shell.css ├── img │ ├── after_copy.png │ ├── before_copy.png │ ├── caution.svg │ ├── enter.png │ ├── enter.svg │ ├── important.svg │ ├── moon.png │ ├── note.svg │ ├── question.svg │ ├── search.svg │ ├── sun.png │ ├── tip.svg │ └── warning.svg ├── index.html └── js │ ├── change_mode.js │ ├── copy_code.js │ ├── global_js_configuration.js │ ├── navigator.js │ ├── next_front.js │ └── picture_preview.js ├── examples ├── bar1.py ├── bar2.py ├── line_1.py ├── line_2.py └── stackbar_graph.py ├── images ├── paper │ ├── 1.png │ ├── 10.png │ ├── 11.png │ ├── 12.png │ ├── 13.png │ ├── 14.png │ ├── 15.png │ ├── 16.png │ ├── 17.png │ ├── 18.png │ ├── 19.png │ ├── 2.png │ ├── 20.png │ ├── 21.png │ ├── 3.png │ ├── 4.png │ ├── 6.png │ ├── 7.png │ ├── 8.png │ └── 9.png └── paperplotlib │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 6.png │ ├── 7.png │ ├── 8.png │ └── result.png ├── md-docs ├── README.md ├── dir.yml ├── graph │ ├── 折线图.md │ └── 柱状图.md ├── 使用方法.md ├── 使用示例.md └── 配色选择.md ├── paperplotlib ├── __init__.py ├── bar_graph.py ├── color.css ├── color.py ├── font │ └── consola-1.ttf ├── graph.py ├── line_graph.py └── stackbar_graph.py ├── pyproject.toml └── test.py /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test with Coverage 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v2 16 | 17 | - name: Set up Python 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: 3.9 21 | 22 | - name: Install dependencies 23 | run: | 24 | python -m pip install --upgrade pip 25 | pip install coverage 26 | pip install matplotlib 27 | pip install numpy 28 | 29 | - name: Run tests with coverage 30 | run: | 31 | coverage run -m unittest 32 | coverage xml -i 33 | env: 34 | COVERAGE_RUN: True 35 | 36 | - name: Upload coverage report to Codecov 37 | uses: codecov/codecov-action@v4 38 | with: 39 | file: ./coverage.xml -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | 162 | a.py 163 | .vscode/ 164 | /*.png 165 | all.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 kami-lu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | i = 1 3 | 4 | # 判断平台是 Windows 还是 Linux 5 | ifeq ($(OS),Windows_NT) 6 | RM = del 7 | else 8 | RM = rm 9 | endif 10 | 11 | .PHONY: test lexer cover 12 | 13 | test: 14 | @python test/$(i).py 15 | 16 | cover: 17 | coverage run test.py 18 | coverage html 19 | 20 | all: 21 | @python all.py $(i) 22 | 23 | clean: 24 | $(RM) *.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # paperplotlib 2 | 3 | [![PyPI download month](https://img.shields.io/pypi/dm/paperplotlib.svg)](https://pypi.python.org/pypi/paperplotlib/) [![PyPI versionfury.io](https://badge.fury.io/py/paperplotlib.svg)](https://pypi.python.org/pypi/paperplotlib/) [![PyPI pyversions](https://img.shields.io/pypi/pyversions/paperplotlib.svg)](https://pypi.python.org/pypi/paperplotlib/) [![codecov](https://codecov.io/gh/luzhixing12345/paperplotlib/branch/main/graph/badge.svg?)](https://codecov.io/gh/luzhixing12345/paperplotlib) 4 | 5 | paperplotlib 是基于 matplotlib 的论文实验数据绘图库, 意在快速绘制论文实验结果部分中常见的图表 6 | 7 | 本项目意在通过简洁的 API 调用来完成论文实验数据图的快速绘制 8 | 9 | ## 安装 10 | 11 | ```bash 12 | pip install paperplotlib 13 | ``` 14 | 15 | ## 快速开始 16 | 17 | ```python 18 | import paperplotlib as ppl 19 | import numpy as np 20 | 21 | # 随机生成一个 5 x 7 的数据 22 | a = 5 23 | b = 7 24 | y = np.random.randint(10, 100, size=(a, b)) 25 | 26 | # 初始化一个对象 27 | graph = ppl.BarGraph() 28 | 29 | # 传入数据/组/列的文字信息 30 | group_names = [f"group {i}" for i in range(a)] 31 | column_names = [f"column {i}" for i in range(b)] 32 | graph.plot_2d(y, group_names, column_names) 33 | 34 | # 调整x/y轴文字 35 | graph.x_label = "The number of data" 36 | graph.y_label = "Throughput (Mbps)" 37 | 38 | # 保存图片 39 | graph.save() 40 | ``` 41 | 42 | ![](./images/paperplotlib/result.png) 43 | 44 | ## 使用文档 45 | 46 | 视频介绍: [【项目分享】论文实验数据绘图](https://www.bilibili.com/video/BV1Qx421m7hx/) 47 | 48 | 更多使用说明请参考: [paperplotlib 使用文档](https://luzhixing12345.github.io/paperplotlib/) 49 | 50 | ## 示例 51 | 52 | 本仓库下的 examples/ 包含可以运行的示例, 可以在 [examples](./examples) 中查看 53 | 54 | ## 参考 55 | 56 | - [matplotlib](https://matplotlib.org/stable/users/index.html) 57 | - [matplotlib.pyplot的使用总结大全](https://www.zhihu.com/tardis/zm/art/139052035?source_id=1003) 58 | - [matplotlib.pyplot常用函数讲解大全(一)](https://zhuanlan.zhihu.com/p/139475633) 59 | - [matplotlib.pyplot常用函数讲解大全(二)](https://zhuanlan.zhihu.com/p/139946399) 60 | - [Presentation练习_科研论文中插图的配色原理与方案](https://www.bilibili.com/video/BV1cJ4m1j7No/) -------------------------------------------------------------------------------- /docs/articles/graph/折线图/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Document 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |

折线图

19 |
20 |
zood
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /docs/articles/graph/柱状图/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Document 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |

柱状图

19 |
20 |
zood
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /docs/articles/md-docs/README/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Document 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |

paperplotlib

paperplotlib 是基于 matplotlib 的论文实验数据绘图库, 意在快速绘制论文实验结果部分中常见的图表

本项目意在通过简洁的 API 调用来完成论文实验数据图的快速绘制

视频介绍: 【项目分享】论文实验数据绘图

安装

pip install paperplotlib

快速开始

import paperplotlib as ppl
19 | import numpy as np
20 | 
21 | # 随机生成一个 5 x 7 的数据
22 | a = 5
23 | b = 7
24 | y = np.random.randint(10, 100, size=(a, b))
25 | 
26 | # 初始化一个对象
27 | graph = ppl.BarGraph()
28 | 
29 | # 传入数据/组/列的文字信息
30 | group_names = [f"group {i}" for i in range(a)]
31 | column_names = [f"column {i}" for i in range(b)]
32 | graph.plot_2d(y, group_names, column_names)
33 | 
34 | # 调整x/y轴文字
35 | graph.x_label = "The number of data"
36 | graph.y_label = "Throughput (Mbps)"
37 | 
38 | # 保存图片
39 | graph.save()

参考

40 |
41 |
zood
42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /docs/articles/md-docs/使用方法/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Document 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |

使用方法

使用 paperplotlib(下称ppl) 只需要四步

  1. 初始化一个实例对象 graph
  1. 准备好你的数据, 将数据添加到 graph
  1. 根据需要适当修改属性
  1. 保存图片

ppl 为您简化了初始化, 基础配置, 配色, 数据位置等一些小细节, 您可以非常简单方便的绘制一张美观的数据图

总体介绍

ppl 提供了多数据图表的绘图

绘图 api 的使用方式非常类似, 分别对应四步

import paperplotlib as ppl
19 | 
20 | # 1. 初始化一个实例对象
21 | graph = ppl.BarGraph()
22 | 
23 | # 2. 准备好你的数据, 将数据添加到 `graph` 中
24 | x = [2, 4, 8, 16, 32]
25 | y = [2, 4, 8, 16, 32]
26 | graph.plot(x, y)
27 | 
28 | # 3. 根据需要适当修改属性
29 | graph.x_label = "The number of data"
30 | graph.y_label = "Throughput (Mbps)"
31 | 
32 | # 4. 保存图片
33 | graph.save()

两个类均只有两个传入数据的接口: plot plot_2d

plot 用于绘制简单的一维数据, 如下所示, 传入 x,y 坐标即可

plot_2d 通常来说更为常用, 常见于论文 benchmark/workload 的数据结果中

plot_2d 的函数原型如下, 共有四个参数

def plot_2d(
34 |         self,
35 |         data: List[List[float]],
36 |         group_names: List[str],
37 |         column_names: List[str],
38 |         emphasize_index: int = -1
39 |     ):
40 |     ...

一个简单易懂的示例如下:

import paperplotlib as ppl
41 | import numpy as np
42 | 
43 | # 随机生成一个 5 x 7 的数据
44 | a = 5
45 | b = 7
46 | y = np.random.randint(10, 100, size=(a, b))
47 | 
48 | # 初始化一个对象
49 | graph = ppl.BarGraph()
50 | 
51 | # 传入数据/组/列的文字信息
52 | group_names = [f"group {i}" for i in range(a)]
53 | column_names = [f"column {i}" for i in range(b)]
54 | graph.plot_2d(y, group_names, column_names)
55 | 
56 | # 调整x/y轴文字
57 | graph.x_label = "The number of data"
58 | graph.y_label = "Throughput (Mbps)"
59 | 
60 | # 保存图片
61 | graph.save()

其中 emphasize_index 参数用于高亮某一列, 可以传入列的索引值

这通常应用在某一列是新的方法, 其他的是对比方法, 此时其他列将会选择相近的颜色而突出该列

配色

ppl 的配色采用偏冷色调的蓝色, 且是不建议调整的, 对于 1-7 列均可以采用统一的风格绘制

对于超过 7 种配色的情况, 采用渐变色处理

其他配置

初始化阶段可以传入一个参数来决定配色

style_id = 3
62 | graph = ppl.BarGraph(style_id)

下图顺次对应 1 - 5

20240413204928

长图

对于有大量数据集结果的情况, 您可以将 width_picture 设置为 True, 即可生成一张长图, 可以横跨两栏放置, 类似如下的位置

20240408215030

graph.width_picture = True

x y 坐标

直接设置字符串即可

graph.x_label = "Workload"
63 | graph.y_label = "Max QPS\nNormalized DDR 100%"

画一条横线

graph.add_line(50)

y 轴范围

传入一个元组类型设置范围

graph.y_lim = (0, 1.2)
64 |
65 |
zood
66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /docs/articles/md-docs/使用示例/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Document 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |

使用示例

import paperplotlib as ppl
19 | import numpy as np
20 | 
21 | # 设定数组的行数a和列数b
22 | a = 6
23 | b = 4
24 | y = np.random.randint(10, 100, size=(a, b))
25 | 
26 | group_names = ["Canneal", "Memcached", "XSBench", "Graph500", "HashJoin", "BTree"]
27 | column_names = [f"socket {i}" for i in range(b)]
28 | 
29 | graph = ppl.BarGraph()
30 | graph.plot_2d(y, group_names, column_names)
31 | graph.y_lim = (0, 100)
32 | graph.save()

import paperplotlib as ppl
33 | import numpy as np
34 | 
35 | # 设定数组的行数a和列数b
36 | a = 5
37 | b = 5
38 | y = np.random.rand(a,b)
39 | 
40 | group_names = ["A", "B", "C", "D", "F"]
41 | column_names = ["DDR5:CXL-A = 100:0", "75:25", "50:50", "25:75", "0:100"]
42 | 
43 | graph = ppl.BarGraph()
44 | graph.plot_2d(y, group_names, column_names)
45 | graph.x_label = "Workload"
46 | graph.y_label = "Max QPS\nNormalized DDR 100%"
47 | graph.y_lim = (0, 1.2)
48 | graph.save()

让某一列突出

import paperplotlib as ppl
49 | import numpy as np
50 | 
51 | # 设定数组的行数a和列数b
52 | a = 5
53 | b = 5
54 | y = np.random.rand(a,b)
55 | 
56 | group_names = ["A", "B", "C", "D", "F"]
57 | column_names = ["DDR5:CXL-A = 100:0", "75:25", "50:50", "25:75", "0:100"]
58 | 
59 | graph = ppl.BarGraph()
60 | - graph.plot_2d(y, group_names, column_names)
61 | + graph.plot_2d(y, group_names, column_names, emphasize_index=0)
62 | graph.x_label = "Workload"
63 | graph.y_label = "Max QPS\nNormalized DDR 100%"
64 | graph.y_lim = (0, 1.2)
65 | graph.save()

宽图, 适用于多组数据, 双栏

import paperplotlib as ppl
66 | import numpy as np
67 | 
68 | # 设定数组的行数a和列数b
69 | a = 8
70 | b = 6
71 | y = np.random.randint(10, 100, size=(a, b))
72 | 
73 | group_names = [f"group {i}" for i in range(a)]
74 | column_names = [f"column {i}" for i in range(b)]
75 | 
76 | graph = ppl.BarGraph()
77 | graph.plot_2d(y, group_names, column_names)
78 | graph.x_label = "The number of data"
79 | graph.y_label = "Throughput (Mbps)"
80 | graph.width_picture = True
81 | graph.save()

82 |
83 |
zood
84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /docs/articles/md-docs/配色选择/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Document 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |

配色选择

20240413204928

单色

虽然现在我们选用单色作图的情况并不是很多,因为大多数情况要进行数据比较.如果需要使用单色的情况,建议使用上述的蓝色,要比黑色或者灰色更加活泼.

双色、三色

四色及以上

有多组数据的时候需要考虑协调和易读性.多组数据的插图建议用邻近色搭配,协调且易读,临近颜色搭配给人一种循序渐进的感觉.

邻近色用暖色或者冷色调都可以.

参考

19 |
20 |
zood
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /docs/css/diff.css: -------------------------------------------------------------------------------- 1 | pre[class*="language-"] { 2 | background: #161b22; 3 | } 4 | 5 | pre[class*="language-diff"] .Token { 6 | color: #C1CCCC; 7 | } 8 | 9 | pre[class*="language-diff"] .Token.ADD_FILE, 10 | pre[class*="language-diff"] .Token.SUB_FILE { 11 | color: #569cd6; 12 | } 13 | 14 | pre[class*="language-diff"] .Token.TAG_LINE { 15 | color: #C586C0; 16 | } 17 | 18 | pre[class*="language-diff"] .Token.HighlightLine, 19 | pre[class*="language-diff"] .Token.HighlightToken { 20 | background-color: #2d3c4d; 21 | padding: 2px 0; 22 | } 23 | 24 | pre[class*="language-diff"] .Token.ADD_LINE { 25 | background-color: #033a16; 26 | color: #aff5b4; 27 | } 28 | 29 | pre[class*="language-diff"] .Token.SUB_LINE { 30 | background-color: #67060c; 31 | color: #fad2d0; 32 | } -------------------------------------------------------------------------------- /docs/css/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | width: 100%; 5 | height: 100%; 6 | background-color: #f8f9fa; 7 | position: absolute; 8 | } 9 | 10 | 11 | .markdown-body { 12 | margin-top: 2%; 13 | margin-left: 20%; 14 | margin-right: 20%; 15 | padding: 20px 50px; 16 | -ms-text-size-adjust: 100%; 17 | -webkit-text-size-adjust: 100%; 18 | line-height: 1.5; 19 | /* color: #24292e; */ 20 | font-family: Consolas, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji; 21 | font-size: 16px; 22 | letter-spacing: 0.02em; 23 | line-height: 1.5; 24 | word-wrap: break-word; 25 | border-radius: 10px; 26 | box-shadow: rgba(17, 17, 26, 0.1) 0px 4px 16px, rgba(17, 17, 26, 0.05) 0px 8px 32px; 27 | background-color: #fff; 28 | } 29 | 30 | .light { 31 | background-color: #eee; 32 | color: #3c4858; 33 | } 34 | 35 | .dark { 36 | background-color: #181c27; 37 | color: #c4c6c9; 38 | } 39 | 40 | 41 | .markdown-light { 42 | background-color: #fff; 43 | } 44 | 45 | .markdown-light h1, 46 | .markdown-light h2, 47 | .markdown-light h3, 48 | .markdown-light h4, 49 | .markdown-light h5, 50 | .markdown-light h6 { 51 | color: #000; 52 | } 53 | 54 | .markdown-light a { 55 | color: #0366d6; 56 | transition: all 0.8s; 57 | } 58 | 59 | .markdown-light a:hover { 60 | color: #30a9de; 61 | transition: all 0.8s; 62 | } 63 | 64 | .markdown-dark { 65 | background-color: #252d38; 66 | } 67 | 68 | .markdown-dark a { 69 | color: #1589e9; 70 | transition: all 0.8s; 71 | } 72 | 73 | .markdown-dark a:hover { 74 | color: #30a9de; 75 | transition: all 0.8s; 76 | } 77 | 78 | .markdown-dark h1, 79 | .markdown-dark h2, 80 | .markdown-dark h3, 81 | .markdown-dark h4, 82 | .markdown-dark h5, 83 | .markdown-dark h6 { 84 | color: #c4c6c9; 85 | } 86 | 87 | .markdown-body strong { 88 | font-weight: inherit; 89 | font-weight: bolder; 90 | } 91 | 92 | .markdown-body h1 { 93 | font-size: 2em; 94 | margin: 0.67em 0; 95 | } 96 | 97 | .markdown-body img { 98 | border-style: none; 99 | } 100 | 101 | a { 102 | color: rgb(4, 105, 221); 103 | text-decoration: none; 104 | } 105 | 106 | a:hover { 107 | text-decoration: underline; 108 | } 109 | 110 | blockquote { 111 | margin: 0; 112 | margin-top: 10px; 113 | margin-bottom: 16px; 114 | padding: 1px 1em; 115 | color: #6a737d; 116 | border-left: 0.25em solid #dfe2e5; 117 | border-radius: 3px; 118 | } 119 | 120 | .markdown-body code, 121 | .markdown-body kbd, 122 | .markdown-body pre { 123 | font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; 124 | font-size: 15px; 125 | } 126 | 127 | .markdown-body strong { 128 | font-weight: 600; 129 | } 130 | 131 | .markdown-body hr { 132 | height: 0; 133 | margin: 25px 0; 134 | overflow: hidden; 135 | background: transparent; 136 | border: 0; 137 | border-bottom: 2px solid #dfe2e5; 138 | } 139 | 140 | .markdown-body hr:after, 141 | .markdown-body hr:before { 142 | display: table; 143 | content: ""; 144 | } 145 | 146 | .markdown-body blockquote, 147 | .markdown-body details, 148 | .markdown-body dl, 149 | .markdown-body ol, 150 | .markdown-body p, 151 | .markdown-body table, 152 | .markdown-body ul { 153 | margin-top: 0; 154 | margin-bottom: 8px; 155 | cursor: pointer; 156 | } 157 | 158 | .markdown-body pre { 159 | margin-top: 5px; 160 | margin-bottom: 16px; 161 | padding: 16px; 162 | overflow: auto; 163 | font-size: 85%; 164 | /* max-height: 600px; */ 165 | line-height: 1.45; 166 | background-color: #f6f8fa; 167 | border-radius: 3px; 168 | word-wrap: normal; 169 | box-shadow: rgba(17, 17, 26, 0.1) 0px 0px 16px; 170 | cursor: pointer; 171 | } 172 | 173 | .markdown-body code { 174 | padding: 0.2em 0.4em; 175 | margin: 0; 176 | font-size: 85%; 177 | background-color: rgba(27, 31, 35, 0.05); 178 | border-radius: 3px; 179 | } 180 | 181 | .markdown-body pre>code { 182 | padding: 0; 183 | margin: 0; 184 | font-size: 100%; 185 | word-break: normal; 186 | white-space: pre; 187 | background: transparent; 188 | border: 0; 189 | } 190 | 191 | .markdown-body pre code { 192 | max-width: 100%; 193 | padding: 0; 194 | margin: 0; 195 | overflow: visible; 196 | line-height: inherit; 197 | word-wrap: normal; 198 | background-color: initial; 199 | border: 0; 200 | cursor: pointer; 201 | } 202 | 203 | 204 | 205 | .markdown-body ul { 206 | padding-left: 0; 207 | margin-top: 0; 208 | margin-bottom: 0; 209 | } 210 | 211 | /* .markdown-body ol ol, 212 | .markdown-body ul ol { 213 | list-style-type: lower-roman; 214 | } 215 | 216 | .markdown-body ol ol ol, 217 | .markdown-body ol ul ol, 218 | .markdown-body ul ol ol, 219 | .markdown-body ul ul ol { 220 | list-style-type: lower-alpha; 221 | } */ 222 | 223 | .markdown-body ul { 224 | margin-top: 0; 225 | margin-bottom: 8px; 226 | } 227 | 228 | .markdown-body ol, 229 | .markdown-body ul { 230 | padding-left: 2em; 231 | } 232 | 233 | .markdown-body ol ol, 234 | .markdown-body ol ul, 235 | .markdown-body ul ol, 236 | .markdown-body ul ul { 237 | margin-top: 0; 238 | margin-bottom: 0; 239 | } 240 | 241 | .markdown-body li { 242 | word-wrap: break-all; 243 | } 244 | 245 | .markdown-body li>p { 246 | margin-top: 16px; 247 | } 248 | 249 | .markdown-body li+li { 250 | margin-top: 0.25em; 251 | } 252 | 253 | ul>li>ul>li { 254 | margin-top: 10px; 255 | /* 通过调整10px来设置上边距大小 */ 256 | } 257 | 258 | /* ul 后面的文字加一个间距 */ 259 | .markdown-body>ul~p, 260 | .markdown-body>ol~p, 261 | .markdown-body>ul~blockquote, 262 | .markdown-body>ol~blockquote .markdown-body>ul~pre, 263 | .markdown-body>ol~pre, 264 | .markdown-body>ul~a, 265 | .markdown-body>ol~a { 266 | margin-top: 16px; 267 | } 268 | 269 | .markdown-body table { 270 | border-spacing: 0; 271 | border-collapse: collapse; 272 | } 273 | 274 | .markdown-body table th { 275 | font-weight: 600; 276 | white-space: nowrap; 277 | } 278 | 279 | .markdown-body table td, 280 | .markdown-body table th { 281 | padding: 6px 13px; 282 | border: 1px solid #dfe2e5; 283 | } 284 | 285 | .markdown-body table tr { 286 | background-color: #fff; 287 | border-top: 1px solid #c6cbd1; 288 | } 289 | 290 | .markdown-body table tr:nth-child(2n) { 291 | background-color: #f6f8fa; 292 | } 293 | 294 | .markdown-body table { 295 | max-width: 90%; 296 | border-spacing: 0; 297 | border-collapse: collapse; 298 | } 299 | 300 | .markdown-dark table tr { 301 | background-color: #444444; 302 | border-top: 1px solid #c6cbd1; 303 | color: white; 304 | } 305 | 306 | .markdown-dark table tr:nth-child(2n) { 307 | background-color: #808080; 308 | color: white; 309 | } 310 | 311 | .markdown-body h1, 312 | .markdown-body h2, 313 | .markdown-body h3, 314 | .markdown-body h4, 315 | .markdown-body h5, 316 | .markdown-body h6 { 317 | margin-top: 24px; 318 | margin-bottom: 16px; 319 | font-weight: 600; 320 | line-height: 1.25; 321 | } 322 | 323 | .markdown-body>ul>li:has(input) { 324 | padding-left: 0; 325 | margin-bottom: 0; 326 | } 327 | 328 | .markdown-body ul>li:has(input)>ul { 329 | list-style-type: none; 330 | padding-left: 8px; 331 | } 332 | 333 | .markdown-body img { 334 | max-width: 80%; 335 | max-height: 500px; 336 | display: block; 337 | margin: 0 auto; 338 | box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px; 339 | border-radius: 5px; 340 | } 341 | 342 | .markdown-body pre { 343 | position: relative; 344 | } 345 | 346 | .markdown-body pre #code_copy { 347 | position: absolute; 348 | display: inline-block; 349 | right: 10px; 350 | top: 10px; 351 | /* margin-top: -16px; */ 352 | padding: 3px; 353 | opacity: 0.9; 354 | border: 1px solid rgba(149, 157, 165, 0.2); 355 | border-radius: 5px; 356 | background-color: rgb(196, 196, 196); 357 | z-index: 1; 358 | } 359 | 360 | .markdown-body pre #code_copy:hover { 361 | background-color: #ffffff; 362 | box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px; 363 | border-radius: 5px; 364 | } 365 | 366 | .markdown-body ol ul, 367 | .markdown-body ul ol { 368 | padding-left: 1em; 369 | margin-top: 10px; 370 | } 371 | 372 | .markdown-body ol li blockquote { 373 | margin-top: 10px; 374 | } 375 | 376 | .markdown-body ul li blockquote { 377 | margin-top: 10px; 378 | } 379 | 380 | .markdown-body kbd { 381 | display: inline-block; 382 | padding: 5px 5px; 383 | font: 11px "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; 384 | line-height: 10px; 385 | color: #444d56; 386 | vertical-align: middle; 387 | background-color: #fafbfc; 388 | border: solid 1px #d1d5da; 389 | border-bottom-color: #c6cbd1; 390 | border-radius: 3px; 391 | -webkit-box-shadow: inset 0 -1px 0 #c6cbd1; 392 | box-shadow: inset 0 -1px 0 #c6cbd1; 393 | } 394 | 395 | .markdown-light ::selection { 396 | background-color: #d1ecf9; 397 | } 398 | 399 | .markdown-dark ::selection { 400 | background-color: #3f4041; 401 | } 402 | 403 | .markdown-body pre code ::selection { 404 | background-color: #264f78; 405 | } 406 | 407 | .markdown-body h1, 408 | .markdown-body h2 { 409 | padding-bottom: 0.3em; 410 | border-bottom: 1px solid #eaecef; 411 | } 412 | 413 | .markdown-body .icon-note, 414 | .markdown-body .icon-tip, 415 | .markdown-body .icon-warning, 416 | .markdown-body .icon-important, 417 | .markdown-body .icon-caution, 418 | .markdown-body .icon-question { 419 | margin-left: 0; 420 | margin-right: 1%; 421 | box-shadow: none; 422 | /* use default display instead of block */ 423 | display: inline; 424 | } 425 | 426 | .markdown-body blockquote div { 427 | align-items: center; 428 | display: flex; 429 | justify-content: left; 430 | margin-bottom: 5px; 431 | } 432 | 433 | .changeMode { 434 | position: absolute; 435 | right: 50px; 436 | top: 50px; 437 | padding: 5px; 438 | border-radius: 50%; 439 | background-color: #e3e3e3; 440 | box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, 441 | rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, 442 | rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px; 443 | cursor: pointer; 444 | } 445 | 446 | .change-article { 447 | font-family: Consolas, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji; 448 | border: 0; 449 | width: 100%; 450 | margin-right: 20%; 451 | height: 100px; 452 | margin-top: 50px; 453 | border-radius: 10px; 454 | transition: all 0.5s; 455 | box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px; 456 | } 457 | 458 | .change-dark { 459 | background-color: #6a737d; 460 | color: #f8f9fa; 461 | } 462 | 463 | /* 切换文章按钮 */ 464 | .next-front { 465 | position: fixed; 466 | right: 6%; 467 | top: 60%; 468 | width: 10%; 469 | /* white-space:nowrap; */ 470 | } 471 | 472 | .next-front .change-article:hover { 473 | cursor: pointer; 474 | font-weight: bolder; 475 | transition: all 0.5s; 476 | box-shadow: rgba(100, 100, 111, 0.4) 0px 7px 29px 0px; 477 | } 478 | 479 | .next-front .change-article:active { 480 | width: 95%; 481 | height: 90px; 482 | } 483 | 484 | /* 对目录树的调整 */ 485 | 486 | .dir-tree::-webkit-scrollbar { 487 | display: none; 488 | /* Chrome Safari */ 489 | } 490 | 491 | .dir-tree { 492 | font-family: Consolas, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji; 493 | position: fixed; 494 | left: 6%; 495 | top: 10%; 496 | width: 10%; 497 | max-width: 200px; 498 | box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px; 499 | background-color: #f6f8fa; 500 | border-radius: 10px; 501 | max-height: 80%; 502 | overflow: auto; 503 | scrollbar-width: none; 504 | /* Firefox */ 505 | -ms-overflow-style: none; 506 | /* IE 10+ */ 507 | text-overflow: ellipsis; 508 | /* 过长文字省略号 */ 509 | white-space: nowrap; 510 | /*禁止文字折行*/ 511 | } 512 | 513 | .dir-tree ul { 514 | font-family: Consolas, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji; 515 | font-size: 18px; 516 | letter-spacing: 0.02em; 517 | margin-top: 25px; 518 | margin-bottom: 25px; 519 | margin-left: -10px; 520 | list-style: none; 521 | position: relative; 522 | /* box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px; */ 523 | } 524 | 525 | .dir-tree ul li a { 526 | display: inline-block; 527 | max-width: 100%; 528 | overflow: hidden; 529 | text-overflow: ellipsis; 530 | white-space: nowrap; 531 | } 532 | 533 | .dir-tree ul ul li { 534 | font-family: Consolas, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji; 535 | letter-spacing: 0.02em; 536 | font-size: 16px; 537 | margin-left: -10px; 538 | margin-top: -10px; 539 | /* margin-bottom: 10px; */ 540 | padding-left: 5px; 541 | padding-top: 10px; 542 | padding-bottom: 0px; 543 | border-radius: 10px; 544 | /* padding-right: -10px; */ 545 | } 546 | 547 | /* .dir-tree ul ul li:hover { 548 | background-color: #eee; 549 | } */ 550 | 551 | .dir-tree a { 552 | text-decoration: none; 553 | color: #000; 554 | /* https://www.bilibili.com/video/BV1Dc41147sW */ 555 | /* background: linear-gradient(to right, #000, #000) no-repeat right bottom; 556 | background-size: 0 2px; 557 | transition: background-size 1000ms; */ 558 | } 559 | 560 | .dir-tree a:hover { 561 | font-weight: bold; 562 | color: #000; 563 | /* background-position-x: left; 564 | background-size: 100% 2px; */ 565 | } 566 | 567 | .dir-tree ul ul li a { 568 | color: #000; 569 | } 570 | 571 | .dir-tree ul ul li a:hover { 572 | color: #000; 573 | } 574 | 575 | .link-active { 576 | font-weight: bold !important; 577 | border-bottom: 2px solid #555; 578 | padding-bottom: 2px; 579 | } 580 | 581 | .link-active-dark { 582 | font-weight: bold !important; 583 | border-bottom: 2px solid #f3f3f3; 584 | padding-bottom: 2px; 585 | } 586 | 587 | /* 图片下的文字的样式 */ 588 | 589 | .markdown-body p>p { 590 | color: #a3a1a1; 591 | margin-top: 10px; 592 | text-align: center; 593 | line-height: 100%; 594 | } 595 | 596 | .preview-image { 597 | position: fixed; 598 | top: 50%; 599 | left: 50%; 600 | transform: translate(-50%, -50%); 601 | width: 60%; 602 | z-index: 200; 603 | } 604 | 605 | #black_overlay { 606 | display: none; 607 | position: fixed; 608 | top: 0; 609 | left: 0; 610 | width: 100%; 611 | height: 100%; 612 | background-color: rgba(0, 0, 0, 0.3); 613 | z-index: 100; 614 | } 615 | 616 | .zood { 617 | width: 100%; 618 | text-align: center; 619 | vertical-align: middle; 620 | margin-top: 10px; 621 | /* display: flex; */ 622 | } 623 | 624 | .zood a { 625 | text-decoration: none; 626 | color: grey; 627 | /* display: block; */ 628 | } 629 | 630 | .zood a:hover { 631 | text-decoration: underline; 632 | } 633 | 634 | 635 | .markdown-body ul:has(input[type="checkbox"]) { 636 | list-style-type: none; 637 | padding-left: 0.5em; 638 | /* margin-bottom: 8px; */ 639 | } 640 | 641 | .markdown-body input[type="checkbox"] { 642 | margin-right: 0.5em; 643 | /* 复选框和文本之间的间距 */ 644 | } 645 | 646 | /* 对小标题索引树的调整 */ 647 | 648 | .header-navigator { 649 | display: none; 650 | position: fixed; 651 | width: 15%; 652 | left: 83%; 653 | top: 10%; 654 | font-family: Consolas, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji; 655 | font-size: 18px; 656 | letter-spacing: 0.02em; 657 | scrollbar-width: none; 658 | /* Firefox */ 659 | -ms-overflow-style: none; 660 | /* IE 10+ */ 661 | text-overflow: ellipsis; 662 | /* 过长文字省略号 */ 663 | white-space: nowrap; 664 | /*禁止文字折行*/ 665 | list-style: none; 666 | max-height: 50%; 667 | overflow: auto; 668 | } 669 | 670 | .header-navigator::-webkit-scrollbar { 671 | display: none; 672 | /* Chrome Safari */ 673 | } 674 | 675 | .header-navigator a { 676 | color: #3c4858; 677 | text-decoration: none; 678 | } 679 | 680 | .header-navigator a:hover { 681 | font-weight: bolder; 682 | } 683 | 684 | .header-navigator ul { 685 | font-family: Consolas, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji; 686 | font-size: 14px; 687 | letter-spacing: 0.02em; 688 | margin-top: 10px; 689 | margin-bottom: 10px; 690 | margin-left: -10px; 691 | list-style: none; 692 | position: relative; 693 | } 694 | 695 | .header-navigator ul ul { 696 | margin-left: -20px; 697 | } 698 | 699 | .header-navigator a { 700 | display: inline-block; 701 | max-width: 100%; 702 | overflow: hidden; 703 | text-overflow: ellipsis; 704 | white-space: nowrap; 705 | } 706 | 707 | pre[class^="language-"] { 708 | background: #1f1f1f; 709 | color: white; 710 | } 711 | 712 | 713 | .lb-loader, 714 | .lightbox { 715 | text-align: center; 716 | line-height: 0; 717 | position: absolute; 718 | left: 0 719 | } 720 | 721 | body.lb-disable-scrolling { 722 | overflow: hidden 723 | } 724 | 725 | .lightboxOverlay { 726 | position: absolute; 727 | top: 0; 728 | left: 0; 729 | z-index: 9999; 730 | background-color: #000; 731 | filter: alpha(Opacity=80); 732 | opacity: .8; 733 | display: none 734 | } 735 | 736 | .lightbox { 737 | width: 100%; 738 | z-index: 10000; 739 | font-weight: 400; 740 | outline: 0 741 | } 742 | 743 | .lightbox .lb-image { 744 | display: block; 745 | height: auto; 746 | max-width: inherit; 747 | max-height: none; 748 | border-radius: 3px; 749 | border: 4px solid #fff 750 | } 751 | 752 | .lightbox a img { 753 | border: none 754 | } 755 | 756 | .lb-outerContainer { 757 | position: relative; 758 | width: 250px; 759 | height: 250px; 760 | margin: 0 auto; 761 | border-radius: 4px; 762 | background-color: #fff 763 | } 764 | 765 | .lb-outerContainer:after { 766 | content: ""; 767 | display: table; 768 | clear: both 769 | } 770 | 771 | .lb-loader { 772 | top: 43%; 773 | height: 25%; 774 | width: 100% 775 | } 776 | 777 | .lb-cancel { 778 | display: block; 779 | width: 32px; 780 | height: 32px; 781 | margin: 0 auto; 782 | } 783 | 784 | .lb-nav { 785 | position: absolute; 786 | top: 0; 787 | left: 0; 788 | height: 100%; 789 | width: 100%; 790 | z-index: 10 791 | } 792 | 793 | .lb-container>.nav { 794 | left: 0 795 | } 796 | 797 | .lb-nav a { 798 | outline: 0; 799 | background-image: url(data:image/gif;base64,R0lGODlhAQABAPAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==) 800 | } 801 | 802 | .lb-next, 803 | .lb-prev { 804 | height: 100%; 805 | cursor: pointer; 806 | display: block 807 | } 808 | 809 | .lb-nav a.lb-prev { 810 | width: 34%; 811 | left: 0; 812 | float: left; 813 | filter: alpha(Opacity=0); 814 | opacity: 0; 815 | -webkit-transition: opacity .6s; 816 | -moz-transition: opacity .6s; 817 | -o-transition: opacity .6s; 818 | transition: opacity .6s 819 | } 820 | 821 | .lb-nav a.lb-prev:hover { 822 | filter: alpha(Opacity=100); 823 | opacity: 1 824 | } 825 | 826 | .lb-nav a.lb-next { 827 | width: 64%; 828 | right: 0; 829 | float: right; 830 | filter: alpha(Opacity=0); 831 | opacity: 0; 832 | -webkit-transition: opacity .6s; 833 | -moz-transition: opacity .6s; 834 | -o-transition: opacity .6s; 835 | transition: opacity .6s 836 | } 837 | 838 | .lb-nav a.lb-next:hover { 839 | filter: alpha(Opacity=100); 840 | opacity: 1 841 | } 842 | 843 | .lb-dataContainer { 844 | margin: 0 auto; 845 | padding-top: 5px; 846 | width: 100%; 847 | border-bottom-left-radius: 4px; 848 | border-bottom-right-radius: 4px 849 | } 850 | 851 | .lb-dataContainer:after { 852 | content: ""; 853 | display: table; 854 | clear: both 855 | } 856 | 857 | .lb-data { 858 | padding: 0 4px; 859 | color: #ccc 860 | } 861 | 862 | .lb-data .lb-details { 863 | width: 85%; 864 | float: left; 865 | text-align: left; 866 | line-height: 1.1em 867 | } 868 | 869 | .lb-data .lb-caption { 870 | font-size: 13px; 871 | font-weight: 700; 872 | line-height: 1em 873 | } 874 | 875 | .lb-data .lb-caption a { 876 | color: #4ae 877 | } 878 | 879 | .lb-data .lb-number { 880 | display: block; 881 | clear: left; 882 | padding-bottom: 1em; 883 | font-size: 12px; 884 | color: #999 885 | } 886 | 887 | .lb-data .lb-close { 888 | display: block; 889 | float: right; 890 | width: 30px; 891 | height: 30px; 892 | text-align: right; 893 | outline: 0; 894 | filter: alpha(Opacity=70); 895 | opacity: .7; 896 | -webkit-transition: opacity .2s; 897 | -moz-transition: opacity .2s; 898 | -o-transition: opacity .2s; 899 | transition: opacity .2s 900 | } 901 | 902 | .lb-data .lb-close:hover { 903 | cursor: pointer; 904 | filter: alpha(Opacity=100); 905 | opacity: 1 906 | } 907 | 908 | /* 对于 phone & ipad 单独适配 */ 909 | 910 | @media screen and (max-width: 768px) { 911 | .dir-tree { 912 | display: none; 913 | } 914 | 915 | .markdown-body { 916 | margin-left: 0; 917 | margin-top: 0; 918 | margin-right: 0; 919 | margin-bottom: 0; 920 | border-radius: initial; 921 | } 922 | 923 | .next-front { 924 | position: relative; 925 | right: initial; 926 | top: initial; 927 | width: initial; 928 | display: flex; 929 | justify-content: center; 930 | align-items: center; 931 | /* right: 4%; 932 | width: 5%; 933 | top: 60%; */ 934 | } 935 | 936 | .change-article { 937 | margin: initial; 938 | border-radius: initial; 939 | /* 边框颜色 */ 940 | border: 1px solid rgba(149, 157, 165, 0.2); 941 | } 942 | 943 | .zood { 944 | display: none; 945 | } 946 | } -------------------------------------------------------------------------------- /docs/css/python.css: -------------------------------------------------------------------------------- 1 | pre[class*="language-python"] { 2 | background: #161b22; 3 | } 4 | 5 | pre[class*="language-python"] .Token { 6 | color: #C1CCCC; 7 | } 8 | 9 | pre[class*="language-python"] .Token.ID { 10 | color: #88D6FE; 11 | } 12 | 13 | pre[class*="language-python"] .Token.BraceDepth-0 { 14 | color: #FFD700; 15 | } 16 | 17 | pre[class*="language-python"] .Token.BraceDepth-1 { 18 | color: #DA70D6; 19 | } 20 | 21 | pre[class*="language-python"] .Token.BraceDepth-2 { 22 | color: #1A9DFF; 23 | } 24 | 25 | pre[class*="language-python"] .Token.HighlightLine, 26 | pre[class*="language-python"] .Token.HighlightToken { 27 | background-color: #2d3c4d; 28 | padding: 2px 0; 29 | } 30 | 31 | pre[class*="language-python"] .Token.COMMA, 32 | pre[class*="language-python"] .Token.COLON { 33 | color: #C1CCCC; 34 | } 35 | 36 | pre[class*="language-python"] .Token.UnaryOp, 37 | pre[class*="language-python"] .Token.NUMBER { 38 | color: #A2CEA8; 39 | } 40 | 41 | pre[class*="language-python"] .Token.STR { 42 | color: #CE9178; 43 | } 44 | 45 | pre[class*="language-python"] .Token.COMMENT { 46 | color: #6A9955; 47 | } 48 | 49 | 50 | pre[class*="language-python"] .Token.IF, 51 | pre[class*="language-python"] .Token.ELSE, 52 | pre[class*="language-python"] .Token.FOR, 53 | pre[class*="language-python"] .Token.WHILE, 54 | pre[class*="language-python"] .Token.ELIF, 55 | pre[class*="language-python"] .Token.RETURN, 56 | pre[class*="language-python"] .Token.BREAK, 57 | pre[class*="language-python"] .Token.IN, 58 | pre[class*="language-python"] .Token.FROM, 59 | pre[class*="language-python"] .Token.IMPORT, 60 | pre[class*="language-python"] .Token.CONTINUE, 61 | pre[class*="language-python"] .Token.TRY, 62 | pre[class*="language-python"] .Token.EXCEPT, 63 | pre[class*="language-python"] .Token.AS, 64 | pre[class*="language-python"] .Token.PASS, 65 | pre[class*="language-python"] .Token.DEL, 66 | pre[class*="language-python"] .Token.FINALLY, 67 | pre[class*="language-python"] .Token.ASSERT, 68 | pre[class*="language-python"] .Token.WITH, 69 | pre[class*="language-python"] .Token.RAISE, 70 | pre[class*="language-python"] .Token.YIELD { 71 | color: #C586C0; 72 | } 73 | 74 | pre[class*="language-python"] .Token.NONE, 75 | pre[class*="language-python"] .Token.IS, 76 | pre[class*="language-python"] .Token.TRUE, 77 | pre[class*="language-python"] .Token.FALSE, 78 | pre[class*="language-python"] .Token.NOT, 79 | pre[class*="language-python"] .Token.AND, 80 | pre[class*="language-python"] .Token.GLOBAL, 81 | pre[class*="language-python"] .Token.LAMBDA, 82 | pre[class*="language-python"] .Token.NONLOCAL, 83 | pre[class*="language-python"] .Token.OR { 84 | color: #569cd6; 85 | } 86 | 87 | pre[class*="language-python"] .Token.CLASS, 88 | pre[class*="language-python"] .Token.DEF { 89 | color: #569cd6; 90 | } 91 | 92 | pre[class*="language-python"] .Token.ClassName, 93 | pre[class*="language-python"] .Token.ClassInstantiation, 94 | pre[class*="language-python"] .Token.ImportLibName { 95 | color: #4EC9B0; 96 | } 97 | 98 | pre[class*="language-python"] .Token.FunctionName, 99 | pre[class*="language-python"] .Token.FunctionCall { 100 | color: #DCDCAA; 101 | } 102 | 103 | pre[class*="language-python"] .Token.INT, 104 | pre[class*="language-python"] .Token.STRR, 105 | pre[class*="language-python"] .Token.BOOL, 106 | pre[class*="language-python"] .Token.SUPER { 107 | color: #4EC9B0; 108 | } 109 | 110 | pre[class*="language-python"] .Token.EnumID { 111 | color: #4FC1FF; 112 | } -------------------------------------------------------------------------------- /docs/css/shell.css: -------------------------------------------------------------------------------- 1 | pre[class*="language-shell"] { 2 | background: #161b22; 3 | } 4 | 5 | pre[class*="language-shell"] .Token { 6 | color: #C1CCCC; 7 | } 8 | 9 | pre[class*="language-shell"] .Token.BraceDepth-0 { 10 | color: #FFD700; 11 | } 12 | 13 | pre[class*="language-shell"] .Token.BraceDepth-1 { 14 | color: #DA70D6; 15 | } 16 | 17 | pre[class*="language-shell"] .Token.BraceDepth-2 { 18 | color: #1A9DFF; 19 | } 20 | 21 | pre[class*="language-shell"] .Token.HighlightLine, 22 | pre[class*="language-shell"] .Token.HighlightToken { 23 | background-color: #2d3c4d; 24 | padding: 2px 0; 25 | } 26 | 27 | pre[class*="language-shell"] .Token.Program, 28 | pre[class*="language-shell"] .Token.Function, 29 | pre[class*="language-shell"] .Token.BACK_SLASH { 30 | color: #DCDCAA; 31 | } 32 | 33 | 34 | pre[class*="language-shell"] .Token.STRING { 35 | color: #CE9178; 36 | } 37 | 38 | pre[class*="language-shell"] .Token.NUMBER { 39 | color: #A2CEA8; 40 | } 41 | 42 | pre[class*="language-shell"] .Token.COMMENT { 43 | color: #6A9955; 44 | } 45 | 46 | pre[class*="language-shell"] .Token.Keyword { 47 | color: #C586C0; 48 | } 49 | 50 | pre[class*="language-shell"] .Token.OPTION { 51 | color: #b3b2b2; 52 | } 53 | 54 | pre[class*="language-shell"] .Token.Variant, 55 | pre[class*="language-shell"] .Token.VARIANT { 56 | color: #88D6FE; 57 | } 58 | 59 | pre[class*="language-shell"] .Token.Format { 60 | color: #88D6FE; 61 | } 62 | 63 | pre[class*="language-shell"] .Token.Control { 64 | color: --control; 65 | } 66 | 67 | pre[class*="language-shell"] .Token.Url { 68 | border-bottom: 1px solid #C1CCCC; 69 | } 70 | 71 | pre[class*="language-shell"] .Token.HOST_NAME { 72 | color: #4EC9B0; 73 | } 74 | 75 | pre[class*="language-shell"] .Token.DIR_PATH { 76 | color: #569cd6; 77 | } 78 | 79 | /* pre[class*="language-shell"] .Token.TreeNormal { 80 | color: #D4D4D4; 81 | } 82 | 83 | pre[class*="language-shell"] .Token.TreeExe { 84 | color: lime; 85 | } 86 | 87 | pre[class*="language-shell"] .Token.TreeIgnore { 88 | color: #C1CCCC; 89 | } */ 90 | pre[class*="language-shell"] .Token.HOST_NAME { 91 | color: #00c867; 92 | } -------------------------------------------------------------------------------- /docs/img/after_copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/docs/img/after_copy.png -------------------------------------------------------------------------------- /docs/img/before_copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/docs/img/before_copy.png -------------------------------------------------------------------------------- /docs/img/caution.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/img/enter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/docs/img/enter.png -------------------------------------------------------------------------------- /docs/img/enter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/img/important.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/img/moon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/docs/img/moon.png -------------------------------------------------------------------------------- /docs/img/note.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/img/question.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/img/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/img/sun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/docs/img/sun.png -------------------------------------------------------------------------------- /docs/img/tip.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/img/warning.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Document 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |

paperplotlib

paperplotlib 是基于 matplotlib 的论文实验数据绘图库, 意在快速绘制论文实验结果部分中常见的图表

本项目意在通过简洁的 API 调用来完成论文实验数据图的快速绘制

视频介绍: 【项目分享】论文实验数据绘图

安装

pip install paperplotlib

快速开始

import paperplotlib as ppl
19 | import numpy as np
20 | 
21 | # 随机生成一个 5 x 7 的数据
22 | a = 5
23 | b = 7
24 | y = np.random.randint(10, 100, size=(a, b))
25 | 
26 | # 初始化一个对象
27 | graph = ppl.BarGraph()
28 | 
29 | # 传入数据/组/列的文字信息
30 | group_names = [f"group {i}" for i in range(a)]
31 | column_names = [f"column {i}" for i in range(b)]
32 | graph.plot_2d(y, group_names, column_names)
33 | 
34 | # 调整x/y轴文字
35 | graph.x_label = "The number of data"
36 | graph.y_label = "Throughput (Mbps)"
37 | 
38 | # 保存图片
39 | graph.save()

参考

40 |
41 |
zood
42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /docs/js/change_mode.js: -------------------------------------------------------------------------------- 1 | 2 | var global_sun_src; 3 | var global_moon_src; 4 | 5 | function changeToLight(body, markdown_part, box, change_article_boxes) { 6 | body.className = 'light'; 7 | markdown_part.className = 'markdown-body markdown-light' 8 | box.src = global_sun_src; 9 | for (b of change_article_boxes) { 10 | b.classList.remove('change-dark'); 11 | } 12 | var dirTree = document.querySelector('.dir-tree'); 13 | dirTree.style.background = '#f6f8fa'; 14 | var allLinks = dirTree.querySelectorAll('a'); 15 | for (var i = 0; i < allLinks.length; i++) { 16 | allLinks[i].style.color = 'black'; 17 | } 18 | 19 | var navigator = document.querySelector('.header-navigator'); 20 | var allLinks = navigator.querySelectorAll('a'); 21 | for (var i = 0; i < allLinks.length; i++) { 22 | allLinks[i].style.color = 'black'; 23 | } 24 | 25 | var activate_links = dirTree.querySelectorAll('.link-active-dark'); 26 | for (var activate_link of activate_links) { 27 | activate_link.className = 'link-active'; 28 | } 29 | var search_bar = document.querySelector('.search-bar'); 30 | if (search_bar) { 31 | search_bar.style.background = '#f6f8fa'; 32 | } 33 | box.state = !box.state; 34 | localStorage.setItem('theme', 'light'); 35 | } 36 | 37 | function changeToDark(body, markdown_part, box, change_article_boxes) { 38 | body.className = 'dark'; 39 | markdown_part.className = 'markdown-body markdown-dark' 40 | box.src = global_moon_src; 41 | for (b of change_article_boxes) { 42 | b.classList.add('change-dark'); 43 | } 44 | var dirTree = document.querySelector('.dir-tree'); 45 | dirTree.style.background = '#252D38'; 46 | var allLinks = dirTree.querySelectorAll('a'); 47 | for (var i = 0; i < allLinks.length; i++) { 48 | allLinks[i].style.color = 'white'; 49 | } 50 | 51 | var navigator = document.querySelector('.header-navigator'); 52 | var allLinks = navigator.querySelectorAll('a'); 53 | for (var i = 0; i < allLinks.length; i++) { 54 | allLinks[i].style.color = 'white'; 55 | } 56 | 57 | var activate_links = dirTree.querySelectorAll('.link-active'); 58 | for (var activate_link of activate_links) { 59 | activate_link.className = 'link-active-dark'; 60 | } 61 | var search_bar = document.querySelector('.search-bar'); 62 | if (search_bar) { 63 | search_bar.style.background = '#252D38'; 64 | } 65 | box.state = !box.state; 66 | localStorage.setItem('theme', 'dark'); 67 | } 68 | 69 | function changeThemeMode() { 70 | let body = document.body; 71 | let markdown_part = document.querySelector('.markdown-body') 72 | let box = document.getElementById('changeThemeMode') 73 | let change_article_boxes = document.getElementsByClassName('change-article') 74 | if (box.state) { 75 | changeToLight(body, markdown_part, box, change_article_boxes) 76 | } else { 77 | changeToDark(body, markdown_part, box, change_article_boxes) 78 | } 79 | 80 | } 81 | 82 | // 添加切换颜色 83 | function addChangeModeButton(sun_src, moon_src) { 84 | global_sun_src = sun_src; 85 | global_moon_src = moon_src; 86 | var change_mode_button = document.createElement('img') 87 | change_mode_button.src = sun_src; 88 | change_mode_button.className = 'changeMode' 89 | change_mode_button.id = 'changeThemeMode' 90 | change_mode_button.onclick = changeThemeMode 91 | change_mode_button.state = false; // light: false | dark: true 92 | document.body.appendChild(change_mode_button) 93 | // 主题保持 94 | const savedTheme = localStorage.getItem('theme'); 95 | // 如果保存的主题存在,则设置当前主题为保存的主题 96 | if (savedTheme) { 97 | let body = document.body; 98 | let markdown_part = document.querySelector('.markdown-body') 99 | let change_article_boxes = document.getElementsByClassName('change-article') 100 | if (savedTheme == "dark") { 101 | changeToDark(body, markdown_part, change_mode_button, change_article_boxes); 102 | } else { 103 | change_mode_button.state = true; 104 | changeToLight(body, markdown_part, change_mode_button, change_article_boxes) 105 | } 106 | } 107 | } 108 | 109 | -------------------------------------------------------------------------------- /docs/js/copy_code.js: -------------------------------------------------------------------------------- 1 | 2 | var global_before_copy_url; 3 | var global_after_copy_url; 4 | 5 | function add(block) { 6 | 7 | var clip_board = document.createElement('img'); 8 | clip_board.id = 'code_copy'; 9 | clip_board.src = global_before_copy_url; 10 | clip_board.onclick = function () { 11 | clip_board.src = global_after_copy_url; 12 | var range = document.createRange(); 13 | range.selectNodeContents(block.firstChild); 14 | var selection = window.getSelection(); 15 | selection.removeAllRanges(); 16 | selection.addRange(range); 17 | navigator.clipboard.writeText(block.firstChild.innerText); 18 | } 19 | block.appendChild(clip_board) 20 | } 21 | 22 | 23 | 24 | function horizon_wheel(event, block, maxScroll) { 25 | event.preventDefault(); 26 | const scrollAmount = event.deltaY * 0.5; 27 | const imageElement = document.getElementById('code_copy'); 28 | const computedStyle = window.getComputedStyle(imageElement); 29 | const currentRight = parseInt(computedStyle.getPropertyValue('right')); 30 | 31 | // 判断是否已滚动到左右边缘 32 | if (block.scrollLeft === 0 && scrollAmount < 0) { 33 | // 已经滚动到左边缘并且向左滚动,不做任何操作 34 | } else if (maxScroll-block.scrollLeft <= 50 && scrollAmount > 0) { 35 | // 已经滚动到右边缘并且向右滚动,不做任何操作 36 | // imageElement.style.right = `${currentRight - scrollAmount}px`; 37 | } else { 38 | imageElement.style.right = `${currentRight - scrollAmount}px`; 39 | // console.log(block.scrollLeft,maxScroll) 40 | block.scrollLeft += scrollAmount; 41 | } 42 | } 43 | 44 | function remove(block) { 45 | var clip_board = document.getElementById('code_copy') 46 | block.removeChild(clip_board) 47 | } 48 | 49 | 50 | function addCodeCopy(before_copy_url, after_copy_url) { 51 | global_before_copy_url = before_copy_url; 52 | global_after_copy_url = after_copy_url; 53 | // 为所有代码段添加可以复制的标记 54 | var code_blocks = document.getElementsByTagName('pre') 55 | for (var i = 0; i < code_blocks.length; i++) { 56 | const code_block = code_blocks[i]; 57 | code_block.addEventListener("mouseenter", () => add(code_block)); 58 | code_block.addEventListener("mouseleave", () => remove(code_block)); 59 | // if (code_block.scrollWidth > code_block.clientWidth) { 60 | // // 如果有横向滚动,阻止页面默认的竖直滚动,并将滚动事件重定向到
 元素上
61 |         //     const blockWidth = code_block.offsetWidth;
62 |         //     const maxScroll = code_block.scrollWidth - blockWidth;
63 |         //     code_block.addEventListener('wheel', (event) => horizon_wheel(event, code_block, maxScroll));
64 |         // }
65 |     }
66 | }


--------------------------------------------------------------------------------
/docs/js/global_js_configuration.js:
--------------------------------------------------------------------------------
 1 | // 保存所有全局修改的配置
 2 | 
 3 | 
 4 | // 美化选择框
 5 | // - [ ] xxx
 6 | // - [x] aaa
 7 | var inputs = document.getElementsByTagName('input')
 8 | for(var i=0;i 来加载 Giscus
17 | var giscus = document.createElement('div');
18 | giscus.setAttribute('class', 'giscus');
19 | markdown_part.appendChild(giscus);
20 | 
21 | var currentUrl = window.location.href.slice(0, -1);
22 | var dirTree = document.querySelector(".dir-tree");
23 | var links = dirTree.querySelectorAll("a");
24 | 
25 | // 如果保存的主题存在,则设置当前主题为保存的主题
26 | const savedTheme = localStorage.getItem('theme');
27 | if (savedTheme !== null) {
28 |     if (savedTheme === 'light') {
29 |         markdown_part.className = 'markdown-body markdown-light'
30 |     } else {
31 |         markdown_part.className = 'markdown-body markdown-dark'
32 |     }
33 | }
34 | links.forEach(function(link) {
35 |   if (link.href === currentUrl) {
36 |     link.scrollIntoView({block: 'center', inline:'nearest', container: dirTree });
37 |     if (savedTheme) {
38 |         if (savedTheme == 'dark') {
39 |             link.classList.add("link-active-dark");
40 |         } else {
41 |             link.classList.add("link-active");
42 |         }
43 |     } else {
44 |         link.classList.add("link-active");
45 |     }
46 |   }
47 | });
48 | 
49 | // 代码段可编辑, 可选中
50 | var code_blocks = document.getElementsByTagName('pre');
51 | for (var i = 0; i < code_blocks.length; i++) {
52 |     code_blocks[i].setAttribute('contenteditable', 'true');
53 | }
54 | 
55 | document.onkeydown = function (e) {
56 |     // 对于左/右键被按下的情况, 切换至上一页下一页
57 |     if (e.key === "ArrowLeft") {
58 |         // console.log("左箭头键被按下");
59 |         // 找到第一个 change-article 类的 button
60 |         var button = document.querySelector(".change-article");
61 |         if (button.getAttribute('url') !== '.') {
62 |             window.location= button.getAttribute('url')
63 |         }
64 |     } else if (e.key === "ArrowRight") {
65 |         // console.log("右箭头键被按下");
66 |         // 找到最后一个 change-article 类的 button
67 |         var button = document.querySelector(".change-article:last-child");
68 |         if (button.getAttribute('url') !== '.') {
69 |             window.location= button.getAttribute('url')
70 |         }
71 |         
72 |     }
73 | }


--------------------------------------------------------------------------------
/docs/js/navigator.js:
--------------------------------------------------------------------------------
 1 | 
 2 | const divElement = document.getElementsByClassName("header-navigator")[0]; // 获取目标div元素
 3 | 
 4 | // 监听窗口尺寸变化
 5 | 
 6 | function handleResize() {
 7 |     var screenWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
 8 | 
 9 |     if (screenWidth > 768) {
10 | 
11 |         divElement.style.display = "block"; // 将display属性设置为block,以显示元素
12 | 
13 |         let navigator_links = document.querySelectorAll('.header-navigator ul li a[href^="#"]');
14 |         navigator_links.forEach(link => {
15 |             link.addEventListener('click', function (event) {
16 |                 event.preventDefault();
17 |                 let target = document.querySelector(this.getAttribute('href'));
18 |                 target.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'nearest' });
19 |                 // 修改网页 URL
20 |                 let url = window.location.href.split('#')[0];
21 |                 let newUrl = url + this.getAttribute('href');
22 |                 history.pushState(null, null, newUrl);
23 | 
24 |             });
25 |         });
26 | 
27 |         function isScrolledIntoView(elem) {
28 |             var docViewTop = window.pageYOffset;
29 |             var docViewBottom = docViewTop + window.innerHeight;
30 |             var elemTop = elem.offsetTop;
31 |             var elemBottom = elemTop + elem.offsetHeight;
32 |             return ((elemTop <= docViewBottom) && (elemBottom >= docViewTop));
33 |         }
34 | 
35 |         var headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
36 |         var previousHeading;
37 | 
38 |         window.addEventListener('scroll', function () {
39 |             var found = false;
40 |             for (var heading of headings) {
41 |                 if (!found && isScrolledIntoView(heading)) {
42 |                     var heading_id = heading.id;
43 |                     var link = document.querySelector(`a[href="#${heading_id}"]`);
44 |                     if (link) {
45 |                         link.style.fontWeight = 'bold';
46 |                         previousHeading = heading;
47 |                         found = true;
48 |                     }
49 |                 } else {
50 |                     var heading_id = heading.id;
51 |                     var link = document.querySelector(`a[href="#${heading_id}"]`);
52 |                     if (link) {
53 |                         link.style.fontWeight = 'normal';
54 |                     }
55 |                 }
56 |             }
57 | 
58 |             // If no heading is found, set the previous heading to bold
59 |             if (!found && previousHeading) {
60 |                 var previousLink = document.querySelector(`a[href="#${previousHeading.id}"]`);
61 |                 if (previousLink) {
62 |                     previousLink.style.fontWeight = 'bold';
63 |                 }
64 |             }
65 |         });
66 |     } else {
67 |         // 删除导航栏
68 |         divElement.style.display = "none";
69 |     }
70 | }
71 | 
72 | handleResize()
73 | 
74 | window.addEventListener('resize', handleResize)


--------------------------------------------------------------------------------
/docs/js/next_front.js:
--------------------------------------------------------------------------------
 1 | 
 2 | function addButton(x,text,url) {
 3 | 
 4 |     var button = document.createElement('button');
 5 |     button.innerText = text;
 6 |     button.setAttribute('url',url)
 7 |     button.className = 'change-article';
 8 |     button.onclick = function () {
 9 |         // 如果在原地, 不跳转
10 |         if (this.getAttribute('url') === '.') {
11 |             return;
12 |         }
13 |         window.location= this.getAttribute('url')
14 |     }
15 |     x.appendChild(button)
16 | }
17 | 
18 | function addLink(front_url,next_url,control) {
19 |     
20 |     let body = document.body;
21 |     var next_front = document.createElement('div')
22 |     next_front.className = 'next-front'
23 | 
24 |     // a: 只激活前一个
25 |     // b: 只激活后一个
26 |     // ab: 全部激活
27 |     // x: 全部不激活
28 |     if (control == 'x') {
29 |         return;
30 |     } else if (control == 'a') {
31 |         addButton(next_front,'上一个',front_url)
32 |     } else if (control == 'b') {
33 |         addButton(next_front,'下一个',next_url)
34 |     } else {
35 |         addButton(next_front,'上一个',front_url)
36 |         addButton(next_front,'下一个',next_url)
37 |     }
38 | 
39 |     body.appendChild(next_front)
40 | }
41 | 
42 | 


--------------------------------------------------------------------------------
/examples/bar1.py:
--------------------------------------------------------------------------------
 1 | import sys
 2 | import os
 3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
 4 | 
 5 | import paperplotlib as ppl
 6 | import numpy as np
 7 | 
 8 | # 随机生成一个 5 x 7 的数据
 9 | a = 5
10 | b = 3
11 | y = np.random.randint(10, 100, size=(a, b))
12 | 
13 | # 初始化一个对象
14 | graph = ppl.BarGraph()
15 | 
16 | # 传入数据/组/列的文字信息
17 | group_names = [f"group {i}" for i in range(a)]
18 | column_names = [f"column {i}" for i in range(b)]
19 | graph.plot_2d(y, group_names, column_names)
20 | 
21 | # 调整x/y轴文字
22 | graph.x_label = "The number of data"
23 | graph.y_label = "Throughput (Mbps)"
24 | 
25 | # 保存图片
26 | graph.save("bar1.png")


--------------------------------------------------------------------------------
/examples/bar2.py:
--------------------------------------------------------------------------------
 1 | import sys
 2 | import os
 3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
 4 | 
 5 | import paperplotlib as ppl
 6 | 
 7 | x = [2, 4, 8, 16, 32]
 8 | y = [2, 4, 8, 16, 32]
 9 | graph = ppl.BarGraph()
10 | graph.plot(x, y)
11 | graph.save('bar2.png')


--------------------------------------------------------------------------------
/examples/line_1.py:
--------------------------------------------------------------------------------
 1 | 
 2 | # 将上级目录加入环境变量
 3 | import sys
 4 | import os
 5 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
 6 | 
 7 | import paperplotlib as ppl
 8 | import numpy as np
 9 | 
10 | # 100个数据
11 | y = np.random.randint(10, 100, size=(100,))
12 | x = range(100)
13 | 
14 | graph = ppl.LineGraph()
15 | graph.plot(x,y)
16 | 
17 | graph.x_label = "The number of data"
18 | graph.y_label = "Throughput (Mbps)"
19 | # graph.disable_x_ticks = True
20 | # graph.disable_points = True
21 | graph.save("line_1.png")


--------------------------------------------------------------------------------
/examples/line_2.py:
--------------------------------------------------------------------------------
 1 | # 将上级目录加入环境变量
 2 | import sys
 3 | import os
 4 | 
 5 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
 6 | 
 7 | import paperplotlib as ppl
 8 | import numpy as np
 9 | 
10 | a = 3
11 | b = 50
12 | x = [i for i in range(b)]
13 | y = np.random.randint(10, 100, size=(a, b))
14 | 
15 | 
16 | line_names = ["line 1", "line 2", "line 3", "line 4", "line 5"]
17 | 
18 | graph = ppl.LineGraph()
19 | graph.plot_2d(x, y, line_names)
20 | 
21 | graph.x_label = "The number of data"
22 | graph.y_label = "Throughput (Mbps)"
23 | # graph.disable_x_ticks = True
24 | # graph.disable_points = True
25 | graph.title = "Line 2"
26 | graph.save("line_2.png")
27 | 


--------------------------------------------------------------------------------
/examples/stackbar_graph.py:
--------------------------------------------------------------------------------
 1 | import sys
 2 | import os
 3 | 
 4 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
 5 | 
 6 | 
 7 | import paperplotlib as ppl
 8 | import unittest
 9 | import numpy as np
10 | 
11 | 
12 | import paperplotlib as ppl
13 | 
14 | # 创建一个堆叠条形图
15 | stackbar_graph = ppl.StackBarGraph()
16 | 
17 | # 设置数据
18 | labels = [
19 |     "alloc_migration_target",
20 |     "try_to_migrate",
21 |     "move_to_new_folio",
22 |     "folio_add_lru" "remove_migration_ptes",
23 |     "migrate_folio_done",
24 | ]
25 | 
26 | # move_to_new_folio(30.311% 545/1798)
27 | # try_to_migrate(19.188% 345/1798)
28 | # migrate_folio_done(8.732% 157/1798)
29 | # alloc_migration_target(8.676% 156/1798)
30 | # remove_migration_ptes(8.287% 149/1798)
31 | # folio_add_lru(6.897% 124/1798)
32 | 
33 | # 百分比数据(按图片中的顺序)
34 | percentages = [8.676, 19.188, 30.311, 6.897, 8.287, 8.732]
35 | 
36 | # 绘制堆叠条形图
37 | stackbar_graph.direction = "horizontal"
38 | stackbar_graph.thinkness = 0.2
39 | stackbar_graph.plot(percentages, labels, name="migrate_page_batch")
40 | stackbar_graph.adjust_legend(alignment=3, font_size=20)
41 | # 保存图像
42 | stackbar_graph.save("stackbar_graph.png")
43 | 


--------------------------------------------------------------------------------
/images/paper/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paper/1.png


--------------------------------------------------------------------------------
/images/paper/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paper/10.png


--------------------------------------------------------------------------------
/images/paper/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paper/11.png


--------------------------------------------------------------------------------
/images/paper/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paper/12.png


--------------------------------------------------------------------------------
/images/paper/13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paper/13.png


--------------------------------------------------------------------------------
/images/paper/14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paper/14.png


--------------------------------------------------------------------------------
/images/paper/15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paper/15.png


--------------------------------------------------------------------------------
/images/paper/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paper/16.png


--------------------------------------------------------------------------------
/images/paper/17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paper/17.png


--------------------------------------------------------------------------------
/images/paper/18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paper/18.png


--------------------------------------------------------------------------------
/images/paper/19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paper/19.png


--------------------------------------------------------------------------------
/images/paper/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paper/2.png


--------------------------------------------------------------------------------
/images/paper/20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paper/20.png


--------------------------------------------------------------------------------
/images/paper/21.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paper/21.png


--------------------------------------------------------------------------------
/images/paper/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paper/3.png


--------------------------------------------------------------------------------
/images/paper/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paper/4.png


--------------------------------------------------------------------------------
/images/paper/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paper/6.png


--------------------------------------------------------------------------------
/images/paper/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paper/7.png


--------------------------------------------------------------------------------
/images/paper/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paper/8.png


--------------------------------------------------------------------------------
/images/paper/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paper/9.png


--------------------------------------------------------------------------------
/images/paperplotlib/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paperplotlib/1.png


--------------------------------------------------------------------------------
/images/paperplotlib/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paperplotlib/2.png


--------------------------------------------------------------------------------
/images/paperplotlib/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paperplotlib/3.png


--------------------------------------------------------------------------------
/images/paperplotlib/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paperplotlib/4.png


--------------------------------------------------------------------------------
/images/paperplotlib/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paperplotlib/5.png


--------------------------------------------------------------------------------
/images/paperplotlib/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paperplotlib/6.png


--------------------------------------------------------------------------------
/images/paperplotlib/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paperplotlib/7.png


--------------------------------------------------------------------------------
/images/paperplotlib/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paperplotlib/8.png


--------------------------------------------------------------------------------
/images/paperplotlib/result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/images/paperplotlib/result.png


--------------------------------------------------------------------------------
/md-docs/README.md:
--------------------------------------------------------------------------------
 1 | # paperplotlib
 2 | 
 3 | paperplotlib 是基于 matplotlib 的论文实验数据绘图库, 意在快速绘制论文实验结果部分中常见的图表
 4 | 
 5 | 本项目意在通过简洁的 API 调用来完成论文实验数据图的快速绘制
 6 | 
 7 | 视频介绍: [【项目分享】论文实验数据绘图](https://www.bilibili.com/video/BV1Qx421m7hx/)
 8 | 
 9 | ## 安装
10 | 
11 | ```bash
12 | pip install paperplotlib
13 | ```
14 | 
15 | ## 快速开始
16 | 
17 | ```python
18 | import paperplotlib as ppl
19 | import numpy as np
20 | 
21 | # 随机生成一个 5 x 7 的数据
22 | a = 5
23 | b = 7
24 | y = np.random.randint(10, 100, size=(a, b))
25 | 
26 | # 初始化一个对象
27 | graph = ppl.BarGraph()
28 | 
29 | # 传入数据/组/列的文字信息
30 | group_names = [f"group {i}" for i in range(a)]
31 | column_names = [f"column {i}" for i in range(b)]
32 | graph.plot_2d(y, group_names, column_names)
33 | 
34 | # 调整x/y轴文字
35 | graph.x_label = "The number of data"
36 | graph.y_label = "Throughput (Mbps)"
37 | 
38 | # 保存图片
39 | graph.save()
40 | ```
41 | 
42 | ![](https://raw.githubusercontent.com/luzhixing12345/paperplotlib/master/images/paperplotlib/result.png)
43 | 
44 | ## 参考
45 | 
46 | - [matplotlib](https://matplotlib.org/stable/users/index.html)
47 | - [matplotlib.pyplot的使用总结大全](https://www.zhihu.com/tardis/zm/art/139052035?source_id=1003)
48 | - [matplotlib.pyplot常用函数讲解大全(一)](https://zhuanlan.zhihu.com/p/139475633)
49 | - [matplotlib.pyplot常用函数讲解大全(二)](https://zhuanlan.zhihu.com/p/139946399)
50 | - [Presentation练习_科研论文中插图的配色原理与方案](https://www.bilibili.com/video/BV1cJ4m1j7No/)


--------------------------------------------------------------------------------
/md-docs/dir.yml:
--------------------------------------------------------------------------------
1 | .:
2 | - README: 1
3 | - 使用方法: 2
4 | - 使用示例: 3
5 | - 配色选择: 4
6 | graph:
7 | - 柱状图: 1
8 | - 折线图: 2
9 | 


--------------------------------------------------------------------------------
/md-docs/graph/折线图.md:
--------------------------------------------------------------------------------
1 | 
2 | # 折线图
3 | 


--------------------------------------------------------------------------------
/md-docs/graph/柱状图.md:
--------------------------------------------------------------------------------
1 | 
2 | # 柱状图
3 | 


--------------------------------------------------------------------------------
/md-docs/使用方法.md:
--------------------------------------------------------------------------------
  1 | 
  2 | # 使用方法
  3 | 
  4 | 使用 paperplotlib(下称ppl) 只需要四步
  5 | 
  6 | 1. 初始化一个实例对象 `graph`
  7 | 2. 准备好你的数据, 将数据添加到 `graph` 中
  8 | 3. 根据需要适当修改属性
  9 | 4. 保存图片
 10 | 
 11 | ppl 为您简化了初始化, 基础配置, 配色, 数据位置等一些小细节, 您可以非常简单方便的绘制一张美观的数据图
 12 | 
 13 | ## 总体介绍
 14 | 
 15 | ppl 提供了多数据图表的绘图
 16 | 
 17 | - 柱状图 (BarGraph)
 18 | - 折线图 (LineGraph)
 19 | 
 20 | 绘图 api 的使用方式非常类似, 分别对应四步
 21 | 
 22 | ```python
 23 | import paperplotlib as ppl
 24 | 
 25 | # 1. 初始化一个实例对象
 26 | graph = ppl.BarGraph()
 27 | 
 28 | # 2. 准备好你的数据, 将数据添加到 `graph` 中
 29 | x = [2, 4, 8, 16, 32]
 30 | y = [2, 4, 8, 16, 32]
 31 | graph.plot(x, y)
 32 | 
 33 | # 3. 根据需要适当修改属性
 34 | graph.x_label = "The number of data"
 35 | graph.y_label = "Throughput (Mbps)"
 36 | 
 37 | # 4. 保存图片
 38 | graph.save()
 39 | ```
 40 | 
 41 | 两个类均只有**两个传入数据的接口**: `plot` `plot_2d`
 42 | 
 43 | `plot` 用于绘制简单的一维数据, 如下所示, 传入 x,y 坐标即可
 44 | 
 45 | 
 46 | ![](https://raw.githubusercontent.com/luzhixing12345/paperplotlib/master/images/paperplotlib/5.png)
 47 | 
 48 | `plot_2d` 通常来说更为常用, 常见于论文 benchmark/workload 的数据结果中
 49 | 
 50 | `plot_2d` 的函数原型如下, 共有四个参数
 51 | 
 52 | ```python
 53 | def plot_2d(
 54 |         self,
 55 |         data: List[List[float]],
 56 |         group_names: List[str],
 57 |         column_names: List[str],
 58 |         emphasize_index: int = -1
 59 |     ):
 60 |     ...
 61 | ```
 62 | 
 63 | - **data**: 一个二维数组, 第一维代表有几组, 第二维代表有几列
 64 | - **group_names**: 每个组的名称
 65 | - **column_names**: 每一列的名称
 66 | - **emphasize_index**: 高亮的列索引(可选)
 67 | 
 68 | 一个简单易懂的示例如下:
 69 | 
 70 | ```python
 71 | import paperplotlib as ppl
 72 | import numpy as np
 73 | 
 74 | # 随机生成一个 5 x 7 的数据
 75 | a = 5
 76 | b = 7
 77 | y = np.random.randint(10, 100, size=(a, b))
 78 | 
 79 | # 初始化一个对象
 80 | graph = ppl.BarGraph()
 81 | 
 82 | # 传入数据/组/列的文字信息
 83 | group_names = [f"group {i}" for i in range(a)]
 84 | column_names = [f"column {i}" for i in range(b)]
 85 | graph.plot_2d(y, group_names, column_names)
 86 | 
 87 | # 调整x/y轴文字
 88 | graph.x_label = "The number of data"
 89 | graph.y_label = "Throughput (Mbps)"
 90 | 
 91 | # 保存图片
 92 | graph.save()
 93 | ```
 94 | 
 95 | ![](https://raw.githubusercontent.com/luzhixing12345/paperplotlib/master/images/paperplotlib/6.png)
 96 | 
 97 | 其中 `emphasize_index` 参数用于高亮某一列, 可以传入列的索引值
 98 | 
 99 | > 这通常应用在某一列是新的方法, 其他的是对比方法, 此时其他列将会选择相近的颜色而突出该列
100 | 
101 | ![](https://raw.githubusercontent.com/luzhixing12345/paperplotlib/master/images/paperplotlib/3.png)
102 | 
103 | ## 配色
104 | 
105 | ppl 的配色采用偏冷色调的蓝色, **且是不建议调整的**, 对于 1-7 列均可以采用统一的风格绘制
106 | 
107 | ![](https://raw.githubusercontent.com/luzhixing12345/paperplotlib/master/images/paperplotlib/1.png)
108 | 
109 | ![](https://raw.githubusercontent.com/luzhixing12345/paperplotlib/master/images/paperplotlib/2.png)
110 | 
111 | 对于超过 7 种配色的情况, 采用渐变色处理
112 | 
113 | ![](https://raw.githubusercontent.com/luzhixing12345/paperplotlib/master/images/paperplotlib/7.png)
114 | 
115 | ## 其他配置
116 | 
117 | 初始化阶段可以传入一个参数来决定配色
118 | 
119 | ```python
120 | style_id = 3
121 | graph = ppl.BarGraph(style_id)
122 | ```
123 | 
124 | > 下图顺次对应 1 - 5
125 | 
126 | ![20240413204928](https://raw.githubusercontent.com/learner-lu/picbed/master/20240413204928.png)
127 | 
128 | ### 长图
129 | 
130 | 对于有大量数据集结果的情况, 您可以将 `width_picture` 设置为 `True`, 即可生成一张长图, 可以横跨两栏放置, 类似如下的位置
131 | 
132 | ![20240408215030](https://raw.githubusercontent.com/learner-lu/picbed/master/20240408215030.png)
133 | 
134 | ```python
135 | graph.width_picture = True
136 | ```
137 | 
138 | ### x y 坐标
139 | 
140 | 直接设置字符串即可
141 | 
142 | ```python
143 | graph.x_label = "Workload"
144 | graph.y_label = "Max QPS\nNormalized DDR 100%"
145 | ```
146 | 
147 | ### 画一条横线
148 | 
149 | ```python
150 | graph.add_line(50)
151 | ```
152 | 
153 | ### y 轴范围
154 | 
155 | 传入一个元组类型设置范围
156 | 
157 | ```python
158 | graph.y_lim = (0, 1.2)
159 | ```


--------------------------------------------------------------------------------
/md-docs/使用示例.md:
--------------------------------------------------------------------------------
 1 | 
 2 | # 使用示例
 3 | 
 4 | ```python
 5 | import paperplotlib as ppl
 6 | import numpy as np
 7 | 
 8 | # 设定数组的行数a和列数b
 9 | a = 6
10 | b = 4
11 | y = np.random.randint(10, 100, size=(a, b))
12 | 
13 | group_names = ["Canneal", "Memcached", "XSBench", "Graph500", "HashJoin", "BTree"]
14 | column_names = [f"socket {i}" for i in range(b)]
15 | 
16 | graph = ppl.BarGraph()
17 | graph.plot_2d(y, group_names, column_names)
18 | graph.y_lim = (0, 100)
19 | graph.save()
20 | ```
21 | 
22 | ![](https://raw.githubusercontent.com/luzhixing12345/paperplotlib/master/images/paper/1.png)
23 | ![](https://raw.githubusercontent.com/luzhixing12345/paperplotlib/master/images/paperplotlib/1.png)
24 | 
25 | ```python
26 | import paperplotlib as ppl
27 | import numpy as np
28 | 
29 | # 设定数组的行数a和列数b
30 | a = 5
31 | b = 5
32 | y = np.random.rand(a,b)
33 | 
34 | group_names = ["A", "B", "C", "D", "F"]
35 | column_names = ["DDR5:CXL-A = 100:0", "75:25", "50:50", "25:75", "0:100"]
36 | 
37 | graph = ppl.BarGraph()
38 | graph.plot_2d(y, group_names, column_names)
39 | graph.x_label = "Workload"
40 | graph.y_label = "Max QPS\nNormalized DDR 100%"
41 | graph.y_lim = (0, 1.2)
42 | graph.save()
43 | ```
44 | 
45 | ![](https://raw.githubusercontent.com/luzhixing12345/paperplotlib/master/images/paper/2.png)
46 | ![](https://raw.githubusercontent.com/luzhixing12345/paperplotlib/master/images/paperplotlib/2.png)
47 | 
48 | 让某一列突出
49 | 
50 | ```diff
51 | import paperplotlib as ppl
52 | import numpy as np
53 | 
54 | # 设定数组的行数a和列数b
55 | a = 5
56 | b = 5
57 | y = np.random.rand(a,b)
58 | 
59 | group_names = ["A", "B", "C", "D", "F"]
60 | column_names = ["DDR5:CXL-A = 100:0", "75:25", "50:50", "25:75", "0:100"]
61 | 
62 | graph = ppl.BarGraph()
63 | - graph.plot_2d(y, group_names, column_names)
64 | + graph.plot_2d(y, group_names, column_names, emphasize_index=0)
65 | graph.x_label = "Workload"
66 | graph.y_label = "Max QPS\nNormalized DDR 100%"
67 | graph.y_lim = (0, 1.2)
68 | graph.save()
69 | ```
70 | 
71 | ![](https://raw.githubusercontent.com/luzhixing12345/paperplotlib/master/images/paperplotlib/3.png)
72 | 
73 | 
74 | 宽图, 适用于多组数据, 双栏
75 | 
76 | ```python
77 | import paperplotlib as ppl
78 | import numpy as np
79 | 
80 | # 设定数组的行数a和列数b
81 | a = 8
82 | b = 6
83 | y = np.random.randint(10, 100, size=(a, b))
84 | 
85 | group_names = [f"group {i}" for i in range(a)]
86 | column_names = [f"column {i}" for i in range(b)]
87 | 
88 | graph = ppl.BarGraph()
89 | graph.plot_2d(y, group_names, column_names)
90 | graph.x_label = "The number of data"
91 | graph.y_label = "Throughput (Mbps)"
92 | graph.width_picture = True
93 | graph.save()
94 | ```
95 | 
96 | ![](https://raw.githubusercontent.com/luzhixing12345/paperplotlib/master/images/paperplotlib/4.png)
97 | 


--------------------------------------------------------------------------------
/md-docs/配色选择.md:
--------------------------------------------------------------------------------
 1 | 
 2 | # 配色选择
 3 | 
 4 | ![20240413204928](https://raw.githubusercontent.com/learner-lu/picbed/master/20240413204928.png)
 5 | 
 6 | ## 单色
 7 | 
 8 | 虽然现在我们选用单色作图的情况并不是很多,因为大多数情况要进行数据比较.如果需要使用单色的情况,建议使用上述的**蓝色**,要比黑色或者灰色更加活泼.
 9 | 
10 | ## 双色、三色
11 | 
12 | - 双色、三色搭配要避免选择标准色,千篇一律的标准色搭配容易显得没有质感.
13 | - 双色搭配,可以选择两个互补色,就是两个差别非常大的颜色,这样的对比会更加明显.一般会选黑色、深棕色和其它颜色做搭配.
14 | - 三色搭配,可以选差异性比较大的颜色.颜色少的时候可以偏向于选择深色系颜色做搭配.
15 | 
16 | ## 四色及以上
17 | 
18 | 有多组数据的时候需要考虑协调和易读性.多组数据的插图建议用邻近色搭配,协调且易读,临近颜色搭配给人一种循序渐进的感觉.
19 | 
20 | 邻近色用暖色或者冷色调都可以.
21 | 
22 | ## 参考
23 | 
24 | - [审稿人爱的配色这里都有](https://zhuanlan.zhihu.com/p/674796591)
25 | - [跟顶刊学图片配色,1分钟完成一套审稿人最爱的配色方案!](https://zhuanlan.zhihu.com/p/636789620)
26 | - [MATLAB | MATLAB配色不够用?全网最全的colormap补充包来啦!](https://zhuanlan.zhihu.com/p/580945672)
27 | - [分享 | 顶刊高质量论文插图配色(含RGB值及16进制HEX码)(第一期)](https://zhuanlan.zhihu.com/p/670396774)
28 | - [分享 | 顶刊高质量论文插图配色(含RGB值及16进制HEX码)(第二期)](https://zhuanlan.zhihu.com/p/674906660)
29 | - [调色板界的"扛把子"--palettable](https://zhuanlan.zhihu.com/p/374631766)
30 | - [Python数据分析之Seaborn(配色方案)](https://cloud.tencent.com/developer/article/1670419)
31 | - [跟顶刊学配色!SCI论文插图经典配色实例第1期](https://zhuanlan.zhihu.com/p/593320758)
32 | - [coolors trending](https://coolors.co/palettes/trending)
33 | - [eagle cn best-color-tools-for-designers](https://cn.eagle.cool/blog/post/best-color-tools-for-designers)


--------------------------------------------------------------------------------
/paperplotlib/__init__.py:
--------------------------------------------------------------------------------
1 | 
2 | from .line_graph import LineGraph
3 | from .bar_graph import BarGraph
4 | from .stackbar_graph import StackBarGraph
5 | from .color import *


--------------------------------------------------------------------------------
/paperplotlib/bar_graph.py:
--------------------------------------------------------------------------------
 1 | from typing import List, Union
 2 | from .graph import Graph
 3 | from .color import COLOR
 4 | import numpy as np
 5 | 
 6 | 
 7 | class BarGraph(Graph):
 8 |     def __init__(self, style_id: int = 1) -> None:
 9 |         super().__init__(style_id=style_id)
10 | 
11 |         self._bar_width = 0.3  # 柱状图宽度 [0-1] (default 0.8)
12 |         self._group_threshold = 0.15  # 组间距
13 | 
14 |     def plot(self, x_data: List[Union[str, int]], y_data: List[float]):
15 |         """
16 |         绘制一维柱状图
17 | 
18 |         ## Parameters
19 |         x_data: x 轴数据
20 |         y_data: y 轴数据
21 |         """
22 |         # x 轴坐标等距
23 |         x_ticks = range(len(x_data))
24 |         self.ax.bar(x_ticks, y_data, width=self._bar_width, color=COLOR.get_colors(1, self.style_id))
25 |         # x 轴标签和位置的映射
26 |         self.ax.set_xticks(x_ticks, x_data)
27 | 
28 |     def plot_2d(
29 |         self, data: List[List[float]], group_names: List[str], column_names: List[str], emphasize_index: int = -1
30 |     ):
31 |         """
32 |         绘制二维柱状图
33 | 
34 |         data: 二维列表,每个元素为一组数据
35 |         group_names: 每个组的名称
36 |         column_names: 每一列的名称
37 |         emphasize_index: 高亮的列索引
38 |         """
39 |         assert np.shape(data) == (len(group_names), len(column_names)), "二维数据应为二维列表"
40 | 
41 |         group_len = len(group_names)
42 |         column_len = len(column_names)
43 | 
44 |         if emphasize_index != -1:
45 |             assert (
46 |                 type(emphasize_index) == int and emphasize_index < len(column_names) and emphasize_index >= 0
47 |             ), f"emphasize_index应在[0, {len(column_names)})之间"
48 | 
49 |         # 如果列数很多, 考虑到组间距, 所以重新计算一下柱状图宽度
50 |         if column_len >= 3:
51 |             self._bar_width = (0.5 - self._group_threshold) / column_len * 2
52 | 
53 |         colors = COLOR.get_colors(column_len, self.style_id, emphasize_index)
54 |         for i in range(column_len):
55 |             bar_pos = -column_len + 2 * i + 1
56 |             x_ticks = [j + bar_pos / 2 * self._bar_width for j in range(group_len)]
57 |             bar_data = [data[j][i] for j in range(group_len)]
58 |             self._bars = self.ax.bar(
59 |                 x_ticks, bar_data, width=self._bar_width, color=colors[i], edgecolor="black", linewidth=0.5
60 |             )
61 |         self.ax.set_xticks(range(group_len), group_names)
62 |         self.ax.tick_params(bottom=False)
63 | 
64 |         self.set_label_legend(column_names)
65 | 
66 |     def add_line(self, y: int, line_style="-"):
67 |         self.ax.axhline(y, linestyle=line_style, linewidth=0.5, color="black")
68 | 


--------------------------------------------------------------------------------
/paperplotlib/color.css:
--------------------------------------------------------------------------------
 1 | .cold {
 2 |     color: #acbcdb #012790;
 3 | }
 4 | 
 5 | /* Demystifying CXL Memory with Genuine CXL-Ready Systems and Devices */
 6 | .style-1 {
 7 |     color: #0070c0;
 8 |     color: #ffc000 #0070c0;
 9 |     color: #ffc000 #4472c4 #63c0cf;
10 |     color: #ffc000 #ed7d31 #0070c0 #63c0cf;
11 |     color: #ffc000 #ed7d31 #5b9bd5 #4bbabd #358d8f;
12 |     color: #ffc000 #ed7d31 #5b9bd5 #0070c0 #4bbabd #358d8f;
13 |     color: #ffc000 #ed7d31 #2f5597 #5b9bd5 #0070c0 #4bbabd #358d8f;
14 | }
15 | 
16 | .style-2 {
17 |     color: #0e3e8b #fbe57c;
18 |     color: #0e3e8b #366bae #fbe57c;
19 |     color: #0e3e8b #366bae #e2eaef #fbe57c;
20 |     color: #0e3e8b #366bae #e2eaef #fbe57c #ddb33d;
21 | }
22 | 
23 | .style-3 {
24 |     color: #5c8fc6 #8dc2e9 #d6a370 #794d35 ;
25 |     color: #0f2b61 #5c8fc6 #8dc2e9 #d6a370  #794d35;
26 |     color: #0f2b61 #5c8fc6 #8dc2e9 #d7af88  #d6a370  #794d35;
27 | }
28 | 
29 | .style-4 {
30 |     color: #3851a3 #72aacc #fdba6c #eb5d3b;
31 |     color: #3851a3 #72aacc #fdba6c #eb5d3b #a90226;
32 |     color: #3851a3 #72aacc #fefbba #fdba6c #eb5d3b #a90226 ;
33 |     color: #3851a3 #72aacc #cae9f3 #fefbba #fdba6c #eb5d3b #a90226;
34 | }
35 | 
36 | .style-5 {
37 |     color: #405095 #59b5e8 #d9d3e8 #fbf9ce;
38 |     color: #405095 #3e8fbf #59b5e8 #d9d3e8 #fbf9ce;
39 |     color: #405095 #3e8fbf #59b5e8 #a6c0e5 #d9d3e8 #fbf9ce;
40 |     color: #405095 #3e8fbf #59b5e8 #a6c0e5 #d9d3e8 #fbf9ce #faf6a3;
41 | }
42 | 
43 | .style-6 {
44 |     color: #205898 #5c9bcf #c9cedb #dcdbc7;
45 |     color: #205898 #5c9bcf #cee3f0 #c9cedb #dcdbc7;
46 |     color: #205898 #5c9bcf #cee3f0 #c9cedb #dcdbc7 #d8d59c;
47 | }
48 | 
49 | /* Harnessing Integrated CPU-GPU System Memory for HPC: a first look into Grace Hopper */
50 | .style-7 {
51 |     color: #4184f3;
52 |     color: #4184f3 #e94234;
53 |     color: #4184f3 #e94234 #fabb03;
54 |     color: #4184f3 #e94234 #fabb03 #33a852;
55 |     color: #4184f3 #e94234 #fabb03 #33a852 #46bdc5;
56 | }


--------------------------------------------------------------------------------
/paperplotlib/color.py:
--------------------------------------------------------------------------------
 1 | import re
 2 | import os
 3 | import numpy as np
 4 | import matplotlib.colors as mcolors
 5 | from typing import List, Dict
 6 | 
 7 | 
 8 | class Color:
 9 | 
10 |     def __init__(self) -> None:
11 |         self.colors: Dict[str, Dict[int, List[str]]] = {}
12 | 
13 |     def add(self, name: str, hex_groups: List[List[str]]):
14 |         self.colors[name] = {}
15 |         for hex_group in hex_groups:
16 |             self.colors[name][len(hex_group)] = hex_group
17 | 
18 |     def get_colors(self, color_num: int, style_id: int = 1, emphasize_index: int = -1) -> List[str]:
19 |         if emphasize_index != -1:
20 |             return self.get_emphasize(emphasize_index, color_num)
21 |         # 对于更多颜色的情况, 采用渐变
22 |         style_name = f"style-{style_id}"
23 |         assert style_name in self.colors, f"{style_name} 不存在, 有效的样式为 {list(self.colors.keys())}"
24 |             
25 |         colors = self.colors[style_name].get(color_num)
26 |         # 如果没有该数量的颜色, 采用渐变
27 |         if colors is None:
28 |             colors = generate_color_gradient(self.colors["cold"][2][0], self.colors["cold"][2][1], color_num)
29 |         return colors
30 | 
31 |     def get_emphasize(self, index, color_num: int) -> List[str]:
32 |         emphasized_color = "#ffc000"
33 |         colors = generate_color_gradient(self.colors["cold"][2][0], self.colors["cold"][2][1], color_num)
34 |         colors.insert(index, emphasized_color)
35 |         return colors
36 | 
37 | 
38 | def parse_colors() -> Color:
39 |     """
40 |     从 color.css 中解析颜色
41 |     """
42 |     with open(os.path.join(os.path.dirname(__file__), "color.css")) as f:
43 |         content = f.read()
44 | 
45 |     plot_color = Color()
46 |     css_classes = re.finditer(r"\.(.*?) \{(.*?)\}", content, re.DOTALL)
47 |     for css_class in css_classes:
48 |         color_name = css_class.group(1).strip()
49 |         color_values = css_class.group(2).split("\n")
50 |         hex_groups: List[List[str]] = []
51 |         for color_value in color_values:
52 |             if len(color_value) == 0:
53 |                 continue
54 |             hex_values = re.findall(r"#[0-9a-fA-F]{6}", color_value)
55 |             hex_groups.append(hex_values)
56 |         plot_color.add(color_name, hex_groups)
57 | 
58 |     return plot_color
59 | 
60 | 
61 | def hex_to_rgb(hex_color):
62 |     # 将十六进制颜色代码转换为RGB元组
63 |     return mcolors.hex2color(hex_color)
64 | 
65 | 
66 | def rgb_to_hex(rgb_color):
67 |     # 将RGB元组转换为十六进制颜色代码
68 |     return mcolors.rgb2hex(rgb_color)
69 | 
70 | 
71 | def generate_color_gradient(hex_color1, hex_color2, num_colors):
72 |     # 将十六进制颜色代码转换为RGB元组
73 |     color1 = hex_to_rgb(hex_color1)
74 |     color2 = hex_to_rgb(hex_color2)
75 | 
76 |     # 生成颜色渐变
77 |     r = np.linspace(color1[0], color2[0], num_colors)
78 |     g = np.linspace(color1[1], color2[1], num_colors)
79 |     b = np.linspace(color1[2], color2[2], num_colors)
80 | 
81 |     gradient_colors = [rgb_to_hex((r[i], g[i], b[i])) for i in range(num_colors)]
82 |     return gradient_colors
83 | 
84 | COLOR = parse_colors()
85 | 


--------------------------------------------------------------------------------
/paperplotlib/font/consola-1.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhixing12345/paperplotlib/15338a52e915dd37bf37ab821d760b0412cdc564/paperplotlib/font/consola-1.ttf


--------------------------------------------------------------------------------
/paperplotlib/graph.py:
--------------------------------------------------------------------------------
  1 | import matplotlib
  2 | 
  3 | # 非交互式 GUI 使用 Agg
  4 | matplotlib.use("Agg")
  5 | import matplotlib.pyplot as plt
  6 | import os
  7 | from typing import List, Optional, Union, Tuple
  8 | from matplotlib.font_manager import FontProperties
  9 | import matplotlib.font_manager as fm
 10 | 
 11 | 
 12 | class Graph:
 13 |     """
 14 |     图表
 15 |     """
 16 |     def __init__(self, style_id: int = 1, subplots: Tuple[int, int] = None) -> None:
 17 |         self.style_id = style_id
 18 |         self.subplots = subplots
 19 | 
 20 |         if subplots is not None:
 21 |             self.fig, self.ax = plt.subplots(subplots[0], subplots[1])
 22 |         else:
 23 |             self.fig = plt.figure(figsize=(8, 4))
 24 |             self.ax = self.fig.add_subplot(111)
 25 | 
 26 |         # -- configuation --
 27 |         self.x_label: Optional[str] = None  # x轴标签
 28 |         self.y_label: Optional[str] = None  # y轴标签
 29 |         self.width_picture = False  # 是否是宽图
 30 |         self.grid = "y"  # 网格线 x | y | xy | None
 31 |         self.y_lim: Optional[Tuple[float, float]] = None
 32 | 
 33 |         # 基本属性
 34 |         self.grid_color = "#dedede"  # 网格线颜色
 35 |         self.grid_style = "-"  # 网格线类型 - | --
 36 |         self.grid_width = 1  # 网格线宽度
 37 |         self.grid_alpha = 0.8  # 网格线透明度
 38 | 
 39 |         # 保存图片
 40 |         self.dpi = 300
 41 |         self.bbox_inches = "tight"  # 适当上下左右留白
 42 | 
 43 |         self.title: Optional[str] = None  # 图表标题
 44 | 
 45 |         font_path = f"{os.path.dirname(__file__)}/font/consola-1.ttf"
 46 |         fm.fontManager.addfont(font_path)
 47 |         plt.rcParams["font.family"] = "Consolas"
 48 | 
 49 |         # legend
 50 |         self.legend_labels = None
 51 |         self.legend_loc = None
 52 |         self.legend_bbox_to_anchor = None
 53 |         self.legend_ncols = None
 54 |         self.legend_font_size = 'medium'
 55 | 
 56 |     def plot(self, x_data: List[float], y_data: List[float]):  # pragma: no cover
 57 |         """
 58 |         填入数据
 59 |         """
 60 |         raise NotImplementedError("请在子类中实现此方法")
 61 | 
 62 |     def plot_2d(
 63 |         self, y_data: List[List[float]], group_names: List[str], column_names: List[str], emphasize_index: int = -1
 64 |     ):  # pragma: no cover
 65 |         """
 66 |         绘制二维柱状图
 67 | 
 68 |         ## Parameters
 69 |         y_data: 二维列表,每个元素为一组数据
 70 |         group_names: 每个组的名称
 71 |         column_names: 每一列的名称
 72 |         """
 73 |         raise NotImplementedError("请在子类中实现此方法")
 74 | 
 75 |     def _create_graph(self):  # pragma: no cover
 76 |         self._check_config()
 77 | 
 78 |         if self.width_picture:
 79 |             self.fig.set_size_inches(16, 4)
 80 |         self.ax.set_xlabel(self.x_label)
 81 |         self.ax.set_ylabel(self.y_label)
 82 | 
 83 |         if self.grid is not None:
 84 |             if "x" in self.grid:
 85 |                 self.ax.xaxis.grid(
 86 |                     True,
 87 |                     linestyle=self.grid_style,
 88 |                     linewidth=self.grid_width,
 89 |                     color=self.grid_color,
 90 |                     alpha=self.grid_alpha,
 91 |                 )
 92 |             if "y" in self.grid:
 93 |                 self.ax.yaxis.grid(
 94 |                     True,
 95 |                     linestyle=self.grid_style,
 96 |                     linewidth=self.grid_width,
 97 |                     color=self.grid_color,
 98 |                     alpha=self.grid_alpha,
 99 |                 )
100 |             self.ax.set_axisbelow(True)
101 | 
102 |         if self.y_lim is not None:
103 |             self.ax.set_ylim(self.y_lim)
104 | 
105 |         if self.title is not None:
106 |             self.fig.text(0.5, -0.02, self.title, ha="center", fontsize=14, weight="bold")
107 | 
108 |         if self.legend_labels is not None:
109 |             self.legend = self.ax.legend(
110 |                 self.legend_labels,
111 |                 loc=self.legend_loc,  # 居中置顶
112 |                 ncols=self.legend_ncols,  # 横向排布
113 |                 bbox_to_anchor=self.legend_bbox_to_anchor,  # 置于图外侧
114 |                 handlelength=1,  # 图例长宽, 修改为正方形
115 |                 handleheight=1,  # 图例长宽, 修改为正方形
116 |                 handletextpad=0.4,  # 缩短文字和图例的间距
117 |                 fontsize=self.legend_font_size,  # 图例文字大小
118 |             )
119 | 
120 |     def adjust_graph(self):
121 |         """
122 |         子类中可以重写该函数来调整图表
123 |         """
124 | 
125 |     def save(self, path: str = "result.png"):
126 |         """
127 |         保存图片
128 |         """
129 |         self._create_graph()
130 |         self.adjust_graph()
131 |         plt.tight_layout()
132 |         plt.savefig(path, dpi=self.dpi, bbox_inches=self.bbox_inches)
133 |         print(f"save picture in {path}")
134 | 
135 |     def _check_config(self):
136 |         """
137 |         检查配置的属性是否设置的合理
138 |         """
139 |         assert self.grid in ["x", "y", "xy", None], "grid 参数值只能是 x | y | xy | None"
140 |         assert self.width_picture in [True, False], "width_picture 参数值只能是 True | False"
141 | 
142 |     def set_label_legend(self, column_names, position: str = "w", alignment: str = "-"):
143 |         """
144 |         position should be 1 or 2 of 'wasd'
145 | 
146 |         w/a/s/d means up/left/down/right in keyboard
147 |         """
148 |         self.legend_labels = column_names
149 | 
150 |         # https://matplotlib.org/stable/api/legend_api.html#module-matplotlib.legend
151 |         self.legend_loc = "upper center"
152 |         self.legend_bbox_to_anchor = (0.5, 1.15)
153 |         self.legend_ncols = len(column_names)
154 | 
155 |         # legend position
156 | 
157 |         # bbox_to_anchor
158 |         # x:相对于图形的水平位置(通常 0 到 1 的值,1 表示图的最右边).
159 |         # y:相对于图形的垂直位置(通常 0 到 1 的值,1 表示图的顶部)
160 |         if position == "w":
161 |             self.legend_loc = "upper center"
162 |             self.legend_bbox_to_anchor = (0.5, 1.15)
163 | 
164 |         elif position == "d":
165 |             self.legend_loc = "upper left"
166 |             self.legend_bbox_to_anchor = (1.05, 1)
167 | 
168 |         elif position == "wd":
169 |             self.legend_loc = "upper right"
170 |             self.legend_bbox_to_anchor = None
171 | 
172 |         # legend alignment
173 |         if alignment == "-":
174 |             self.legend_ncols = len(column_names)
175 |         elif alignment == "|":
176 |             self.legend_ncols = 1
177 |         elif type(alignment) == int:
178 |             self.legend_ncols = alignment
179 | 
180 |     def adjust_legend(self, position: str = None, alignment: str = None, bbox_to_anchor: Tuple[float, float] = None, font_size: int = None):
181 | 
182 |         if position:
183 |             self.legend_loc(position)
184 | 
185 |         if alignment:
186 |             if alignment == "-":
187 |                 self.legend_ncols = len(self.legend_labels)
188 |             elif alignment == "|":
189 |                 self.legend_ncols = 1
190 |             elif type(alignment) == int:
191 |                 self.legend_ncols = alignment
192 |             else:
193 |                 raise ValueError("alignment should be int or '-' or '|'")
194 | 
195 |         if bbox_to_anchor:
196 |             self.legend_bbox_to_anchor = bbox_to_anchor
197 | 
198 |         if font_size:
199 |             self.legend_font_size = font_size


--------------------------------------------------------------------------------
/paperplotlib/line_graph.py:
--------------------------------------------------------------------------------
 1 | from .graph import Graph
 2 | from typing import List
 3 | from .color import COLOR
 4 | import matplotlib.ticker as ticker
 5 | 
 6 | 
 7 | class LineGraph(Graph):
 8 |     def __init__(self) -> None:
 9 |         super().__init__()
10 |         self.grid = "xy"
11 |         # https://matplotlib.org/stable/api/markers_api.html
12 |         self.all_markers = ["o", "^", "x", "s", "D", "*", "+", "v", "p", "P", "h", "H", "1", "2", "3", "4", "X"]
13 |         self.disable_x_ticks = False  # 是否禁用 x 轴刻度
14 |         self.disable_points = False  # 是否禁用点
15 |         self.line_width = 1.5
16 | 
17 |     def adjust_graph(self):
18 | 
19 |         # 线条宽度
20 |         for line in self.ax.get_lines():
21 |             line.set_linewidth(self.line_width)
22 | 
23 |         if self.disable_x_ticks:
24 |             self.ax.xaxis.set_major_locator(ticker.NullLocator())
25 |         if self.disable_points:
26 |             # 修改 marker
27 |             for line in self.ax.get_lines():
28 |                 line.set_marker("")
29 |             # 修改图例中的 marker
30 |             legend = self.ax.get_legend()
31 |             if legend is not None:  # 检查图例是否存在
32 |                 for legend_line in legend.get_lines():
33 |                     legend_line.set_marker("")
34 | 
35 |     def plot(self, x_data: List[int], y_data: List[float]):
36 |         """
37 |         绘制一维折线图
38 | 
39 |         ## Parameters
40 |         x_data: x 轴数据
41 |         y_data: y 轴数据
42 |         """
43 |         # x 轴坐标等距
44 |         if x_data is None:
45 |             x_data = range(len(y_data))
46 |         x_ticks = range(len(x_data))
47 |         self.ax.plot(
48 |             x_ticks, y_data, linewidth=2, marker="o", markersize=5, color=COLOR.get_colors(1, self.style_id)[0]
49 |         )
50 |         # x 轴标签和位置的映射
51 |         self.ax.set_xticks(x_ticks, x_data)
52 | 
53 |     def plot_2d(self, x_data: List[int], y_data: List[List[float]], line_names: List[str], emphasize_index: int = -1):
54 |         # x 轴坐标等距
55 |         if x_data is None:
56 |             x_data = range(len(y_data[0]))
57 |         x_ticks = range(len(x_data))
58 |         line_number = len(line_names)
59 | 
60 |         assert line_number <= len(self.all_markers), "markers 数量不足"
61 |         markers = self.all_markers[:line_number]
62 |         colors = COLOR.get_colors(line_number, self.style_id, emphasize_index)
63 |         for i, y in enumerate(y_data):
64 |             self.ax.plot(x_ticks, y, linewidth=2, label=line_names[i], marker=markers[i], markersize=5, color=colors[i])
65 |         # x 轴标签和位置的映射
66 |         self.ax.set_xticks(x_ticks, x_data)
67 |         self.legend = self.ax.legend(
68 |             line_names,
69 |             loc="upper center",  # 居中置顶
70 |             ncols=line_number,  # 横向排布
71 |             bbox_to_anchor=(0.5, 1.15),  # 置于图外侧
72 |             handlelength=1,  # 图例长宽, 修改为正方形
73 |             handleheight=1,  # 图例长宽, 修改为正方形
74 |             handletextpad=0.4,  # 缩短文字和图例的间距
75 |             fontsize="x-small" if line_number >= 7 else "medium",  # 图例文字大小
76 |         )
77 | 


--------------------------------------------------------------------------------
/paperplotlib/stackbar_graph.py:
--------------------------------------------------------------------------------
 1 | from typing import List, Tuple, Union
 2 | from .graph import Graph
 3 | from .color import COLOR
 4 | import numpy as np
 5 | 
 6 | 
 7 | class StackBarGraph(Graph):
 8 |     """
 9 |     堆叠条形图
10 |     """
11 | 
12 |     def __init__(self, style_id: int = 1, subplots: Tuple[int, int] = None) -> None:
13 |         super().__init__(style_id, subplots)
14 | 
15 |         self.percentage = False  # 是否转换为百分比
16 |         # 方向
17 |         self.direction = "vertical"  # or horizontal
18 | 
19 |         self._bar_width = 0.3
20 |         self.thinkness = 0.5 # [0-1] 越大收缩越多
21 | 
22 |     def adjust_graph(self):
23 |         
24 |         left_margin = self.thinkness  # 可以根据需要调整空白的大小
25 |         right_margin = self.thinkness
26 |         
27 |         if self.direction == "vertical":
28 |             self.fig.set_size_inches(4, 6)
29 |             current_xlim = self.ax.get_xlim()
30 |             # 设置新的 x 轴范围
31 |             self.ax.set_xlim([current_xlim[0] - left_margin, current_xlim[1] + right_margin])
32 |         elif self.direction == "horizontal":
33 |             self.fig.set_size_inches(16, 4)
34 |             current_ylim = self.ax.get_ylim()
35 |             # 设置新的 y 轴范围
36 |             self.ax.set_ylim([current_ylim[0] - left_margin, current_ylim[1] + right_margin])
37 |         else:
38 |             raise ValueError("direction must be vertical or horizontal")
39 | 
40 |     def plot(self, data: List[float], labels: List[str], name: str = ""):
41 |         """
42 |         @param x_data: x 轴数据
43 |         @param y_data: labels
44 |         """
45 | 
46 |         colors = COLOR.get_colors(len(labels), self.style_id)
47 |         cumulative = 0
48 | 
49 |         if self.percentage:
50 |             data = np.array(data) / np.sum(data) * 100
51 | 
52 |         for data, color, label in zip(data, colors, labels):
53 |             if self.direction == "vertical":
54 |                 self.ax.bar(
55 |                     [name],
56 |                     data,
57 |                     color=color,
58 |                     bottom=cumulative,
59 |                     label=label,
60 |                     width=self._bar_width,
61 |                     edgecolor="black",
62 |                     linewidth=0.5,
63 |                 )
64 |             else:
65 |                 self.ax.barh(
66 |                     [name],
67 |                     data,
68 |                     color=color,
69 |                     left=cumulative,
70 |                     label=label,
71 |                     height=self._bar_width,
72 |                     edgecolor="black",
73 |                     linewidth=0.5,
74 |                 )
75 | 
76 |             cumulative += data
77 | 
78 |         self.set_label_legend(labels, position="w", alignment="|")
79 | 


--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
 1 | [tool.poetry]
 2 | name = "paperplotlib"
 3 | version = "0.0.7"
 4 | description = "论文实验数据绘图"
 5 | authors = ["luzhixing12345 "]
 6 | license = "MIT"
 7 | readme = "README.md"
 8 | repository = "https://github.com/luzhixing12345/paperplotlib"
 9 | documentation = "https://luzhixing12345.github.io/paperplotlib/"
10 | 
11 | 
12 | [tool.poetry.dependencies]
13 | python = "^3.7"
14 | matplotlib = "^3.7.0"
15 | numpy = "^1.23.5"
16 | 
17 | [build-system]
18 | requires = ["poetry-core"]
19 | build-backend = "poetry.core.masonry.api"
20 | 


--------------------------------------------------------------------------------
/test.py:
--------------------------------------------------------------------------------
 1 | import sys
 2 | import os
 3 | 
 4 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
 5 | 
 6 | 
 7 | import paperplotlib as ppl
 8 | import unittest
 9 | import numpy as np
10 | 
11 | 
12 | class Test(unittest.TestCase):
13 |     
14 |     def test_bar_1d(self):
15 |         x = [2, 4, 8, 16, 32]
16 |         y = [2, 4, 8, 16, 32]
17 |         graph = ppl.BarGraph()
18 |         graph.plot(x, y)
19 |         graph.x_label = "The number of data"
20 |         graph.y_label = "Throughput (Mbps)"
21 | 
22 |         graph.save()
23 | 
24 |     
25 |     def test_bar_2d(self):
26 |         # 设定数组的行数a和列数b
27 |         a = 8  # 例如:5行
28 |         b = 4  # 例如:3列
29 | 
30 |         # 使用numpy的random.randint函数生成一个a行b列的随机整数数组
31 |         # 假设我们想要的随机数范围是从0到99
32 |         y = np.random.randint(10, 100, size=(a, b))
33 | 
34 |         group_names = [f"group {i}" for i in range(a)]
35 |         column_names = [f"column {i}" for i in range(b)]
36 | 
37 |         g1 = ppl.BarGraph()
38 |         g1.plot_2d(y, group_names, column_names)
39 |         g1.x_label = "The number of data"
40 |         g1.y_label = "Throughput (Mbps)"
41 |         g1.width_picture = True
42 |         g1.add_line(50)
43 |         g1.save()
44 |         
45 |         g2 = ppl.BarGraph()
46 |         g2.plot_2d(y, group_names, column_names, 0)
47 |         
48 |         g3 = ppl.BarGraph()
49 |         a = 6
50 |         b = 10
51 |         y = np.random.randint(10, 100, size=(a, b))
52 |         group_names = [f"group {i}" for i in range(a)]
53 |         column_names = [f"column {i}" for i in range(b)]
54 |         g3.plot_2d(y, group_names, column_names)
55 |     
56 |     def test_line_1d(self):
57 |         x = [2, 4, 8, 16, 32]
58 |         y = [2, 4, 8, 16, 32]
59 |         graph = ppl.LineGraph()
60 |         graph.plot(x, y)
61 |         graph.x_label = "The number of data"
62 |         graph.y_label = "Throughput (Mbps)"
63 |         graph.y_lim = (0, 50)
64 |         graph.disable_x_ticks = True
65 |         graph.disable_points = True
66 |         graph.save()
67 |     
68 |     def test_line_2d(self):
69 |         a = 5
70 |         b = 6
71 |         y = np.random.randint(10, 100, size=(a, b))
72 | 
73 |         x_data = [i ** 2 for i in range(1, b+1)]
74 |         line_names = [f"line {i}" for i in range(b)]
75 | 
76 |         g1 = ppl.LineGraph()
77 |         g1.plot_2d(x_data, y, line_names)
78 |         g1.x_label = "The number of data"
79 |         g1.y_label = "Throughput (Mbps)"
80 |         g1.save()
81 | 
82 | 
83 | 
84 | if __name__ == "__main__": # pragma: no cover
85 |     unittest.main()


--------------------------------------------------------------------------------