Skip to content

Commit b84cc8e

Browse files
author
hatsy
committed
Ported plotting from plotly to bokeh passing only json from backend to frontend
1 parent 69e4ba2 commit b84cc8e

File tree

8 files changed

+106
-77
lines changed

8 files changed

+106
-77
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ ghostdriver.log
99
*.swo
1010
__pycache__/
1111
node_modules/
12+
*.pyc

cesium_app/handlers/plot_features.py

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,8 @@
44

55

66
class PlotFeaturesHandler(BaseHandler):
7-
def _get_featureset(self, featureset_id):
8-
try:
9-
f = Featureset.get(Featureset.id == featureset_id)
10-
except Featureset.DoesNotExist:
11-
raise AccessError('No such feature set')
12-
13-
if not f.is_owned_by(self.get_username()):
14-
raise AccessError('No such feature set')
15-
16-
return f
17-
18-
def get(self, featureset_id=None):
19-
fset = self._get_featureset(featureset_id)
20-
features_to_plot = sorted(fset.features_list)[0:4]
21-
data, layout = plot.feature_scatterplot(fset.file.uri, features_to_plot)
22-
23-
self.success({'data': data, 'layout': layout})
7+
def get(self, featureset_id):
8+
fset = Featureset.get_if_owned(featureset_id, self.get_username())
9+
features_to_plot = sorted(fset.features_list)[0:4] # TODO from form
10+
docs_json, render_items = plot.feature_scatterplot(fset.file.uri, features_to_plot)
11+
self.success({'docs_json': docs_json, 'render_items': render_items})

cesium_app/models.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,18 @@ class Featureset(BaseModel):
158158
def is_owned_by(self, username):
159159
return self.project.is_owned_by(username)
160160

161+
@staticmethod
162+
def get_if_owned(fset_id, username):
163+
try:
164+
f = Featureset.get(Featureset.id == fset_id)
165+
except Featureset.DoesNotExist:
166+
raise AccessError('No such feature set')
167+
168+
if not f.is_owned_by(username):
169+
raise AccessError('No such feature set')
170+
171+
return f
172+
161173

162174
class Model(BaseModel):
163175
"""ORM model of the Model table"""

cesium_app/plot.py

Lines changed: 56 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1+
from itertools import cycle, islice
12
import numpy as np
2-
import pandas as pd
3-
from sklearn.metrics import confusion_matrix
4-
import plotly
5-
import plotly.offline as py
6-
from plotly.tools import FigureFactory as FF
7-
83
from cesium import featurize
9-
from .config import cfg
4+
from bokeh.plotting import figure
5+
from bokeh.layouts import gridplot
6+
from bokeh.palettes import PuBu as palette
7+
from bokeh.core.json_encoder import serialize_json
8+
from bokeh.document import Document
9+
from bokeh.util.serialization import make_id
1010

1111

