# -*- coding: utf-8 -*-
"""Visual components."""
from typing import Dict, Optional, List, Union
from bowtie._component import Component, jdumps, jsbool
from bowtie._progress import Progress
# pylint: disable=too-few-public-methods
class _Visual(Component):
"""Abstract class all visual components inherit.
Used to test if a an object is a visual component.
"""
# pylint: disable=abstract-method
def __init__(self) -> None:
self.progress = Progress()
super().__init__()
@property
def _instantiate(self) -> str:
# pylint: disable=protected-access
begin, end = self.progress._tags
tagwrap = begin + '{component}' + self._tagbase + end
return self._insert(tagwrap, self._comp)
[docs]class Table(_Visual):
"""Ant Design table with filtering and sorting."""
_TEMPLATE = 'table.jsx'
_COMPONENT = 'AntTable'
_PACKAGE = None
_ATTRS = ('columns={{{columns}}} '
'resultsPerPage={{{results_per_page}}}')
def __init__(self, data=None, columns: Optional[List[Union[int, str]]] = None,
results_per_page: int = 10) -> None:
"""Create a table and optionally initialize the data.
Parameters
----------
data : pd.DataFrame, optional
columns : list, optional
List of column names to display.
results_per_page : int, optional
Number of rows on each pagination of the table.
"""
super().__init__()
self.data = []
self.columns = []
if data:
self.data, self.columns = self._make_data(data)
elif columns:
self.columns = self._make_columns(columns)
self.results_per_page = results_per_page
self._comp = self._tag.format(
columns=self.columns,
results_per_page=self.results_per_page
)
@staticmethod
def _make_columns(columns):
"""Transform list of columns into AntTable format."""
return [dict(title=str(c),
dataIndex=str(c),
key=str(c))
for c in columns]
@staticmethod
def _make_data(data):
"""Transform table data into JSON."""
jsdata = []
for idx, row in data.iterrows():
row.index = row.index.astype(str)
rdict = row.to_dict()
rdict.update(dict(key=str(idx)))
jsdata.append(rdict)
return jsdata, Table._make_columns(data.columns)
# pylint: disable=no-self-use
[docs] def do_data(self, data):
"""Replace the columns and data of the table.
Parameters
----------
data : pandas.DataFrame
Returns
-------
None
"""
return self._make_data(data)
[docs] def do_columns(self, columns):
"""Update the columns of the table.
Parameters
----------
columns : array-like
List of strings.
Returns
-------
None
"""
return self._make_columns(columns)
[docs]class SmartGrid(_Visual):
"""Griddle table with filtering and sorting."""
_TEMPLATE = 'griddle.jsx'
_COMPONENT = 'SmartGrid'
_PACKAGE = 'griddle-react@1.11.2'
_ATTRS = None
def __init__(self) -> None:
"""Create the table, optionally set the columns."""
super().__init__()
self._comp = self._tag
# pylint: disable=no-self-use
[docs] def do_update(self, data):
"""Update the data of the table.
Parameters
----------
data : pandas.DataFrame
Returns
-------
None
"""
return data.to_dict(orient='records')
def get(self, data):
"""
Get the table data.
Returns
-------
list
Each entry in the list is a dict of labels and values for a row.
"""
return data
[docs]class SVG(_Visual):
"""SVG image.
Mainly for matplotlib plots.
"""
_TEMPLATE = 'svg.jsx'
_COMPONENT = 'SVG'
_PACKAGE = None
_ATTRS = 'preserveAspectRatio={{{preserve_aspect_ratio}}}'
def __init__(self, preserve_aspect_ratio: bool = False) -> None:
"""Create SVG component.
Parameters
----------
preserve_aspect_ratio : bool, optional
If ``True`` it preserves the aspect ratio otherwise
it will stretch to fill up the space available.
"""
super().__init__()
self.preserve_aspect_ratio = preserve_aspect_ratio
self._comp = self._tag.format(
preserve_aspect_ratio=jsbool(self.preserve_aspect_ratio)
)
# pylint: disable=no-self-use
[docs] def do_image(self, image):
"""Replace the image.
Parameters
----------
image : str
Generated by ``savefig`` from matplotlib with ``format=svg``.
Returns
-------
None
Examples
--------
This shows how to update an SVG widget with Matplotlib.
>>> from io import StringIO
>>> import matplotlib
>>> matplotlib.use('Agg')
>>> import matplotlib.pyplot as plt
>>> image = SVG()
>>>
>>> def callback(x):
... sio = StringIO()
... plt.plot(range(5))
... plt.savefig(sio, format='svg')
... sio.seek(0)
... s = sio.read()
... idx = s.find('<svg')
... s = s[idx:]
... image.do_image(s)
"""
return image
[docs]class Plotly(_Visual):
"""Plotly component.
Useful for many kinds of plots.
"""
_TEMPLATE = 'plotly.jsx'
_COMPONENT = 'PlotlyPlot'
_PACKAGE = 'plotly.js@1.34.0'
_ATTRS = 'initState={{{init}}}'
def __init__(self, init: Optional[Dict] = None) -> None:
"""Create a Plotly component.
Parameters
----------
init : dict, optional
Initial Plotly data to plot.
"""
super().__init__()
if init is None:
init = dict(data=[], layout={'autosize': False})
self.init = init
self._comp = self._tag.format(
init=jdumps(self.init)
)
# Events
def on_click(self):
"""React to Plotly click event.
| **Payload:** TODO.
Returns
-------
str
Name of event.
"""
return self.get_click
def on_beforehover(self):
"""Emit an event before hovering over a point.
| **Payload:** TODO.
Returns
-------
str
Name of event.
"""
return self.get_hover
def on_hover(self):
"""Emit an event after hovering over a point.
| **Payload:** TODO.
Returns
-------
str
Name of event.
"""
return self.get_hover
def on_unhover(self):
"""Emit an event when hover is removed.
| **Payload:** TODO.
Returns
-------
str
Name of event.
"""
return self.get_hover
def on_select(self):
"""Emit an event when points are selected with a tool.
| **Payload:** TODO.
Returns
-------
str
Name of event.
"""
return self.get_select
def on_relayout(self):
"""Emit an event when the chart axes change.
| **Payload:** TODO.
Returns
-------
str
Name of event.
"""
return self.get_layout
# Commands
# pylint: disable=no-self-use
[docs] def do_all(self, plot):
"""Replace the entire plot.
Parameters
----------
plot : dict
Dict that can be plotted with Plotly.
It should have this structure: ``{data: [], layout: {}}``.
Returns
-------
None
"""
return plot
[docs] def do_data(self, data):
"""Replace the data portion of the plot.
Parameters
----------
data : list of traces
List of data to replace the old data.
Returns
-------
None
"""
return data
[docs] def do_layout(self, layout):
"""Update the layout.
Parameters
----------
layout : dict
Contains layout information.
Returns
-------
None
"""
return layout
[docs] def do_config(self, config):
"""Update the configuration of the plot.
Parameters
----------
config : dict
Plotly config information.
Returns
-------
None
"""
return config
def get(self, data):
"""Get the current selection of points.
Returns
-------
list
"""
return data
def get_select(self, data):
"""Get the current selection of points.
Returns
-------
list
"""
return data
def get_click(self, data):
"""Get the current selection of points.
Returns
-------
list
"""
return data
def get_hover(self, data):
"""Get the current selection of points.
Returns
-------
list
"""
return data
def get_layout(self, data):
"""Get the current layout.
Returns
-------
list
"""
return data