mirror of https://github.com/apache/superset.git
commit
81dbd142e1
|
@ -1,9 +1,27 @@
|
||||||
import pandas
|
import pandas
|
||||||
|
from collections import defaultdict
|
||||||
import copy
|
import copy
|
||||||
|
import json
|
||||||
from pandas.io.json import dumps
|
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__(
|
def __init__(
|
||||||
self, df,
|
self, df,
|
||||||
chart_type="spline",
|
chart_type="spline",
|
||||||
|
@ -144,7 +162,8 @@ class Highchart(object):
|
||||||
if df.index.dtype.kind in "M":
|
if df.index.dtype.kind in "M":
|
||||||
x_axis["type"] = "datetime"
|
x_axis["type"] = "datetime"
|
||||||
if df.index.dtype.kind == 'O':
|
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)
|
print list(df.index)
|
||||||
if self.grid:
|
if self.grid:
|
||||||
x_axis["gridLineWidth"] = 1
|
x_axis["gridLineWidth"] = 1
|
||||||
|
@ -174,10 +193,38 @@ class Highchart(object):
|
||||||
chart["yAxis"].append(yAxis2)
|
chart["yAxis"].append(yAxis2)
|
||||||
|
|
||||||
|
|
||||||
@property
|
class HighchartBubble(BaseHighchart):
|
||||||
def javascript_cmd(self):
|
def __init__(self, df, target_div='chart', height=800):
|
||||||
js = dumps(self.chart)
|
self.df = df
|
||||||
js = js.replace('"{{TOOLTIP_FORMATTER}}"', self.tooltip_formatter).replace("\n", " ")
|
self.chart = {
|
||||||
if self.stockchart:
|
'chart': {
|
||||||
return "new Highcharts.StockChart(%s);" % js
|
'type': 'bubble',
|
||||||
return "new Highcharts.Chart(%s);" %js
|
'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
|
||||||
|
|
|
@ -39,12 +39,16 @@ form input.form-control {
|
||||||
<hr>
|
<hr>
|
||||||
<form id="query" method="GET" style="display: none;">
|
<form id="query" method="GET" style="display: none;">
|
||||||
<div>{{ form.viz_type.label }}: {{ form.viz_type(class_="form-control select2") }}</div>
|
<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 }}
|
<div>{{ form.granularity.label }}
|
||||||
<i class="fa fa-info-circle" data-toggle="tooltip" data-placement="right"
|
<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'"
|
title="Supports natural language time as in '10 seconds', '1 day' or '1 week'"
|
||||||
id="blah"></i>
|
id="blah"></i>
|
||||||
{{ form.granularity(class_="form-control select2_free_granularity") }}</div>
|
{{ form.granularity(class_="form-control select2_free_granularity") }}</div>
|
||||||
|
{% endif %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-xs-6">{{ form.since.label }}
|
<div class="col-xs-6">{{ form.since.label }}
|
||||||
|
@ -56,7 +60,9 @@ form input.form-control {
|
||||||
{{ form.until(class_="form-control select2_free_until") }}</div>
|
{{ form.until(class_="form-control select2_free_until") }}</div>
|
||||||
</div>
|
</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 %}
|
{% block extra_fields %}{% endblock %}
|
||||||
<hr>
|
<hr>
|
||||||
<h4>Filters</h4>
|
<h4>Filters</h4>
|
||||||
|
@ -85,11 +91,19 @@ form input.form-control {
|
||||||
|
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<h3>{{ viz.verbose_name }}
|
<h3>{{ viz.verbose_name }}
|
||||||
<span class="label label-success">{{ "{0:0.2f}".format(results.duration.total_seconds()) }} s</span>
|
{% if results %}
|
||||||
<span class="label label-info btn" data-toggle="modal" data-target="#query_modal">query</span>
|
<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>
|
</h3>
|
||||||
<hr/>
|
<hr/>
|
||||||
{% block viz %}
|
{% block viz %}
|
||||||
|
{% if error_msg %}
|
||||||
|
<span class="alert alert-danger">{{ error_msg }}</span>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% if debug %}
|
{% if debug %}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{% extends "panoramix/datasource.html" %}
|
{% extends "panoramix/datasource.html" %}
|
||||||
{% block viz %}
|
{% block viz %}
|
||||||
|
{{ super() }}
|
||||||
<div id="chart"></div>
|
<div id="chart"></div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -14,6 +15,21 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div>{{ form.limit.label }}: {{ form.limit(class_="form-control select2") }}</div>
|
<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 %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block tail %}
|
{% block tail %}
|
||||||
|
@ -23,6 +39,7 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
<script src="{{ url_for("static", filename="highcharts.js") }}"></script>
|
<script src="{{ url_for("static", filename="highcharts.js") }}"></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<script src="{{ url_for("static", filename="highcharts-more.js") }}"></script>
|
||||||
<script>
|
<script>
|
||||||
$( document ).ready(function() {
|
$( document ).ready(function() {
|
||||||
Highcharts.setOptions({
|
Highcharts.setOptions({
|
||||||
|
|
|
@ -194,8 +194,9 @@ class Panoramix(BaseView):
|
||||||
json.dumps(obj.get_query(), indent=4),
|
json.dumps(obj.get_query(), indent=4),
|
||||||
status=200,
|
status=200,
|
||||||
mimetype="application/json")
|
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()
|
pass
|
||||||
|
#return obj.render_no_data()
|
||||||
return obj.render()
|
return obj.render()
|
||||||
|
|
||||||
@has_access
|
@has_access
|
||||||
|
@ -221,8 +222,9 @@ class Panoramix(BaseView):
|
||||||
json.dumps(obj.get_query(), indent=4),
|
json.dumps(obj.get_query(), indent=4),
|
||||||
status=200,
|
status=200,
|
||||||
mimetype="application/json")
|
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_no_data()
|
||||||
|
|
||||||
return obj.render()
|
return obj.render()
|
||||||
|
|
||||||
@has_access
|
@has_access
|
||||||
|
|
88
app/viz.py
88
app/viz.py
|
@ -3,7 +3,7 @@ from flask import flash, request
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from app import utils
|
from app import utils
|
||||||
from app.highchart import Highchart
|
from app.highchart import Highchart, HighchartBubble
|
||||||
from wtforms import Form, SelectMultipleField, SelectField, TextField
|
from wtforms import Form, SelectMultipleField, SelectField, TextField
|
||||||
import config
|
import config
|
||||||
from pydruid.utils.filters import Dimension, Filter
|
from pydruid.utils.filters import Dimension, Filter
|
||||||
|
@ -67,21 +67,28 @@ def form_factory(datasource, form_args=None, extra_fields_dict=None):
|
||||||
class BaseViz(object):
|
class BaseViz(object):
|
||||||
verbose_name = "Base Viz"
|
verbose_name = "Base Viz"
|
||||||
template = "panoramix/datasource.html"
|
template = "panoramix/datasource.html"
|
||||||
|
hidden_fields = []
|
||||||
def __init__(self, datasource, form_data, view):
|
def __init__(self, datasource, form_data, view):
|
||||||
self.datasource = datasource
|
self.datasource = datasource
|
||||||
self.form_class = self.form_class()
|
self.form_class = self.form_class()
|
||||||
|
self.view = view
|
||||||
self.form_data = form_data
|
self.form_data = form_data
|
||||||
self.metrics = form_data.getlist('metrics') or ['count']
|
self.metrics = form_data.getlist('metrics') or ['count']
|
||||||
self.groupby = form_data.getlist('groupby') or []
|
self.groupby = form_data.getlist('groupby') or []
|
||||||
|
|
||||||
self.results = self.bake_query()
|
self.error_msg = ""
|
||||||
self.df = self.results.df
|
self.results = None
|
||||||
self.view = view
|
try:
|
||||||
if self.df is not None:
|
self.results = self.bake_query()
|
||||||
if 'timestamp' in self.df.columns:
|
self.df = self.results.df
|
||||||
self.df.timestamp = pd.to_datetime(self.df.timestamp)
|
if self.df is not None:
|
||||||
self.df_prep()
|
if 'timestamp' in self.df.columns:
|
||||||
self.form_prep()
|
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):
|
def form_class(self):
|
||||||
return form_factory(self.datasource, request.args)
|
return form_factory(self.datasource, request.args)
|
||||||
|
@ -190,6 +197,68 @@ class HighchartsViz(BaseViz):
|
||||||
compare = False
|
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):
|
class TimeSeriesViz(HighchartsViz):
|
||||||
verbose_name = "Time Series - Line Chart"
|
verbose_name = "Time Series - Line Chart"
|
||||||
chart_type = "spline"
|
chart_type = "spline"
|
||||||
|
@ -320,4 +389,5 @@ viz_types = OrderedDict([
|
||||||
['stacked_ts_bar', TimeSeriesStackedBarViz],
|
['stacked_ts_bar', TimeSeriesStackedBarViz],
|
||||||
['dist_bar', DistributionBarViz],
|
['dist_bar', DistributionBarViz],
|
||||||
['pie', DistributionPieViz],
|
['pie', DistributionPieViz],
|
||||||
|
['bubble', BubbleViz],
|
||||||
])
|
])
|
||||||
|
|
Loading…
Reference in New Issue