class HorizonBuilder(LineBuilder): """Produces glyph renderers representing a horizon chart from many input types. The builder handles ingesting the data, deriving settings when not provided, building the renderers, then setting ranges, and modifying the chart as needed. """ # class configuration glyph = HorizonGlyph default_attributes = { 'color': ColorAttr(sort=False), 'series': IdAttr(sort=False) } # primary input properties pos_color = Color("#006400", help=""" The color of the positive folds. (default: "#006400") """) neg_color = Color("#6495ed", help=""" The color of the negative folds. (default: "#6495ed") """) num_folds = Int(3, help=""" The number of folds stacked on top of each other. (default: 3) """) flip_neg = Bool(default=True, help="""When True, the negative values will be plotted as their absolute value, then their individual axes is flipped. If False, then the negative values will still be taken as their absolute value, but the base of their shape will start from the same origin as the positive values. """) # derived properties series_count = Int(help="""Count of the unique series names.""") bins = List(Float, help="""The binedges calculated from the number of folds, and the maximum value of the entire source data.""") series_column = String( help="""The column that contains the series names.""") fold_height = Float(help="""The size of the bin.""") def setup(self): super(HorizonBuilder, self).setup() # collect series names and columns selected to color by if self.attributes['series'].columns is None: self.series_column = self.attributes['color'].columns[0] else: self.series_column = self.attributes['series'].columns[0] if len(self.series_names) == 0: self.set_series(self.series_column) self.series_count = len(self.series_names) def process_data(self): super(HorizonBuilder, self).process_data() # calculate group attributes, useful for each horizon glyph self.fold_height = max(self.y.max, abs(self.y.min)) / self.num_folds self.bins = [ bin_id * self.fold_height for bin_id in range(self.num_folds + 1) ] # manually set attributes to have constant color ds = ColumnDataSource(self._data.df) self.attributes['series'].setup(data=ds, columns=self.series_column) self.attributes['color'].setup(data=ds, columns=self.pos_color) def set_ranges(self): super(HorizonBuilder, self).set_ranges() self.x_range = DataRange1d(range_padding=0) self.y_range.start = 0 self.y_range.end = self.y.max
class HistogramBuilder(BarBuilder): """Generates one to many histograms with unique attributes. The HistogramBuilder is responsible for producing a chart containing one to many histograms from table-like inputs. """ bins = Either(List(Float), Int, default=None, help=""" If bins is an int, it defines the number of equal-width bins in the given range. If bins is a sequence, it defines the bin edges, including the rightmost edge, allowing for non-uniform bin widths. (default: None, use Freedman-Diaconis rule) """) density = Bool(False, help=""" Whether to normalize the histogram. If True, the result is the value of the probability *density* function at the bin, normalized such that the *integral* over the range is 1. If False, the result will contain the number of samples in each bin. For more info check :class:`~bkcharts.glyphs.HistogramGlyph` documentation. (default: False) """) glyph = HistogramGlyph def setup(self): super(HistogramBuilder, self).setup() # when we create multiple histograms, we set the alpha to support overlap if self.attributes['color'].columns is not None: self.fill_alpha = 0.6 def get_extra_args(self): """Build kwargs that are unique to the histogram builder.""" return dict(bins=self.bins, density=self.density) def _apply_inferred_index(self): # ignore this for now, unless histogram later adds handling of indexed data pass def set_ranges(self): """Push the Bar data into the ColumnDataSource and calculate the proper ranges. """ x_max = max([comp_glyph.x_max for comp_glyph in self.comp_glyphs]) x_min = min([comp_glyph.x_min for comp_glyph in self.comp_glyphs]) y_max = max([comp_glyph.y_max for comp_glyph in self.comp_glyphs]) y_min = min([comp_glyph.y_min for comp_glyph in self.comp_glyphs]) x_buffer = ((x_max + x_min)/2.0)*0.05 self.x_range = Range1d(start=x_min - x_buffer, end=x_max + x_buffer) self.y_range = Range1d(start=y_min, end=y_max * 1.1)
class BinnedStat(Stat): """ Base class for shared functionality accross bins and aggregates dimensions for plotting. """ bin_stat = Instance(BinStats, help=""" A mapping between each dimension and associated binning calculations. """) bins = List(Instance(Bin), help=""" A list of the `Bin` instances that were produced as result of the inputs. Iterating over `Bins` will iterate over this list. Each `Bin` can be inspected for metadata about the bin and the values associated with it. """) stat = Instance(Stat, default=Count(), help=""" The statistical operation to be used on the values in each bin. """) bin_column = String() centers_column = String() aggregate = Bool(default=True) bin_values = Bool(default=False) bin_width = Float() def __init__(self, values=None, column=None, bins=None, stat='count', source=None, **properties): if isinstance(stat, str): stat = stats[stat]() properties['column'] = column or 'vals' properties['stat'] = stat properties['values'] = values properties['source'] = source self._bins = bins super(BinnedStat, self).__init__(**properties) def _get_stat(self): stat_kwargs = {} if self.source is not None: stat_kwargs['source'] = self.source stat_kwargs['column'] = self.column elif self.values is not None: stat_kwargs['values'] = self.values stat_kwargs['bins'] = self._bins return BinStats(**stat_kwargs) def update(self): self.bin_stat = self._get_stat() self.bin_stat.update()
class HorizonGlyph(AreaGlyph): num_folds = Int(default=3, help="""The count of times the data is overlapped.""") series = Int(default=0, help="""The id of the series as the order it will appear, starting from 0.""") series_count = Int() fold_height = Float(help="""The height of one fold.""") bins = List(Float, help="""The binedges calculated from the number of folds, and the maximum value of the entire source data.""") graph_ratio = Float( help="""Scales heights of each series based on number of folds and the number of total series being plotted. """) pos_color = Color("#006400", help="""The color used for positive values.""") neg_color = Color("#6495ed", help="""The color used for negative values.""") flip_neg = Bool(default=True, help="""When True, the negative values will be plotted as their absolute value, then their individual axes is flipped. If False, then the negative values will still be taken as their absolute value, but the base of their shape will start from the same origin as the positive values. """) def __init__(self, bins=None, **kwargs): # fill alpha depends on how many folds will be layered kwargs['fill_alpha'] = 1.0 / kwargs['num_folds'] if bins is not None: kwargs['bins'] = bins # each series is shifted up to a synthetic y-axis kwargs['base'] = kwargs['series'] * max( bins) / kwargs['series_count'] kwargs['graph_ratio'] = float(kwargs['num_folds']) / float( kwargs['series_count']) super(HorizonGlyph, self).__init__(**kwargs) def build_source(self): data = {} # Build columns for the positive values pos_y = self.y.copy() pos_y[pos_y < 0] = 0 xs, ys = self._build_dims(self.x, pos_y) # list of positive colors and alphas colors = [self.pos_color] * len(ys) alphas = [(bin_idx * self.fill_alpha) for bin_idx in range(0, len(self.bins))] # If we have negative values at all, add the values for those as well if self.y.min() < 0: neg_y = self.y.copy() neg_y[neg_y > 0] = 0 neg_y = abs(neg_y) neg_xs, neg_ys = self._build_dims(self.x, neg_y, self.flip_neg) xs += neg_xs ys += neg_ys colors += ([self.neg_color] * len(neg_ys)) alphas += alphas # create clipped representation of each band data['x_values'] = xs data['y_values'] = ys data['fill_color'] = colors data['fill_alpha'] = colors data['line_color'] = colors return data def _build_dims(self, x, y, flip=False): """ Creates values needed to plot each fold of the horizon glyph. Bins the data based on the binning passed into the glyph, then copies and clips the values for each bin. Args: x (`pandas.Series`): array of x values y (`pandas.Series`): array of y values flip (bool): whether to flip values, used when handling negative values Returns: tuple(list(`numpy.ndarray`), list(`numpy.ndarray`)): returns a list of arrays for the x values and list of arrays for the y values. The data has been folded and transformed so the patches glyph presents the data in a way that looks like an area chart. """ # assign bins to each y value bin_idx = pd.cut(y, bins=self.bins, labels=False, include_lowest=True) xs, ys = [], [] for idx, bin in enumerate(self.bins[0:-1]): # subtract off values associated with lower bins, to get into this bin temp_vals = y.copy() - (idx * self.fold_height) # clip the values between the fold range and zero temp_vals[bin_idx > idx] = self.fold_height * self.graph_ratio temp_vals[bin_idx < idx] = 0 temp_vals[bin_idx == idx] = self.graph_ratio * temp_vals[bin_idx == idx] # if flipping, we must start the values from the top of each fold's range if flip: temp_vals = (self.fold_height * self.graph_ratio) - temp_vals base = self.base + (self.fold_height * self.graph_ratio) else: base = self.base # shift values up based on index of series temp_vals += self.base val_idx = temp_vals > 0 if pd.Series.any(val_idx): ys.append(temp_vals) xs.append(x) # transform clipped data so it always starts and ends at its base value if len(ys) > 0: xs, ys = map( list, zip(*[ generate_patch_base(x, y, base=base) for x, y in zip(xs, ys) ])) return xs, ys def build_renderers(self): # parse all series. We exclude the first attr as it's the x values # added for the index glyph = Patches(xs='x_values', ys='y_values', fill_alpha=self.fill_alpha, fill_color='fill_color', line_color='line_color') renderer = GlyphRenderer(data_source=self.source, glyph=glyph) yield renderer
class DataTabulator(HTMLBox): """A Bokeh Model that enables easy use of Tabulator tables See http://tabulator.info/ """ configuration = Dict(String, Any) columns = List(Instance(TableColumn), help=""" The list of child column widgets. """) download = Bool(default=False) editable = Bool(default=True) filename = String(default="table.csv") follow = Bool(True) frozen_rows = List(Int) groupby = List(String) hidden_columns = List(String) layout = Enum('fit_data', 'fit_data_fill', 'fit_data_stretch', 'fit_data_table', 'fit_columns', default="fit_data") source = Instance(ColumnDataSource) styles = Dict(Int, Dict(Int, List(String))) pagination = Nullable(String) page = Nullable(Int) page_size = Int() max_page = Int() sorters = List(Dict(String, String)) theme = Enum(*TABULATOR_THEMES, default="simple") theme_url = String(default=THEME_URL) __css__ = [THEME_URL + 'tabulator_simple.min.css'] __javascript__ = [JS_SRC, MOMENT_SRC] __js_require__ = { 'paths': { 'tabulator': JS_SRC[:-3] }, 'exports': { 'tabulator': 'Tabulator' } } __js_skip__ = {'tabulator': __javascript__}
class BarBuilder(Builder): """This is the Bar builder and it is in charge of plotting Bar chart (grouped and stacked) in an easy and intuitive way. Essentially, it utilizes a standardized way to ingest the data, make the proper calculations and generate renderers. The renderers reference the transformed data, which represent the groups of data that were derived from the inputs. We additionally make calculations for the ranges. The x_range is categorical, and is made either from the label argument or from the `pandas.DataFrame.index`. The y_range can be supplied as the parameter continuous_range, or will be calculated as a linear range (Range1d) based on the supplied values. The bar builder is and can be further used as a base class for other builders that might also be performing some aggregation across derived groups of data. """ # ToDo: add label back as a discrete dimension values = Dimension('values') dimensions = ['values'] # req_dimensions = [['values']] default_attributes = {'label': CatAttr(), 'color': ColorAttr(), 'line_color': ColorAttr(default='white'), 'stack': CatAttr(), 'group': CatAttr()} agg = Enum(Aggregation, default='sum') max_height = Float(1.0) min_height = Float(0.0) bar_width = Float(default=0.8) fill_alpha = Float(default=0.8) glyph = BarGlyph comp_glyph_types = Override(default=[BarGlyph]) label_attributes = ['stack', 'group'] label_only = Bool(False) values_only = Bool(False) _perform_stack = False _perform_group = False def setup(self): if self.attributes['color'].columns is None: if self.attributes['stack'].columns is not None: self.attributes['color'].setup(columns=self.attributes['stack'].columns) if self.attributes['group'].columns is not None: self.attributes['color'].setup(columns=self.attributes['group'].columns) if self.attributes['stack'].columns is not None: self._perform_stack = True if self.attributes['group'].columns is not None: self._perform_group = True # ToDo: perform aggregation validation # Not given values kw, so using only categorical data if self.values.dtype.name == 'object' and len(self.attribute_columns) == 0: # agg must be count self.agg = 'count' self.attributes['label'].set_columns(self.values.selection) else: pass self._apply_inferred_index() if self.xlabel is None: if self.attributes['label'].columns is not None: self.xlabel = str( ', '.join(self.attributes['label'].columns).title()).title() else: self.xlabel = self.values.selection if self.ylabel is None: if not self.label_only: self.ylabel = '%s( %s )' % ( self.agg.title(), str(self.values.selection).title()) else: self.ylabel = '%s( %s )' % ( self.agg.title(), ', '.join(self.attributes['label'].columns).title()) def _apply_inferred_index(self): """Configure chart when labels are provided as index instead of as kwarg.""" # try to infer grouping vs stacking labels if (self.attributes['label'].columns is None and self.values.selection is not None): if self.attributes['stack'].columns is not None: special_column = 'unity' else: special_column = 'index' self._data['label'] = special_column self.attributes['label'].setup(data=ColumnDataSource(self._data.df), columns=special_column) self.xlabel = '' def set_ranges(self): """Push the Bar data into the ColumnDataSource and calculate the proper ranges. """ x_items = self.attributes['label'].items if x_items is None: x_items = '' x_labels = [] # Items are identified by tuples. If the tuple has a single value, # we unpack it for item in x_items: item = self._get_label(item) x_labels.append(str(item)) self.x_range = FactorRange(factors=x_labels) y_shift = abs(0.1 * ((self.min_height + self.max_height) / 2)) if self.min_height < 0: start = self.min_height - y_shift else: start = 0.0 if self.max_height > 0: end = self.max_height + y_shift else: end = 0.0 self.y_range = Range1d(start=start, end=end) def get_extra_args(self): if self.__class__ is not BarBuilder: attrs = self.properties(with_bases=False) return {attr: getattr(self, attr) for attr in attrs} else: return {}
class AreaGlyph(LineGlyph): # ToDo: should these be added to composite glyph? stack = Bool(default=False) dodge = Bool(default=False) base = Float(default=0.0, help="""Lower bound of area.""") def __init__(self, **kwargs): line_color = kwargs.get('line_color', None) fill_color = kwargs.get('fill_color', None) color = kwargs.get('color', None) if color is not None: # apply color to line and fill kwargs['fill_color'] = color kwargs['line_color'] = color elif line_color is not None and fill_color is None: # apply line color to fill color by default kwargs['fill_color'] = line_color super(AreaGlyph, self).__init__(**kwargs) self.setup() def build_source(self): data = super(AreaGlyph, self).build_source() x0, y0 = generate_patch_base(data['x_values'], data['y_values']) data['x_values'] = [x0] data['y_values'] = [y0] return data def build_renderers(self): # parse all series. We exclude the first attr as it's the x values # added for the index glyph = Patches(xs='x_values', ys='y_values', fill_alpha=self.fill_alpha, fill_color=self.fill_color, line_color=self.line_color) renderer = GlyphRenderer(data_source=self.source, glyph=glyph) yield renderer def __stack__(self, glyphs): # ToDo: need to handle case of non-aligned indices, see pandas concat # ToDo: need to address how to aggregate on an index when required if self.stack: # build a list of series areas = [] for glyph in glyphs: areas.append( pd.Series(glyph.source.data['y_values'][0], index=glyph.source.data['x_values'][0])) # concat the list of indexed y values into dataframe df = pd.concat(areas, axis=1) # calculate stacked values along the rows stacked_df = df.cumsum(axis=1) # lower bounds of each area series are diff between stacked and orig values lower_bounds = stacked_df - df # reverse the df so the patch is drawn in correct order lower_bounds = lower_bounds.iloc[::-1] # concat the upper and lower bounds together stacked_df = pd.concat([stacked_df, lower_bounds]) # update the data in the glyphs for i, glyph in enumerate(glyphs): glyph.source.data['x_values'] = [stacked_df.index.values] glyph.source.data['y_values'] = [stacked_df.ix[:, i].values] def get_nested_extent(self, col, func): return [getattr(arr, func)() for arr in self.source.data[col]] @property def x_max(self): return max(self.get_nested_extent('x_values', 'max')) @property def x_min(self): return min(self.get_nested_extent('x_values', 'min')) @property def y_max(self): return max(self.get_nested_extent('y_values', 'max')) @property def y_min(self): return min(self.get_nested_extent('y_values', 'min'))
class DatetimePicker(InputWidget): ''' Calendar-based date picker widget. ''' value = String(help=""" The initial or picked date. """) min_date = Nullable(Either(Date, Datetime), help=""" Optional earliest allowable date. """) max_date = Nullable(Either(Date, Datetime), help=""" Optional latest allowable date. """) disabled_dates = List(Either(Date, Datetime, Tuple(Date, Date), Tuple(Datetime, Datetime)), default=[], help=""" A list of dates of ``(start, end)`` date ranges to make unavailable for selection. All other dates will be avalable. .. note:: Only one of ``disabled_dates`` and ``enabled_dates`` should be specified. """) enabled_dates = List(Either(Date, Datetime, Tuple(Date, Date), Tuple(Datetime, Datetime)), default=[], help=""" A list of dates of ``(start, end)`` date ranges to make available for selection. All other dates will be unavailable. .. note:: Only one of ``disabled_dates`` and ``enabled_dates`` should be specified. """) position = Enum(CalendarPosition, default="auto", help=""" Where the calendar is rendered relative to the input when ``inline`` is False. """) inline = Bool(default=False, help=""" Whether the calendar sholud be displayed inline. """) enable_time = Bool(default=True) enable_seconds = Bool(default=True) military_time = Bool(default=True) date_format = String("Y-m-d H:i:S") mode = String(default="single", help=""" Should either be "single" or "range".""")
class ChordBuilder(Builder): """ This is the Chord builder and it is in charge of plotting Chord graphs in an easy and intuitive way. Essentially, we provide a way to ingest the data, make the proper calculations and push the references into a source object. We additionally make calculations for the ranges. And finally add the needed glyphs (markers) taking the references from the source. """ default_attributes = { 'color': ColorAttr(), 'marker': MarkerAttr(), 'stack': CatAttr() } dimensions = ['values'] values = Dimension('values') arcs_data = Instance(ColumnDataSource) text_data = Instance(ColumnDataSource) connection_data = Instance(ColumnDataSource) origin = String() destination = String() value = Any() square_matrix = Bool() label = Seq(Any()) matrix = Array(Array(Either(Float(), Int()))) def set_ranges(self): rng = 1.1 if not self.label else 1.8 self.x_range = Range1d(-rng, rng) self.y_range = Range1d(-rng, rng) def setup(self): # Process only if not a square_matrix if not self.square_matrix: source = self.values._data[self.origin] target = self.values._data[self.destination] union = source.append(target).unique() N = union.shape[0] m = pd.DataFrame(np.zeros((N, N)), columns=union, index=union) if not self.label: self.label = list(union) if self.value is None: for _, row in self.values._data.iterrows(): m[row[self.origin]][row[self.destination]] += 1 self.matrix = m.get_values() if self.value is not None: if isinstance(self.value, int) or isinstance( self.value, float): for _, row in self.values._data.iterrows(): m[row[self.origin]][row[self.destination]] = self.value self.matrix = m.get_values() elif isinstance(self.value, str): for _, row in self.values._data.iterrows(): m[row[self.origin]][row[self.destination]] = row[ self.value] self.matrix = m.get_values().T else: # It's already a square matrix self.matrix = self._data.df.get_values() if self.label: assert len(self.label) == self.matrix.shape[0] def process_data(self): weights_of_areas = (self.matrix.sum(axis=0) + self.matrix.sum(axis=1)) - self.matrix.diagonal() areas_in_radians = (weights_of_areas / weights_of_areas.sum()) * (2 * pi) # We add a zero in the begging for the cumulative sum points = np.zeros((areas_in_radians.shape[0] + 1)) points[1:] = areas_in_radians points = points.cumsum() colors = [ color_in_equal_space(area / areas_in_radians.shape[0]) for area in range(areas_in_radians.shape[0]) ] arcs_data = pd.DataFrame({ 'start_angle': points[:-1], 'end_angle': points[1:], 'line_color': colors }) self.arcs_data = ColumnDataSource(arcs_data) # Text if self.label: text_radius = 1.1 angles = (points[:-1] + points[1:]) / 2.0 text_positions = pd.DataFrame({ 'angles': angles, 'text_x': np.cos(angles) * text_radius, 'text_y': np.sin(angles) * text_radius, 'text': list(self.label) }) self.text_data = ColumnDataSource(text_positions) # Lines all_areas = [] for i in range(areas_in_radians.shape[0]): all_areas.append( Area(weights_of_areas[i], points[:-1][i], points[1:][i])) all_connections = [] for j, region1 in enumerate(self.matrix): # Get the connections origin region source = all_areas[j] color = colors[j] weight = weights_of_areas[j] for k, region2 in enumerate(region1): # Get the connection destination region target = all_areas[k] for _ in range(int(region2)): p1 = source.free_points.pop() p2 = target.free_points.pop() # Get both regions free points and create a connection with the data all_connections.append(p1 + p2 + [color, weight]) connections_df = pd.DataFrame(all_connections, dtype=str) connections_df.columns = [ "start_x", "start_y", "end_x", "end_y", "colors", "weight" ] connections_df["cx0"] = connections_df.start_x.astype("float64") / 2 connections_df["cy0"] = connections_df.start_y.astype("float64") / 2 connections_df["cx1"] = connections_df.end_x.astype("float64") / 2 connections_df["cy1"] = connections_df.end_y.astype("float64") / 2 connections_df.weight = ( connections_df.weight.astype("float64") / connections_df.weight.astype("float64").sum()) * 3000 self.connection_data = ColumnDataSource(connections_df) def yield_renderers(self): """Use the marker glyphs to display the arcs and beziers. Takes reference points from data loaded at the ColumnDataSource. """ beziers = Bezier(x0='start_x', y0='start_y', x1='end_x', y1='end_y', cx0='cx0', cy0='cy0', cx1='cx1', cy1='cy1', line_alpha='weight', line_color='colors') yield GlyphRenderer(data_source=self.connection_data, glyph=beziers) arcs = Arc(x=0, y=0, radius=1, line_width=10, start_angle='start_angle', end_angle='end_angle', line_color='line_color') yield GlyphRenderer(data_source=self.arcs_data, glyph=arcs) if self.label: text_props = { "text_color": "#000000", "text_font_size": "8pt", "text_align": "left", "text_baseline": "middle" } labels = Text(x='text_x', y='text_y', text='text', angle='angles', **text_props) yield GlyphRenderer(data_source=self.text_data, glyph=labels)
class Bins(Stat): """Bins and aggregates dimensions for plotting. Takes the inputs and produces a list of bins that can be iterated over and inspected for their metadata. The bins provide easy access to consistent labeling, bounds, and values. """ bin_stat = Instance(BinStats, help=""" A mapping between each dimension and associated binning calculations. """) bins = List(Instance(Bin), help=""" A list of the `Bin` instances that were produced as result of the inputs. Iterating over `Bins` will iterate over this list. Each `Bin` can be inspected for metadata about the bin and the values associated with it. """) stat = Instance(Stat, default=Count(), help=""" The statistical operation to be used on the values in each bin. """) bin_count = Int(help=""" An optional list of the number of bins to use for each dimension. If a single value is provided, then the same number of bins will be used for each. """) bin_column = String() centers_column = String() aggregate = Bool(default=True) bin_values = Bool(default=False) bin_width = Float() def __init__(self, values=None, column=None, bins=None, stat='count', source=None, **properties): if isinstance(stat, str): stat = stats[stat]() properties['column'] = column or 'vals' properties['bins'] = bins properties['stat'] = stat properties['values'] = values properties['source'] = source super(Bins, self).__init__(**properties) def _get_stat(self): stat_kwargs = {} if self.source is not None: stat_kwargs['source'] = self.source stat_kwargs['column'] = self.column elif self.values is not None: stat_kwargs['values'] = self.values stat_kwargs['bin_count'] = self.bin_count return BinStats(**stat_kwargs) def update(self): self.bin_stat = self._get_stat() self.bin_stat.update() def calculate(self): bin_str = '_bin' self.bin_column = self.column + bin_str bin_models = [] binned, bin_bounds = pd.cut(self.bin_stat.get_data(), self.bin_stat.bin_count, retbins=True, include_lowest=True, precision=0) self.bin_width = np.round(bin_bounds[2] - bin_bounds[1], 1) if self.source is not None: # add bin column to data source self.source.add(binned.tolist(), name=self.bin_column) df = self.source.to_df() else: df = pd.DataFrame({ self.column: self.values, self.bin_column: binned }) for name, group in df.groupby(self.bin_column): bin_models.append( Bin(bin_label=name, values=group[self.column], stat=self.stat)) self.bins = bin_models centers = binned.copy() centers = centers.astype(str) for bin in self.bins: centers[binned == bin.label] = bin.center self.centers_column = self.column + '_center' if self.source is not None: self.source.add(centers.tolist(), name=self.centers_column) else: df[self.centers_column] = centers def __getitem__(self, item): return self.bins[item] def apply(self, data): self.set_data(data.source) return self.source.to_df() def sort(self, ascending=True): if self.bins is not None: self.bins = list( sorted(self.bins, key=lambda x: x.center, reverse=~ascending))
class AcePlot(HTMLBox): """ A Bokeh model that wraps around a Ace editor and renders it inside a Bokeh plot. """ __javascript_raw__ = [ 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.11/ace.js', 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.11/ext-language_tools.js', 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.11/ext-modelist.js' ] __tarball__ = { 'tar': 'https://registry.npmjs.org/ace-builds/-/ace-builds-1.4.11.tgz', 'src': 'package/src-min/', 'dest': 'ajax/libs/1.4.11', 'exclude': ['snippets'] } @classproperty def __javascript__(cls): return bundled_files(cls) @classproperty def __js_skip__(cls): return {'ace': cls.__javascript__} __js_require__ = { 'paths': { ('ace', ('ace/ace', 'ace/ext-language_tools')): '//cdnjs.cloudflare.com/ajax/libs/ace/1.4.7' }, 'exports': { 'ace': 'ace' }, 'shim': { 'ace/ext-language_tools': { 'deps': ["ace/ace"] }, 'ace/ext-modelist': { 'deps': ["ace/ace"] } } } code = String() theme = Enum(ace_themes, default='chrome') filename = String() language = String() annotations = List(Dict(String, Any), default=[]) readonly = Bool(default=False) print_margin = Bool(default=False) height = Override(default=300) width = Override(default=300)
class HistogramGlyph(AggregateGlyph): """Depicts the distribution of values using rectangles created by binning. The histogram represents a distribution, so will likely include other options for displaying it, such as KDE and cumulative density. """ # derived models bins = Instance(BinnedStat, help="""A stat used to calculate the bins. The bins stat includes attributes about each composite bin.""") bars = List(Instance(BarGlyph), help="""The histogram is comprised of many BarGlyphs that are derived from the values.""") density = Bool(False, help=""" Whether to normalize the histogram. If True, the result is the value of the probability *density* function at the bin, normalized such that the *integral* over the range is 1. If False, the result will contain the number of samples in each bin. For more info check :class:`~bkcharts.stats.Histogram` documentation. (default: False) """) def __init__(self, values, label=None, color=None, bins=None, **kwargs): if label is not None: kwargs['label'] = label kwargs['values'] = values if color is not None: kwargs['color'] = color # remove width, since this is handled automatically kwargs.pop('width', None) # keep original bins setting private since it just needs to be # delegated to the Histogram stat self._bins = bins super(HistogramGlyph, self).__init__(**kwargs) self.setup() def _set_sources(self): # No need to set sources, since composite glyphs handle this pass def build_source(self): # No need to build source, since composite glyphs handle this return None def build_renderers(self): """Yield a bar glyph for each bin.""" # TODO(fpliger): We should expose the bin stat class so we could let # users specify other bins other the Histogram Stat self.bins = Histogram(values=self.values, bins=self._bins, density=self.density) bars = [] for bin in self.bins.bins: bars.append( BarGlyph(label=bin.label[0], x_label=bin.center, values=bin.values, color=self.color, fill_alpha=self.fill_alpha, agg=bin.stat, width=bin.width)) # provide access to bars as children for bounds properties self.bars = self.children = bars for comp_glyph in self.bars: for renderer in comp_glyph.renderers: yield renderer @property def y_min(self): return 0.0
class ExtendedColumn(Column): aria_hidden = Bool(False)
class CustomRow(Box): __implementation__ = "customRow.ts" hide = Bool(False)