Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Bubble charts #15

Merged
merged 1 commit into from
Aug 14, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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],
])