Exemplo n.º 1
0
 def test_patch_bad_columns(self):
     ds = ColumnDataSource(data=dict(a=[10, 11], b=[20, 21]))
     with self.assertRaises(ValueError) as cm:
         ds.patch(dict(c=[(0, 100)]))
     self.assertEqual(str(cm.exception), "Can only patch existing columns (extra: c)")
     with self.assertRaises(ValueError) as cm:
         ds.patch(dict(a=[(0,100)], c=[(0, 100)], d=[(0, 100)]))
     self.assertEqual(str(cm.exception), "Can only patch existing columns (extra: c, d)")
Exemplo n.º 2
0
 def test_patch_bad_columns(self):
     ds = ColumnDataSource(data=dict(a=[10, 11], b=[20, 21]))
     with pytest.raises(
             ValueError,
             match=r"Can only patch existing columns \(extra: c\)"):
         ds.patch(dict(c=[(0, 100)]))
     with pytest.raises(
             ValueError,
             match=r"Can only patch existing columns \(extra: c, d\)"):
         ds.patch(dict(a=[(0, 100)], c=[(0, 100)], d=[(0, 100)]))
Exemplo n.º 3
0
 def test_patch_good_data(self):
     ds = ColumnDataSource(data=dict(a=[10, 11], b=[20, 21]))
     ds._document = "doc"
     stuff = {}
     def mock(*args, **kw):
         stuff['args'] = args
         stuff['kw'] = kw
     ds.data._patch = mock
     ds.patch(dict(a=[(0,100), (1,101)], b=[(0,200)]))
     self.assertEqual(stuff['args'], ("doc", ds, dict(a=[(0,100), (1,101)], b=[(0,200)])))
     self.assertEqual(stuff['kw'], {})
Exemplo n.º 4
0
 def test_patch_good_slice_indices(self):
     ds = ColumnDataSource(data=dict(a=[10, 11, 12, 13, 14, 15], b=[20, 21, 22, 23, 24, 25]))
     ds._document = "doc"
     stuff = {}
     mock_setter = object()
     def mock(*args, **kw):
         stuff['args'] = args
         stuff['kw'] = kw
     ds.data._patch = mock
     ds.patch(dict(a=[(slice(2), [100, 101]), (slice(3, 5), [100, 101])], b=[(slice(0, None, 2), [100, 101, 102])]), mock_setter)
     assert stuff['args'] == ("doc", ds, dict(a=[(slice(2), [100, 101]), (slice(3, 5), [100, 101])], b=[(slice(0, None, 2), [100, 101, 102])]), mock_setter)
     assert stuff['kw'] == {}
Exemplo n.º 5
0
 def test_patch_good_simple_indices(self):
     ds = ColumnDataSource(data=dict(a=[10, 11], b=[20, 21]))
     ds._document = "doc"
     stuff = {}
     mock_setter = object()
     def mock(*args, **kw):
         stuff['args'] = args
         stuff['kw'] = kw
     ds.data._patch = mock
     ds.patch(dict(a=[(0,100), (1,101)], b=[(0,200)]), mock_setter)
     assert stuff['args'] == ("doc", ds, dict(a=[(0,100), (1,101)], b=[(0,200)]), mock_setter)
     assert stuff['kw'] == {}
