1from itertools import cycle
2
3import numpy as np
4import pandas as pd
5import bokeh.plotting
6import bokeh.io
7import bokeh.models
8
9from .external_coffeescript import build_callback
10
11color_list = bokeh.palettes.Category20[20]
12
13def pc(df):
14    fig = bokeh.plotting.figure(width=1500,
15                                height=1200,
16                                tools=['reset', 'undo', 'save', 'pan', 'wheel_zoom', 'tap'],
17                               )
18
19    box_select = bokeh.models.tools.BoxSelectTool(name='box_select')
20    box_select.callback = build_callback('pc_box_select')
21    fig.add_tools(box_select)
22    fig.toolbar.active_drag = box_select
23
24    if df.columns.nlevels == 2:
25        names = ['{0}: {1}'.format(gn, en) for gn, en in df.columns.values]
26    else:
27        names = df.columns
28
29    lines = []
30    for y, ((row_name, row), color) in enumerate(zip(df.iterrows(), cycle(color_list))):
31        vals = row.values
32        data = {
33            'x': list(range(len(vals))),
34            'y': vals,
35            'name': [row_name]*len(names),
36            'dq': [False for _ in vals],
37        }
38
39        source = bokeh.models.ColumnDataSource(data=data, name='source_by_y_{0}'.format(y))
40
41        line = fig.line(x='x', y='y',
42                        source=source,
43                        line_alpha=0.7,
44                        line_width=3,
45                        color=color,
46                        name='line_{0}'.format(y),
47                       )
48        line.hover_glyph = line.glyph
49        lines.append(line)
50
51    columns = df.columns.values
52
53    colors = [c for c, _ in zip(cycle(color_list), df.index)]
54
55    for x, exp_name in enumerate(columns):
56        vals = df[exp_name].values
57        data = {
58            'x': [x for _ in vals],
59            'y': vals,
60            'dq': [False for _ in vals],
61            'color': colors,
62            'size': [6 for _ in vals],
63            'alpha': [0.9 for _ in vals],
64        }
65
66        source = bokeh.models.ColumnDataSource(data=data, name='source_by_x_{0}'.format(x))
67        circles = fig.circle(x='x', y='y',
68                             source=source,
69                             color='color',
70                             fill_alpha='alpha',
71                             line_alpha=0,
72                             size='size',
73                            )
74        source.callback = build_callback('pc_selection')
75
76    for x in [0, len(columns) - 1]:
77        vals = df.iloc[:, x].values
78
79        if x == 0:
80            text_align = 'right'
81            x_offset = -10
82        else:
83            text_align = 'left'
84            x_offset = 10
85
86        data = {
87            'x': [x for _ in vals],
88            'y': vals,
89            'label': df.index.values,
90            'hover_color': colors,
91            'text_color': ['black' for _ in vals],
92            'text_alpha': [0.9 for _ in vals],
93            'highlight': [False for _ in vals],
94            'dq': [False for _ in vals],
95        }
96
97        source = bokeh.models.ColumnDataSource(data=data)
98
99        labels = bokeh.models.LabelSet(x='x',
100                                       y='y',
101                                       text='label',
102                                       level='glyph',
103                                       x_offset=x_offset,
104                                       y_offset=0,
105                                       source=source,
106                                       text_font_size='12pt',
107                                       text_align=text_align,
108                                       text_alpha='text_alpha',
109                                       text_color='text_color',
110                                       text_baseline='middle',
111                                       name='labels',
112                                       text_font='monospace',
113                                      )
114        fig.add_layout(labels)
115
116    hover = bokeh.models.HoverTool(line_policy='interp',
117                                   renderers=lines,
118                                   tooltips=None,
119                                   name='hover_tool',
120                                  )
121    hover.callback = build_callback('pc_hover')
122    fig.add_tools(hover)
123    fig.xgrid.visible = False
124
125    fig.xaxis.minor_tick_line_color = None
126
127    code = '''
128    dict = {dict}
129    return dict[tick]
130    '''.format(dict=dict(enumerate(names)))
131
132    fig.xaxis.ticker = bokeh.models.FixedTicker(ticks=list(range(len(names))))
133    fig.xaxis.formatter = bokeh.models.FuncTickFormatter(code=code)
134    fig.xaxis.major_label_text_font_size = '12pt'
135    fig.xaxis.major_label_orientation = np.pi / 4
136
137    fig.yaxis.axis_label = 'nts between cut'
138    fig.yaxis.axis_label_text_font_style = 'normal'
139    fig.yaxis.axis_label_text_font_size = '16pt'
140    fig.yaxis.major_label_text_font_size = '12pt'
141
142    fig.x_range = bokeh.models.Range1d(-0.1 * len(names), 1.1 * (len(names) - 1))
143    fig.x_range.callback = bokeh.models.CustomJS.from_coffeescript(code=f'''
144    if cb_obj.start < {-0.1 * len(names)}
145        cb_obj.start = {-0.1 * len(names)}
146
147    if cb_obj.end > {1.1 * (len(names) - 1)}
148        cb_obj.end = {1.1 * (len(names) - 1)}
149    ''')
150
151    fig.y_range.callback = bokeh.models.CustomJS.from_coffeescript(code=f'''
152    if cb_obj.start < 0
153        cb_obj.start = 0
154
155    if cb_obj.end > {df.max().max() * 1.05}
156        cb_obj.end = {df.max().max() * 1.05}
157    ''')
158
159    clear_constraints = bokeh.models.widgets.Button(label='Clear constraints',
160                                                    width=50,
161                                                    name='clear_constraints',
162                                                   )
163    clear_constraints.callback = build_callback('pc_clear')
164
165    data = dict(x=[], width=[], top=[], bottom=[])
166    constraint_source = bokeh.models.ColumnDataSource(data=data, name='constraints')
167    vbar = bokeh.models.glyphs.VBar(x='x',
168                                    top='top',
169                                    bottom='bottom',
170                                    width='width',
171                                    fill_alpha=0.15,
172                                    fill_color='black',
173                                    line_width=0,
174                                    line_alpha=0,
175                                   )
176
177    fig.add_glyph(constraint_source, vbar, selection_glyph=vbar, nonselection_glyph=vbar)
178
179    fig.min_border = 100
180
181    text_input = bokeh.models.widgets.TextInput(title='Highlight:', name='search')
182    text_input.callback = build_callback('pc_search')
183
184    columns = [
185        bokeh.layouts.column([fig]),
186        bokeh.layouts.column([bokeh.layouts.Spacer(height=100), clear_constraints, text_input]),
187    ]
188
189    bokeh.io.show(bokeh.layouts.row(columns))
190