class Dial(ValueIndicator): """ A Dial represents a value in some range as a position on an annular dial. It is similar to a Gauge but more minimal visually. """ annulus_width = param.Number(default=0.2, doc=""" Width of the radial annulus as a fraction of the total.""") bounds = param.Range(default=(0, 100), doc=""" The upper and lower bound of the dial.""") colors = param.List(default=None, doc=""" Color thresholds for the Dial, specified as a list of tuples of the fractional threshold and the color to switch to.""") default_color = param.String(default='lightblue', doc=""" Color of the radial annulus if not color thresholds are supplied.""") end_angle = param.Number(default=25, doc=""" Angle at which the dial ends.""") format = param.String(default='{value}%', doc=""" Formatting string for the value indicator and lower/upper bounds.""") height = param.Integer(default=250, bounds=(1, None)) nan_format = param.String(default='-', doc=""" How to format nan values.""") needle_color = param.String(default='black', doc=""" Color of the Dial needle.""") needle_width = param.Number(default=0.1, doc=""" Radial width of the needle.""") start_angle = param.Number(default=-205, doc=""" Angle at which the dial starts.""") tick_size = param.String(default=None, doc=""" Font size of the Dial min/max labels.""") title_size = param.String(default=None, doc=""" Font size of the Dial title.""") unfilled_color = param.String(default='whitesmoke', doc=""" Color of the unfilled region of the Dial.""") value_size = param.String(default=None, doc=""" Font size of the Dial value label.""") value = param.Number(default=25, allow_None=True, doc=""" Value to indicate on the dial a value within the declared bounds.""") width = param.Integer(default=250, bounds=(1, None)) _manual_params = [ 'value', 'start_angle', 'end_angle', 'bounds', 'annulus_width', 'format', 'background', 'needle_width', 'tick_size', 'title_size', 'value_size', 'colors', 'default_color', 'unfilled_color', 'height', 'width', 'nan_format', 'needle_color' ] _data_params = _manual_params _rename = {'background': 'background_fill_color'} def __init__(self, **params): super().__init__(**params) self._update_value_bounds() @param.depends('bounds', watch=True) def _update_value_bounds(self): self.param.value.bounds = self.bounds def _get_data(self): vmin, vmax = self.bounds value = self.value if value is None: value = float('nan') fraction = (value - vmin) / (vmax - vmin) start = (np.radians(360 - self.start_angle) - pi % (2 * pi)) + pi end = (np.radians(360 - self.end_angle) - pi % (2 * pi)) + pi distance = (abs(end - start) % (pi * 2)) if end > start: distance = (pi * 2) - distance radial_fraction = distance * fraction angle = start if np.isnan(fraction) else (start - radial_fraction) inner_radius = 1 - self.annulus_width color = self.default_color for val, clr in (self.colors or [])[::-1]: if fraction <= val: color = clr annulus_data = { 'starts': np.array([start, angle]), 'ends': np.array([angle, end]), 'color': [color, self.unfilled_color], 'radius': np.array([inner_radius, inner_radius]) } x0s, y0s, x1s, y1s, clrs = [], [], [], [], [] colors = self.colors or [] for (val, _), (_, clr) in zip(colors[:-1], colors[1:]): tangle = start - (distance * val) if (vmin + val * (vmax - vmin)) <= value: continue x0, y0 = np.cos(tangle), np.sin(tangle) x1, y1 = x0 * inner_radius, y0 * inner_radius x0s.append(x0) y0s.append(y0) x1s.append(x1) y1s.append(y1) clrs.append(clr) threshold_data = { 'x0': x0s, 'y0': y0s, 'x1': x1s, 'y1': y1s, 'color': clrs } center_radius = 1 - self.annulus_width / 2. x, y = np.cos(angle) * center_radius, np.sin(angle) * center_radius needle_start = pi + angle - (self.needle_width / 2.) needle_end = pi + angle + (self.needle_width / 2.) needle_data = { 'x': np.array([x]), 'y': np.array([y]), 'start': np.array([needle_start]), 'end': np.array([needle_end]), 'radius': np.array([center_radius]) } value = self.format.format(value=value).replace('nan', self.nan_format) min_value = self.format.format(value=vmin) max_value = self.format.format(value=vmax) tminx, tminy = np.cos(start) * center_radius, np.sin( start) * center_radius tmaxx, tmaxy = np.cos(end) * center_radius, np.sin(end) * center_radius tmin_angle, tmax_angle = start + pi, end + pi % pi scale = (self.height / 400) title_size = self.title_size if self.title_size else '%spt' % (scale * 32) value_size = self.value_size if self.value_size else '%spt' % (scale * 48) tick_size = self.tick_size if self.tick_size else '%spt' % (scale * 18) text_data = { 'x': np.array([0, 0, tminx, tmaxx]), 'y': np.array([-.2, -.5, tminy, tmaxy]), 'text': [self.name, value, min_value, max_value], 'rot': np.array([0, 0, tmin_angle, tmax_angle]), 'size': [title_size, value_size, tick_size, tick_size], 'color': ['black', color, 'black', 'black'] } return annulus_data, needle_data, threshold_data, text_data def _get_model(self, doc, root=None, parent=None, comm=None): params = self._process_param_change(self._init_params()) model = figure(x_range=(-1, 1), y_range=(-1, 1), tools=[], outline_line_color=None, toolbar_location=None, width=self.width, height=self.height, **params) model.xaxis.visible = False model.yaxis.visible = False model.grid.visible = False annulus, needle, threshold, text = self._get_data() # Draw annulus annulus_source = ColumnDataSource(data=annulus, name='annulus_source') model.annular_wedge(x=0, y=0, inner_radius='radius', outer_radius=1, start_angle='starts', end_angle='ends', line_color='gray', color='color', direction='clock', source=annulus_source) # Draw needle needle_source = ColumnDataSource(data=needle, name='needle_source') model.wedge(x='x', y='y', radius='radius', start_angle='start', end_angle='end', fill_color=self.needle_color, line_color=self.needle_color, source=needle_source, name='needle_renderer') # Draw thresholds threshold_source = ColumnDataSource(data=threshold, name='threshold_source') model.segment(x0='x0', x1='x1', y0='y0', y1='y1', line_color='color', source=threshold_source, line_width=2) # Draw labels text_source = ColumnDataSource(data=text, name='label_source') model.text(x='x', y='y', text='text', font_size='size', text_align='center', text_color='color', source=text_source, text_baseline='top', angle='rot') if root is None: root = model self._models[root.ref['id']] = (model, parent) return model def _manual_update(self, events, model, doc, root, parent, comm): update_data = False for event in events: if event.name in ('width', 'height'): model.update(**{event.name: event.new}) if event.name in self._data_params: update_data = True elif event.name == 'needle_color': needle_r = model.select(name='needle_renderer') needle_r.glyph.line_color = event.new needle_r.glyph.fill_color = event.new if not update_data: return annulus, needle, threshold, labels = self._get_data() model.select(name='annulus_source').data.update(annulus) model.select(name='needle_source').data.update(needle) model.select(name='threshold_source').data.update(threshold) model.select(name='label_source').data.update(labels)
class ChannelViewer(BaseViewer): cmap = param.Selector(list(available_cmaps.keys()), doc='Colormap name') # actual colormap, needed to handle named colormaps and instances of colormap _cmap = param.Parameter(next(iter(available_cmaps.values()))) intensity_bounds = param.Range( doc= 'Image clipping bounds. If not specified, imgae (min,max) will be used' ) slider_limits = param.NumericTuple( (0, 2**16 - 1), doc='(min,max) values for intensity slider') opacity = param.Number(1., bounds=(0., 1.), step=0.01, doc='Channel opacity', instantiate=True) bitdepth = param.Selector( default=8, objects=[8, 16, 'int8', 'int16'], doc= 'bitdepth of the rasterized image. 16 bits is useful for labels with object above 255' ) raster_aggregator = param.String( 'default', doc= 'Aggreation method to downsample the image. e.g. use "first" for labels' ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._set_intensity_bounds_limits(self.slider_limits) self._watch_selected_cmap() @param.depends('cmap', watch=True) def _watch_selected_cmap(self): self._cmap = available_cmaps[self.cmap] def _set_intensity_bounds_limits(self, limits): '''Updates the bounds of intensity slider and change current value if out of bound''' if self.intensity_bounds and self.intensity_bounds[1] > limits[1]: self.intensity_bounds = (self.intensity_bounds[0], limits[1]) elif self.intensity_bounds and self.intensity_bounds[0] < limits[0]: self.intensity_bounds = (limits[0], self.intensity_bounds[1]) self.param.intensity_bounds.bounds = limits @param.depends('intensity_bounds') def _update_intensity_bounds(self, elem): xs = elem.dimension_values(0, expanded=False) ys = elem.dimension_values(1, expanded=False) img = elem.dimension_values(2, flat=False) dtype = { 8: np.uint8, 16: np.uint16, 'int8': np.int8, 'int16': np.int16 }[self.bitdepth] bounds = self.intensity_bounds if bounds is None: bounds = (img.min(), img.max()) img = rescale_intensity(img, in_range=bounds, out_range=dtype).astype(dtype) return elem.clone((xs, ys, img)) @param.depends() def _set_aspect_ratio(self, elem): # hack to avoid bug with rasterize + invert_yaxis + aspect='equal' # manually set data_aspect=1 options = elem.opts.get().options frame_w = options.get('frame_width', None) frame_h = options.get('frame_height', None) if frame_w and frame_h: # already set return elem wstart, wstop = elem.dimension_values(0, expanded=False)[[0, -1]] hstart, hstop = elem.dimension_values(1, expanded=False)[[0, -1]] w = wstop - wstart h = hstop - hstart if frame_w: return elem.opts( opts.Image(frame_height=int(round(frame_w / w * h)))) elif frame_h: return elem.opts( opts.Image(frame_width=int(round(frame_h / h * w)))) else: return elem def _call(self, dmap): dmap = dmap.apply(lambda ds: ds.to(hv.Image, group='channel')) dmap = rasterize(dmap, aggregator=self.raster_aggregator, expand=False) dmap = dmap.apply(self._set_aspect_ratio) dmap = dmap.apply(self._update_intensity_bounds) return dmap.apply.opts(cmap=self.param._cmap, alpha=self.param.opacity) def widgets(self): return pn.WidgetBox(self.param.cmap, self.param.intensity_bounds, self.param.opacity)
class Gauge(ValueIndicator): """ A Gauge represents a value in some range as a position on speedometer or gauge. It is similar to a Dial but visually a lot busier. """ annulus_width = param.Integer(default=10, doc=""" Width of the gauge annulus.""") bounds = param.Range(default=(0, 100), doc=""" The upper and lower bound of the dial.""") colors = param.List(default=None, doc=""" Color thresholds for the Gauge, specified as a list of tuples of the fractional threshold and the color to switch to.""") custom_opts = param.Dict(doc=""" Additional options to pass to the ECharts Gauge definition.""") height = param.Integer(default=300, bounds=(0, None)) end_angle = param.Number(default=-45, doc=""" Angle at which the gauge ends.""") format = param.String(default='{value}%', doc=""" Formatting string for the value indicator.""") num_splits = param.Integer(default=10, doc=""" Number of splits along the gauge.""") show_ticks = param.Boolean(default=True, doc=""" Whether to show ticks along the dials.""") show_labels = param.Boolean(default=True, doc=""" Whether to show tick labels along the dials.""") start_angle = param.Number(default=225, doc=""" Angle at which the gauge starts.""") tooltip_format = param.String(default='{b} : {c}%', doc=""" Formatting string for the hover tooltip.""") title_size = param.Integer(default=18, doc=""" Size of title font.""") value = param.Number(default=25, doc=""" Value to indicate on the gauge a value within the declared bounds.""") width = param.Integer(default=300, bounds=(0, None)) _rename = {} _source_transforms = { 'annulus_width': None, 'bounds': None, 'colors': None, 'custom_opts': None, 'end_angle': None, 'format': None, 'num_splits': None, 'show_ticks': None, 'show_labels': None, 'start_angle': None, 'tooltip_format': None, 'title_size': None, 'value': None } @property def _widget_type(self): if 'panel.models.echarts' not in sys.modules: from ..models.echarts import ECharts else: ECharts = getattr(sys.modules['panel.models.echarts'], 'ECharts') return ECharts def __init__(self, **params): super().__init__(**params) self._update_value_bounds() @param.depends('bounds', watch=True) def _update_value_bounds(self): self.param.value.bounds = self.bounds def _process_param_change(self, msg): msg = super()._process_param_change(msg) vmin, vmax = msg.pop('bounds', self.bounds) msg['data'] = { 'tooltip': { 'formatter': msg.pop('tooltip_format', self.tooltip_format) }, 'series': [{ 'name': 'Gauge', 'type': 'gauge', 'axisTick': { 'show': msg.pop('show_ticks', self.show_ticks) }, 'axisLabel': { 'show': msg.pop('show_labels', self.show_labels) }, 'title': { 'fontWeight': 'bold', 'fontSize': msg.pop('title_size', self.title_size) }, 'splitLine': { 'show': True }, 'radius': '100%', 'detail': { 'formatter': msg.pop('format', self.format) }, 'min': vmin, 'max': vmax, 'startAngle': msg.pop('start_angle', self.start_angle), 'endAngle': msg.pop('end_angle', self.end_angle), 'splitNumber': msg.pop('num_splits', self.num_splits), 'data': [{ 'value': msg.pop('value', self.value), 'name': self.name }], 'axisLine': { 'lineStyle': { 'width': msg.pop('annulus_width', self.annulus_width), } } }] } colors = msg.pop('colors', self.colors) if colors: msg['data']['series'][0]['axisLine']['lineStyle']['color'] = colors custom_opts = msg.pop('custom_opts', self.custom_opts) if custom_opts: gauge = msg['data']['series'][0] for k, v in custom_opts.items(): if k not in gauge or not isinstance(gauge[k], dict): gauge[k] = v else: gauge[k].update(v) return msg
class SagSwellExplorer(hv.streams.Stream): alpha = param.Magnitude(default=0.75, doc="Alpha value for the map opacity") plot = param.ObjectSelector(default="Sag", objects=["Sag", "Swell"]) colormap = param.ObjectSelector(default=cm["fire"], objects=cm.values()) numEvents = param.Range(default=(1, 300), bounds=(1, 300), doc="""Filter for event count""") ByDay = param.Boolean(False, doc="Filter By Day") DayNum = param.Integer( 1, bounds=(1, 15)) # Stop at 15 since that's all the data we have loaded ByFeeder = param.Boolean(False, doc="Filter By Feeder") Feeder = param.ObjectSelector( default="28GM012002", objects=df.FEEDER_ID.sort_values().unique().tolist()) BySUB = param.Boolean(False, doc="Filter By SUB") Substations = param.ObjectSelector( default="28GM", objects=df.SUB.sort_values().unique().tolist()) maxpix = param.Integer(12) threshhold = param.Number(0.6, bounds=(0.1, 1.0)) def make_view(self, x_range=None, y_range=None, **kwargs): #map_tiles = tiles.opts(style=dict(alpha=self.alpha), plot=options) points = hv.Points( df, kdims=['X_CORD', 'Y_CORD'], vdims=['EVENT_COUNT', 'EventType', 'SUB', 'day', 'FEEDER_ID']) if (self.BySUB & self.ByFeeder & self.ByDay): selected = points.select(EventType=self.plot, EVENT_COUNT=self.numEvents, SUB=self.Substations, day=self.DayNum, FEEDER_ID=self.Feeder) elif (self.BySUB & self.ByFeeder & (not self.ByDay)): selected = points.select(EventType=self.plot, EVENT_COUNT=self.numEvents, SUB=self.Substations, FEEDER_ID=self.Feeder) elif (self.BySUB & (not self.ByFeeder) & self.ByDay): selected = points.select(EventType=self.plot, EVENT_COUNT=self.numEvents, SUB=self.Substations, day=self.DayNum) elif (self.BySUB & (not self.ByFeeder) & (not self.ByDay)): selected = points.select(EventType=self.plot, EVENT_COUNT=self.numEvents, SUB=self.Substations) elif ((not self.BySUB) & self.ByFeeder & self.ByDay): selected = points.select(EventType=self.plot, EVENT_COUNT=self.numEvents, day=self.DayNum, FEEDER_ID=self.Feeder) elif ((not self.BySUB) & self.ByFeeder & (not self.ByDay)): selected = points.select(EventType=self.plot, EVENT_COUNT=self.numEvents, FEEDER_ID=self.Feeder) elif ((not self.BySUB) & (not self.ByFeeder) & self.ByDay): selected = points.select(EventType=self.plot, EVENT_COUNT=self.numEvents, day=self.DayNum) else: selected = points.select(EventType=self.plot, EVENT_COUNT=self.numEvents) SagSwellPts = datashade(selected, x_sampling=1, y_sampling=1, cmap=self.colormap, dynamic=False, x_range=x_range, y_range=y_range, width=640, height=380) dsss = dynspread(SagSwellPts, shape='circle', max_px=self.maxpix, threshold=self.threshhold) #return map_tiles * dsss return dsss def jtdp(self, x_range, y_range, **kwargs): pointdec = hv.Points(df, kdims=['X_CORD', 'Y_CORD'], vdims=['EVENT_COUNT', 'FEEDER_ID']) selecteddec = pointdec.select(EventType=self.plot, EVENT_COUNT=self.numEvents) dm2 = decimate( selecteddec, x_range=x_range, y_range=y_range, dynamic=False ).opts( style={'Points': dict(alpha=0.0, color='blue', size=self.maxpix)}) return dm2 def dec_tab(self, x_range, y_range, bounds, **kwargs): #%opts Table [ fig_size=550 width=600 height=380] b0 = bounds[0] b2 = bounds[2] b1 = bounds[1] b3 = bounds[3] xr = bounds[2] - bounds[0] yr = bounds[3] - bounds[1] if (not ((xr < 50000) & (yr < 50000))): b0 = b2 = b1 = b3 = 0.0 win32api.MessageBox(0, "SELECTED AREA TOO LARGE! ", 'dec_tab', 0x00001000) pointdec = hv.Points(df, kdims=['X_CORD', 'Y_CORD'], vdims=[ 'EVENT_COUNT', 'EventType', 'SUB', 'day', 'FEEDER_ID', 'XFMR', 'Phase' ]) if (self.BySUB & self.ByFeeder & self.ByDay): selected = pointdec.select(X_CORD=(b0, b2), Y_CORD=(b1, b3), EventType=self.plot, EVENT_COUNT=self.numEvents, SUB=self.Substations, day=self.DayNum, FEEDER_ID=self.Feeder) elif (self.BySUB & self.ByFeeder & (not self.ByDay)): selected = pointdec.select(X_CORD=(b0, b2), Y_CORD=(b1, b3), EventType=self.plot, EVENT_COUNT=self.numEvents, SUB=self.Substations, FEEDER_ID=self.Feeder) elif (self.BySUB & (not self.ByFeeder) & self.ByDay): selected = pointdec.select(X_CORD=(b0, b2), Y_CORD=(b1, b3), EventType=self.plot, EVENT_COUNT=self.numEvents, SUB=self.Substations, day=self.DayNum) elif (self.BySUB & (not self.ByFeeder) & (not self.ByDay)): selected = pointdec.select(X_CORD=(b0, b2), Y_CORD=(b1, b3), EventType=self.plot, EVENT_COUNT=self.numEvents, SUB=self.Substations) elif ((not self.BySUB) & self.ByFeeder & self.ByDay): selected = pointdec.select(X_CORD=(b0, b2), Y_CORD=(b1, b3), EventType=self.plot, EVENT_COUNT=self.numEvents, day=self.DayNum, FEEDER_ID=self.Feeder) elif ((not self.BySUB) & self.ByFeeder & (not self.ByDay)): selected = pointdec.select(X_CORD=(b0, b2), Y_CORD=(b1, b3), EventType=self.plot, EVENT_COUNT=self.numEvents, FEEDER_ID=self.Feeder) elif ((not self.BySUB) & (not self.ByFeeder) & self.ByDay): selected = pointdec.select(X_CORD=(b0, b2), Y_CORD=(b1, b3), EventType=self.plot, EVENT_COUNT=self.numEvents, day=self.DayNum) else: selected = pointdec.select(X_CORD=(b0, b2), Y_CORD=(b1, b3), EventType=self.plot, EVENT_COUNT=self.numEvents) #bp=selected.select( X_CORD=(b0, b2),Y_CORD=(b1, b3) ) tab = hv.Table( selected, kdims=[], vdims=['EventType', 'SUB', 'FEEDER_ID', 'XFMR', 'Phase']) return tab
class MyParamRange(param.Parameterized): my_ranges = param.Range(default=(-50, 50))
class Q(param.Parameterized): q = param.Range((0, 2), bounds=(0, 1))
class LinearGauge(ValueIndicator): """ A LinearGauge represents a value in some range as a position on an linear scale. It is similar to a Dial/Gauge but visually more compact. Reference: https://panel.holoviz.org/reference/indicators/LinearGauge.html :Example: >>> LinearGauge(value=30, default_color='red', bounds=(0, 100)) """ bounds = param.Range(default=(0, 100), doc=""" The upper and lower bound of the gauge.""") default_color = param.String(default='lightblue', doc=""" Color of the radial annulus if not color thresholds are supplied.""") colors = param.Parameter(default=None, doc=""" Color thresholds for the gauge, specified as a list of tuples of the fractional threshold and the color to switch to.""") format = param.String(default='{value:.2f}%', doc=""" Formatting string for the value indicator and lower/upper bounds.""") height = param.Integer(default=300, bounds=(1, None)) horizontal = param.Boolean(default=False, doc=""" Whether to display the linear gauge horizontally.""") nan_format = param.String(default='-', doc=""" How to format nan values.""") needle_color = param.String(default='black', doc=""" Color of the gauge needle.""") show_boundaries = param.Boolean(default=False, doc=""" Whether to show the boundaries between colored regions.""") unfilled_color = param.String(default='whitesmoke', doc=""" Color of the unfilled region of the LinearGauge.""") title_size = param.String(default=None, doc=""" Font size of the gauge title.""") tick_size = param.String(default=None, doc=""" Font size of the gauge tick labels.""") value_size = param.String(default=None, doc=""" Font size of the gauge value label.""") value = param.Number(default=25, allow_None=True, doc=""" Value to indicate on the dial a value within the declared bounds.""") width = param.Integer(default=125, bounds=(1, None)) _manual_params = [ 'value', 'bounds', 'format', 'title_size', 'value_size', 'horizontal', 'height', 'colors', 'tick_size', 'unfilled_color', 'width', 'nan_format', 'needle_color' ] _data_params = [ 'value', 'bounds', 'format', 'nan_format', 'needle_color', 'colors' ] _rerender_params = ['horizontal'] _rename = { 'background': 'background_fill_color', 'show_boundaries': None, 'default_color': None } _updates = False def __init__(self, **params): super().__init__(**params) self._update_value_bounds() @param.depends('bounds', watch=True) def _update_value_bounds(self): self.param.value.bounds = self.bounds @property def _color_intervals(self): vmin, vmax = self.bounds value = self.value ncolors = len(self.colors) if self.colors else 1 interval = (vmax-vmin) if math.isfinite(value): fraction = value / interval idx = round(fraction * (ncolors-1)) else: fraction = 0 idx = 0 if not self.colors: intervals = [ (fraction, self.default_color) ] intervals.append((1, self.unfilled_color)) elif self.show_boundaries: intervals = [ c if isinstance(c, tuple) else ((i+1)/(ncolors), c) for i, c in enumerate(self.colors) ] else: intervals = [ self.colors[idx] if isinstance(self.colors[0], tuple) else (fraction, self.colors[idx]) ] intervals.append((1, self.unfilled_color)) return intervals def _get_data(self): vmin, vmax = self.bounds value = self.value interval = (vmax-vmin) colors, values = [], [vmin] above = False prev = None for (v, color) in self._color_intervals: val = v*interval if val == prev: continue elif val > value: if not above: colors.append(color) values.append(value) above = True color = self.unfilled_color colors.append(color) values.append(val) prev = val value = self.format.format(value=value).replace('nan', self.nan_format) return ( {'y0': values[:-1], 'y1': values[1:], 'color': colors}, {'y': [self.value], 'text': [value]} ) def _get_model(self, doc, root=None, parent=None, comm=None): params = self._process_param_change(self._init_params()) model = figure( outline_line_color=None, toolbar_location=None, tools=[], x_axis_location='above', y_axis_location='right', **params ) model.grid.visible = False model.xaxis.major_label_standoff = 2 model.yaxis.major_label_standoff = 2 model.xaxis.axis_label_standoff = 2 model.yaxis.axis_label_standoff = 2 self._update_name(model) self._update_title_size(model) self._update_tick_size(model) self._update_figure(model) self._update_axes(model) self._update_renderers(model) self._update_bounds(model) if root is None: root = model self._models[root.ref['id']] = (model, parent) return model def _update_name(self, model): model.xaxis.axis_label = self.name model.yaxis.axis_label = self.name def _update_title_size(self, model): title_size = self.title_size or f'{self.width/6}px' model.xaxis.axis_label_text_font_size = title_size model.yaxis.axis_label_text_font_size = title_size def _update_tick_size(self, model): tick_size = self.tick_size or f'{self.width/9}px' model.xaxis.major_label_text_font_size = tick_size model.yaxis.major_label_text_font_size = tick_size def _update_renderers(self, model): model.renderers = [] data, needle_data = self._get_data() bar_source = ColumnDataSource(data=data, name='bar_source') needle_source = ColumnDataSource(data=needle_data, name='needle_source') if self.horizontal: model.hbar( y=0.1, left='y0', right='y1', height=1, color='color', source=bar_source ) wedge_params = {'y': 0.5, 'x': 'y', 'angle': np.deg2rad(180)} text_params = { 'y': -0.4, 'x': 0, 'text_align': 'left', 'text_baseline': 'top' } else: model.vbar( x=0.1, bottom='y0', top='y1', width=0.9, color='color', source=bar_source ) wedge_params = {'x': 0.5, 'y': 'y', 'angle': np.deg2rad(90)} text_params = { 'x': -0.4, 'y': 0, 'text_align': 'left', 'text_baseline': 'bottom', 'angle': np.deg2rad(90) } model.scatter( fill_color=self.needle_color, line_color=self.needle_color, source=needle_source, name='needle_renderer', marker='triangle', size=int(self.width/8), level='overlay', **wedge_params ) value_size = self.value_size or f'{self.width/8}px' model.text( text='text', source=needle_source, text_font_size=value_size, **text_params ) def _update_bounds(self, model): if self.horizontal: x_range, y_range = tuple(self.bounds), (-0.8, 0.5) else: x_range, y_range = (-0.8, 0.5), tuple(self.bounds) model.x_range.update(start=x_range[0], end=x_range[1]) model.y_range.update(start=y_range[0], end=y_range[1]) def _update_axes(self, model): vmin, vmax = self.bounds interval = (vmax-vmin) if self.show_boundaries: ticks = [vmin] + [v*interval for (v, _) in self._color_intervals] else: ticks = [vmin, vmax] ticker = FixedTicker(ticks=ticks) if self.horizontal: model.xaxis.visible = True model.xaxis.ticker = ticker model.yaxis.visible = False else: model.xaxis.visible = False model.yaxis.visible = True model.yaxis.ticker = ticker def _update_figure(self, model): params = self._process_param_change(self._init_params()) if self.horizontal: params.update(width=self.height, height=self.width) else: params.update(width=self.width, height=self.height) model.update(**params) def _manual_update(self, events, model, doc, root, parent, comm): update_data = False for event in events: if event.name in ('width', 'height'): self._update_figure(model) elif event.name == 'bounds': self._update_bounds(model) self._update_renderers(model) elif event.name in self._data_params: update_data = True elif event.name == 'needle_color': needle_r = model.select(name='needle_renderer') needle_r.glyph.line_color = event.new needle_r.glyph.fill_color = event.new elif event.name == 'horizontal': self._update_bounds(model) self._update_figure(model) self._update_axes(model) self._update_renderers(model) elif event.name == 'name': self._update_name(model) elif event.name == 'tick_size': self._update_tick_size(model) elif event.name == 'title_size': self._update_title_size(model) if not update_data: return data, needle_data = self._get_data() model.select(name='bar_source').data.update(data) model.select(name='needle_source').data.update(needle_data)
class MedNumApp(param.Parameterized): localisation = param.String( default="Toulouse", label="" ) # default=["Toulouse"], objects=list(ifrag_cont_df_merged.nom_com.unique()), label='', doc="A string") score = param.Range( default=(0, 250), bounds=(0, 250), ) # , name="Score") interfaces_num = param.ListSelector(label="") infos_num = param.ListSelector(label="") comp_admin = param.ListSelector(label="") comp_usage_num = param.ListSelector(label="") point_ref = param.Selector( objects=["Pays", "Région", "Département", "Intercommune", "Commune"], label="Point de référence", ) donnees_infra = param.Action(lambda x: x, doc="""Données Infra-Communales""", precedence=0.7) export_data = param.Action( lambda x: x.timestamps.append(dt.datetime.utcnow()), doc="""Exporter les résultats""", precedence=0.7, ) edit_report = param.Action( lambda x: x.timestamps.append(dt.datetime.utcnow()), doc="""Editer un rapport""", precedence=0.7, ) tiles = StamenTerrain() def __init__(self, **params): super(MedNumApp, self).__init__(**params) self.param.interfaces_num.objects = OPTIONS_INT_NUM self.param.infos_num.objects = OPTIONS_X_INFOS self.param.comp_admin.objects = OPTIONS_X_COMP_ADMIN self.param.comp_usage_num.objects = OPTIONS_X_COMP_USAGE indic_w_g_value_1 = { "name": "indic1_1", "indicators": [ dict(name="accès", main=True, value=85, max_value=100), dict(name="information", value=118), # dict(name="indic3", value=168), dict(name="Interfaces", value=53), ], } indic_w_g_value_2 = { "indicators": [ dict(name="Compétences", main=True, value=135, max_value=180), dict(name="indic3_2", value=115), dict(name="indic4", value=155), ] } self.indicator_w_gauge_1 = IndicatorsWithGauge(**indic_w_g_value_1) self.indicator_w_gauge_2 = IndicatorsWithGauge(**indic_w_g_value_2) self.indicator_glob_stats = self.glob_stats() def lat_widgets(self): score_panel = pn.Column("# Score", self.param.score) point_ref_panel = pn.Column( "# Point de reference", pn.Param( self.param.point_ref, widgets={ "point_ref": pn.widgets.RadioBoxGroup, }, ), ) export_panel = pn.Column("# Aller plus loin", self.param.export_data, self.param.edit_report) localisation_panel = pn.Column("# Localisation", self.param.localisation) spec_interfaces = { "interfaces_num": pn.widgets.TreeViewCheckBox, "infos_num": pn.widgets.TreeViewCheckBox, "comp_admin": pn.widgets.TreeViewCheckBox, "comp_usage_num": pn.widgets.TreeViewCheckBox, } g_params = [ pn.Param(self.param[p], widgets={p: w}) for p, w in spec_interfaces.items() ] pn.Column(*g_params) indicateurs = pn.Column("# Indicateurs", *g_params) ordered_panel = pn.Column( localisation_panel, score_panel, indicateurs, point_ref_panel, export_panel, width=400, ) return ordered_panel def link_ctrl_params_to_indic_params(self): try: return self.indicator_w_gauge.view except: return "" @pn.depends('score', watch=True) def glob_stats(self): label = "Score Global" score_min, score_max = self.score HTML = """ <h1>{loc}</h1> <b>{score_name}</b> | {score_min}->{score_max}<br> <b>{pop_name}</b> | {population} """.format(loc=self.localisation, score_name=label, score_min=score_min, score_max=score_max, pop_name="Population", population="Beaucoup !!") return pn.pane.HTML(HTML) def top_panel(self): try: return pn.Row(self.indicator_glob_stats, self.indicator_w_gauge_1.view, self.indicator_w_gauge_2.view, css_classes=["top-custom"], height=120) except: return "" @param.depends("localisation") # , interfaces_num) def plot(self): commune_plot = gv.Polygons( ifrag_cont_df_merged[ifrag_cont_df_merged.nom_com == self.localisation], vdims=vdims, ) return self.tiles * commune_plot.opts( color=indice, width=600, height=600, fill_alpha=0.5) def view(self): self.view = pn.Row( self.lat_widgets(), pn.Spacer(width=10), pn.Column(self.top_panel, pn.Spacer(height=80), self.plot), ) def panel(self): return self.lat_widgets()
class InterpolateMesh(param.Parameterized): """ Loaded bathymetry scatterset must be in columnar format with column labels being 'x', 'y', 'z' """ map_width = param.Integer(default=800, precedence=-1) map_height = param.Integer(default=600, precedence=-1) filepath = param.String(default=os.path.join( ROOTDIR, 'tests', 'test_files', 'SanDiego', 'SanDiego_bathy.csv')) load_data = param.Action(lambda self: self.param.trigger('load_data'), label='Load', precedence=1) scatter = param.DataFrame(default=pd.DataFrame(), precedence=-1) scatter_projection = param.ClassSelector(default=Projection(), class_=Projection) scatter_toggle = param.Boolean( default=False, doc='Toggle the visibility of the scatterset', label='View scatter', precedence=24) interp_button = param.Action( lambda self: self.param.trigger('interp_button'), label='Interpolate', precedence=25) interpolation_option = param.ObjectSelector( default='idw', objects=['linear', 'natural_neighbor', 'idw'], precedence=1) nodal_function = param.ObjectSelector( default='constant', objects=['constant', 'gradient_plane', 'quadratic'], precedence=1.1) truncation = param.Boolean(default=False, precedence=2) truncation_range = param.Range(default=(-10, 100), bounds=(None, None), softbounds=(-40, 150), label='', precedence=-1) adh_mod = param.ClassSelector(class_=AdhModel) cmap_opts = param.ClassSelector(default=ColormapOpts(), class_=ColormapOpts) display_range = param.ClassSelector(default=DisplayRangeOpts(), class_=DisplayRangeOpts) def __init__(self, adh_mod, **params): super(InterpolateMesh, self).__init__(adh_mod=adh_mod, **params) # set defaults for initialized example self.display_range.param.color_range.bounds = (10, 90) self.display_range.color_range = (10, 90) self.cmap_opts.colormap = cc.rainbow self.scatter_projection.set_crs(ccrs.GOOGLE_MERCATOR) self.adh_mod.wmts.source = gv.tile_sources.EsriImagery # print(self.projection.param.UTM_zone_hemi.constant, self.projection.crs_label) self.opts = (opts.Curve(height=self.map_height, width=self.map_width, xaxis=None, line_width=1.50, color='red', tools=['hover']), opts.Path(height=self.map_height, width=self.map_width, line_width=3, color='black'), opts.Image(height=self.map_height, width=self.map_width, cmap=self.cmap_opts.param.colormap, clim=self.display_range.param.color_range, colorbar=True, clipping_colors={ 'NaN': 'transparent', 'min': 'transparent' }, axiswise=True), opts.RGB(height=self.map_height, width=self.map_width), opts.Points(height=self.map_height, width=self.map_width, color_index='z', cmap=self.cmap_opts.param.colormap, clim=self.display_range.param.color_range, size=10, tools=['hover'], padding=(0.1, 0.1), colorbar=True), opts.TriMesh(height=self.map_height, width=self.map_width, color_index='z', cmap=self.cmap_opts.param.colormap, clim=self.display_range.param.color_range, tools=['hover'], padding=(0.1, 0.1), colorbar=True), opts.VLine(color='black')) # opts.defaults(*self.opts) @param.depends('load_data', watch=True) def _load(self): self.scatter = pd.read_csv(self.filepath, sep=',', names=['x', 'y', 'z']) self.scatter_pts = gv.Points(data=self.scatter, crs=self.scatter_projection.get_crs(), vdims=['z'], kdims=['x', 'y']) self.scatter_toggle = True @param.depends('truncation', watch=True) def _truncation(self): if self.truncation: self.param.truncation_range.precedence = 2.4 else: self.param.truncation_range.precedence = -1 @param.depends('interpolation_option', watch=True) def _update_nodal_function(self): if self.interpolation_option == 'linear': self.param.nodal_function.precedence = -1 else: self.param.nodal_function.precedence = 1.1 @param.depends('interp_button', watch=True) def interpolate(self): if self.interpolation_option != 'idw': interp_object = xmsinterp.interpolate.InterpLinear( pts=self.scatter.values) if self.interpolation_option == 'natural_neighbor': interp_object.interp_to_pt( (0, 0)) # this will force triangle creation interp_object.set_use_natural_neighbor( nodal_func_type=self.nodal_function, nd_func_pt_search_opt="nearest_pts") else: interp_object = xmsinterp.interpolate.InterpIdw( pts=self.scatter.values) interp_object.set_nodal_function( nodal_func_type=self.nodal_function) if self.truncation: interp_object.set_truncation_max_min(self.truncation_range[1], self.truncation_range[0]) z = interp_object.interp_to_pts( self.adh_mod.mesh.mesh_points.data[['x', 'y']].values) self.adh_mod.mesh.mesh_points.data['z'] = z self.adh_mod.mesh.tri_mesh = gv.TriMesh( (self.adh_mod.mesh.tris[['v0', 'v1', 'v2']], self.adh_mod.mesh.mesh_points)).apply.opts(*self.opts) self.adh_mod.mesh.elevation_toggle = True # @param.depends('scatter_toggle', watch=True) def view_scatter(self): # print('view_scatter') if self.scatter_toggle and not self.scatter.empty: return self.scatter_pts.apply.opts( color_index='z', cmap=self.cmap_opts.param.colormap, clim=self.display_range.param.color_range, colorbar=True, marker='o', line_color=None) else: return Curve([]) @param.depends('adh_mod.mesh.elements_toggle', 'adh_mod.mesh.elevation_toggle', 'interp_button', 'scatter_toggle', watch=True) def view_map(self): # print('view_map method') if self.adh_mod.mesh.elevation_toggle: elevation = rasterize(self.adh_mod.mesh.tri_mesh, aggregator=ds.mean('z'), precompute=True).apply.opts( opts.Image( cmap=self.cmap_opts.colormap, clim=self.display_range.color_range, height=self.map_height, width=self.map_width)) else: elevation = Curve([]).opts(height=self.map_height, width=self.map_width) # return self.adh_mod.mesh.view_bathy() * self.adh_mod.mesh.view_elements(line_color='yellow') * base_map * self.view_scatter() return elevation * self.adh_mod.mesh.view_elements( line_color='yellow') * hv.DynamicMap( self.adh_mod.wmts.view) * self.view_scatter() @param.output(adh_mod=AdhModel) def output(self): return self.adh_mod def panel(self): load_tab = pn.Column( pn.panel(self.param, parameters=['filepath'], show_name=False), pn.panel(self.scatter_projection.param, expand_button=False), pn.panel(self.param, parameters=['load_data'], show_name=False)) map_pane = self.view_map interp_pane = pn.Column( pn.panel(self.param, parameters=['interpolation_option', 'nodal_function'], show_name=False), pn.Row( pn.panel(self.param, parameters=['truncation', 'truncation_range'], show_name=False), ), pn.panel(self.param, parameters=['interp_button'], show_name=False)) display_tab = pn.Column( pn.panel(self.cmap_opts.param, parameters=['colormap'], show_name=False), pn.panel(self.display_range.param, parameters=['color_range'], show_name=False), pn.panel(self.param, parameters=['scatter_toggle'], show_name=False), pn.panel(self.adh_mod.mesh.param, parameters=['elements_toggle', 'elevation_toggle'], show_name=False), pn.panel(self.adh_mod.wmts.param, parameters=['source'], expand_button=False, show_name=False)) tool_panel = pn.Tabs(('Load Data', load_tab), ('Display', display_tab), ('Interpolate', interp_pane)) return pn.Row(map_pane, tool_panel)
class SpatialSubsetter(NullSubsetter): latitude_range = param.Range(default=(-90, 90), bounds=(-90, 90)) longitude_range = param.Range(default=(0, 360), bounds=(0, 360))
class ReactiveDashboard(param.Parameterized): title = pn.pane.Markdown("# Smart House Search") pins = param.List(default=[]) lat_longs = param.List(default=[]) # house_df = get_dummy_house_df() house_df = house_df_default hover = HoverTool(tooltips=TOOLTIPS) details_area = pn.pane.Markdown("# Details") price_range = get_price_range() minimum_price = param.Selector(objects=list(price_range)) maximum_price = param.Selector(objects=list(price_range), default=price_range[-1]) price_slider = param.Range( label='Price range', default=(options['price_min'], options['price_max']), bounds=(0, options['price_max']), ) rooms_slider = param.Range(label='Bedrooms', default=(0, 7), bounds=(0, 7)) bathrooms_slider = param.Range(label='Bathrooms', default=(0, 7), bounds=(0, 7)) type = param.ListSelector(label='Type of property', default=options['type'], objects=options['type']) transit_time = param.Range(label='Transit time [mins]', default=(0, options['transit_time_max']), bounds=(0, options['transit_time_max'])) map_background = hv.element.tiles.OSM().opts(width=600, height=550) stream = hv.streams.Tap(source=map_background, x=np.nan, y=np.nan) def get_easting_northing(self): self.house_df['easting'] = self.house_df.apply( lambda x: lon_lat_to_easting_northing(x['long'], x['lat'])[0], axis=1) self.house_df['northing'] = self.house_df.apply( lambda x: lon_lat_to_easting_northing(x['long'], x['lat'])[1], axis=1) @pn.depends('price_slider', 'rooms_slider', 'bathrooms_slider', 'pins', watch=False) def house_plot(self): if 'northing' not in self.house_df.columns: self.get_easting_northing() df_filtered = self.house_df[ (self.house_df['price'] <= self.price_slider[1]) & (self.house_df['price'] >= self.price_slider[0])] df_filtered = df_filtered[ (df_filtered['bedrooms'] <= self.rooms_slider[1]) & (df_filtered['bedrooms'] >= self.rooms_slider[0])] df_filtered = df_filtered[ (df_filtered['bathrooms'] <= self.rooms_slider[1]) & (df_filtered['bathrooms'] >= self.rooms_slider[0])] # Create a bokeh figure and source here: # range bounds supplied in web mercator coordinates xrange = (df_filtered['easting'].round(decimals=2).min(), df_filtered['easting'].round(decimals=2).max()) yrange = (df_filtered['northing'].round(decimals=2).min(), df_filtered['northing'].round(decimals=2).max()) df_source = ColumnDataSource(df_filtered) tools = [ResetTool(), PanTool(), WheelZoomTool()] p = figure(x_range=xrange, y_range=yrange, x_axis_type="mercator", y_axis_type="mercator", plot_width=600, plot_height=600, tools=tools) p.add_tile(OSM_tile_source) print(df_filtered.shape) circle_renderer = p.circle( x='easting', y='northing', fill_color='midnightblue', fill_alpha=0.95, line_color='dodgersblue', hover_fill_color='firebrick', line_alpha=0.91, source=df_source, size=10, # hover_line_color='black', line_width=0) tool_circle_hover = HoverTool(renderers=[circle_renderer], tooltips=TOOLTIPS) p.add_tools(tool_circle_hover) return p def filter_df(self): if 'northing' not in self.house_df.columns: self.get_easting_northing() display_df = self.house_df[ (self.house_df['price'] <= self.maximum_price) & (self.house_df['price'] >= self.minimum_price)] cols = [ 'lat', 'long', 'easting', 'northing', 'detail_url', 'key', 'mls_number', 'id', 'photo_url' ] for col in cols: if col in display_df.columns: display_df = display_df.drop(columns=col, axis=1) display_df['size'] = display_df['size'].apply( lambda x: x.split()[0] if x else -999).astype(float) display_df = display_df.set_index('address') return display_df[[ 'photo', 'price', 'DateSold', 'PriceLastSold', 'Assessment Price', 'bedrooms', 'bathrooms', 'size', 'lot_size', 'type', 'stories' ]] @pn.depends("stream", watch=False) def distance_df(self, x, y): lat = easting_northing_to_lon_lat(x, y)[1] long = easting_northing_to_lon_lat(x, y)[0] self.lat_longs.append(['enter name', lat, long]) df = pd.DataFrame(self.lat_longs, columns=['Name', 'Latitude', 'Longitude']).dropna().style.hide_index() return pn.widgets.Tabulator(df.data, pagination='remote', page_size=10, sizing_mode='scale_both', show_index=False) @pn.depends("stream", "pins") def location(self, x, y): if x and y: self.pins.append([x, y]) return self.house_plot def panel(self): result = bootstrap price_slider = pn.widgets.RangeSlider.from_param( self.param.price_slider, step=10000, format='0.0a') result.sidebar.append(price_slider) result.sidebar.append(self.param.rooms_slider) result.sidebar.append(self.param.bathrooms_slider) result.sidebar.append(self.param.type) result.sidebar.append(self.param.transit_time) image_format = r'<div> <img src="<%= value %>" height="70" alt="<%= value %>" width="70" style="float: left; margin: 0px 15px 15px 0px;" border="2" ></img> </div>' tabulator_formatters = { 'price': NumberFormatter(format='$0,0'), 'size': NumberFormatter(format='0,0 sqft'), 'photo': HTMLTemplateFormatter(template=image_format) } df_widget = pn.widgets.Tabulator(self.filter_df(), pagination='remote', page_size=10, formatters=tabulator_formatters, sizing_mode='scale_both') df_widget.add_filter(self.param.price_slider, 'price') df_widget.add_filter(self.param.rooms_slider, 'bedrooms') # df_pins = pn.widgets.Tabulator(self.distance_df(), pagination='remote', page_size=10, sizing_mode='scale_both') layout = pn.Row( pn.Card(pn.bind(self.location, x=self.stream.param.x, y=self.stream.param.y), title="Map", sizing_mode='stretch_height'), pn.Column( pn.Card(df_widget, title="Properties", sizing_mode='scale_both'))) result.sidebar.append( pn.Card(pn.bind(self.distance_df, x=self.stream.param.x, y=self.stream.param.y), title="Pins", sizing_mode='scale_both')) bootstrap.main.append(layout) bootstrap.main.append(pn.Card(self.details_area, title='Details')) return result
def test_get_soft_bounds(self): q = param.Range((1, 3), bounds=(0, 10), softbounds=(1, 9)) self.assertEqual(q.get_soft_bounds(), (1, 9))
class Q(param.Parameterized): q = param.Range(bounds=(0, 10))
class Q(param.Parameterized): q = param.Range(bounds=(0, 10), inclusive_bounds=(False, True))
class Test(param.Parameterized): num = param.Range()
class ufloat(param.Parameterized): # deployment properties d_depth = param.Number(500, bounds=(10, 4000), step=10, label='depth [m]', doc="Deployment depth [m]") d_T = param.Number(30, bounds=(1, 300), step=1, label='T [days]', doc="Deployment time length [days]") d_delta_rho = param.Number(5, bounds=(1, 30), step=.5, label='delta_rho [kg/m3]', doc="Deployment density delta [kg/m^3]") # hull properties #h_length = param.Number(0.8, bounds=(.1,2.), step=.02, # label='length [m]', doc="Hull length [m]") h_radius = param.Range(default=(1., 30.), bounds=(1., 50.), label='radius [cm]', doc="Hull radius [cm]") h_thickness = param.Number(.5, bounds=(.1, 2.), step=.1, label='thickness [cm]', doc="Hull thickness [cm]") h_cap_thickness = param.Number(.5, bounds=(.1, 3.), step=.1, label='end cap thickness [cm]', doc="End cap thickness [cm]") #h_density = param.Number(2700, bounds=(1000,3000), step=50., # label='density [kg/m3]', doc="Hull density [kg/m^3]") h_material = param.Selector(objects=list(_materials), label='size', doc="Material") # piston properties #p_length = param.Number(.2, bounds=(.1,1.), step=.01, # label='length',doc="Piston length [m]") p_lambda = param.Number(.5, bounds=(.1, 1.), step=.01, label='piston length normalized [1]', doc="Piston length normalized [1]") p_density = param.Number(2700, bounds=(1000, 3000), step=50., label='density [kg/m3]', doc="Piston density [kg/m^3]") p_efficiency = param.Number(.1, bounds=(.01, 1.), step=.01, label='efficiency [1]', doc="Piston energetic efficiency [1]") p_speed = param.Number(.1, bounds=(.01, 10), step=.01, label='speed [m/h]', doc="Piston speed [m/h]") # electronics properties e_volume = param.Number(250, bounds=(10, 1000), step=10, label='volume [cm3]', doc="Electronics volume [cm3]") e_mass = param.Number(.400, bounds=(.1, 1.), step=.05, label='mass [kg]', doc="Electronics mass [kg]") e_c = param.Number(.1, bounds=(.01, 1.), step=.01, label='conssumption [W]', doc="Electronics conssumption [W]") # battery selector b_cell = param.Selector( objects={v['long_name']: k for k, v in _batteries.items()}, label='size', doc="type") #b_lithium = param.Boolean(True, label='lithium', doc="Battery type") # Option buttons o_cap = param.Boolean(True, label='cap', doc="Cap") o_compressibility = param.Boolean(True, label='compressibility', doc="Compressibility") _params = [ 'd_depth', 'd_T', 'd_delta_rho', 'h_radius', 'h_thickness', 'h_cap_thickness', # 'h_length', 'h_material', #'h_density', 'p_lambda', 'p_density', 'p_efficiency', 'p_speed', #'p_length', 'e_volume', 'e_mass', 'e_c', 'b_cell', #'b_lithium', 'o_cap', 'o_compressibility', ] _scales = { 'd_depth': 1., 'd_T': 86400., 'd_delta_rho': 1., #'h_length': 1., 'h_radius': 1e-2, 'h_thickness': 1e-2, 'h_cap_thickness': 1e-2, #'h_density': 1, 'h_material': None, 'p_lambda': 1., #'p_length': 1., 'p_density': 1., 'p_efficiency': 1., 'p_speed': 1 / 3600., 'e_volume': 1e-6, 'e_mass': 1., 'e_c': 1., 'b_cell': None, #'b_lithium': None, 'o_cap': None, 'o_compressibility': None, } _mapping = {p[0]: p for p in _items} def get_part(self, item): assert item in self._params return self._mapping[item.split('_')[0]] def get_key_and_part(self, item): assert item in self._params item_split = item.split('_') return '_'.join(item_split[1:]), self._mapping[item_split[0]] def __init__(self, **params): super(ufloat, self).__init__(**params) # will mirror data in dicts: self.D = {i: {} for i in _items} self.D['total'] = {} # hull material #self.D['hull'].update(alu_6061) # constraints self.constraints = {} # init figures self.renderers = { v: {} for v in ['volume', 'length', 'mass', 'constraints', 'speed'] } _tools = 'pan,wheel_zoom,box_zoom,hover,crosshair,reset' TOOLTIPS = [ ("(radius, y)", "($x, $y)"), ] # for linked crosshair: # https://stackoverflow.com/questions/37965669/how-do-i-link-the-crosshairtool-in-bokeh-over-several-plots f_volume = figure( y_axis_label='[liters]', tools=_tools, tooltips=TOOLTIPS, ) f, r = f_volume, self.renderers['volume'] for i in ['hull', 'piston', 'battery', 'electronics', 'total']: r[i] = f.line([], [], color=_colors[i], line_width=3, legend_label=i) f.legend.background_fill_alpha = 0.1 # f_length = figure(y_axis_label='[cm]', tools=_tools, tooltips=TOOLTIPS, x_range=f_volume.x_range) f, r = f_length, self.renderers['length'] for i in ['hull', 'piston']: r[i + '_radius'] = f.line( [], [], color=_colors[i], line_width=2, legend_label=i + ' radius', ) r[i + '_length'] = f.line( [], [], color=_colors[i], line_width=4, legend_label=i + ' length', ) r['hull_radius_gamma'] = f.line([], [], color=_colors['hull'], line_width=3, line_dash='4 4', legend_label='h radius/Gamma^{1/2}') #f.xaxis.axis_label = 'hull radius [cm]' f.legend.background_fill_alpha = 0.1 # f_mass = figure(y_axis_label='[kg]', y_axis_location='right', tools=_tools, tooltips=TOOLTIPS, x_range=f_volume.x_range) f, r = f_mass, self.renderers['mass'] for i in ['battery', 'hull', 'total']: r[i] = f.line([], [], color=_colors[i], line_width=3, legend_label=i) f.legend.background_fill_alpha = 0.1 # f_constraints = figure( y_axis_label='[1]', y_axis_location='right', tools=_tools, tooltips=TOOLTIPS, x_range=f_volume.x_range, y_range=(0, 5), ) f, r = f_constraints, self.renderers['constraints'] r['stress'] = line(f, _colors['hull'], 'stress') r['buckling'] = line(f, _colors['hull'], 'buckling', line_dash='dashed') r['compressibility'] = line(f, 'green', '1/compressibility') r['piston_length'] = line(f, _colors['piston'], 'piston length') r['piston_radius'] = line(f, _colors['piston'], 'piston radius', line_dash='dashed') f.xaxis.axis_label = 'hull radius [cm]' f.add_layout( Span(location=1, dimension='width', line_color='black', line_width=1)) f.legend.background_fill_alpha = 0.1 # f_speed = figure( y_axis_label='[cm/s]', tools=_tools, tooltips=TOOLTIPS, x_range=f_volume.x_range, ) f, r = f_speed, self.renderers['speed'] r['maxspeed'] = line(f, 'black', 'max speed') f.legend.background_fill_alpha = 0.1 # self.figures = gridplot( [[f_volume, f_mass], [f_length, f_constraints], [f_speed, None]], plot_width=500, plot_height=300, ) # self.update() def _update_dict_params(self): # push and rescale data in central dict for p in self._params: _k, _p = self.get_key_and_part(p) _d = self.D[_p] if p == 'h_radius': _d['radius'] = np.linspace(self.h_radius[0], self.h_radius[1], 100) / 1e2 elif p == 'h_material': _d['material'] = self.h_material _d.update(_materials[self.h_material]) elif p == 'o_cap': _d['cap'] = int(self.o_cap) elif p == 'o_compressibility': _d['compressibility'] = int(self.o_compressibility) elif self._scales[p]: _d[_k] = getattr(self, p) * self._scales[p] def _update_battery(self): """ J/kg alcaline: 1.2V*1Ah=1.2Wh pour 136g lithium: 3.6V*9Ah=32.4Wh pour 96g """ b = {**_batteries[self.b_cell]} b['volume'] = np.pi * b['radius']**2 * b['length'] self.D['battery']['cell'] = b # density = b['weight'] / b['volume'] V_reference = 3.5 # volts V_ratio = b['voltage'] / V_reference # need to be verified edensity = (b['assumed_capacity'] * 3600 / V_ratio / b['weight']) self.D['battery']['density'] = density self.D['battery']['edensity'] = edensity def _update_mechanical_constraints(self, length=True): p = self.D['deployment']['pressure'] # stress e = self.D['hull']['thickness'] a = self.D['hull']['radius'] - e / 2. sigma = p * a / e * np.sqrt(1 + 1 / 4) # needs to be lower than self.D['hull']['Re'] # !! should checks that internal radius < thickness > 10 self.constraints['stress'] = (self.D['hull']['Re'], sigma) # buckling pressure E = self.D['hull']['E'] nu = self.D['hull']['nu'] t = e n = 2 # number of lobes l = self.D['hull']['length'] r = self.D['hull']['radius'] _r = (np.pi * r / (n * l))**2 q_func = lambda n: (E * t / r / (1 + _r / 2) * (1 / n**2 / (1 + 1 / _r)**2 + (n * t / r)**2 / 12 / (1 - nu**2) * (1 + _r)**2)) q_prime = np.maximum(q_func(2), q_func(3)) # needs to be larger than 1.2 p self.constraints['buckling'] = (q_prime, 1.2 * p) def _update_compressibilities(self): p = self.D['deployment']['depth'] * g * rho0 e = self.D['hull']['thickness'] a = self.D['hull']['radius'] - e / 2. E = self.D['hull']['E'] nu = self.D['hull']['nu'] # mechanical compressibility gamma = a / e / E * (5 / 2 - 2 * nu) # 1/Pa gamma = gamma * self.D['options'][ 'compressibility'] # set to 0 if option turned off self.D['hull']['mechanical_compressibility'] = gamma self.constraints['compressibility'] = (gamma_water, gamma) # thermal compressibility self.D['hull']['thermal_compressibility'] = 3 * self.D['hull']['alpha'] def _update_line(self, figure_key, item, data, x, scale): #_d = self.D[item][data_key] _d = data if isinstance(_d, float): _d = np.ones_like(x) * _d (self.renderers[figure_key][item].data_source.data.update({ 'x': x * 1e2, 'y': _d * scale, })) @param.depends(*_params, watch=True) def update(self): # update data containers self._update_dict_params() self._update_battery() d, h, p, b, e, o = (self.D[i] for i in _items) t = self.D['total'] # solve for volume first self._update_compressibilities() d['pressure'] = rho0 * g * d['depth'] # below may not be right for very compressible floats Gamma = abs(d['delta_rho'] / rho0 + (gamma_water - h['mechanical_compressibility']) * d['pressure']) ## lambda = l_piston/l sigma = (np.pi * Gamma / p['lambda'] * d['pressure'] * p['speed'] / p['efficiency']) caps_mass = 2. * np.pi * h['cap_thickness'] * h['radius']**2 * h[ 'density'] caps_mass = caps_mass * o['cap'] # turn off if option not set h['volume'] = ( caps_mass +d['T']*sigma/b['edensity']*h['radius']**2 +d['T']*e['c']/b['edensity'] + e['mass'] ) \ /( rho0 - 2*h['thickness']/h['radius']*h['density'] - p['density'] * Gamma ) # negative volumes are flagged h['volume'][np.where(h['volume'] < 0)] = np.NaN # propagate volume h['length'] = h['volume'] / (np.pi * h['radius']**2) # piston radius p['length'] = p['lambda'] * h['length'] # if lambda is used p['radius'] = np.sqrt(h['length'] / p['length'] * Gamma) * h['radius'] # piston conssumption p['c'] = (rho0 * g * d['depth'] * np.pi * p['radius']**2 * p['speed'] / p['efficiency']) # battery mass b['mass'] = d['T'] * (e['c'] + p['c']) / b['edensity'] b['volume'] = b['mass'] / b['density'] # other parameters h['mass'] = (2. * np.pi * h['radius'] * h['thickness'] * h['length'] * h['density'] + caps_mass) p['volume'] = np.pi * p['radius']**2 * p['length'] p['mass'] = p['density'] * p['volume'] t['mass'] = h['mass'] + p['mass'] + b['mass'] + e['mass'] t['volume'] = p['volume'] + b['volume'] + e['volume'] # https://doi.org/10.1016/j.earscirev.2014.06.001 Cd = 1. max_speed = np.sqrt(2 * g * abs(Gamma) * t['mass'] / (rho0 * np.pi * h['radius']**2 * Cd)) # update constraints self.constraints['piston_length'] = (h['length'], p['length']) self.constraints['piston_radius'] = (h['radius'], p['radius']) self._update_mechanical_constraints() # update plots for _p in ['hull', 'piston', 'battery', 'electronics', 'total']: self._update_line('volume', _p, self.D[_p]['volume'], h['radius'], 1e3) # for _p in [ 'hull', 'piston', ]: self._update_line('length', _p + '_radius', self.D[_p]['radius'], h['radius'], 1e2) self._update_line('length', _p + '_length', self.D[_p]['length'], h['radius'], 1e2) (self.renderers['length']['hull_radius_gamma'].data_source.data.update( { 'x': h['radius'] * 1e2, 'y': np.sqrt(Gamma) * h['radius'] * 1e2 })) # manually adjust y range #_ylim = (0., 3.*np.nanmax(p['radius'])*1e2) _ylim = (0., min(100, np.nanmax(h['length']) * 1e2)) self.figures.children[1].children[2][0].y_range = Range1d(*_ylim) # for _p in ['hull', 'battery', 'total']: self._update_line('mass', _p, self.D[_p]['mass'], h['radius'], 1) # r = self.renderers['constraints'] for k, v in self.constraints.items(): (r[k].data_source.data.update({ 'x': h['radius'] * 1e2, 'y': v[0] / v[1] })) # (self.renderers['speed']['maxspeed'].data_source.data.update({ 'x': h['radius'] * 1e2, 'y': max_speed * 1e2 })) @param.depends(*_params) def variables_view(self): return self.figures def panel(self): w = self._widgets_panel() return pn.Column( pn.Row(w[0], w[1], w[2]), pn.Row(w[3], w[4], w[5]), self.variables_view, ) def _widgets_panel(self): return ( pn.Column('### {}'.format('Deployment'), pn.panel(self.param.d_depth), pn.panel(self.param.d_T), pn.panel(self.param.d_delta_rho)), pn.Column( '### {}'.format('Hull'), pn.panel(self.param.h_radius), #pn.panel(self.param.h_length), pn.panel(self.param.h_thickness), pn.panel(self.param.h_cap_thickness), #pn.panel(self.param.h_density), pn.panel(self.param.h_material), ), pn.Column( '### {}'.format('Piston'), #pn.panel(self.param.p_length), pn.panel(self.param.p_lambda), pn.panel(self.param.p_density), pn.panel(self.param.p_efficiency), pn.panel(self.param.p_speed), ), pn.Column( '### {}'.format('Electronics'), pn.panel(self.param.e_volume), pn.panel(self.param.e_mass), pn.panel(self.param.e_c), ), pn.Column( '### {}'.format('Battery'), pn.panel(self.param.b_cell), #pn.panel(self.param.b_lithium), ), pn.Column( '### {}'.format('Options'), pn.panel(self.param.o_cap), pn.panel(self.param.o_compressibility), ), )
class Test(param.Parameterized): a = param.Range(default=(0.1, 0.5), bounds=(0, 1.1))
class DisplayRangeOpts(param.Parameterized): color_range = param.Range(default=(0.0, 10), bounds=(-10, 20))