Exemplo n.º 6
0
class Tabulator(Widget):
    """The Tabulator Pane wraps the [Tabulator](http://tabulator.info/) table to provide an
awesome interative table.

You can
- Specify a `configuration` dictionary at instantation. See http://tabulator.info/.
- Provide an initial `value` as a Pandas DataFrame or Bokeh ColumnDataSource.
- `stream` (append) to the `value`.
- `patch` (update) the `value`.

Example: Data specified in configuration

>>> from awesome_panel_extensions.widgets.tabulator import Tabulator
>>> configuration = {
...     "layout": "fitColumns",
...     "data": [
...         {"x": [1], "y": 'a'},
...         {"x": [2], "y": 'b'}
...         ],
...     "initialSort":[
...         {"column":"y", "dir":"desc"},
...     ],
...     "columns":[
...         {"title": "Value", "field":"x"},
...         {"title": "Item", "field":"y", "hozAlign":"right", "formatter":"money"}
...     ],
... }
>>> Tabulator(configuration=configuration)
Tabulator(...)

Example: Data specified as Pandas.DataFrame value

>>> import pandas as pd
>>> configuration = {
...     "layout": "fitColumns",
...     "initialSort":[
...         {"column":"y", "dir":"desc"},
...     ],
...     "columns":[
...         {"title": "Value", "field":"x"},
...         {"title": "Item", "field":"y", "hozAlign":"right", "formatter":"money"}
...     ],
... }
>>> value = pd.DataFrame([
...     {"x": [1], "y": 'a'},
...     {"x": [2], "y": 'b'}
... ])
>>> Tabulator(configuration=configuration, value=value)
Tabulator(...)

Example: Data specified as Bokeh ColumnDataSource value

>>> configuration = {
...     "layout": "fitColumns",
...     "initialSort":[
...         {"column":"y", "dir":"desc"},
...     ],
...     "columns":[
...         {"title": "Value", "field":"x"},
...         {"title": "Item", "field":"y", "hozAlign":"right", "formatter":"money"}
...     ],
... }
>>> value = ColumnDataSource({"x": [1,2], "y": ["a", "b"]})
>>> Tabulator(configuration=configuration, value=value)
Tabulator(...)
"""

    value = param.Parameter(
        doc="""One of pandas.DataFrame or bokeh.models.ColumnDataSource.

        Please note when specifying a Pandas.Dataframe we currently have some narrow requirements

        - The index should be a single index which should be called 'index' and be unique. To make
        sure things work we suggest you `.reset_index()` before usage.
        """)
    selection = param.List(
        doc="The list of selected row indexes. For example [1,4]. Default is []"
    )
    configuration = param.Dict(
        constant=True,
        doc=
        """The initial Tabulator configuration. See https://tabulator.info for lots of
        examples.

        If None is provided at instantiation, then {'autocolumns': True} is added

        Please note that in order to get things working we add the below to the configuration
        on the js side.
        {
            "rowSelectionChanged": rowSelectionChanged,
            "cellEdited": cellEdited,
            "index": "index",
        }
        """,
    )

    height = param.Integer(
        default=300,
        bounds=(0, None),
        doc=
        """The height of the Tabulator table. Specifying a height is mandatory.""",
    )

    _source = param.ClassSelector(
        class_=ColumnDataSource,
        doc="Used to transfer the `value` efficiently to frontend")
    _cell_change = param.Dict(
        doc=
        """Changed whenever the user updates a cell in the client. Sends something like
        {"c": "<COLUMN NAME>", "i": "<INDEX>", "v": "<NEW VALUE>"}. Used to transfer the change
        efficiently and update the DataFrame as I could not find a similar property/ event on the
        ColumnDataSource""")

    _rename = {
        "value": None,
        "selection": None,
        "_source": "source",
    }
    _widget_type = _BkTabulator

    def __init__(self, **params):
        """The Tabulator Pane wraps the [Tabulator](http://tabulator.info/) table to provide an
        awesome interative table.

You can
- Specify a `configuration` dictionary at instantation. See http://tabulator.info/.
- Provide an initial `value` as a Pandas DataFrame or Bokeh ColumnDataSource.
- `stream` (append) to the `value`.
- `patch` (update) the `value`.

Example: Data specified in configuration

>>> from awesome_panel_extensions.widgets.tabulator import Tabulator
>>> configuration = {
...     "layout": "fitColumns",
...     "data": [
...         {"x": [1], "y": 'a'},
...         {"x": [2], "y": 'b'}
...         ],
...     "initialSort":[
...         {"column":"y", "dir":"desc"},
...     ],
...     "columns":[
...         {"title": "Value", "field":"x"},
...         {"title": "Item", "field":"y", "hozAlign":"right", "formatter":"money"}
...     ],
... }
>>> Tabulator(configuration=configuration)
Tabulator(...)

Example: Data specified as Pandas.DataFrame value

>>> import pandas as pd
>>> configuration = {
...     "layout": "fitColumns",
...     "initialSort":[
...         {"column":"y", "dir":"desc"},
...     ],
...     "columns":[
...         {"title": "Value", "field":"x"},
...         {"title": "Item", "field":"y", "hozAlign":"right", "formatter":"money"}
...     ],
... }
>>> value = pd.DataFrame([
...     {"x": [1], "y": 'a'},
...     {"x": [2], "y": 'b'}
... ])
>>> Tabulator(configuration=configuration, value=value)
Tabulator(...)

Example: Data specified as Bokeh ColumnDataSource value

>>> configuration = {
...     "layout": "fitColumns",
...     "initialSort":[
...         {"column":"y", "dir":"desc"},
...     ],
...     "columns":[
...         {"title": "Value", "field":"x"},
...         {"title": "Item", "field":"y", "hozAlign":"right", "formatter":"money"}
...     ],
... }
>>> value = ColumnDataSource({"x": [1,2], "y": ["a", "b"]})
>>> Tabulator(configuration=configuration, value=value)
Tabulator(...)
"""
        if "configuration" not in params:
            params["configuration"] = _DEFAULT_CONFIGURATION.copy()
        if "selection" not in params:
            params["selection"] = []

        super().__init__(**params)

        self._pause_cds_updates = False
        self._update_column_data_source()

    @param.depends("value", watch=True)
    def _update_column_data_source(self, *_):
        if self._pause_cds_updates:
            return

        if self.value is None:
            self._source = ColumnDataSource({})
        elif isinstance(self.value, pd.DataFrame):
            if (not isinstance(self.value.index, pd.RangeIndex)
                    or self.value.index.start != 0
                    or self.value.index.step != 1):
                raise ValueError(
                    "Please provide a DataFrame with RangeIndex starting at 0 and with step 1"
                )

            if self._source:
                self._source.data = self.value
            else:
                self._source = ColumnDataSource(self.value)
        elif isinstance(self.value, ColumnDataSource):
            self._source = self.value
        else:
            raise ValueError("The `data` provided is not of a supported type!")

    @param.depends("_cell_change", watch=True)
    def _update_value_with_cell_change(self):
        if isinstance(self.value, pd.DataFrame):
            column = self._cell_change["c"]  # pylint: disable=unsubscriptable-object
            index = self._cell_change["i"]  # pylint: disable=unsubscriptable-object
            new_value = self._cell_change["v"]  # pylint: disable=unsubscriptable-object
            self._pause_cds_updates = True
            self.value.at[index, column] = new_value
            self.param.trigger("value")
            self._pause_cds_updates = False

    def stream(self,
               stream_value: Union[pd.DataFrame, pd.Series, Dict],
               reset_index: bool = True):
        """Streams (appends) the `stream_value` provided to the existing value in an efficient
        manner.

        Args:
            stream_value (Union[pd.DataFrame, pd.Series, Dict]): The new value(s) to append to the
                existing value.
            reset_index (bool, optional): If the stream_value is a DataFrame and `reset_index` is
                True then the index of it is reset if True. Helps to keep the index unique and
                named `index`. Defaults to True.

        Raises:
            ValueError: Raised if the stream_value is not a supported type.

        Example: Stream a Series to a DataFrame

        >>> value = pd.DataFrame({"x": [1, 2], "y": ["a", "b"]})
        >>> tabulator = Tabulator(value=value)
        >>> stream_value = pd.Series({"x": 4, "y": "d"})
        >>> tabulator.stream(stream_value)
        >>> tabulator.value.to_dict("list")
        {'x': [1, 2, 4], 'y': ['a', 'b', 'd']}

        Example: Stream a Dataframe to a Dataframe

        >>> value = pd.DataFrame({"x": [1, 2], "y": ["a", "b"]})
        >>> tabulator = Tabulator(value=value)
        >>> stream_value = pd.DataFrame({"x": [3, 4], "y": ["c", "d"]})
        >>> tabulator.stream(stream_value)
        >>> tabulator.value.to_dict("list")
        {'x': [1, 2, 3, 4], 'y': ['a', 'b', 'c', 'd']}


        Example: Stream a Dictionary row to a DataFrame

        >>> value = pd.DataFrame({"x": [1, 2], "y": ["a", "b"]})
        >>> tabulator = Tabulator(value=value)
        >>> stream_value = {"x": 4, "y": "d"}
        >>> tabulator.stream(stream_value)
        >>> tabulator.value.to_dict("list")
        {'x': [1, 2, 4], 'y': ['a', 'b', 'd']}

         Example: Stream a Dictionary of Columns to a Dataframe

        >>> value = pd.DataFrame({"x": [1, 2], "y": ["a", "b"]})
        >>> tabulator = Tabulator(value=value)
        >>> stream_value = {"x": [3, 4], "y": ["c", "d"]}
        >>> tabulator.stream(stream_value)
        >>> tabulator.value.to_dict("list")
        {'x': [1, 2, 3, 4], 'y': ['a', 'b', 'c', 'd']}
        """

        if isinstance(self.value, pd.DataFrame):
            value_index_start = self.value.index.max() + 1
            if isinstance(stream_value, pd.DataFrame):
                if reset_index:
                    stream_value = stream_value.reset_index(drop=True)
                    stream_value.index += value_index_start
                self._pause_cds_updates = True
                self.value = pd.concat([self.value, stream_value])
                self._source.stream(stream_value)
                self._pause_cds_updates = False
            elif isinstance(stream_value, pd.Series):
                self._pause_cds_updates = True
                self.value.loc[value_index_start] = stream_value
                self._source.stream(stream_value)
                self.param.trigger("value")
                self._pause_cds_updates = False
            elif isinstance(stream_value, dict):
                if stream_value:
                    try:
                        stream_value = pd.DataFrame(stream_value)
                    except ValueError:
                        stream_value = pd.Series(stream_value)
                    self.stream(stream_value)
            else:
                raise ValueError(
                    "The patch value provided is not a DataFrame, Series or Dict!"
                )
        else:
            self._pause_cds_updates = True
            self._source.stream(stream_value)
            self.param.trigger("value")
            self._pause_cds_updates = False

    def patch(self, patch_value: Union[pd.DataFrame, pd.Series, Dict]):
        """Patches (updates) the existing value with the `patch_value` in an efficient manner.

        Args:
            patch_value (Union[pd.DataFrame, pd.Series, Dict]): The value(s) to patch the
                existing value with.

        Raises:
            ValueError: Raised if the patch_value is not a supported type.



        Example: Patch a DataFrame with a Dictionary row.

        >>> value = pd.DataFrame({"x": [1, 2], "y": ["a", "b"]})
        >>> tabulator = Tabulator(value=value)
        >>> patch_value = {"x": [(0, 3)]}
        >>> tabulator.patch(patch_value)
        >>> tabulator.value.to_dict("list")
        {'x': [3, 2], 'y': ['a', 'b']}

         Example: Patch a Dataframe with a Dictionary of Columns.

        >>> value = pd.DataFrame({"x": [1, 2], "y": ["a", "b"]})
        >>> tabulator = Tabulator(value=value)
        >>> patch_value = {"x": [(slice(2), (3,4))], "y": [(1,'d')]}
        >>> tabulator.patch(patch_value)
        >>> tabulator.value.to_dict("list")
        {'x': [3, 4], 'y': ['a', 'd']}

        Example: Patch a DataFrame with a Series. Please note the index is used in the update

        >>> value = pd.DataFrame({"x": [1, 2], "y": ["a", "b"]})
        >>> tabulator = Tabulator(value=value)
        >>> patch_value = pd.Series({"index": 1, "x": 4, "y": "d"})
        >>> tabulator.patch(patch_value)
        >>> tabulator.value.to_dict("list")
        {'x': [1, 4], 'y': ['a', 'd']}

        Example: Patch a Dataframe with a Dataframe. Please note the index is used in the update.

        >>> value = pd.DataFrame({"x": [1, 2], "y": ["a", "b"]})
        >>> tabulator = Tabulator(value=value)
        >>> patch_value = pd.DataFrame({"x": [3, 4], "y": ["c", "d"]})
        >>> tabulator.patch(patch_value)
        >>> tabulator.value.to_dict("list")
        {'x': [3, 4], 'y': ['c', 'd']}
        """
        if isinstance(self.value, pd.DataFrame):
            if isinstance(patch_value, pd.DataFrame):
                patch_value_dict = self._patch_dataframe_to_dict(patch_value)
                self.patch(patch_value_dict)
            elif isinstance(patch_value, pd.Series):
                patch_value_dict = self._patch_series_to_dict(patch_value)
                self.patch(patch_value_dict)
            elif isinstance(patch_value, dict):
                self._patch_from_dict(patch_value)
            else:
                raise ValueError(
                    f"""Patching a patch_value of type {type(patch_value)} is not supported.
                    Please provide a DataFrame, Series or Dict""")
        else:
            if isinstance(patch_value, dict):
                self._pause_cds_updates = True
                self._source.patch(patch_value)
                self.param.trigger("value")
                self._pause_cds_updates = False
            else:
                raise ValueError(
                    f"""Patching a patch_value of type {type(patch_value)} is not supported.
                    Please provide a dict""")

    @staticmethod
    def _patch_dataframe_to_dict(patch_value: pd.DataFrame) -> Dict:
        patch_value_dict: Dict[Any, Any] = {}
        for column in patch_value.columns:
            patch_value_dict[column] = []
            for index in patch_value.index:
                patch_value_dict[column].append(
                    (index, patch_value.loc[index, column]))

        return patch_value_dict

    @staticmethod
    def _patch_series_to_dict(patch_value: pd.Series) -> Dict:
        if "index" in patch_value:  # Series orient is row
            patch_value_dict = {
                k: [(patch_value["index"], v)]
                for k, v in patch_value.items()
            }
            patch_value_dict.pop("index")
        else:  # Series orient is column
            patch_value_dict = {patch_value.name: list(patch_value.items())}
        return patch_value_dict

    def _patch_from_dict(self, patch_value: Dict):
        self._pause_cds_updates = True
        for key, value in patch_value.items():
            for update in value:
                self.value.loc[update[0], key] = update[1]
        self._source.patch(patch_value)
        self.param.trigger("value")
        self._pause_cds_updates = False

    @classmethod
    def to_columns_configuration(
            cls, value: Union[pd.DataFrame,
                              ColumnDataSource]) -> List[Dict[str, str]]:
        """Returns a nice starter `columns` dictionary from the specified `value`.

        Args:
            value (Union[pd.DataFrame, ColumnDataSource]): The data source to transform.

        Returns:
            Dict: The columns configuration

        Example:

        >>> import pandas as pd
        >>> value = {"name": ["python", "panel"]}
        >>> df = pd.DataFrame(value)
        >>> Tabulator.to_columns_configuration(df)
        [{'title': 'Name', 'field': 'name', 'sorter': 'string', 'formatter': 'plaintext', \
'hozAlign': 'left'}]
        """
        col_conf = []
        for field in value.columns:
            dtype = str(value.dtypes[field])
            conf = cls._core(field=field, dtype=dtype)
            col_conf.append(conf)
        return col_conf

    @classmethod
    def _core(cls, field: str, dtype: str) -> Dict[str, str]:
        dtype_str = str(dtype)
        return {
            "title": cls._to_title(field),
            "field": field,
            "sorter": _SORTERS.get(dtype_str, "string"),
            "formatter": _FORMATTERS.get(dtype_str, "plaintext"),
            "hozAlign": _HOZ_ALIGNS.get(dtype_str, "left"),
        }

    @staticmethod
    def _to_title(field: str) -> str:
        return field.replace("_", " ").title()

    @staticmethod
    def config(css: Optional[str] = "default"):
        """Adds the specified css theme to pn.config.css_files

        Args:
            css (Optional[str], optional): [description]. Defaults to "default".
        """
        if css:
            href = CSS_HREFS[css]
            if href not in pn.config.css_files:
                pn.config.css_files.append(href)

    @property
    def selected_values(self) -> Union[pd.DataFrame, ColumnDataSource, None]:
        """Returns the selected rows of the data based

        Raises:
            ValueError: If the value is not of the supported type.

        Returns:
            Union[pd.DataFrame, ColumnDataSource, None]: The selected values of the same type as
                value. Based on the the current selection.
        """
        # Selection is a list of row indices. For example [0,2]
        if self.value is None:
            return None
        if isinstance(self.value, pd.DataFrame):
            return self.value.iloc[self.selection, ]
        if isinstance(self.value, ColumnDataSource):
            # I could not find a direct way to get a selected ColumnDataSource
            selected_data = self.value.to_df().iloc[self.selection, ]
            return ColumnDataSource(selected_data)
        raise ValueError("The value is not of a supported type!")

    @param.depends("selection", watch=True)
    def _update_source_selected_indices(self, *_):
        self._source.selected.indices = self.selection

    def _get_model(self, doc, root=None, parent=None, comm=None):
        model = super()._get_model(doc=doc,
                                   root=root,
                                   parent=parent,
                                   comm=comm)
        if root is None:
            root = model
        self._link_props(model.source.selected, ["indices"], doc, root, comm)
        return model

    def _process_events(self, events):
        if "indices" in events:
            self.selection = events.pop("indices")
        super()._process_events(events)
