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