1212
def feature_scatterplot(fset_path, features_to_plot):
@@ -21,42 +21,56 @@ def feature_scatterplot(fset_path, features_to_plot):
2121
2222
Returns
2323
-------
24-
(fig.data, fig.layout)
25-
Returns (fig.data, fig.layout) where `fig` is an instance of
26-
`plotly.tools.FigureFactory`.
24+
(str, str)
25+
Returns (script, div) tags for the desired plot as output by
26+
`bokeh.embed.components`.
2727
"""
2828
fset, data = featurize.load_featureset(fset_path)
29-
fset = fset[features_to_plot]
29+
X = fset[features_to_plot]
30+
if 'target' in fset and fset.target.values.dtype != np.float:
31+
y = fset.target.values
32+
labels = np.unique(y)
33+
else:
34+
y = [None] * len(X)
35+
labels = [None]
3036

31-
if 'label' in data:
32-
fset['label'] = data['label']
33-
index = 'label'
37+
if len(labels) in palette:
38+
colors = palette[len(labels)]
3439
else:
35-
index = None
36-
37-
# TODO replace 'trace {i}' with class labels
38-
fig = FF.create_scatterplotmatrix(fset, diag='box', index=index,
39-
height=800, width=800)
40-
41-
py.plot(fig, auto_open=False, output_type='div')
42-
43-
return fig.data, fig.layout
44-
45-
46-
#def prediction_heatmap(pred_path):
47-
# with xr.open_dataset(pred_path) as pset:
48-
# pred_df = pd.DataFrame(pset.prediction.values, index=pset.name,
49-
# columns=pset.class_label.values)
50-
# pred_labels = pred_df.idxmax(axis=1)
51-
# C = confusion_matrix(pset.label, pred_labels)
52-
# row_sums = C.sum(axis=1)
53-
# C = C / row_sums[:, np.newaxis]
54-
# fig = FF.create_annotated_heatmap(C, x=[str(el) for el in
55-
# pset.class_label.values],
56-
# y=[str(el) for el in
57-
# pset.class_label.values],
58-
# colorscale='Viridis')
59-
#
60-
# py.plot(fig, auto_open=False, output_type='div')
61-
#
62-
# return fig.data, fig.layout
40+
all_colors = sorted(palette.items(), key=lambda x: x[0],
41+
reverse=True)[0][1]
42+
colors = list(islice(cycle(all_colors), len(labels)))
43+
44+
plots = np.array([[figure(width=300, height=200)
45+
for j in range(len(features_to_plot))]
46+
for i in range(len(features_to_plot))])
47+
for (i, j), p in np.ndenumerate(plots):
48+
for l, c in zip(labels, colors):
49+
if l is not None:
50+
inds = np.where(y == l)[0]
51+
else:
52+
inds = np.arange(len(X))
53+
p.circle(X.values[inds, i], X.values[inds, j], color=c,
54+
legend=(l if (i == j and l is not None) else None))
55+
p.legend.location = 'bottom_right'
56+
p.legend.label_text_font_size = '6pt'
57+
p.legend.spacing = 0
58+
p.legend.padding = 0
59+
p.xaxis.axis_label = features_to_plot[i]
60+
p.yaxis.axis_label = features_to_plot[j]
61+
62+
plot = gridplot(plots.tolist(), ncol=len(features_to_plot), mergetools=True)
63+
64+
# Convert plot to json objects necessary for rendering with bokeh on the
65+
# frontend
66+
render_items = [{'docid':plot._id, 'elementid':make_id()}]
67+
68+
doc = Document()
69+
doc.add_root(plot)
70+
docs_json_inner = doc.to_json()
71+
docs_json = {render_items[0]['docid']:docs_json_inner}
72+
73+
docs_json = serialize_json(docs_json)
74+
render_items = serialize_json(render_items)
75+
76+
return docs_json, render_items

public/index.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@
55
<title>Cesium</title>
66
<link rel="stylesheet" href="css/base.css" />
77
<link rel="icon" type="image/png" href="favicon.png"/>
8+
9+
<!-- Bokeh -->
10+
<link
11+
href="http://cdn.pydata.org/bokeh/release/bokeh-0.12.5.min.css"
12+
rel="stylesheet" type="text/css">
13+
<link
14+
href="http://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.5.min.css"
15+
rel="stylesheet" type="text/css">
16+
<script src="http://cdn.pydata.org/bokeh/release/bokeh-0.12.5.min.js"></script>
17+
<script src="http://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.5.min.js"></script>
18+
819
<link href='https://fonts.googleapis.com/css?family=Fira+Sans:400,700' rel='stylesheet' type='text/css'>
920
</head>
1021
<body>

public/scripts/Plot.jsx

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
11
import React, { Component } from 'react';
22
import { connect } from 'react-redux';
3-
import Plotly from './custom-plotly';
43
import { showNotification } from './Notifications';
54

5+
function bokeh_render_plot(node, docs_json, render_items) {
6+
// Create bokeh div element
7+
var bokeh_div = document.createElement("div");
8+
var inner_div = document.createElement("div");
9+
bokeh_div.setAttribute("class", "bk-root" );
10+
inner_div.setAttribute("class", "bk-plotdiv");
11+
inner_div.setAttribute("id", render_items[0].elementid);
12+
bokeh_div.appendChild(inner_div);
13+
node.appendChild(bokeh_div);
14+
15+
// Generate plot
16+
Bokeh.safely(function() {
17+
Bokeh.embed.embed_items(docs_json, render_items);
18+
});
19+
}
620

721
class Plot extends Component {
822
constructor(props) {
@@ -32,16 +46,17 @@ class Plot extends Component {
3246
if (!plotData) {
3347
return <b>Please wait while we load your plotting data...</b>;
3448
}
35-
36-
let { data, layout } = plotData;
49+
var docs_json = JSON.parse(plotData.docs_json);
50+
var render_items = JSON.parse(plotData.render_items);
3751

3852
return (
3953
plotData &&
4054
<div
4155
ref={
4256
(node) => {
43-
node && Plotly.plot(node, data, layout);
44-
}}
57+
node && bokeh_render_plot(node, docs_json, render_items)
58+
}
59+
}
4560
/>
4661
);
4762
}

public/scripts/custom-plotly.js

Lines changed: 0 additions & 13 deletions
This file was deleted.

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ distributed>=1.14.3
1212
selenium
1313
pytest
1414
joblib>=0.11
15+
bokeh==0.12.5

0 commit comments

Comments
 (0)