Exemplo n.º 7
0
 def test_patch_bad_slice_indices(self):
     ds = ColumnDataSource(
         data=dict(a=[10, 11, 12, 13, 14, 15], b=[20, 21, 22, 23, 24, 25]))
     with pytest.raises(ValueError) as cm:
         ds.patch(dict(a=[(slice(10), list(range(10)))]))
         assert str(
             cm.exception
         ) == "Out-of bounds slice index stop (10) in patch for column: a"
     with pytest.raises(ValueError) as cm:
         ds.patch(dict(a=[(slice(10, 1), list(range(10)))]))
         assert str(
             cm.exception
         ) == "Patch slices must have start < end, got slice(10, 1, None)"
     with pytest.raises(ValueError) as cm:
         ds.patch(dict(a=[(slice(None, 10, -1), list(range(10)))]))
         assert str(
             cm.exception
         ) == "Patch slices must have non-negative (start, stop, step) values, got slice(None, 10, -1)"
     with pytest.raises(ValueError) as cm:
         ds.patch(dict(a=[(slice(10, 1, 1), list(range(10)))]))
         assert str(
             cm.exception
         ) == "Patch slices must have start < end, got slice(10, 1, 1)"
     with pytest.raises(ValueError) as cm:
         ds.patch(dict(a=[(slice(10, 1, -1), list(range(10)))]))
         assert str(
             cm.exception
         ) == "Patch slices must have start < end, got slice(10, 1, -1)"
     with pytest.raises(ValueError) as cm:
         ds.patch(dict(a=[(slice(1, 10, -1), list(range(10)))]))
         assert str(
             cm.exception
         ) == "Patch slices must have non-negative (start, stop, step) values, got slice(1, 10, -1)"
