Skip to content

Commit

Permalink
Adding Bubble charts
Browse files Browse the repository at this point in the history
  • Loading branch information
mistercrunch committed Aug 14, 2015
1 parent 36a58e8 commit 0cfa0e6
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 25 deletions.
65 changes: 56 additions & 9 deletions app/highchart.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
import pandas
from collections import defaultdict
import copy
import json
from pandas.io.json import dumps


class Highchart(object):
class BaseHighchart(object):
stockchart = False
tooltip_formatter = ""
target_div = 'chart'
@property
def javascript_cmd(self):
js = dumps(self.chart)
js = (
js.replace('"{{TOOLTIP_FORMATTER}}"', self.tooltip_formatter)
.replace("\n", " ")
)
if self.stockchart:
return "new Highcharts.StockChart(%s);" % js
return "new Highcharts.Chart(%s);" %js


class Highchart(BaseHighchart):
def __init__(
self, df,
chart_type="spline",
Expand Down Expand Up @@ -144,7 +162,8 @@ def serialize_xaxis(self):
if df.index.dtype.kind in "M":
x_axis["type"] = "datetime"
if df.index.dtype.kind == 'O':
x_axis['categories'] = sorted(list(df.index)) if self.sort_columns else list(df.index)
x_axis['categories'] = sorted(
list(df.index)) if self.sort_columns else list(df.index)
print list(df.index)
if self.grid:
x_axis["gridLineWidth"] = 1
Expand Down Expand Up @@ -174,10 +193,38 @@ def serialize_yaxis(self):
chart["yAxis"].append(yAxis2)


@property
def javascript_cmd(self):
js = dumps(self.chart)
js = js.replace('"{{TOOLTIP_FORMATTER}}"', self.tooltip_formatter).replace("\n", " ")
if self.stockchart:
return "new Highcharts.StockChart(%s);" % js
return "new Highcharts.Chart(%s);" %js
class HighchartBubble(BaseHighchart):
def __init__(self, df, target_div='chart', height=800):
self.df = df
self.chart = {
'chart': {
'type': 'bubble',
'zoomType': 'xy'
},
'title': {'text': None},
'plotOptions': {
'bubble': {
'tooltip': {
'headerFormat': '<b>{series.name}</b><br>',
'pointFormat': '<b>{point.name}</b>: {point.x}, {point.y}, {point.z}'
}
}
},
}
chart = self.chart
chart['series'] = self.series()
chart['chart']['renderTo'] = target_div
if height:
chart['chart']["height"] = height

def series(self):
#df = self.df[['name', 'x', 'y', 'z']]
df = self.df
series = defaultdict(list)
for row in df.to_dict(orient='records'):
series[row['group']].append(row)
l = []
for k, v in series.items():
l.append({'data': v, 'name': k})
print(json.dumps(l, indent=2))
return l
22 changes: 18 additions & 4 deletions app/templates/panoramix/datasource.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,16 @@ <h3>
<hr>
<form id="query" method="GET" style="display: none;">
<div>{{ form.viz_type.label }}: {{ form.viz_type(class_="form-control select2") }}</div>
<div>{{ form.metrics.label }}: {{ form.metrics(class_="form-control select2") }}</div>
{% if 'metrics' not in viz.hidden_fields %}
<div>{{ form.metrics.label }}: {{ form.metrics(class_="form-control select2") }}</div>
{% endif %}
{% if 'granularity' not in viz.hidden_fields %}
<div>{{ form.granularity.label }}
<i class="fa fa-info-circle" data-toggle="tooltip" data-placement="right"
title="Supports natural language time as in '10 seconds', '1 day' or '1 week'"
id="blah"></i>
{{ form.granularity(class_="form-control select2_free_granularity") }}</div>
{% endif %}
<div class="row">
<div class="form-group">
<div class="col-xs-6">{{ form.since.label }}
Expand All @@ -56,7 +60,9 @@ <h3>
{{ form.until(class_="form-control select2_free_until") }}</div>
</div>
</div>
<div>{{ form.groupby.label }}: {{ form.groupby(class_="form-control select2") }}</div>
{% if 'groupby' not in viz.hidden_fields %}
<div>{{ form.groupby.label }}: {{ form.groupby(class_="form-control select2") }}</div>
{% endif %}
{% block extra_fields %}{% endblock %}
<hr>
<h4>Filters</h4>
Expand Down Expand Up @@ -85,11 +91,19 @@ <h4>Filters</h4>

<div class="col-md-9">
<h3>{{ viz.verbose_name }}
<span class="label label-success">{{ "{0:0.2f}".format(results.duration.total_seconds()) }} s</span>
<span class="label label-info btn" data-toggle="modal" data-target="#query_modal">query</span>
{% if results %}
<span class="label label-success">
{{ "{0:0.2f}".format(results.duration.total_seconds()) }} s
</span>
<span class="label label-info btn"
data-toggle="modal" data-target="#query_modal">query</span>
{% endif %}
</h3>
<hr/>
{% block viz %}
{% if error_msg %}
<span class="alert alert-danger">{{ error_msg }}</span>
{% endif %}
{% endblock %}

{% if debug %}
Expand Down
17 changes: 17 additions & 0 deletions app/templates/panoramix/viz_highcharts.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{% extends "panoramix/datasource.html" %}
{% block viz %}
{{ super() }}
<div id="chart"></div>
{% endblock %}

Expand All @@ -14,6 +15,21 @@
</div>
{% endif %}
<div>{{ form.limit.label }}: {{ form.limit(class_="form-control select2") }}</div>
{% if form.series %}
<div>{{ form.series.label }}: {{ form.series(class_="form-control select2") }}</div>
{% endif %}
{% if form.entity %}
<div>{{ form.entity.label }}: {{ form.entity(class_="form-control select2") }}</div>
{% endif %}
{% if form.x %}
<div>{{ form.x.label }}: {{ form.x(class_="form-control select2") }}</div>
{% endif %}
{% if form.y %}
<div>{{ form.y.label }}: {{ form.y(class_="form-control select2") }}</div>
{% endif %}
{% if form.size %}
<div>{{ form.size.label }}: {{ form.size(class_="form-control select2") }}</div>
{% endif %}
{% endblock %}

{% block tail %}
Expand All @@ -23,6 +39,7 @@
{% else %}
<script src="{{ url_for("static", filename="highcharts.js") }}"></script>
{% endif %}
<script src="{{ url_for("static", filename="highcharts-more.js") }}"></script>
<script>
$( document ).ready(function() {
Highcharts.setOptions({
Expand Down
8 changes: 5 additions & 3 deletions app/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,9 @@ def table(self, table_id):
json.dumps(obj.get_query(), indent=4),
status=200,
mimetype="application/json")
if obj.df is None or obj.df.empty:
return obj.render_no_data()
if not hasattr(obj, 'df') or obj.df is None or obj.df.empty:
pass
#return obj.render_no_data()
return obj.render()

@has_access
Expand All @@ -221,8 +222,9 @@ def datasource(self, datasource_name):
json.dumps(obj.get_query(), indent=4),
status=200,
mimetype="application/json")
if obj.df is None or obj.df.empty:
if not hasattr(obj, 'df') or obj.df is None or obj.df.empty:
return obj.render_no_data()

return obj.render()

@has_access
Expand Down
88 changes: 79 additions & 9 deletions app/viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import pandas as pd
from collections import OrderedDict
from app import utils
from app.highchart import Highchart
from app.highchart import Highchart, HighchartBubble
from wtforms import Form, SelectMultipleField, SelectField, TextField
import config
from pydruid.utils.filters import Dimension, Filter
Expand Down Expand Up @@ -67,21 +67,28 @@ class QueryForm(OmgWtForm):
class BaseViz(object):
verbose_name = "Base Viz"
template = "panoramix/datasource.html"
hidden_fields = []
def __init__(self, datasource, form_data, view):
self.datasource = datasource
self.form_class = self.form_class()
self.view = view
self.form_data = form_data
self.metrics = form_data.getlist('metrics') or ['count']
self.groupby = form_data.getlist('groupby') or []

self.results = self.bake_query()
self.df = self.results.df
self.view = view
if self.df is not None:
if 'timestamp' in self.df.columns:
self.df.timestamp = pd.to_datetime(self.df.timestamp)
self.df_prep()
self.form_prep()
self.error_msg = ""
self.results = None
try:
self.results = self.bake_query()
self.df = self.results.df
if self.df is not None:
if 'timestamp' in self.df.columns:
self.df.timestamp = pd.to_datetime(self.df.timestamp)
self.df_prep()
self.form_prep()
except Exception as e:
self.error_msg = str(e)


def form_class(self):
return form_factory(self.datasource, request.args)
Expand Down Expand Up @@ -190,6 +197,68 @@ class HighchartsViz(BaseViz):
compare = False


class BubbleViz(HighchartsViz):
verbose_name = "Bubble Chart"
chart_type = 'bubble'
hidden_fields = ['granularity', 'metrics', 'groupby']

def form_class(self):
datasource = self.datasource
limits = [0, 5, 10, 25, 50, 100, 500]
return form_factory(self.datasource, request.args,
extra_fields_dict={
#'compare': TextField('Period Compare',),
'series': SelectField(
'Series', choices=[
(s, s) for s in datasource.groupby_column_names]),
'entity': SelectField(
'Entity', choices=[
(s, s) for s in datasource.groupby_column_names]),
'x': SelectField(
'X Axis', choices=datasource.metrics_combo),
'y': SelectField(
'Y Axis', choices=datasource.metrics_combo),
'size': SelectField(
'Bubble Size', choices=datasource.metrics_combo),
'limit': SelectField(
'Limit', choices=[(s, s) for s in limits]),
})

def query_obj(self):
d = super(BubbleViz, self).query_obj()
d['granularity'] = 'all'
d['groupby'] = [request.args.get('series')]
self.x_metric = request.args.get('x')
self.y_metric = request.args.get('y')
self.z_metric = request.args.get('size')
self.entity = request.args.get('entity')
self.series = request.args.get('series')
d['metrics'] = [
self.x_metric,
self.y_metric,
self.z_metric,
]
if not all(d['metrics'] + [self.entity, self.series]):
raise Exception("Pick a metric for x, y and size")
return d

def render(self):
metrics = self.metrics

if not self.error_msg:
df = self.df
df['x'] = df[[self.x_metric]]
df['y'] = df[[self.y_metric]]
df['z'] = df[[self.z_metric]]
df['name'] = df[[self.entity]]
df['group'] = df[[self.series]]
chart = HighchartBubble(df)
return super(BubbleViz, self).render(chart_js=chart.javascript_cmd)
else:
return super(BubbleViz, self).render(error_msg=self.error_msg)



class TimeSeriesViz(HighchartsViz):
verbose_name = "Time Series - Line Chart"
chart_type = "spline"
Expand Down Expand Up @@ -320,4 +389,5 @@ def render(self):
['stacked_ts_bar', TimeSeriesStackedBarViz],
['dist_bar', DistributionBarViz],
['pie', DistributionPieViz],
['bubble', BubbleViz],
])

0 comments on commit 0cfa0e6

Please sign in to comment.