1#!/usr/bin/env python3 2# 3# Use pandas + bokeh to create graphs/charts/plots for stats CSV (to_csv.py). 4# 5 6import os 7import pandas as pd 8from bokeh.io import curdoc 9from bokeh.models import ColumnDataSource, HoverTool 10from bokeh.plotting import figure 11from bokeh.palettes import Dark2_5 as palette 12from bokeh.models.formatters import DatetimeTickFormatter 13 14import pandas_bokeh 15import argparse 16import itertools 17from fnmatch import fnmatch 18 19datecolumn = '0time' 20 21 22if __name__ == '__main__': 23 parser = argparse.ArgumentParser(description='Graph CSV files') 24 parser.add_argument('infiles', nargs='+', type=str, 25 help='CSV files to plot.') 26 parser.add_argument('--cols', type=str, 27 help='Columns to plot (CSV list)') 28 parser.add_argument('--skip', type=str, 29 help='Columns to skip (CSV list)') 30 parser.add_argument('--group-by', type=str, 31 help='Group data series by field') 32 parser.add_argument('--chart-cols', type=int, default=3, 33 help='Number of chart columns') 34 parser.add_argument('--plot-width', type=int, default=400, 35 help='Per-plot width') 36 parser.add_argument('--plot-height', type=int, default=300, 37 help='Per-plot height') 38 parser.add_argument('--out', type=str, default='out.html', 39 help='Output file (HTML)') 40 args = parser.parse_args() 41 42 outpath = args.out 43 if args.cols is None: 44 cols = None 45 else: 46 cols = args.cols.split(',') 47 cols.append(datecolumn) 48 49 if args.skip is None: 50 assert cols is None, "--cols and --skip are mutually exclusive" 51 skip = None 52 else: 53 skip = args.skip.split(',') 54 55 group_by = args.group_by 56 57 pandas_bokeh.output_file(outpath) 58 curdoc().theme = 'dark_minimal' 59 60 figs = {} 61 plots = [] 62 for infile in args.infiles: 63 64 colors = itertools.cycle(palette) 65 66 cols_to_use = cols 67 68 if skip is not None: 69 # First read available fields 70 avail_cols = list(pd.read_csv(infile, nrows=1)) 71 72 cols_to_use = [c for c in avail_cols 73 if len([x for x in skip if fnmatch(c, x)]) == 0] 74 75 df = pd.read_csv(infile, 76 parse_dates=[datecolumn], 77 index_col=datecolumn, 78 usecols=cols_to_use) 79 title = os.path.basename(infile) 80 print(f"{infile}:") 81 82 if group_by is not None: 83 84 grp = df.groupby([group_by]) 85 86 # Make one plot per column, skipping the index and group_by cols. 87 for col in df.keys(): 88 if col in (datecolumn, group_by): 89 continue 90 91 print("col: ", col) 92 93 for _, dg in grp: 94 print(col, " dg:\n", dg.head()) 95 figtitle = f"{title}: {col}" 96 p = figs.get(figtitle, None) 97 if p is None: 98 p = figure(title=f"{title}: {col}", 99 plot_width=args.plot_width, 100 plot_height=args.plot_height, 101 x_axis_type='datetime', 102 tools="hover,box_zoom,wheel_zoom," + 103 "reset,pan,poly_select,tap,save") 104 figs[figtitle] = p 105 plots.append(p) 106 107 p.add_tools(HoverTool( 108 tooltips=[ 109 ("index", "$index"), 110 ("time", "@0time{%F}"), 111 ("y", "$y"), 112 ("desc", "$name"), 113 ], 114 formatters={ 115 "@0time": "datetime", 116 }, 117 mode='vline')) 118 119 p.xaxis.formatter = DatetimeTickFormatter( 120 minutes=['%H:%M'], 121 seconds=['%H:%M:%S']) 122 123 source = ColumnDataSource(dg) 124 125 val = dg[group_by][0] 126 for k in dg: 127 if k != col: 128 continue 129 130 p.line(x=datecolumn, y=k, source=source, 131 legend_label=f"{k}[{val}]", 132 name=f"{k}[{val}]", 133 color=next(colors)) 134 135 continue 136 137 else: 138 p = df.plot_bokeh(title=title, 139 kind='line', show_figure=False) 140 141 plots.append(p) 142 143 for p in plots: 144 p.legend.click_policy = "hide" 145 146 grid = [] 147 for i in range(0, len(plots), args.chart_cols): 148 grid.append(plots[i:i+args.chart_cols]) 149 150 pandas_bokeh.plot_grid(grid) 151