├── .gitignore ├── LICENSE ├── README.md ├── chart ├── __init__.py ├── core.py ├── data_loader.py └── exceptions.py ├── demo ├── .gitignore ├── a.dat ├── b.dat ├── complex.json ├── list.json ├── pivot.dat └── pivot_complex.dat ├── run.py └── styles ├── rcParams_1.json ├── rcParams_2.json ├── rcParams_2_pf.json ├── rcParams_3.json ├── rcParams_4.json └── rcParams_5.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__ 3 | .DS_Store 4 | *.pdf 5 | out* 6 | .idea 7 | .vs -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Surlavi 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ExpFigureTool是快速将实验结果批量生成实验图(折线图,柱状图)的工具 2 | 3 | 基于Python和[matplotlib](https://matplotlib.org/) 4 | 5 | ## 环境要求 6 | - Python 2 or 3 (python 3支持测试中) 7 | - matplotlib >= 2.0 8 | - future库 (`pip install future`) 9 | - Latex环境 10 | - Windows下建议使用wsl 11 | 12 | ## 使用教程 13 | 14 | - 在有了实验数据文件之后,需要编写一个json文件,用来表示如何从实验数据生成实验图,参考`demo/list.json`,具体说明见下 15 | 16 | - 执行`python run.py list.json`即可根据`list.json`绘图, 17 | 18 | - list.json文件中包含一个json数组,这个数组有若干个FigItem组成,每一个FigItem表示由一个数据文件绘制一张实验图 19 | 20 | ```javascript 21 | [ 22 | { 23 | // FigItem 24 | }, 25 | { 26 | // Another FigItem 27 | } 28 | // ... 29 | ] 30 | ``` 31 | 32 | - 每一个FigItem包含以下信息 33 | 34 | ```javascript 35 | { 36 | "file": "", // 数据文件名 37 | // 以下信息非必须,有默认值 38 | "output": "", // 输出文件名,默认为输入文件名后缀换为pdf 39 | "style": 1, // 预设的格式,可选1~5,默认为1,分别对应style文件夹中 40 | "chart.type": "", // 输出图表类型,可选"line"(折线图)或"bar"(柱状图),默认为折线图 41 | "separator": "," // 数据分割字符,默认为空白字符(空格、制表符) 42 | } 43 | ``` 44 | 45 | - FigItem根据数据文件的不同可以分为两类 46 | 47 | - 简单表格(参考demo/a.dat,demo/b.dat) 48 | - 第一行为标题信息 49 | 50 | - 第一列为对应的x值,第一列第一个元素用于显示x label 51 | 52 | - 其余列为不同组下的y值,每列第一个元素用于显示图例 53 | 54 | - FigItem中需包含 55 | 56 | ```javascript 57 | { 58 | "y_label": "", // 纵轴label 59 | } 60 | ``` 61 | 62 | - 数据透视表(参考demo/pivot.dat) 63 | 64 | - 很多情况下实验结果文件以数据透视表(pivot table)的形式呈现,即每一行代表一个项目(实验结果),不同的列代表不同的域,如: 65 | 66 | | Name | Gender | Age | Cource | Score | 67 | | --------- | ------ | ---- | ------- | ----- | 68 | | Zhang San | Male | 18 | Math | 100 | 69 | | Li Si | Male | 19 | Math | 98 | 70 | | Xiao Hong | Female | 20 | English | 95 | 71 | 72 | - ExpFigureTool可以直接根据该格式类型的文件生成实验图 73 | 74 | - 对应的FigItem中需要包含以下项目 75 | 76 | ```javascript 77 | { 78 | "file": "", // 数据文件名,同简单表格 79 | "pivotTable": true, // 表示数据格式为数据透视表 80 | "pivotTable.category": "Gender", // 表示category的列 81 | "pivotTable.independentVariable": "Age", // 自变量(x轴)的列 82 | "pivotTable.value": "Score", // 因变量(y轴)的列 83 | 84 | "pivotTable.point": "mean", // 对于每一个点的取值方式,可选mean和median 85 | "pivotTable.errorBar": "std" // error bar类型,可选min-max,std,percentile 86 | } 87 | ``` 88 | 89 | - FigItem中的其他常用设置项 90 | 91 | ```javascript 92 | { 93 | "xlabel": "Age", // 将覆盖默认显示的x label 94 | "ylabel": "Score", // 将覆盖默认显示的y label 95 | "xtick.lim": [0, 0.8], // x轴显示范围 96 | "xtick.nbins": 3, // x轴最多显示3个label 97 | "xtick.interval": 0.2, // x轴刻度间隔,不可与nbins同用 98 | "xticks": [3, 4, 5], // 指定x轴上显示哪些label,适用于数值类型的x轴,覆盖上面除lim外的所有设置,可与对数坐标轴同用 99 | "ytick.lim": [0, 0.8], // y轴,同上 100 | "ytick.interval": 0.2, 101 | "ytick.nbins": 5, 102 | "yticks": [1, 2, 3], 103 | "marker": false, // 折线图中是否显示数据点,默认为true 104 | } 105 | ``` 106 | 107 | 108 | 109 | ## 其他功能 110 | 111 | - 所有文本支持latex语法,如`$\alpha$` 112 | - json文件中支持`//`开头作为注释 113 | - `demo/complex.json`中包含了更多高级功能的使用方法,可作为参考 114 | 115 | ## 待完善功能 116 | 117 | - 错误提示 -------------------------------------------------------------------------------- /chart/__init__.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | from chart.core import DrawingCore 3 | -------------------------------------------------------------------------------- /chart/core.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | from __future__ import print_function 3 | from future.utils import lmap, lfilter 4 | import matplotlib 5 | matplotlib.use('Agg') # reset matplotlib 6 | 7 | from chart.data_loader import NormalDataLoader, PivotTableDataLoader 8 | from exceptions import FigureToolException 9 | from matplotlib import pyplot as plt 10 | from matplotlib.ticker import FuncFormatter, ScalarFormatter, NullFormatter, FixedLocator 11 | import numpy as np 12 | from functools import reduce 13 | import json 14 | import os 15 | import re 16 | import matplotlib.ticker as ticker 17 | from collections import OrderedDict 18 | 19 | colors = [ 20 | ['#3399CC', '#99CC33', '#CC0033', '#663399', '#FF9900', '#336666'], 21 | ["#e41a1c", "#377eb8", "#4daf4a", "#984ea3", "#ff7f00", "#75755c", "#a65628", "#f781bf",], 22 | ["#8dd3c7", "#bebada", "#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#fccde5",] 23 | ] 24 | 25 | line_types = ['s-', '^-', 'o-', 'p-', 'H-', 'd-', 'h-', '<-', '2-', 'v-'] 26 | 27 | bar_patterns = ('//', 'xxx', '\\\\', '*', 'o', 'O', '.') 28 | 29 | 30 | class DrawingCore: 31 | def __init__(self, filename, settings, mode): 32 | try: 33 | print("%s " % filename, end="") 34 | self.filename = filename 35 | self.mode = mode 36 | self.x_points = [] 37 | self.y_points_arr = [] 38 | self.x_label = None 39 | self.y_label = None 40 | self.categories = [] 41 | self.legends = [] 42 | self.colors = colors[0] 43 | self.bar_colors = self.colors 44 | self.point_types = line_types 45 | 46 | self.output_file = self.filename.split('.')[0] + '.pdf' 47 | if 'output' in settings: 48 | self.output_file = settings['output'] 49 | 50 | self.draw_data = [] 51 | self.draw_data_output_file = ".".join(self.output_file.split('.')[0:-1]) + '.txt' 52 | 53 | self.settings = { 54 | 'x_label': None, 55 | 'y_label': None, 56 | 'chart.type': 'line', 57 | 'errorBar': False, 58 | 'pivotTable': False, 59 | 'pivotTable.point': 'median', 60 | 'pivotTable.errorBar': 'min-max', 61 | 'style': 4, 62 | 'marker': True, 63 | 'separator': None, 64 | 'legend.loc': 'best', 65 | 'legend.ncol': '1', 66 | 'legend.mode': None, 67 | 'legend.bbox_to_anchor': None, 68 | 'legend.handlelength': None, 69 | 70 | 'data.n_groups': None, 71 | 72 | 'xtick.log': False, 73 | 'ytick.log': False, 74 | 75 | 'xtick.force_non_digit': False, 76 | 'xtick.order': False, 77 | 78 | 'grid.horizontal': None, 79 | 'grid.vertical': None, 80 | 81 | 'lines.alpha': 1, 82 | 83 | 'categories': False, 84 | 85 | 'filter': False 86 | } 87 | self.settings.update(settings) 88 | self.x_label = self.settings['x_label'] 89 | self.y_label = self.settings['y_label'] 90 | self.rcParams = {} 91 | if 'rcParams' in settings: 92 | self.rcParams = settings['rcParams'] 93 | 94 | self.init_plt() 95 | self.init_data() 96 | 97 | self.draw() 98 | except FigureToolException as e: 99 | print("error detected:", e) 100 | 101 | def init_plt(self): 102 | style_file = os.path.join(os.path.split(os.path.realpath(__file__))[0], '../styles', 'rcParams_' + str(self.settings['style']) + '.json') 103 | f = open(style_file) 104 | input_str = "\n".join(f.readlines()) 105 | input_str = re.sub(r'\\\n', '', input_str) 106 | input_str = re.sub(r'//.*\n', '\n', input_str) 107 | params = json.loads(input_str, object_pairs_hook=OrderedDict) 108 | if self.mode == "draft": 109 | params.pop('font.family') 110 | params.pop('font.serif') 111 | params.pop('font.sans-serif') 112 | params.pop('font.cursive') 113 | params.pop('font.monospace') 114 | params.pop('text.usetex') 115 | params.pop('text.latex.unicode') 116 | plt.rcParams.update(params) 117 | plt.clf() 118 | f.close() 119 | 120 | def init_data(self): 121 | if self.settings['pivotTable']: 122 | self.data = PivotTableDataLoader() 123 | self.data.load(self.filename, self.settings['separator'], data_filter=self.settings['filter']) 124 | self.data.set_fields(self.settings['pivotTable.category'], self.settings['pivotTable.independentVariable'], self.settings['pivotTable.value']) 125 | self.data.set_default_func(self.settings['pivotTable.point']) 126 | if not self.x_label: 127 | self.x_label = self.settings['pivotTable.independentVariable'] 128 | if not self.y_label: 129 | self.y_label = self.settings['pivotTable.value'] 130 | else: 131 | self.data = NormalDataLoader() 132 | self.data.load(self.filename, self.settings['separator']) 133 | if not self.x_label: 134 | self.x_label = self.data.category_name 135 | 136 | def draw(self): 137 | plt.grid(True, linestyle='--') 138 | ax = plt.gca() 139 | if not self.settings['grid.vertical']: 140 | ax.xaxis.grid(False) 141 | if not self.settings['grid.horizontal']: 142 | ax.yaxis.grid(False) 143 | # ax.spines['right'].set_color('none') 144 | # ax.spines['top'].set_color('none') 145 | 146 | if self.settings['pivotTable'] and self.settings['pivotTable.errorBar']: 147 | self.settings['errorBar'] = True 148 | if self.settings['chart.type'] == 'line': 149 | self.draw_line() 150 | elif self.settings['chart.type'] == 'bar': 151 | self.draw_bar() 152 | 153 | if 'xtick.lim' in self.settings: 154 | plt.xlim(self.settings['xtick.lim']) 155 | if 'ytick.lim' in self.settings: 156 | plt.ylim(self.settings['ytick.lim']) 157 | if 'xtick.interval' in self.settings: 158 | start, end = ax.get_xlim() 159 | ax.xaxis.set_ticks(np.arange(start, end + self.settings['xtick.interval'], self.settings['xtick.interval'])) 160 | if 'xtick.nbins' in self.settings: 161 | ax.locator_params(axis='x', nbins=self.settings['xtick.nbins']) 162 | if 'ytick.interval' in self.settings: 163 | start, end = ax.get_ylim() 164 | ax.yaxis.set_ticks(np.arange(start, end + self.settings['ytick.interval'], self.settings['ytick.interval'])) 165 | if 'ytick.nbins' in self.settings: 166 | ax.locator_params(axis='y', nbins=self.settings['ytick.nbins']) 167 | if 'xtick.use_k' in self.settings: 168 | ax.xaxis.set_major_formatter(FuncFormatter(lambda x, y: str(int(x / 1000)) + 'k')) 169 | if 'ytick.use_k' in self.settings: 170 | ax.yaxis.set_major_formatter(FuncFormatter(lambda x, y: str(int(x / 1000)) + 'k')) 171 | 172 | scalar_formatter = ScalarFormatter() 173 | scalar_formatter.set_scientific(False) 174 | scalar_formatter.labelOnBase = False 175 | null_formatter = NullFormatter() 176 | null_formatter.labelOnBase = False 177 | 178 | log_formatter = ticker.FuncFormatter(lambda y,pos: ('{{:.{:1d}f}}'.format(int(np.maximum(-np.log10(y),0)))).format(y)) 179 | 180 | if self.settings['xtick.log']: 181 | ax.set_xscale('log') 182 | if self.settings['xtick.log'] != True: 183 | ax.set_xscale('log', basex=self.settings['xtick.log']) 184 | ax.xaxis.set_major_formatter(log_formatter) 185 | ax.xaxis.set_minor_formatter(null_formatter) 186 | if self.settings['ytick.log']: 187 | ax.set_yscale('log') 188 | if self.settings['ytick.log'] != True: 189 | ax.set_yscale(self.settings['ytick.log']) 190 | ax.yaxis.set_major_formatter(log_formatter) 191 | ax.yaxis.set_minor_formatter(null_formatter) 192 | 193 | if 'xticks' in self.settings: 194 | ax.xaxis.set_ticks(self.settings['xticks'], lmap(str, self.settings['xticks'])) 195 | fixed_locator = FixedLocator(self.settings['xticks']) 196 | ax.xaxis.set_major_locator(fixed_locator) 197 | 198 | if 'yticks' in self.settings: 199 | ax.yaxis.set_ticks(self.settings['yticks'], lmap(str, self.settings['yticks'])) 200 | fixed_locator = FixedLocator(self.settings['yticks']) 201 | ax.yaxis.set_major_locator(fixed_locator) 202 | 203 | ax.xaxis.set_ticks_position('bottom') 204 | ax.yaxis.set_ticks_position('left') 205 | 206 | plt.xlabel(self.x_label) 207 | plt.ylabel(self.y_label) 208 | 209 | plt.legend(loc=self.settings['legend.loc'], ncol=int(self.settings['legend.ncol']), 210 | mode=self.settings['legend.mode'], bbox_to_anchor=self.settings['legend.bbox_to_anchor'], 211 | handlelength=self.settings['legend.handlelength'], 212 | borderpad=0.3) 213 | 214 | 215 | plt.savefig(self.output_file) 216 | plt.close() 217 | 218 | if self.settings['errorBar']: 219 | self.output_draw_data() 220 | 221 | print('done.') 222 | 223 | def draw_line(self): 224 | x_points = self.data.X 225 | if self.settings['xtick.order']: 226 | x_points = self.settings['xtick.order'] 227 | is_digit = reduce(lambda x, y: x and not re.match(r'[+-]?\d+(.\d+)?$', y) == None, x_points, True) 228 | if self.settings['xtick.force_non_digit']: 229 | is_digit = False 230 | map_dict = {} 231 | if not is_digit: 232 | x_ticks = x_points 233 | x_points = range(0, len(x_points)) 234 | plt.xticks(range(0, len(x_points)), x_ticks) 235 | plt.xlim((-1, len(x_points))) 236 | for i in range(0, len(x_points)): 237 | map_dict[x_ticks[i]] = i 238 | 239 | for i in range(0, len(self.data.categories)): 240 | points_type = self.point_types[i] 241 | if self.settings['categories']: 242 | if i >= len(self.settings['categories']): 243 | break 244 | if type(self.settings['categories']) == list: 245 | cat = self.settings['categories'][i] 246 | cat_name = cat 247 | elif type(self.settings['categories']) == OrderedDict: 248 | cat = list(self.settings['categories'])[i] 249 | cat_name = self.settings['categories'][cat] 250 | else: 251 | cat = self.data.categories[i] 252 | cat_name = cat 253 | points = self.data.grouped_data(cat) 254 | if not self.settings['marker']: 255 | points_type = points_type[1:2] 256 | 257 | y_points = points[1] 258 | new_xp = points[0] 259 | if is_digit: 260 | x_points = [float(x) for x in points[0]] 261 | 262 | # if is digit all data should be sorted 263 | tps = [] 264 | for _ in range(len(x_points)): 265 | tps.append((x_points[_], y_points[_], new_xp[_])) 266 | tps = sorted(tps, key=lambda __: __[0]) 267 | x_points = map(lambda __: __[0], tps) 268 | y_points = map(lambda __: __[1], tps) 269 | new_xp = map(lambda __: __[2], tps) 270 | else: 271 | if self.settings['xtick.order']: 272 | new_xp = [] 273 | y_points = [] 274 | for x in self.settings['xtick.order']: 275 | if x in points[0]: 276 | new_xp.append(x) 277 | y_points.append(points[1][points[0].index(x)]) 278 | x_points = [map_dict[x] for x in new_xp] 279 | if i >= len(self.colors): 280 | print("too many lines") 281 | 282 | # transform y points to float 283 | y_points = lmap(float, y_points) 284 | plt.plot(x_points, y_points, points_type, color=self.colors[i], label=cat_name, 285 | alpha=self.settings['lines.alpha']) 286 | if self.settings['errorBar']: 287 | points_err = self.get_error_data(cat, new_xp) 288 | # print points_err 289 | plt.errorbar(x_points, y_points, yerr=points_err, ecolor=self.colors[i], 290 | color=self.colors[i], alpha=self.settings['lines.alpha'], 291 | fmt="none", elinewidth=3, capsize=5) 292 | 293 | def get_error_data(self, cat, x_points): 294 | points = self.data.grouped_data(cat) 295 | f_data = [points[0], points[1]] 296 | 297 | if self.settings['pivotTable.errorBar'] == 'min-max': 298 | points_up = np.array(self.data.grouped_data(cat, 'max')[1]) 299 | points_down = np.array(self.data.grouped_data(cat, 'min')[1]) 300 | f_data.append(points_down) 301 | f_data.append(points_up) 302 | points_err = [np.array(points[1]) - points_down, points_up - np.array(points[1])] 303 | elif self.settings['pivotTable.errorBar'] == 'percentile': 304 | points_up = np.array(self.data.grouped_data(cat, 'percentile_95')[1]) 305 | points_down = np.array(self.data.grouped_data(cat, 'percentile_5')[1]) 306 | f_data.append(points_down) 307 | f_data.append(points_up) 308 | points_err = [np.array(points[1]) - points_down, points_up - np.array(points[1])] 309 | elif self.settings['pivotTable.errorBar'] == 'std': 310 | points_delta = np.array(self.data.grouped_data(cat, 'std')[1]) 311 | f_data.append(points_delta) 312 | points_err = [points_delta, points_delta] 313 | else: 314 | raise Exception("Error bar function not implemented") 315 | 316 | ret_err = [[],[]] 317 | # print points[0].index(u'4') 318 | # print x_points 319 | for x in x_points: 320 | idx = points[0].index(x) 321 | ret_err[0].append(points_err[0][idx]) 322 | ret_err[1].append(points_err[1][idx]) 323 | 324 | self.draw_data.append((cat, f_data)) 325 | 326 | return ret_err 327 | 328 | def draw_bar(self): 329 | tot_width = 0.8 330 | x_points_origin = (self.data.X) 331 | if self.settings['xtick.order']: 332 | x_points_origin = self.settings['xtick.order'] 333 | 334 | map_dict = {} 335 | x_ticks = x_points_origin 336 | for i in range(0, len(x_points_origin)): 337 | map_dict[x_ticks[i]] = i 338 | 339 | xs = range(0, len(x_points_origin)) 340 | categories = self.data.categories 341 | if self.settings['categories']: 342 | categories = self.settings['categories'] 343 | width = tot_width / len(categories) 344 | # print width 345 | for i in range(0, len(categories)): 346 | if self.settings['categories']: 347 | if i >= len(self.settings['categories']): 348 | break 349 | if type(self.settings['categories']) == list: 350 | cat = self.settings['categories'][i] 351 | cat_name = cat 352 | elif type(self.settings['categories']) == OrderedDict: 353 | cat = self.settings['categories'].keys()[i] 354 | cat_name = self.settings['categories'][cat] 355 | else: 356 | cat = self.data.categories[i] 357 | cat_name = cat 358 | points = self.data.grouped_data(cat) 359 | 360 | y_points = points[1] 361 | x_points = xs 362 | new_xp = points[0] 363 | if self.settings['xtick.order']: 364 | new_xp = [] 365 | y_points = [] 366 | for x in self.settings['xtick.order']: 367 | if x in points[0]: 368 | new_xp.append(x) 369 | y_points.append(points[1][points[0].index(x)]) 370 | x_points = map(lambda xx: map_dict[xx], new_xp) 371 | 372 | # print map(lambda x: x - tot_width / 2 + i * width, xs) 373 | if not self.settings['errorBar']: 374 | bars = plt.bar(lmap(lambda x: x - tot_width / 2 + i * width + width / 2, x_points), lmap(lambda x: float(x), y_points), 375 | width, color=self.bar_colors[i], label=cat_name, 376 | ) 377 | else: 378 | points_err = self.get_error_data(cat, new_xp) 379 | bars = plt.bar(lmap(lambda x: x - tot_width / 2 + i * width + width / 2, x_points), lmap(lambda x: float(x), y_points), 380 | width, 381 | color=self.bar_colors[i], 382 | label=cat_name, yerr=points_err, capsize=3, 383 | # hatch=bar_patterns[i], 384 | edgecolor=[self.bar_colors[i]] * len(x_points) # hack for matplotlib 2.1.0 385 | ) 386 | # for bar in bars: 387 | # bar.set_hatch(bar_patterns[i]) 388 | plt.xlim(-0.7, len(xs) - 0.3) 389 | plt.xticks(lmap(lambda x: x, range(0, len(x_points_origin))), x_points_origin) 390 | 391 | def output_draw_data(self): 392 | f = open(self.draw_data_output_file, "w") 393 | 394 | for d in self.draw_data: 395 | f.write("%s\n" % d[0]) 396 | 397 | header = "%10s" % "x" 398 | header += "%10s" % self.settings['pivotTable.point'] 399 | if self.settings['pivotTable.errorBar'] == 'min-max': 400 | header += "%10s%10s" % ("y-min", "y-max") 401 | elif self.settings['pivotTable.errorBar'] == 'percentile': 402 | header += "%10s%10s" % ("y-5th", "y-10th") 403 | elif self.settings['pivotTable.errorBar'] == 'std': 404 | header += "%10s" % "y-std-dev" 405 | 406 | f.write(header + "\n") 407 | f.write("=" * 40 + "\n") 408 | 409 | def isNum(value): 410 | try: 411 | value + 1 412 | except TypeError: 413 | return False 414 | else: 415 | return True 416 | 417 | for i in range(0, len(d[1][0])): 418 | for j in range(0, len(d[1])): 419 | o = d[1][j][i] 420 | if isNum(o): 421 | o = "%.6lf" % d[1][j][i] 422 | f.write("%10s" % o) 423 | f.write("\n") 424 | 425 | f.write("\n") 426 | f.write("-" * 50 + "\n\n\n\n") 427 | 428 | f.close() 429 | -------------------------------------------------------------------------------- /chart/data_loader.py: -------------------------------------------------------------------------------- 1 | from future.utils import lmap, lfilter 2 | import numpy as np 3 | import re 4 | from collections import OrderedDict 5 | from functools import reduce 6 | from exceptions import FigureToolException 7 | 8 | 9 | class DataLoader(object): 10 | def load(self, data_filename, separator=None): 11 | raise NotImplementedError 12 | 13 | @property 14 | def categories(self): 15 | raise NotImplementedError 16 | 17 | def grouped_data(self, cat): 18 | raise NotImplementedError 19 | 20 | @property 21 | def X(self): 22 | raise NotImplementedError 23 | 24 | 25 | class NormalDataLoader(DataLoader): 26 | def load(self, data_filename, separator=None): 27 | f = open(data_filename) 28 | self.raw_data = [lmap(lambda x: x.strip(), x.strip().split(separator)) for x in filter(len, f)] 29 | self._categories = self.raw_data[0][1:] 30 | self._X = [x[0] for x in self.raw_data[1:]] 31 | self.grouped_Y = {} 32 | for i in range(0, len(self._categories)): 33 | cat = self._categories[i] 34 | self.grouped_Y[cat] = [x[i + 1] for x in self.raw_data[1:]] 35 | 36 | @property 37 | def category_name(self): 38 | return self.raw_data[0][0] 39 | 40 | @property 41 | def categories(self): 42 | return self._categories 43 | 44 | def grouped_data(self, cat): 45 | return (self._X, self.grouped_Y[cat]) 46 | 47 | @property 48 | def X(self): 49 | return self._X 50 | 51 | 52 | class PivotTableDataLoader(DataLoader): 53 | @staticmethod 54 | def lines2data(lines, separator): 55 | ret = [] 56 | table_width = -1 57 | for i in range(0, len(lines)): 58 | l = lines[i] 59 | if len(l) == 0: 60 | pass 61 | row = lmap(lambda x: x.strip(), l.strip().split(separator)) 62 | row_len = len(row) 63 | ret.append(row) 64 | if table_width == -1: 65 | table_width = row_len 66 | elif row_len != table_width: 67 | msg = "Line %d has %d items, while previous lines has %d items." % (i, row_len, table_width) 68 | raise FigureToolException(msg) 69 | return ret 70 | 71 | def load(self, data_filename, separator=None, data_filter=None): 72 | f = open(data_filename) 73 | # self.raw_data = [lmap(lambda x: x.strip(), x.strip().split(separator)) for x in filter(len, self.raw_lines)] 74 | self.raw_data = self.lines2data(f.readlines(), separator) 75 | self.fields = self.raw_data[0] 76 | 77 | if type(data_filter) is OrderedDict: 78 | for k in data_filter: 79 | v = data_filter[k] 80 | idx = self.fields.index(k) 81 | new_vec = [self.raw_data[0]] 82 | for x in self.raw_data[1:]: 83 | if re.match(v, x[idx]) != None: 84 | new_vec.append(x) 85 | self.raw_data = new_vec 86 | # filter(lambda x: x[idx] == v, self.raw_data[1:]) 87 | # print self.raw_data 88 | 89 | def set_fields(self, category_field, independent_variable_field, value_field): 90 | self.category_field = category_field 91 | self.indep_var_field = independent_variable_field 92 | self.value_field = value_field 93 | 94 | def set_default_func(self, func): 95 | self.default_func = func 96 | 97 | @property 98 | def categories(self): 99 | idx = self.fields.index(self.category_field) 100 | categories = [x[idx] for x in self.raw_data[1:]] 101 | func = lambda x, y: x if y in x else x + [y] 102 | ret = reduce(func, [[], ] + categories) 103 | return sorted(ret) 104 | 105 | def grouped_data(self, cat, func=None): 106 | idx = self.fields.index(self.value_field) 107 | cat_idx = self.fields.index(self.category_field) 108 | x_idx = self.fields.index(self.indep_var_field) 109 | data = lfilter(lambda x: x[cat_idx] == cat, self.raw_data) 110 | # print data 111 | X = self.X 112 | ret = [] 113 | 114 | if not func: 115 | func = self.default_func 116 | 117 | if func == 'mean': 118 | func = np.mean 119 | elif func == 'max': 120 | func = np.max 121 | elif func == 'min': 122 | func = np.min 123 | elif func == 'median': 124 | func = np.median 125 | elif func == 'std': 126 | func = np.std 127 | elif func == 'percentile_5': 128 | func = lambda x: np.percentile(x, 5) 129 | elif func == 'percentile_95': 130 | func = lambda x: np.percentile(x, 95) 131 | else: 132 | raise Exception("Reduce function not implemented") 133 | 134 | ret_x = [] 135 | 136 | for x in X: 137 | d = lfilter(lambda t: t[x_idx] == x, data) 138 | if len(d) == 0: 139 | continue 140 | ret_x.append(x) 141 | d = [float(x[idx]) for x in d] 142 | ret.append(func(d)) 143 | 144 | return ret_x, ret 145 | 146 | @property 147 | def X(self): 148 | idx = self.fields.index(self.indep_var_field) 149 | Xs = [x[idx] for x in self.raw_data[1:]] 150 | func = lambda x, y: x if y in x else x + [y] 151 | return reduce(func, [[], ] + Xs) 152 | -------------------------------------------------------------------------------- /chart/exceptions.py: -------------------------------------------------------------------------------- 1 | class FigureToolException(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | *.pdf 2 | *.eps 3 | *.txt 4 | -------------------------------------------------------------------------------- /demo/a.dat: -------------------------------------------------------------------------------- 1 | Skewness alpha beta $\theta$ 2 | 0 0.33 0.40 0.53 3 | 1 0.36 0.240 0.43 4 | 2 0.32 0.640 0.434 5 | 3 0.334 0.46 0.463 6 | 4 0.371 0.456 0.473 7 | 5 0.342 0.41 0.413 8 | 6 0.365 0.42 0.423 -------------------------------------------------------------------------------- /demo/b.dat: -------------------------------------------------------------------------------- 1 | Dataset alpha beta $\theta$ 2 | 3 0.33 0.40 0.53 3 | 4 0.36 0.240 0.43 4 | 5 0.32 0.640 0.434 5 | 6 0.334 0.46 0.463 6 | 7 0.371 0.456 0.473 7 | 8 0.342 0.41 0.413 8 | 9 0.365 0.42 0.423 -------------------------------------------------------------------------------- /demo/complex.json: -------------------------------------------------------------------------------- 1 | { 2 | "group1": { // 在很多情况下很多实验图会使用相同的参数,这时可以将FigItem分组 3 | "common": { // common中表示共同的参数 4 | "file": "demo/pivot_complex.dat", 5 | "separator": ",", 6 | 7 | "pivotTable": true, 8 | "pivotTable.category": "Dataset", 9 | "pivotTable.independentVariable": "Memory", // x axis 10 | "pivotTable.value": "Score", // y axis 11 | "pivotTable.point": "mean", // default: mean, other: median 12 | "pivotTable.errorBar": "min-max", 13 | 14 | // 是否绘制网格 15 | "grid.horizontal": true, 16 | "grid.vertical": true 17 | }, 18 | "items": [ // FigItem数组,每一个项目中的key会覆盖common中的同名元素 19 | { 20 | "output": "demo/pivot_complex_1.pdf", 21 | "categories": ["D", "A", "B"] // 可以指定哪些category需要画在图上,同时指定图例的顺序 22 | }, 23 | { 24 | "output": "demo/pivot_complex_2.pdf", 25 | "categories": { 26 | "C": "Cat", 27 | "D": "Dog" 28 | } // 除了数组外,这一项也可以是dict,key表示在原文件中的名称,value是显示在图例中的名称 29 | }, 30 | { 31 | "output": "demo/pivot_complex_3.pdf", 32 | "ytick.log": true, // 将纵轴表示成对数坐标轴 33 | "xtick.force_non_digit": true, // 有时尽管x轴的值可以被解析成数字,但我们需要按字符串处理(如日期) 34 | "xtick.order": ["1", "0.5"] // 在字符串的情况下,我们可能还需要指定它们在x轴上的顺序,同样,可以用这种方式忽略一些点 35 | } 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /demo/list.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "file": "demo/a.dat", 4 | "y_label": "CDF", 5 | "marker": false, 6 | "data.n_groups": 2, 7 | // "ytick.lim": [0.2, 5], 8 | // "ytick.interval": 0.1, 9 | "ytick.nbins": 2, 10 | // "ytick.log": true, 11 | "yticks": [0.7, 0.8], 12 | "lines.alpha": 0.7, 13 | "grid.horizontal": true, 14 | "grid.vertical": true 15 | }, 16 | { 17 | "file": "demo/b.dat", 18 | "chart.type": "bar", 19 | "y_label": "AAE" 20 | }, 21 | { 22 | "file": "demo/pivot.dat", 23 | "output": "demo/pivot_bar.pdf", 24 | "chart.type": "bar", 25 | "separator": ",", 26 | "pivotTable": true, 27 | "pivotTable.category": "Dataset", 28 | "pivotTable.independentVariable": "Memory", // x axis 29 | "pivotTable.value": "Score", // y axis 30 | 31 | "pivotTable.point": "mean", // default: mean, other: median 32 | 33 | // error bar type: 34 | // min-max: min and max of data 35 | // std: standard deviation 36 | // percentile: 5th and 95th percentile of data 37 | "pivotTable.errorBar": "std", 38 | 39 | "grid.horizontal": true 40 | }, 41 | { 42 | "file": "demo/pivot.dat", 43 | "output": "demo/pivot_line.pdf", 44 | "separator": ",", 45 | "grid.horizontal": true, 46 | "grid.vertical": true, 47 | 48 | "pivotTable": true, 49 | "pivotTable.category": "Dataset", 50 | "pivotTable.independentVariable": "Memory", // x axis 51 | "pivotTable.value": "Score", // y axis 52 | 53 | "pivotTable.point": "mean", // default: mean, other: median 54 | "pivotTable.errorBar": "min-max" 55 | } 56 | ] 57 | -------------------------------------------------------------------------------- /demo/pivot.dat: -------------------------------------------------------------------------------- 1 | Dataset,Memory,Score 2 | A, 0.5, 3 3 | A, 0.5, 3 4 | A, 1, 4 5 | A, 1, 6 6 | B, 0.5, 3 7 | B, 0.5, 4 8 | B, 0.5, 4 9 | B, 1, 3.7 10 | B, 1, 3.8 11 | A, 2, 4 12 | B, 2, 7 13 | B, 2, 7.2 14 | B, 2, 7.4 15 | B, 2, 7.3 16 | -------------------------------------------------------------------------------- /demo/pivot_complex.dat: -------------------------------------------------------------------------------- 1 | Dataset,Memory,Score 2 | A, 0.5, 3 3 | A, 0.5, 3 4 | A, 1, 4 5 | A, 1, 6 6 | B, 0.5, 3 7 | B, 0.5, 4 8 | B, 0.5, 4 9 | B, 1, 3.7 10 | B, 1, 3.8 11 | A, 2, 4 12 | B, 2, 7 13 | B, 2, 7.2 14 | B, 2, 7.4 15 | B, 2, 7.3 16 | C, 1, 12 17 | C, 2, 7 18 | D, 3, 6 19 | D, 4, 8 20 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import print_function 3 | from matplotlib import __version__ as mpl_version 4 | 5 | from chart import DrawingCore 6 | import json 7 | import re 8 | import subprocess 9 | import copy 10 | from collections import OrderedDict 11 | import argparse 12 | 13 | 14 | def env_check(args): 15 | # check mpl_version 16 | t = mpl_version.split('.') 17 | if int(t[0]) < 2: 18 | print("matplotlib >= 2.0.0 needed. Now version " + mpl_version + ".") 19 | exit() 20 | 21 | # check tex environment 22 | if args.mode[0] == 'normal': 23 | try: 24 | subprocess.check_output('tex --version') 25 | except subprocess.CalledProcessError: 26 | print("Tex not found. Install tex or run in draft mode: `python run.py --mode=draft`") 27 | exit() 28 | 29 | 30 | def get_args(): 31 | parser = argparse.ArgumentParser() 32 | parser.add_argument('file', nargs='?', help="json file for plot", default="list.json") 33 | parser.add_argument('--mode', nargs=1, help="mode to run (draft/normal)", default="normal") 34 | return parser.parse_args() 35 | 36 | 37 | def read_list_file(): 38 | args = get_args() 39 | env_check(args) 40 | filename = args.file 41 | run_mode = args.mode[0] 42 | 43 | f = open(filename) 44 | input_str = "\n".join(f.readlines()) 45 | input_str = re.sub(r'\\\n', '', input_str) 46 | input_str = re.sub(r'//.*\n', '\n', input_str) 47 | lt = json.loads(input_str, object_pairs_hook=OrderedDict) 48 | f.close() 49 | 50 | if type(lt) is OrderedDict: 51 | print("%s dict detected. Advanced mode..." % filename) 52 | group = None 53 | 54 | if group: 55 | if group in lt: 56 | common = {} 57 | if "common" in lt[group]: 58 | common = lt[group]["common"] 59 | for item in lt[group]["items"]: 60 | nt = copy.copy(common) 61 | nt.update(item) 62 | DrawingCore(nt['file'], nt, run_mode) 63 | else: 64 | print("Group %s not found." % group) 65 | else: 66 | for k in lt: 67 | common = {} 68 | if "common" in lt[k]: 69 | common = lt[k]["common"] 70 | for item in lt[k]["items"]: 71 | nt = copy.copy(common) 72 | nt.update(item) 73 | DrawingCore(nt['file'], nt, run_mode) 74 | else: 75 | for item in lt: 76 | DrawingCore(item['file'], item, run_mode) 77 | 78 | 79 | if __name__ == '__main__': 80 | read_list_file() 81 | -------------------------------------------------------------------------------- /styles/rcParams_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "backend": "GTKAgg", 3 | 4 | "font.family": "serif", 5 | "font.serif": ["Times", "Palatino", "New Century Schoolbook", "Bookman", "Computer Modern Roman"], 6 | "font.sans-serif": ["Helvetica", "Avant Garde", "Computer Modern Sans serif"], 7 | "font.cursive": ["Zapf Chancery"], 8 | "font.monospace": ["Courier", "Computer Modern Typewriter"], 9 | "text.usetex": true, 10 | "text.latex.unicode": true, 11 | 12 | "axes.labelsize": 28, 13 | "axes.linewidth": 0.75, 14 | 15 | "figure.figsize": [8, 4], 16 | "figure.subplot.left": 0.145, 17 | "figure.subplot.right": 0.96, 18 | "figure.subplot.bottom": 0.18, 19 | "figure.subplot.top": 0.96, 20 | "figure.dpi": 150, 21 | 22 | "font.size": 22, 23 | "legend.fontsize": 22, 24 | "xtick.labelsize": 22, 25 | "ytick.labelsize": 22, 26 | 27 | "lines.linewidth": 0.1, 28 | "lines.markersize": 3, 29 | "savefig.dpi": 600 30 | } 31 | -------------------------------------------------------------------------------- /styles/rcParams_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "backend": "GTKAgg", 3 | 4 | "font.family": "serif", 5 | "font.serif": ["Times", "Palatino", "New Century Schoolbook", "Bookman", "Computer Modern Roman"], 6 | "font.sans-serif": ["Helvetica", "Avant Garde", "Computer Modern Sans serif"], 7 | "font.cursive": ["Zapf Chancery"], 8 | "font.monospace": ["Courier", "Computer Modern Typewriter"], 9 | "text.usetex": true, 10 | "text.latex.unicode": true, 11 | 12 | "axes.labelsize": 24, 13 | "axes.linewidth": 0.75, 14 | 15 | "figure.figsize": [6.3, 4], 16 | "figure.subplot.left": 0.17, 17 | "figure.subplot.right": 0.96, 18 | "figure.subplot.bottom": 0.18, 19 | "figure.subplot.top": 0.90, 20 | "figure.dpi": 150, 21 | 22 | "font.size": 22, 23 | "legend.fontsize": 14 , 24 | "xtick.labelsize": 17, 25 | "ytick.labelsize": 17, 26 | 27 | "lines.linewidth": 0.75, 28 | "lines.markersize": 8, 29 | "savefig.dpi": 600 30 | } -------------------------------------------------------------------------------- /styles/rcParams_2_pf.json: -------------------------------------------------------------------------------- 1 | for O_strategy 2 | { 3 | "backend": "GTKAgg", 4 | 5 | "font.family": "serif", 6 | "font.serif": ["Times", "Palatino", "New Century Schoolbook", "Bookman", "Computer Modern Roman"], 7 | "font.sans-serif": ["Helvetica", "Avant Garde", "Computer Modern Sans serif"], 8 | "font.cursive": ["Zapf Chancery"], 9 | "font.monospace": ["Courier", "Computer Modern Typewriter"], 10 | "text.usetex": true, 11 | "text.latex.unicode": true, 12 | 13 | "axes.labelsize": 24, 14 | "axes.linewidth": 0.75, 15 | 16 | "figure.figsize": [6.3, 4], 17 | "figure.subplot.left": 0.17, 18 | "figure.subplot.right": 0.96, 19 | "figure.subplot.bottom": 0.18, 20 | "figure.subplot.top": 0.90, 21 | "figure.dpi": 150, 22 | 23 | "font.size": 22, 24 | "legend.fontsize": 14 , 25 | "xtick.labelsize": 17, 26 | "ytick.labelsize": 17, 27 | 28 | "lines.linewidth": 0.75, 29 | "lines.markersize": 8, 30 | "savefig.dpi": 600 31 | } 32 | 33 | for ACCESS_TIMES: 34 | { 35 | "backend": "GTKAgg", 36 | 37 | "font.family": "serif", 38 | "font.serif": ["Times", "Palatino", "New Century Schoolbook", "Bookman", "Computer Modern Roman"], 39 | "font.sans-serif": ["Helvetica", "Avant Garde", "Computer Modern Sans serif"], 40 | "font.cursive": ["Zapf Chancery"], 41 | "font.monospace": ["Courier", "Computer Modern Typewriter"], 42 | "text.usetex": true, 43 | "text.latex.unicode": true, 44 | 45 | "axes.labelsize": 24, 46 | "axes.linewidth": 0.75, 47 | 48 | "figure.figsize": [6.3, 4], 49 | "figure.subplot.left": 0.23, 50 | "figure.subplot.right": 0.96, 51 | "figure.subplot.bottom": 0.18, 52 | "figure.subplot.top": 0.90, 53 | "figure.dpi": 150, 54 | 55 | "font.size": 22, 56 | "legend.fontsize": 17 , 57 | "xtick.labelsize": 17, 58 | "ytick.labelsize": 17, 59 | 60 | "lines.linewidth": 0.75, 61 | "lines.markersize": 8, 62 | "savefig.dpi": 600 63 | } 64 | 65 | for RE 66 | 67 | { 68 | "backend": "GTKAgg", 69 | 70 | "font.family": "serif", 71 | "font.serif": ["Times", "Palatino", "New Century Schoolbook", "Bookman", "Computer Modern Roman"], 72 | "font.sans-serif": ["Helvetica", "Avant Garde", "Computer Modern Sans serif"], 73 | "font.cursive": ["Zapf Chancery"], 74 | "font.monospace": ["Courier", "Computer Modern Typewriter"], 75 | "text.usetex": true, 76 | "text.latex.unicode": true, 77 | 78 | "axes.labelsize": 24, 79 | "axes.linewidth": 0.75, 80 | 81 | "figure.figsize": [6.3, 4], 82 | "figure.subplot.left": 0.15, 83 | "figure.subplot.right": 0.96, 84 | "figure.subplot.bottom": 0.18, 85 | "figure.subplot.top": 0.90, 86 | "figure.dpi": 150, 87 | 88 | "font.size": 22, 89 | "legend.fontsize": 17 , 90 | "xtick.labelsize": 17, 91 | "ytick.labelsize": 17, 92 | 93 | "lines.linewidth": 0.75, 94 | "lines.markersize": 8, 95 | "savefig.dpi": 600 96 | } 97 | 98 | 99 | for SPEED: 100 | { 101 | "backend": "GTKAgg", 102 | 103 | "font.family": "serif", 104 | "font.serif": ["Times", "Palatino", "New Century Schoolbook", "Bookman", "Computer Modern Roman"], 105 | "font.sans-serif": ["Helvetica", "Avant Garde", "Computer Modern Sans serif"], 106 | "font.cursive": ["Zapf Chancery"], 107 | "font.monospace": ["Courier", "Computer Modern Typewriter"], 108 | "text.usetex": true, 109 | "text.latex.unicode": true, 110 | 111 | "axes.labelsize": 24, 112 | "axes.linewidth": 0.75, 113 | 114 | "figure.figsize": [6.3, 4], 115 | "figure.subplot.left": 0.14, 116 | "figure.subplot.right": 0.96, 117 | "figure.subplot.bottom": 0.18, 118 | "figure.subplot.top": 0.90, 119 | "figure.dpi": 150, 120 | 121 | "font.size": 22, 122 | "legend.fontsize": 17 , 123 | "xtick.labelsize": 17, 124 | "ytick.labelsize": 17, 125 | 126 | "lines.linewidth": 0.75, 127 | "lines.markersize": 8, 128 | "savefig.dpi": 600 129 | } -------------------------------------------------------------------------------- /styles/rcParams_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "backend": "GTKAgg", 3 | 4 | "font.family": "serif", 5 | "font.serif": ["Times", "Palatino", "New Century Schoolbook", "Bookman", "Computer Modern Roman"], 6 | "font.sans-serif": ["Helvetica", "Avant Garde", "Computer Modern Sans serif"], 7 | "font.cursive": ["Zapf Chancery"], 8 | "font.monospace": ["Courier", "Computer Modern Typewriter"], 9 | 10 | "axes.labelsize": 34, 11 | "axes.linewidth": 0.75, 12 | 13 | "figure.figsize": [12, 8], 14 | "figure.subplot.left": 0.20, 15 | "figure.subplot.right": 0.90, 16 | "figure.subplot.bottom": 0.20, 17 | "figure.subplot.top": 0.90, 18 | "figure.dpi": 150, 19 | 20 | "font.size": 28, 21 | "legend.fontsize": 15, 22 | "xtick.labelsize": 28, 23 | "ytick.labelsize": 28, 24 | 25 | "lines.linewidth": 1.5, 26 | "lines.markersize": 20, 27 | "savefig.dpi": 600 28 | } 29 | -------------------------------------------------------------------------------- /styles/rcParams_4.json: -------------------------------------------------------------------------------- 1 | { 2 | "backend": "GTKAgg", 3 | 4 | "font.family": "serif", 5 | "font.serif": ["Times", "Palatino", "New Century Schoolbook", "Bookman", "Computer Modern Roman"], 6 | "font.sans-serif": ["Helvetica", "Avant Garde", "Computer Modern Sans serif"], 7 | "font.cursive": ["Zapf Chancery"], 8 | "font.monospace": ["Courier", "Computer Modern Typewriter"], 9 | "text.usetex": true, 10 | "text.latex.unicode": true, 11 | 12 | "axes.labelsize": 30, 13 | "axes.linewidth": 0.75, 14 | 15 | "figure.figsize": [14, 6], 16 | "figure.subplot.left": 0.20, 17 | "figure.subplot.right": 0.90, 18 | "figure.subplot.bottom": 0.20, 19 | "figure.subplot.top": 0.90, 20 | "figure.dpi": 150, 21 | 22 | "font.size": 5, 23 | "legend.fontsize": 25, 24 | "xtick.labelsize": 28, 25 | "ytick.labelsize": 28, 26 | 27 | "lines.linewidth": 1.5, 28 | "lines.markersize": 20, 29 | "savefig.dpi": 600 30 | } 31 | -------------------------------------------------------------------------------- /styles/rcParams_5.json: -------------------------------------------------------------------------------- 1 | { 2 | "backend": "GTKAgg", 3 | 4 | "font.family": "serif", 5 | "font.serif": ["Times", "Palatino", "New Century Schoolbook", "Bookman", "Computer Modern Roman"], 6 | "font.sans-serif": ["Helvetica", "Avant Garde", "Computer Modern Sans serif"], 7 | "font.cursive": ["Zapf Chancery"], 8 | "font.monospace": ["Courier", "Computer Modern Typewriter"], 9 | "text.usetex": true, 10 | "text.latex.unicode": true, 11 | 12 | "axes.labelsize": 36, 13 | "axes.linewidth": 0.75, 14 | 15 | "figure.figsize": [10.5, 5.5], 16 | "figure.subplot.left": 0.09, 17 | "figure.subplot.right": 0.98, 18 | "figure.subplot.bottom": 0.15, 19 | "figure.subplot.top": 0.96, 20 | "figure.dpi": 150, 21 | 22 | "font.size": 5, 23 | "legend.fontsize": 28, 24 | "xtick.labelsize": 32, 25 | "ytick.labelsize": 32, 26 | 27 | "lines.linewidth": 1.5, 28 | "lines.markersize": 20, 29 | "savefig.dpi": 600 30 | } 31 | --------------------------------------------------------------------------------