cubevis 0.5.13__py3-none-any.whl → 0.5.15__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of cubevis might be problematic. Click here for more details.

@@ -1,348 +0,0 @@
1
- '''
2
- Create panel widgets for various functions
3
- '''
4
-
5
- import panel as pn
6
-
7
- from cubevis.bokeh.state._palette import available_palettes
8
- from cubevis.plot.ms_plot._ms_plot_constants import VIS_AXIS_OPTIONS, AGGREGATOR_OPTIONS, DEFAULT_UNFLAGGED_CMAP, DEFAULT_FLAGGED_CMAP
9
-
10
- def file_selector(description, start_dir, callback):
11
- ''' Return a layout for file selection with input description and start directory.
12
- Includes a TextInput and a FileSelector, with a callback to set TextInput from FileSelector.
13
- '''
14
- filename = pn.widgets.TextInput(
15
- description=description,
16
- name="Filename",
17
- placeholder='Enter filename or use file browser below',
18
- sizing_mode='stretch_width',
19
- )
20
-
21
- file_select = pn.widgets.FileSelector(
22
- start_dir,
23
- )
24
- select_file = pn.bind(callback, file_select)
25
-
26
- fs_card = pn.Card(
27
- file_select,
28
- title='File browser',
29
- collapsed=True,
30
- collapsible=True,
31
- sizing_mode='stretch_width',
32
- )
33
-
34
- return pn.Column(
35
- pn.Row( # [0]
36
- filename, # [0]
37
- select_file # [1]
38
- ),
39
- fs_card, # [1]
40
- width_policy='min',
41
- )
42
-
43
- def style_selector(style_callback, color_range_callback):
44
- ''' Return a layout for style parameters.
45
- Currently supports colormaps, colorbar, and color limits.
46
- '''
47
- cmaps = available_palettes()
48
-
49
- cmap_selector = pn.widgets.Select(
50
- name="Unflagged data colormap",
51
- options=cmaps,
52
- value=DEFAULT_UNFLAGGED_CMAP,
53
- sizing_mode='scale_width',
54
- )
55
-
56
- flagged_cmap_selector = pn.widgets.Select(
57
- name="Flagged data colormap",
58
- options=cmaps,
59
- value=DEFAULT_FLAGGED_CMAP,
60
- sizing_mode='scale_width',
61
- )
62
-
63
- colorbar_checkbox = pn.widgets.Checkbox(
64
- name="Show colorbar",
65
- value=True,
66
- )
67
-
68
- flagged_colorbar_checkbox = pn.widgets.Checkbox(
69
- name="Show flagged colorbar",
70
- value=True,
71
- )
72
-
73
- select_style = pn.bind(style_callback, cmap_selector, flagged_cmap_selector, colorbar_checkbox, flagged_colorbar_checkbox)
74
-
75
- color_mode_selector = pn.widgets.RadioBoxGroup(
76
- options=["No color range", "Auto color range", "Manual color range"],
77
- )
78
-
79
- color_range_slider = pn.widgets.RangeSlider(
80
- name="Colorbar range",
81
- )
82
-
83
- select_color_range = pn.bind(color_range_callback, color_mode_selector, color_range_slider)
84
-
85
- return pn.Column(
86
- pn.Row( # [0]
87
- cmap_selector, # [0]
88
- flagged_cmap_selector, # [1]
89
- ),
90
- pn.Row( # [1]
91
- colorbar_checkbox, # [0]
92
- flagged_colorbar_checkbox, # [1]
93
- ),
94
- select_style, # [2]
95
- pn.Row( # [3]
96
- color_mode_selector, # [0]
97
- color_range_slider, # [1]
98
- ),
99
- select_color_range, # [4]
100
- width_policy='min',
101
- )
102
-
103
- def title_selector(callback):
104
- ''' Return a layout for title input using TextInput '''
105
- title_input = pn.widgets.TextInput(
106
- name="Title",
107
- placeholder="Enter title for plot ('ms' to use MS name)",
108
- sizing_mode='stretch_width',
109
- )
110
- select_title = pn.bind(callback, title_input)
111
- return pn.Row(
112
- title_input,
113
- select_title,
114
- width_policy='min',
115
- )
116
-
117
- def axis_selector(x_axis, y_axis, axis_options, include_vis, callback):
118
- ''' Return layout of selectors for x-axis, y-axis, and vis-axis '''
119
- x_options = axis_options if axis_options else [x_axis]
120
- x_selector = pn.widgets.Select(
121
- name="X Axis",
122
- options=x_options,
123
- value=x_axis,
124
- sizing_mode='scale_width',
125
- )
126
-
127
- y_options = axis_options if axis_options else [y_axis]
128
- y_selector = pn.widgets.Select(
129
- name="Y Axis",
130
- options=y_options,
131
- value=y_axis,
132
- sizing_mode='scale_width',
133
- )
134
-
135
- if include_vis:
136
- # for raster plot
137
- vis_label = pn.pane.LaTeX(
138
- r"Vis Axis",
139
- )
140
- vis_selector = pn.widgets.RadioBoxGroup(
141
- options=VIS_AXIS_OPTIONS,
142
- inline=True,
143
- )
144
- select_axes = pn.bind(callback, x_selector, y_selector, vis_selector)
145
- return pn.Column(
146
- pn.Row( # [0]
147
- x_selector, # [0]
148
- y_selector # [1]
149
- ),
150
- vis_label, # [1]
151
- vis_selector, # [2]
152
- select_axes,
153
- width_policy='min',
154
- )
155
-
156
- # for scatter plot
157
- select_axes = pn.bind(callback, x_selector, y_selector)
158
- return pn.Column(
159
- pn.Row( # [0]
160
- x_selector, # [0]
161
- y_selector # [1]
162
- ),
163
- select_axes,
164
- width_policy='min',
165
- )
166
-
167
-
168
- def aggregation_selector(axis_options, callback):
169
- ''' Return layout of selectors for aggregator and agg axes '''
170
- agg_selector = pn.widgets.Select(
171
- name="Aggregator",
172
- options=AGGREGATOR_OPTIONS,
173
- description="Select aggregation type",
174
- sizing_mode='scale_width',
175
- )
176
-
177
- agg_axis_selector = pn.widgets.MultiSelect(
178
- name="Agg axis or axes",
179
- options=axis_options,
180
- description="Select one or more axes to aggregate",
181
- sizing_mode='scale_width',
182
- )
183
-
184
- select_agg = pn.bind(callback, agg_selector, agg_axis_selector)
185
-
186
- return pn.Row(
187
- agg_selector, # [0]
188
- agg_axis_selector, # [1]
189
- select_agg,
190
- width_policy='min',
191
- )
192
-
193
- def iteration_selector(axis_options, axis_callback, iter_callback):
194
- ''' Return layout of selectors for iteration axis and player selector for value.
195
- Callback sets values in iter value player and iter value range selectors when iter_axis is selected.
196
- '''
197
- iter_options = ['None']
198
- iter_options.extend(axis_options)
199
-
200
- iter_axis_selector = pn.widgets.Select(
201
- name="Iteration axis",
202
- options=iter_options,
203
- description="Select axis over which to iterate",
204
- sizing_mode='scale_width',
205
- )
206
-
207
- update_iter_values = pn.bind(axis_callback, iter_axis_selector)
208
-
209
- iter_value_type = pn.widgets.RadioBoxGroup(
210
- options=['By Value', 'By Range'],
211
- inline=True,
212
- )
213
-
214
- # Single value
215
- iter_value_player = pn.widgets.DiscretePlayer(
216
- name="Iteration value",
217
- show_loop_controls=False,
218
- show_value=False,
219
- value_align='start',
220
- visible_buttons=['first', 'previous', 'next', 'last'],
221
- sizing_mode='scale_width',
222
- )
223
-
224
- # Range
225
- iter_range_start = pn.widgets.IntInput(
226
- name="Iteration start",
227
- start=0,
228
- value=0,
229
- description="Index of first value in iteration",
230
- sizing_mode='scale_width',
231
- )
232
- iter_range_end = pn.widgets.IntInput(
233
- name="Iteration end",
234
- start=0,
235
- value=0,
236
- description="Index of last value in iteration",
237
- sizing_mode='scale_width',
238
- )
239
- subplot_rows = pn.widgets.IntInput(
240
- name="Subplot rows",
241
- start=1,
242
- end=10,
243
- description="Number of rows to display iteration plots",
244
- sizing_mode='scale_width',
245
- )
246
- subplot_columns = pn.widgets.IntInput(
247
- name="Subplot columns",
248
- start=1,
249
- end=10,
250
- description="Number of columns to display iteration plots",
251
- sizing_mode='scale_width',
252
- )
253
- iter_range_widgets = pn.Column(
254
- pn.Row( # [0]
255
- iter_range_start, # [0]
256
- iter_range_end, # [1]
257
- ),
258
- pn.Row( # [1]
259
- subplot_rows, # [0]
260
- subplot_columns, # [1]
261
- ),
262
- pn.pane.LaTeX(
263
- r"Multiple subplots will be shown in new tab",
264
- ),
265
- )
266
-
267
- # Put iter value/range into accordion
268
- iter_value_selectors = pn.Accordion(
269
- ('Select Single Iteration Value', iter_value_player), # [0]
270
- ('Select Iteration Index Range', iter_range_widgets), # [1]
271
- toggle=True,
272
- sizing_mode='stretch_width',
273
- )
274
-
275
- select_iter = pn.bind(iter_callback, iter_axis_selector, iter_value_type, iter_value_player, iter_range_start, iter_range_end, subplot_rows, subplot_columns)
276
-
277
- return pn.Column(
278
- pn.Row( # [0]
279
- iter_axis_selector, # [0]
280
- iter_value_type, # [1]
281
- update_iter_values,
282
- ),
283
- iter_value_selectors, # [1]
284
- select_iter,
285
- width_policy='min',
286
- )
287
-
288
- def selection_selector(ps_callback):
289
- ''' Return layout of selectors for ProcessingSet and MSv4 selection. '''
290
- # Create column of ps selectors; values added from PS summary later.
291
- ps_selection = pn.Column(
292
- height_policy='min',
293
- )
294
- ps_selection.append(
295
- pn.widgets.TextInput(
296
- name="Query",
297
- placeholder='Enter query for summary columns',
298
- sizing_mode='stretch_width',
299
- )
300
- )
301
- select_ps = pn.bind(ps_callback, ps_selection[0])
302
- ps_selection.append(select_ps)
303
-
304
- selection_cards = pn.Accordion(
305
- ("Select ProcessingSet", ps_selection), # [0]
306
- toggle=True,
307
- sizing_mode='stretch_width',
308
- )
309
-
310
- return pn.Column(
311
- selection_cards,
312
- width_policy='min',
313
- )
314
-
315
- def _add_multi_choice(ps_selection, names):
316
- ''' Add Panel Select widgets for list of names with option 'None'.
317
- ps_selection is a Column to which to add selectors.
318
- '''
319
- for name in names:
320
- ps_selection.append(
321
- pn.widgets.MultiChoice(
322
- name=name,
323
- sizing_mode='stretch_width',
324
- )
325
- )
326
-
327
- def plot_starter(callback):
328
- ''' Create a row with a Plot button and spinner with a button callback to start spinner. '''
329
- plot_button = pn.widgets.Button(
330
- name='Plot',
331
- button_style='outline',
332
- button_type='primary',
333
- sizing_mode='fixed',
334
- width=100,
335
- )
336
-
337
- plot_spinner = pn.indicators.LoadingSpinner(
338
- value=False,
339
- size=25,
340
- )
341
-
342
- start_spinner = pn.bind(callback, plot_button)
343
-
344
- return pn.Row(
345
- plot_button, # [0]
346
- plot_spinner, # [1]
347
- start_spinner,
348
- )
@@ -1,292 +0,0 @@
1
- '''
2
- Class to create a raster plot of visibility/spectrum data using plot parameters.
3
- '''
4
-
5
- import holoviews as hv
6
-
7
- # hvPlot extensions used to plot xarray DataArray and pandas DataFrame
8
- # pylint: disable=unused-import
9
- import hvplot.xarray
10
- import hvplot.pandas
11
- # pylint: enable=unused-import
12
-
13
- from cubevis.bokeh.format import get_time_formatter
14
- from cubevis.data.measurement_set.processing_set._ps_coords import set_index_coordinates
15
- from cubevis.plot.ms_plot._xds_plot_axes import get_axis_labels, get_vis_axis_labels, get_coordinate_labels
16
-
17
- class RasterPlot:
18
- '''
19
- Class to create a raster plot from MS data.
20
- Implemented with xArray Dataset and hvPlot, but could change data and plotting backends using same interface.
21
- '''
22
-
23
- def __init__(self):
24
- self._plot_params = {'data': {}, 'plot': {'params': False}, 'style': {}}
25
- self._spw_color_limits = {}
26
- self.set_style_params() # use defaults unless set externally
27
-
28
- def set_style_params(self, unflagged_cmap='Viridis', flagged_cmap='Reds', show_colorbar=True, show_flagged_colorbar=True):
29
- '''
30
- Set styling parameters for the plot. Currently only colorbar settings.
31
- Placeholder for future styling such as fonts.
32
-
33
- Args:
34
- unflagged_cmap (str): colormap to use for unflagged data.
35
- flagged_cmap (str): colormap to use for flagged data.
36
- show_colorbar (bool): Whether to show colorbar with plot. Default True.
37
-
38
- Colormap options: Bokeh palettes
39
- https://docs.bokeh.org/en/latest/docs/reference/palettes.html
40
- '''
41
- style_params = self._plot_params['style']
42
- style_params['unflagged_cmap'] = unflagged_cmap
43
- style_params['flagged_cmap'] = flagged_cmap
44
- style_params['show_colorbar'] = show_colorbar
45
- style_params['show_flagged_colorbar'] = show_flagged_colorbar
46
-
47
- def get_plot_params(self):
48
- ''' Return dict of plot params (default only if data params not set) '''
49
- return self._plot_params
50
-
51
- def set_plot_params(self, data, plot_inputs, ms_name):
52
- '''
53
- Set parameters needed for raster plot from data and plot inputs.
54
- data (xarray Dataset): selected dataset of MSv4 data to plot
55
- plot_inputs (dict): user inputs to plot.
56
- '''
57
- self._plot_params['data']['correlated_data'] = plot_inputs['correlated_data']
58
- self._plot_params['data']['aggregator'] = plot_inputs['aggregator']
59
-
60
- color_mode = plot_inputs['color_mode']
61
- if color_mode == 'manual':
62
- self._plot_params['plot']['color_limits'] = plot_inputs['color_range']
63
- elif color_mode == 'auto':
64
- self._plot_params['plot']['color_limits'] = plot_inputs['auto_color_range']
65
- else:
66
- self._plot_params['plot']['color_limits'] = None
67
-
68
- # Set title from user inputs or auto (ms name and iterator value)
69
- if plot_inputs['title']:
70
- title = plot_inputs['title']
71
- if title in ['ms', "'ms'"]:
72
- self._plot_params['plot']['title'] = self._get_plot_title(data, plot_inputs, ms_name)
73
- else:
74
- self._plot_params['plot']['title'] = title
75
- else:
76
- self._plot_params['plot']['title'] = ''
77
-
78
- # Set x, y, c axis labels and ticks
79
- self._set_axis_labels(data, plot_inputs)
80
-
81
- self._plot_params['plot']['params'] = True
82
-
83
- def reset_plot_params(self):
84
- ''' Remove and invalidate data plot params '''
85
- self._plot_params['plot'] = {'params': False}
86
-
87
- def raster_plot(self, data, logger, is_gui=False):
88
- ''' Create raster plot (hvPlot) for input data (xarray Dataset) using plot params.
89
- Returns Overlay of unflagged and flagged plots.
90
- Optionally add the unflagged data min/max to plot params for interactive gui colorbar range.
91
- '''
92
- if not self._plot_params['plot']['params']:
93
- logger.error('Parameters have not been set from plot data. Cannot plot.')
94
- return None
95
-
96
- data_params = self._plot_params['data']
97
- plot_params = self._plot_params['plot']
98
-
99
- x_axis = plot_params['axis_labels']['x']['axis']
100
- y_axis = plot_params['axis_labels']['y']['axis']
101
- c_axis = plot_params['axis_labels']['c']['axis']
102
-
103
- # Set plot axes to numeric coordinates if needed
104
- xds = set_index_coordinates(data, (x_axis, y_axis))
105
-
106
- # Prefix c_axis name with aggregator
107
- xda_name = c_axis
108
- if data_params['aggregator']:
109
- xda_name = "_".join([data_params['aggregator'], xda_name])
110
-
111
- # Plot unflagged and flagged data
112
- xda = xds[data_params['correlated_data']].where(xds.FLAG == 0.0).rename(xda_name)
113
- unflagged_plot = self._plot_xda(xda)
114
- flagged_xda = xds[data_params['correlated_data']].where(xds.FLAG == 1.0).rename("flagged_" + xda_name)
115
- flagged_plot = self._plot_xda(flagged_xda, True)
116
-
117
- if is_gui: # update data range for colorbar
118
- self._plot_params['data']['data_range'] = (xda.min().values.item(), xda.max().values.item())
119
-
120
- # Make Overlay plot with hover tools
121
- return (flagged_plot * unflagged_plot).opts(
122
- hv.opts.QuadMesh(tools=['hover'])
123
- )
124
-
125
- def _get_plot_title(self, data, plot_inputs, ms_name, include_selections=False):
126
- ''' Form string containing ms name and selected values using data (xArray Dataset) '''
127
- title = f"{ms_name}\n"
128
-
129
- if include_selections:
130
- # TBD: include complete selection?
131
- title = self._add_title_selections(data, title, plot_inputs)
132
- else:
133
- # Add iter_axis selection only
134
- iter_axis = plot_inputs['iter_axis']
135
- if iter_axis:
136
- iter_label = get_coordinate_labels(data, iter_axis)
137
- title += f"{iter_axis} {iter_label}"
138
- return title
139
-
140
- def _add_title_selections(self, data, title, plot_inputs):
141
- ''' Add ProcessingSet and data dimension selections to title '''
142
- selection = plot_inputs['selection'] if 'selection' in plot_inputs else None
143
- dim_selection = plot_inputs['dim_selection'] if 'dim_selection' in plot_inputs else None
144
- data_dims = plot_inputs['data_dims'] if 'data_dims' in plot_inputs else None
145
-
146
- ps_selections = []
147
- dim_selections = []
148
-
149
- for key in selection:
150
- # Add processing set selection: spw, field, source, and intent
151
- # TBD: add data group?
152
- if key == 'spw_name':
153
- spw_selection = f"spw: {selection[key]}"
154
- if 'frequency' in data:
155
- spw_selection += f" ({data.frequency.spectral_window_id})"
156
- ps_selections.append(spw_selection)
157
- elif key == 'field_name':
158
- ps_selections.append(f"field: {selection[key]}")
159
- elif key == 'source_name':
160
- ps_selections.append(f"source: {selection[key]}")
161
- elif key == 'intents':
162
- ps_selections.append(f"intents: {selection[key]}")
163
- elif key in data_dims:
164
- # Add user-selected dimensions to title: name (index)
165
- label = get_coordinate_labels(data, key)
166
- index = selection[key] if isinstance(selection[key], int) else None
167
- selected_dim = f"{key}: {label}"
168
- if index is not None:
169
- index_label = f" (ch {index}) " if key == 'frequency' else f" ({index}) "
170
- selected_dim += index_label
171
- dim_selections.append(selected_dim)
172
-
173
- for key in dim_selection:
174
- # Add auto- or iter-selected dimensions to title: name (index)
175
- label = get_coordinate_labels(data, key)
176
- index = dim_selection[key] if isinstance(dim_selection[key], int) else None
177
- selected_dim = f"{key}: {label}"
178
- if index is not None:
179
- index_label = f" (ch {index}) " if key == 'frequency' else f" ({index}) "
180
- selected_dim += index_label
181
- dim_selections.append(selected_dim)
182
-
183
- title += '\n'.join(ps_selections) + '\n'
184
- title += ' '.join(dim_selections)
185
- return title
186
-
187
- def _set_axis_labels(self, data, plot_inputs):
188
- ''' Set axis, label, and ticks for x, y, and vis axis '''
189
- x_axis_labels = get_axis_labels(data, plot_inputs['x_axis'])
190
- y_axis_labels = get_axis_labels(data, plot_inputs['y_axis'])
191
- c_axis_labels = self._get_c_axis_labels(data, plot_inputs)
192
-
193
- self._set_axis_label_params('x', x_axis_labels)
194
- self._set_axis_label_params('y', y_axis_labels)
195
- self._set_axis_label_params('c', c_axis_labels)
196
-
197
- def _get_c_axis_labels(self, data, plot_inputs):
198
- ''' Set axis and label for c axis using input data xArray Dataset. '''
199
- data_group = plot_inputs['selection']['data_group_name']
200
- correlated_data = plot_inputs['correlated_data']
201
- vis_axis = plot_inputs['vis_axis']
202
- aggregator = plot_inputs['aggregator']
203
-
204
- axis, label = get_vis_axis_labels(data, data_group, correlated_data, vis_axis)
205
- if aggregator:
206
- label = " ".join([aggregator.capitalize(), label])
207
- return (axis, label, None)
208
-
209
- def _set_axis_label_params(self, axis, axis_labels):
210
- # Axis labels are (axis, label, ticks).
211
- if 'axis_labels' not in self._plot_params['plot']:
212
- self._plot_params['plot']['axis_labels'] = {}
213
- self._plot_params['plot']['axis_labels'][axis] = {}
214
- self._plot_params['plot']['axis_labels'][axis]['axis'] = axis_labels[0]
215
- self._plot_params['plot']['axis_labels'][axis]['label'] = axis_labels[1]
216
- self._plot_params['plot']['axis_labels'][axis]['ticks'] = axis_labels[2]
217
-
218
- def _plot_xda(self, xda, is_flagged=False):
219
- # Returns Quadmesh plot if raster 2D data, Scatter plot if raster 1D data, or None if no data
220
- plot_params = self._plot_params['plot']
221
- style_params = self._plot_params['style']
222
-
223
- x_axis = plot_params['axis_labels']['x']['axis']
224
- y_axis = plot_params['axis_labels']['y']['axis']
225
- c_label = plot_params['axis_labels']['c']['label']
226
- c_lim = plot_params['color_limits']
227
-
228
- x_formatter = get_time_formatter() if x_axis == 'time' else None
229
- y_formatter = get_time_formatter() if y_axis == 'time' else None
230
-
231
- # Hide flagged colorbar if unflagged colorbar is shown
232
- if xda.count().values > 0:
233
- if is_flagged:
234
- show_colorbar = style_params['show_flagged_colorbar']
235
- else :
236
- show_colorbar = style_params['show_colorbar']
237
- else:
238
- show_colorbar = False
239
-
240
- if is_flagged:
241
- c_label = "Flagged " + c_label
242
- colormap = style_params['flagged_cmap']
243
- plot_params['flagged_colorbar'] = show_colorbar
244
- else:
245
- colormap = style_params['unflagged_cmap']
246
- plot_params['unflagged_colorbar'] = show_colorbar
247
-
248
- if xda[x_axis].size > 1 and xda[y_axis].size > 1:
249
- # Raster 2D data
250
- plot = xda.hvplot.quadmesh(
251
- x_axis,
252
- y_axis,
253
- clim=c_lim,
254
- cmap=colormap,
255
- clabel=c_label,
256
- title=plot_params['title'],
257
- xlabel=plot_params['axis_labels']['x']['label'],
258
- ylabel=plot_params['axis_labels']['y']['label'],
259
- xformatter=x_formatter,
260
- yformatter=y_formatter,
261
- xticks=plot_params['axis_labels']['x']['ticks'],
262
- yticks=plot_params['axis_labels']['y']['ticks'],
263
- rot=45, # angle for x axis labels
264
- colorbar=show_colorbar,
265
- responsive=True, # resize to fill browser window if True
266
- )
267
- else:
268
- # Cannot raster 1D data, use scatter from pandas dataframe
269
- df = xda.to_dataframe().reset_index() # convert x and y axis from index to column
270
- plot = df.hvplot.scatter(
271
- x=x_axis,
272
- y=y_axis,
273
- c=xda.name,
274
- clim=plot_params['color_limits'],
275
- cmap=colormap,
276
- clabel=c_label,
277
- title=plot_params['title'],
278
- xlabel=plot_params['axis_labels']['x']['label'],
279
- ylabel=plot_params['axis_labels']['y']['label'],
280
- xformatter=x_formatter,
281
- yformatter=y_formatter,
282
- xticks=plot_params['axis_labels']['x']['ticks'],
283
- yticks=plot_params['axis_labels']['y']['ticks'],
284
- rot=45,
285
- marker='s', # square
286
- hover=True,
287
- responsive=True,
288
- )
289
-
290
- if show_colorbar and not is_flagged:
291
- plot = plot.opts(colorbar_position='left')
292
- return plot