Exemplo n.º 8
0
 def test_patch_bad_simple_indices(self):
     ds = ColumnDataSource(data=dict(a=[10, 11], b=[20, 21]))
     with pytest.raises(ValueError) as cm:
         ds.patch(dict(a=[(3, 100)]))
         assert str(cm.exception
                    ) == "Out-of bounds index (3) in patch for column: a"
Exemplo n.º 9
0
 def test_patch_bad_slice_indices(self):
     ds = ColumnDataSource(data=dict(a=[10, 11, 12, 13, 14, 15], b=[20, 21, 22, 23, 24, 25]))
     with self.assertRaises(ValueError) as cm:
         ds.patch(dict(a=[(slice(10), list(range(10)))]))
     self.assertEqual(str(cm.exception), "Out-of bounds slice index stop (10) in patch for column: a")
     with self.assertRaises(ValueError) as cm:
         ds.patch(dict(a=[(slice(10, 1), list(range(10)))]))
     self.assertEqual(str(cm.exception), "Patch slices must have start < end, got slice(10, 1, None)")
     with self.assertRaises(ValueError) as cm:
         ds.patch(dict(a=[(slice(None, 10, -1), list(range(10)))]))
     self.assertEqual(str(cm.exception), "Patch slices must have non-negative (start, stop, step) values, got slice(None, 10, -1)")
     with self.assertRaises(ValueError) as cm:
         ds.patch(dict(a=[(slice(10, 1, 1), list(range(10)))]))
     self.assertEqual(str(cm.exception), "Patch slices must have start < end, got slice(10, 1, 1)")
     with self.assertRaises(ValueError) as cm:
         ds.patch(dict(a=[(slice(10, 1, -1), list(range(10)))]))
     self.assertEqual(str(cm.exception), "Patch slices must have start < end, got slice(10, 1, -1)")
     with self.assertRaises(ValueError) as cm:
         ds.patch(dict(a=[(slice(1, 10, -1), list(range(10)))]))
     self.assertEqual(str(cm.exception), "Patch slices must have non-negative (start, stop, step) values, got slice(1, 10, -1)")
