Skip to content

Commit

Permalink
Series layout (#195)
Browse files Browse the repository at this point in the history
* better serialization of pandas objects

* plotly relayout event

* move exceptions to their own module

* whitespace
  • Loading branch information
jwkvam committed Feb 4, 2018
1 parent 3af7cad commit 0274010
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 56 deletions.
60 changes: 6 additions & 54 deletions bowtie/_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@

from bowtie._compat import makedirs
from bowtie._component import Component, SEPARATOR
from bowtie.exceptions import (
GridIndexError, MissingRowOrColumn,
NoSidebarError, NotStatefulEvent,
NoUnusedCellsError, SizeError, UsedCellsError,
WebpackError, YarnError
)


_Import = namedtuple('_Import', ['module', 'component'])
Expand All @@ -27,60 +33,6 @@
_WEBPACK = './node_modules/.bin/webpack'


class YarnError(Exception):
"""Errors from ``Yarn``."""

pass


class WebpackError(Exception):
"""Errors from ``Webpack``."""

pass


class SizeError(Exception):
"""Size values must be a number."""

pass


class GridIndexError(IndexError):
"""Invalid index into the grid layout."""

pass


class NoUnusedCellsError(Exception):
"""All cells are used."""

pass


class UsedCellsError(Exception):
"""All cells are used."""

pass


class MissingRowOrColumn(Exception):
"""Missing a row or column."""

pass


class NoSidebarError(Exception):
"""Cannot add to the sidebar when it doesn't exist."""

pass


class NotStatefulEvent(Exception):
"""This event is not stateful and cannot be paired with other events."""

pass


def raise_not_number(x):
"""Raise ``SizeError`` if ``x`` is not a number``."""
try:
Expand Down
24 changes: 23 additions & 1 deletion bowtie/_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import eventlet
from eventlet.queue import LightQueue

from bowtie.exceptions import SerializationError


SEPARATOR = '#'

Expand Down Expand Up @@ -67,8 +69,15 @@ def json_conversion(obj):
# pandas isn't an explicit dependency of bowtie
# so we can't assume it's available
import pandas as pd
if isinstance(obj, pd.DatetimeIndex):
return [x.isoformat() for x in obj.to_pydatetime()]
if isinstance(obj, pd.Index):
return obj.tolist()
if isinstance(obj, pd.Series):
try:
return [x.isoformat() for x in obj.dt.to_pydatetime()]
except AttributeError:
return obj.tolist()
except ImportError:
pass

Expand All @@ -82,6 +91,7 @@ def jdumps(data):
return json.dumps(data, default=json_conversion)


# pylint: disable=too-many-return-statements
def encoders(obj):
"""Convert Python object to msgpack encodable ones."""
try:
Expand All @@ -98,8 +108,15 @@ def encoders(obj):
# pandas isn't an explicit dependency of bowtie
# so we can't assume it's available
import pandas as pd
if isinstance(obj, pd.DatetimeIndex):
return [x.isoformat() for x in obj.to_pydatetime()]
if isinstance(obj, pd.Index):
return obj.tolist()
if isinstance(obj, pd.Series):
try:
return [x.isoformat() for x in obj.dt.to_pydatetime()]
except AttributeError:
return obj.tolist()
except ImportError:
pass

Expand All @@ -111,7 +128,12 @@ def encoders(obj):

def pack(x):
"""Encode ``x`` into msgpack with additional encoders."""
return bytes(msgpack.packb(x, default=encoders))
try:
return bytes(msgpack.packb(x, default=encoders))
except TypeError as exc:
message = ('Serialization error, check the data passed to a do_ command. '
'Cannot serialize this object:\n') + str(exc)[16:]
raise SerializationError(message)


def unpack(x):
Expand Down
62 changes: 62 additions & 0 deletions bowtie/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
"""Custom Bowtie exceptions."""


class YarnError(Exception):
"""Errors from ``Yarn``."""

pass


class WebpackError(Exception):
"""Errors from ``Webpack``."""

pass


class SizeError(Exception):
"""Size values must be a number."""

pass


class GridIndexError(IndexError):
"""Invalid index into the grid layout."""

pass


class NoUnusedCellsError(Exception):
"""All cells are used."""

pass


class UsedCellsError(Exception):
"""All cells are used."""

pass


class MissingRowOrColumn(Exception):
"""Missing a row or column."""

pass


class NoSidebarError(Exception):
"""Cannot add to the sidebar when it doesn't exist."""

pass


class NotStatefulEvent(Exception):
"""This event is not stateful and cannot be paired with other events."""

pass


class SerializationError(TypeError):
"""Cannot (de)serialize data."""

pass
17 changes: 17 additions & 0 deletions bowtie/src/plotly.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default class PlotlyPlot extends React.Component {
this.selection = null;
this.click = null;
this.hover = null;
this.layout = null;

var local = sessionStorage.getItem(this.props.uuid);
if (local === null) {
Expand All @@ -30,6 +31,7 @@ export default class PlotlyPlot extends React.Component {
this.props.socket.on(this.props.uuid + '#get_select', this.getSelection);
this.props.socket.on(this.props.uuid + '#get_click', this.getClick);
this.props.socket.on(this.props.uuid + '#get_hover', this.getHover);
this.props.socket.on(this.props.uuid + '#get_layout', this.getLayout);
}

setSelection = data => {
Expand Down Expand Up @@ -59,6 +61,20 @@ export default class PlotlyPlot extends React.Component {
return datum;
}

setLayout = data => {
// https://plot.ly/javascript/plotlyjs-events/#update-data
if (data.hasOwnProperty('xaxis.range[0]') ||
data.hasOwnProperty('xaxis.autorange') ||
data.hasOwnProperty('scene')) {
this.layout = data;
this.props.socket.emit(this.props.uuid + '#relayout', msgpack.encode(data));
}
}

getLayout = (data, fn) => {
fn(msgpack.encode(this.layout));
}

setClick = data => {
var datum = this.processData(data);
this.click = datum;
Expand Down Expand Up @@ -89,6 +105,7 @@ export default class PlotlyPlot extends React.Component {
this.container.on('plotly_selected', this.setSelection);
this.container.on('plotly_click', this.setClick);
this.container.on('plotly_hover', this.setHover);
this.container.on('plotly_relayout', this.setLayout);
}

componentDidMount() {
Expand Down
23 changes: 23 additions & 0 deletions bowtie/visual.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,19 @@ def on_select(self):
"""
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
Expand Down Expand Up @@ -467,3 +480,13 @@ def get_hover(self, data):
"""
return data

def get_layout(self, data):
"""Get the current layout.
Returns
-------
list
"""
return data
2 changes: 1 addition & 1 deletion pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / stateme
output-format=text

# Tells whether to display a full report or only the messages
reports=yes
reports=no

# Activate the evaluation score.
score=yes
Expand Down

0 comments on commit 0274010

Please sign in to comment.