def __init__(self, title=None, title_level=3, title_wrap=False, width=None, height=None, inherit_cfg=True, styles=None, classes=(), anchor=None, **kwargs): self._settings = Cfg( title=title, title_level=title_level, title_wrap=title_wrap, cascading_cfg=Cfg(**kwargs).override(styles or Cfg()), default_cfg=Cfg(), inherit_cfg=inherit_cfg, width=width, height=height, classes=["pybloqs"] + ([classes] if isinstance(classes, str) else list(classes))) # Anchor should not be inherited, so keep outside of Cfg self._anchor = anchor self._id = uuid.uuid4().hex
def _parse_args(args, allowed_cfg_types=(Cfg, )): """ Parse the supplied argument list into a configuration object. :param args: Argument list. :param allowed_cfg_types: The listed configuration object types will be allowed. :return: Tuple of (chart configuration object, plot configuration object OR None) """ plot_cfg = None configs = Cfg() for arg in args: # Construct an instance in case the config object is passed in as a type if isinstance(arg, type): arg = arg() if isinstance(arg, _PlotOpts): if plot_cfg: plot_cfg = plot_cfg.inherit(arg) else: plot_cfg = arg elif isinstance(arg, allowed_cfg_types): configs = configs.inherit(arg) else: raise ValueError( "%s is not recognized as a plot or chart configuration object" % arg) return configs, plot_cfg
def render_html(self, pretty=True, static_output=False, header_block=None, footer_block=None, pdf_page_size="A4"): """Returns html output of the block :param pretty: Toggles pretty printing of the resulting HTML. Not applicable for non-HTML output. :return html-code of the block """ # Render the contents html = root("html", doctype="html") head = append_to(html, "head") head = append_to(head, "meta", charset='utf-8') body = append_to(html, "body") # Make sure that the main style sheet is always included resource_deps = DependencyTracker(default_css_main) if header_block is not None: header_block._write_block(body, Cfg(), id_generator(), resource_deps=resource_deps, static_output=static_output) self._write_block(body, Cfg(), id_generator(), resource_deps=resource_deps, static_output=static_output) if footer_block is not None: footer_block._write_block(body, Cfg(), id_generator(), resource_deps=resource_deps, static_output=static_output) script_inflate.write(head) script_block_core.write(head) if static_output: # Add the load wait poller if there are any JS resources js_elem(body, "var loadWaitPoller=runWaitPoller();") # Write out resources for res in resource_deps: res.write(head) # Render the whole document (the parent of the html tag) content = render(html.parent, pretty=pretty) return content
def _set_chart_defaults(chart_cfg, chart_cls): if chart_cls == "StockChart": chart_cfg = chart_cfg.inherit_many( Chart(zoom_type="x"), PlotOptions(Series(States(Hover(enabled=False, halo=False))))) else: chart_cfg = chart_cfg.inherit_many(Chart(zoom_type="xy")) # Stockcharts forces the Y Axis to be on the right side by default and it # does not honor overrides set globally in JS. Fix it here. if "y_axis" not in chart_cfg: return chart_cfg.inherit(YAxis(opposite=False)) else: base_cfg = Cfg(opposite=False) # In case we have a single y axis, just set the defaults on it if isinstance(chart_cfg.y_axis, Cfg): chart_cfg.y_axis = chart_cfg.y_axis.inherit(base_cfg) else: y_axes = chart_cfg.y_axis # Set the default on all y axes for i in range(len(y_axes)): y_axes[i] = y_axes[i].inherit(base_cfg) return chart_cfg
def _get_styles_string(self, styles_cfg): """ Converts the styles configuration to a CSS styles string. :param styles_cfg: The configuration object to convert. :return: CSS string """ sizing_cfg = Cfg() if self._settings.width is not None: sizing_cfg["width"] = self._settings.width if self._settings.height is not None: sizing_cfg["height"] = self._settings.height # Replace `_` with `-` and make values lowercase to get valid CSS names return cfg_to_css_string(styles_cfg.override(sizing_cfg))
def data(self): """ Function required to support interactive IPython plotting. Should not be used directly. :return: Data to be displayed """ container = root("div") self._write_block(container, Cfg(), id_generator()) # Write children into the output output = BytesIO() for child in container.children: output.write(render(child)) return output.getvalue()
def _set_axis(axis_name): # Get a custom axis, or use/create a default one. axis = subchart_cfg.setdefault( axis_name, None) or axis_defaults.setdefault( axis_name, Cfg()) if isinstance(axis, (list, tuple)): if len(axis) != 1: raise ValueError( "There can be at most one sub-chart axis definition for each axis." ) axis = axis[0] # Get the index of this axis from the list of known axes. Optionally, add a new entry. axis_coll = axes[axis_name] try: axis_idx = axis_coll.index(axis) except ValueError: axis_coll.append(axis) axis_idx = len(axis_coll) - 1 for series_cfg in subchart_series: series_cfg[axis_name] = axis_idx
def _write_contents(self, container, actual_cfg, *args, **kwargs): # The width of one column in percentage content_count = len(self._contents) # Skip layout if there is no content. if content_count > 0: cell_width = 100. / min(self._cols, content_count) row_count = int(math.ceil(content_count / float(self._cols))) for row_i in range(row_count): row_el = append_to(container, "div") row_el["class"] = ["pybloqs-grid-row"] if row_i > 0: row_el["style"] = "clear:both" written_row_item_count = row_i * self._cols for col_i in range(self._cols): item_count = written_row_item_count + col_i if item_count >= content_count: break cell_el = append_to(row_el, "div", style="width:%f%%;float:left;" % cell_width) cell_el["class"] = ["pybloqs-grid-cell"] self._contents[item_count]._write_block( cell_el, actual_cfg if self._cascade_cfg else Cfg(), *args, **kwargs) # Clear the floating, Yarr! append_to(container, "div", style="clear:both")
def _builder(*args, **kwargs): for arg in args: kwargs.update(arg) kwargs["__id"] = hash(name) return Cfg({name: Cfg(kwargs).inherit_many(*def_args, **def_kwargs)})
def _wrap(cfg, name): cfg = cfg.override(Cfg(data=data)) if "name" not in cfg and name is not None: cfg.name = name return [cfg]
def __init__(self, data, *args, **kwargs): """ Create a chart or composite chart from the supplied data. :param data: List, tuple, pandas.Series/DataFrame to use as chart data. In case `data` is a list of Plot objects, a composite chart will be constructed. :param chart_cls: The chart class to use. Available values are "Chart" or "StockChart". StockCharts have extra features like a navigator pane and special handling for timestamps. A sensible default will be chosen based on the data by default. :param flatten: When set to True, the data will be flattened and categories will be extracted from labelled data automatically. Useful when creating plots where both axes need category labels (e.g. heatmaps). :param args: Chart level configuration. Axes definitions will be applied to subplots that do not have custom axes. Plots option sets supplied here will be used by default for any subplots that do not specify their own. :param kwargs: Optional styling arguments. The `style` keyword argument has special meaning in that it allows styling to be grouped as one argument. It is also useful in case a styling parameter name clashes with a standard block parameter. """ chart_cls = kwargs.pop("chart_cls", self._choose_chart_class(data)) flatten = kwargs.pop("flatten", False) switch_zy = kwargs.pop("switch_zy", False) super(Plot, self).__init__(**kwargs) chart_cfg, plot_cfg = self._parse_args(args) # In case we got a list/tuple of Plots, create a composite chart. if isinstance(data, (list, tuple)) and isinstance(data[0], (Plot, NDFrame)): # Parse out any axis defaults axis_defaults = {} for ax_name in ["x_axis", "y_axis"]: axis_cfg = chart_cfg.pop(ax_name, None) if axis_cfg is not None: axis_defaults[ax_name] = axis_cfg axes = Cfg(x_axis=[], y_axis=[]) chart_series = [] for plot_data in data: # Convert data to a plot in case the it was not an actual plot instance # (but a pandas object for example). if not isinstance(plot_data, Plot): plot_data = Plot(plot_data) # If no chart class was defined yet, choose one based on the data. if chart_cls is None: chart_cls = plot_data._chart_cls # Extract the chart configuration. subchart_cfg = plot_data._chart_cfg # Extract the series from the subchart config subchart_series = subchart_cfg.series # Check for axis definitions. def _set_axis(axis_name): # Get a custom axis, or use/create a default one. axis = subchart_cfg.setdefault( axis_name, None) or axis_defaults.setdefault( axis_name, Cfg()) if isinstance(axis, (list, tuple)): if len(axis) != 1: raise ValueError( "There can be at most one sub-chart axis definition for each axis." ) axis = axis[0] # Get the index of this axis from the list of known axes. Optionally, add a new entry. axis_coll = axes[axis_name] try: axis_idx = axis_coll.index(axis) except ValueError: axis_coll.append(axis) axis_idx = len(axis_coll) - 1 for series_cfg in subchart_series: series_cfg[axis_name] = axis_idx _set_axis("x_axis") _set_axis("y_axis") # Add the subplot series to the figure series. chart_series.extend(subchart_series) chart_cfg = chart_cfg.override(axes) chart_cfg.series = chart_series else: if flatten: data, chart_cfg = self._flatten_data(data, chart_cfg, switch_zy=switch_zy) chart_cfg.series = self._construct_plot_series(data, plot_cfg) chart_cfg = self._set_chart_defaults(chart_cfg, chart_cls) self._chart_cfg = chart_cfg self._chart_cls = chart_cls
Creates a chart configuration group. Uniqueness is ensured by attaching an UUID base __id keyword. """ def _builder(*args, **kwargs): for arg in args: kwargs.update(arg) kwargs["__id"] = hash(name) return Cfg({name: Cfg(kwargs).inherit_many(*def_args, **def_kwargs)}) return _builder # Main Chart configuration groups. Chart = _make_chart_cfg("chart") Colors = lambda colors: Cfg({"colors": colors} ) # Colors is an array and not an option group Credits = _make_chart_cfg("credits") Exporting = _make_chart_cfg("exporting") Labels = _make_chart_cfg("labels") Legend = _make_chart_cfg("legend") Loading = _make_chart_cfg("loading") Pane = _make_chart_cfg("pane") Navigation = _make_chart_cfg("navigation") Navigator = _make_chart_cfg("navigator") PlotOptions = _make_chart_cfg("plot_options") RangeSelector = _make_chart_cfg("range_selector") Scrollbar = _make_chart_cfg("scrollbar") Subtitle = _make_chart_cfg("subtitle") Title = _make_chart_cfg("title") Tooltip = _make_chart_cfg("tooltip") TooltipPct = _make_chart_cfg(
def render_html(self, pretty=True, static_output=False, header_block=None, footer_block=None): """Returns html output of the block :param pretty: Toggles pretty printing of the resulting HTML. Not applicable for non-HTML output. :param static_output: Passed down to _write_block. Will render static version of blocks which support this. :param header_block: If not None, header is inlined into a HTML body as table. :param footer_block: If not None, header is inlined into a HTML body as table. :return html-code of the block """ # Render the contents html = root("html", doctype="html") head = append_to(html, "head") append_to(head, "meta", charset='utf-8') body = append_to(html, "body") # Make sure that the main style sheet is always included resource_deps = DependencyTracker(default_css_main) # If header or footer are passed into this function, inline them in the following structure: # # <body> # <table> # <thead><tr><td>Header html</td></tr></thead> # <tfoot><tr><td>Footer html</td></tr></tfoot> # <tbody><tr><td>Body html</td></tr></tbody> # </table> # </body> if header_block is not None or footer_block is not None: content_table = append_to(body, "table") if header_block is not None: header_thead = append_to(content_table, "thead") header_tr = append_to(header_thead, "tr") header_td = append_to(header_tr, "th") header_block._write_block(header_td, Cfg(), id_generator(), resource_deps=resource_deps, static_output=static_output) if footer_block is not None: footer_tfoot = append_to(content_table, "tfoot", id='footer') footer_tr = append_to(footer_tfoot, "tr") footer_td = append_to(footer_tr, "td") footer_block._write_block(footer_td, Cfg(), id_generator(), resource_deps=resource_deps, static_output=static_output) body_tbody = append_to(content_table, "tbody") body_tr = append_to(body_tbody, "tr") body_td = append_to(body_tr, "td") self._write_block(body_td, Cfg(), id_generator(), resource_deps=resource_deps, static_output=static_output) else: self._write_block(body, Cfg(), id_generator(), resource_deps=resource_deps, static_output=static_output) script_inflate.write(head) script_block_core.write(head) if static_output: # Add the load wait poller if there are any JS resources js_elem(body, "var loadWaitPoller=runWaitPoller();") # Write out resources for res in resource_deps: res.write(head) # Render the whole document (the parent of the html tag) content = render(html.parent, pretty=pretty) return content
def _write_contents(self, container, actual_cfg, *args, **kwargs): for content in self._contents: content._write_block(container, actual_cfg if self._cascade_cfg else Cfg(), *args, **kwargs)
def _write_contents(self, container, actual_cfg, *args, **kwargs): for content in self._contents: cell = append_to(container, "div") content._write_block(cell, actual_cfg if self._cascade_cfg else Cfg(), *args, **kwargs)