Exemplo n.º 10
0
 def test_patch_bad_simple_indices(self):
     ds = ColumnDataSource(data=dict(a=[10, 11], b=[20, 21]))
     with self.assertRaises(ValueError) as cm:
         ds.patch(dict(a=[(3, 100)]))
     self.assertEqual(str(cm.exception), "Out-of bounds index (3) in patch for column: a")
Exemplo n.º 11
0
class Controller:
    params = {}
    terminating = False

    def __init__(self, params={}) -> None:
        self.params = {}
        for param in PARAMETERS:
            self.update_parameter(param, PARAMETERS[param][1])

        for param in params:
            self.update_parameter(param, params[param])

        self.engine = self.make_engine()

        data = {}
        data['x'], data['y'], data['color'] = trisect(
            self.engine.agents)
        self.visualisation_source = ColumnDataSource(data)

        self.names = [status.name for status in AgentStatus][::-1]
        self.status_source = ColumnDataSource(pd.DataFrame(
            np.zeros((1, len(self.names))), columns=self.names))

        self.controls = Controls(self)

        self.start()

    def update_parameter(self, key, value):
        try:
            self.params[key] = cajole(
                float(value), PARAMETERS[key][0], PARAMETERS[key][2])
        except:
            self.params[key] = PARAMETERS[key][1]

        if key in ('agents', 'sickness_proximity', 'sickness_duration', 'quarantine_delay'):
            self.params[key] == int(self.params[key])
        elif key == 'initial_immunity':
            self.params[key] /= 100
        elif key == 'distancing_factor':
            self.params['distancing_factor'] /= 100
        elif key == 'quarantining':
            self.params['quarantining'] = self.params['quarantining'] == 1

        if 'quarantining' in self.params and not self.params['quarantining']:
            self.params['quarantine_delay'] = self.params['sickness_duration'] + 1

    def make_engine(self) -> Engine:
        return Engine(n=int(self.params['agents']), SICKNESS_PROXIMITY=int(self.params['sickness_proximity']),
                      SICKNESS_DURATION=int(self.params['sickness_duration']), DISTANCING_FACTOR=self.params['distancing_factor'],
                      QUARANTINE_DELAY=int(self.params['quarantine_delay']), INITIAL_IMMUNITY=self.params['initial_immunity'])

    def show(self) -> None:
        self.visualisation = get_visualisation(self.visualisation_source)
        self.population_health_graph = get_population_health_graph(
            self.names, self.status_source, self.params['agents'])

        curdoc().add_root(gridplot([
            [self.visualisation, self.population_health_graph],
            [self.controls.get_controls(), get_about_us()]
        ], toolbar_location="left", toolbar_options={'logo': None}))

    def start(self) -> None:
        self.update_callback = curdoc().add_periodic_callback(
            self.update, 1000 // TICKS_PER_SECOND)

    def update(self) -> None:
        self.engine.tick()

        s = slice(self.engine.agent_count)
        x, y, color = trisect(self.engine.agents)
        self.visualisation_source.patch({
            'x': [(s, x)],
            'y': [(s, y)],
            'color': [(s, color)]
        })

        self.status_source.stream({
            "index": [self.engine.ticks],
            AgentStatus.DEAD.name: [self.engine.stats.get(AgentStatus.DEAD.name, 0)],
            AgentStatus.IMMUNE.name: [self.engine.stats.get(AgentStatus.IMMUNE.name, 0)],
            AgentStatus.INFECTIOUS.name: [self.engine.stats.get(AgentStatus.INFECTIOUS.name, 0)],
            AgentStatus.SUSCEPTIBLE.name: [self.engine.stats.get(AgentStatus.SUSCEPTIBLE.name, 0)],
        })

        if self.engine.stats.get(AgentStatus.INFECTIOUS.name, 0) == 0 and not self.terminating:
            curdoc().add_timeout_callback(self.terminate, 8000)

    def terminate(self) -> None:
        if self.terminating:
            return

        self.terminating = True

        try:
            curdoc().remove_periodic_callback(self.update_callback)
        except ValueError:
            pass

    def reset(self) -> None:
        self.terminate()

        self.engine = self.make_engine()

        data = {}
        data['x'], data['y'], data['color'] = trisect(
            self.engine.agents)
        self.visualisation_source.data = data

        self.status_source.data = {
            "index": [],
            AgentStatus.DEAD.name: [],
            AgentStatus.IMMUNE.name: [],
            AgentStatus.INFECTIOUS.name: [],
            AgentStatus.SUSCEPTIBLE.name: [],
        }

        self.terminating = False

        curdoc().add_next_tick_callback(self.start)
Exemplo n.º 12
0
 def test_patch_bad_slice_indices(self):
     ds = ColumnDataSource(data=dict(a=[10, 11, 12, 13, 14, 15], b=[20, 21, 22, 23, 24, 25]))
     with pytest.raises(ValueError, match=r"Out-of bounds slice index stop \(10\) in patch for column: a"):
         ds.patch(dict(a=[(slice(10), list(range(10)))]))
     with pytest.raises(ValueError, match=r"Patch slices must have start < end, got slice\(10, 1, None\)"):
         ds.patch(dict(a=[(slice(10, 1), list(range(10)))]))
     with pytest.raises(ValueError, match=r"Patch slices must have non-negative \(start, stop, step\) values, got slice\(None, 10, -1\)"):
         ds.patch(dict(a=[(slice(None, 10, -1), list(range(10)))]))
     with pytest.raises(ValueError, match=r"Patch slices must have start < end, got slice\(10, 1, 1\)"):
         ds.patch(dict(a=[(slice(10, 1, 1), list(range(10)))]))
     with pytest.raises(ValueError, match=r"Patch slices must have start < end, got slice\(10, 1, -1\)"):
         ds.patch(dict(a=[(slice(10, 1, -1), list(range(10)))]))
     with pytest.raises(ValueError, match=r"Patch slices must have non-negative \(start, stop, step\) values, got slice\(1, 10, -1\)"):
         ds.patch(dict(a=[(slice(1, 10, -1), list(range(10)))]))
Exemplo n.º 13
0
 def test_patch_bad_simple_indices(self):
     ds = ColumnDataSource(data=dict(a=[10, 11], b=[20, 21]))
     with pytest.raises(ValueError, match=r"Out-of bounds index \(3\) in patch for column: a"):
         ds.patch(dict(a=[(3, 100)]))
Exemplo n.º 14
0
 def test_patch_bad_columns(self):
     ds = ColumnDataSource(data=dict(a=[10, 11], b=[20, 21]))
     with pytest.raises(ValueError, match=r"Can only patch existing columns \(extra: c\)"):
         ds.patch(dict(c=[(0, 100)]))
     with pytest.raises(ValueError, match=r"Can only patch existing columns \(extra: c, d\)"):
         ds.patch(dict(a=[(0,100)], c=[(0, 100)], d=[(0, 100)]))