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