All sorts of improvements

This commit is contained in:
Maxime 2015-07-27 05:25:32 +00:00
parent c29444e1a7
commit 1c6177ce4b
6 changed files with 82 additions and 21 deletions

View File

@ -1,8 +1,8 @@
# TODO # TODO
* in/notin filters autocomplete * in/notin filters autocomplete
* Highstock, sort legend based on y value: ![stackoverflow](http://stackoverflow.com/questions/6867607/want-to-sort-highcharts-tooltip-results)
* compare time ranges * compare time ranges
* Add verbose_name and label method to metrics and columns * Add verbose_name and label method to metrics and columns
* CSV * CSV
* Save / bookmark / url shortener * Save / bookmark / url shortener
* on save, process metadata / generate metrics * on save, process metadata / generate metrics
* Allow for post aggregations (ratios!)

View File

@ -24,6 +24,7 @@ class Highchart(object):
logy=False, logy=False,
xlim=None, xlim=None,
ylim=None, ylim=None,
sort_legend_y=False,
grid=False, grid=False,
zoom=None): zoom=None):
self.df = df self.df = df
@ -42,6 +43,7 @@ class Highchart(object):
self.polar = polar self.polar = polar
self.grid = grid self.grid = grid
self.stacked = stacked self.stacked = stacked
self.sort_legend_y = sort_legend_y
chart['chart'] = {} chart['chart'] = {}
chart['chart']["type"] = chart_type chart['chart']["type"] = chart_type
@ -61,6 +63,11 @@ class Highchart(object):
if tooltip: if tooltip:
chart['tooltip'] = tooltip chart['tooltip'] = tooltip
if sort_legend_y:
if 'tooltip' not in chart:
chart['tooltip'] = {
'formatter': "{{TOOLTIP_FORMATTER}}"
}
if self.zoom: if self.zoom:
chart["zoomType"] = self.zoom chart["zoomType"] = self.zoom
@ -70,6 +77,39 @@ class Highchart(object):
self.chart = chart self.chart = chart
@property
def tooltip_formatter(self):
if self.compare == 'percent':
tf = """
function() {
var s = '<b>' + Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', new Date(this.x))+'</b><br/>';
var sortedPoints = this.points.sort(function(a, b){
return ((a.point.change > b.point.change) ? -1 : ((a.point.change < b.point.change) ? 1 : 0));
});
$.each(sortedPoints , function(i, point) {
s += '<span style="color:'+ point.series.color +'">\u25CF</span> ' + point.series.name + ': ' + f(point.y) + ' (<b>' + Highcharts.numberFormat(point.point.change, 2) + '%</b>)' + '<br/>';
});
return s;
}
"""
else:
tf = """
function() {
var s = '<b>' + Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', new Date(this.x))+'</b><br/>';
var sortedPoints = this.points.sort(function(a, b){
return ((a.y > b.y) ? -1 : ((a.y < b.y) ? 1 : 0));
});
$.each(sortedPoints , function(i, point) {
s += '<span style="color:'+ point.series.color +'">\u25CF</span> ' + point.series.name + ': ' + f(point.y) + '<br/>';
});
return s;
}
"""
return tf
def serialize_series(self): def serialize_series(self):
df = self.df df = self.df
chart = self.chart chart = self.chart
@ -114,14 +154,6 @@ class Highchart(object):
if self.xlim: if self.xlim:
x_axis["min"] = self.xlim[0] x_axis["min"] = self.xlim[0]
x_axis["max"] = self.xlim[1] x_axis["max"] = self.xlim[1]
'''
if "rot" in kwargs:
x_axis["labels"] = {"rotation": kwargs["rot"]}
if "fontsize" in kwargs:
x_axis.setdefault("labels", {})["style"] = {"fontSize": kwargs["fontsize"]}
if "xticks" in kwargs:
x_axis["tickPositions"] = kwargs["xticks"]
'''
self.chart['xAxis'] = x_axis self.chart['xAxis'] = x_axis
def serialize_yaxis(self): def serialize_yaxis(self):
@ -135,14 +167,6 @@ class Highchart(object):
if self.ylim: if self.ylim:
yAxis["min"] = self.ylim[0] yAxis["min"] = self.ylim[0]
yAxis["max"] = self.ylim[1] yAxis["max"] = self.ylim[1]
'''
if "rot" in kwargs:
yAxis["labels"] = {"rotation": kwargs["rot"]}
if "fontsize" in kwargs:
yAxis.setdefault("labels", {})["style"] = {"fontSize": kwargs["fontsize"]}
if "yticks" in kwargs:
yAxis["tickPositions"] = kwargs["yticks"]
'''
chart["yAxis"] = [yAxis] chart["yAxis"] = [yAxis]
if self.secondary_y: if self.secondary_y:
yAxis2 = copy.deepcopy(yAxis) yAxis2 = copy.deepcopy(yAxis)
@ -153,6 +177,7 @@ class Highchart(object):
@property @property
def javascript_cmd(self): def javascript_cmd(self):
js = dumps(self.chart) js = dumps(self.chart)
js = js.replace('"{{TOOLTIP_FORMATTER}}"', self.tooltip_formatter).replace("\n", " ")
if self.stockchart: if self.stockchart:
return "new Highcharts.StockChart(%s);" % js return "new Highcharts.StockChart(%s);" % js
return "new Highcharts.Chart(%s);" %js return "new Highcharts.Chart(%s);" %js

5
app/static/d3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -23,7 +23,12 @@ form input.form-control {
<div class="col-md-3"> <div class="col-md-3">
<h3> <h3>
{{ datasource.datasource_name }} {{ datasource.datasource_name }}
<a href="/datasourcemodelview/edit/{{ datasource.id }}"><span class="glyphicon glyphicon-edit"></span></a> {% if datasource.description %}
<i class="fa fa-info-circle" data-toggle="tooltip" data-placement="bottom" title="{{ datasource.description }}"></i>
{% endif %}
<a href="/datasourcemodelview/edit/{{ datasource.id }}">
<i class="fa fa-edit"></i>
</a>
</h3> </h3>
<hr> <hr>
@ -59,6 +64,7 @@ form input.form-control {
</button> </button>
<hr> <hr>
<button type="button" class="btn btn-primary" id="druidify">Druidify!</button> <button type="button" class="btn btn-primary" id="druidify">Druidify!</button>
<button type="button" class="btn btn-default" id="bookmark">Bookmark</button>
<hr style="margin-bottom: 0px;"> <hr style="margin-bottom: 0px;">
<img src="{{ url_for("static", filename="panoramix.png") }}" width=250> <img src="{{ url_for("static", filename="panoramix.png") }}" width=250>
</form><br> </form><br>
@ -87,8 +93,10 @@ form input.form-control {
{% block tail_js %} {% block tail_js %}
{{ super() }} {{ super() }}
<script src="{{ url_for("static", filename="d3.min.js") }}"></script>
<script> <script>
$( document ).ready(function() { $( document ).ready(function() {
f = d3.format(".4s");
function getParam(name) { function getParam(name) {
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
@ -98,6 +106,7 @@ $( document ).ready(function() {
$(".select2").select2(); $(".select2").select2();
$("form").slideDown("slow"); $("form").slideDown("slow");
$('[data-toggle="tooltip"]').tooltip();
function set_filters(){ function set_filters(){
for (var i=1; i<10; i++){ for (var i=1; i<10; i++){
@ -125,6 +134,7 @@ $( document ).ready(function() {
}); });
} }
$("#plus").click(add_filter); $("#plus").click(add_filter);
$("#bookmark").click(function () {alert("Not implemented yet...");})
add_filter(); add_filter();
$("#druidify").click(function () { $("#druidify").click(function () {
var i = 1; var i = 1;

View File

@ -8,7 +8,7 @@ from flask.ext.appbuilder import ModelView, CompactCRUDMixin, BaseView, expose
from app import appbuilder, db, models, viz, utils from app import appbuilder, db, models, viz, utils
from flask.ext.appbuilder.security.decorators import has_access, permission_name from flask.ext.appbuilder.security.decorators import has_access, permission_name
import config import config
from wtforms.fields import Field from pydruid.client import doublesum
class ColumnInlineView(CompactCRUDMixin, ModelView): class ColumnInlineView(CompactCRUDMixin, ModelView):
@ -44,7 +44,7 @@ class DatasourceModelView(ModelView):
'datasource_name', 'description', 'owner', 'is_featured', 'is_hidden', 'datasource_name', 'description', 'owner', 'is_featured', 'is_hidden',
'default_endpoint'] 'default_endpoint']
page_size = 100 page_size = 100
order_columns = ['datasource_name'] base_order = ('datasource_name', 'asc')
appbuilder.add_view( appbuilder.add_view(
@ -102,6 +102,21 @@ class Panoramix(BaseView):
flash("Refreshed metadata from Druid!", 'info') flash("Refreshed metadata from Druid!", 'info')
return redirect("/datasourcemodelview/list/") return redirect("/datasourcemodelview/list/")
@expose("/autocomplete/<datasource>/<column>/")
def autocomplete(self, datasource, column):
client = utils.get_pydruid_client()
top = client.topn(
datasource=datasource,
granularity='all',
intervals='2013-10-04/2020-10-10',
aggregations={"count": doublesum("count")},
dimension=column,
metric='count',
threshold=1000,
)
values = sorted([d[column] for d in top[0]['result']])
return json.dumps(values)
appbuilder.add_view_no_menu(Panoramix) appbuilder.add_view_no_menu(Panoramix)
appbuilder.add_link( appbuilder.add_link(
"Refresh Metadata", "Refresh Metadata",

View File

@ -221,6 +221,7 @@ class TimeSeriesViz(HighchartsViz):
verbose_name = "Time Series - Line Chart" verbose_name = "Time Series - Line Chart"
chart_type = "spline" chart_type = "spline"
stockchart = True stockchart = True
sort_legend_y = True
def render(self): def render(self):
metrics = self.metrics metrics = self.metrics
@ -228,13 +229,17 @@ class TimeSeriesViz(HighchartsViz):
df = df.pivot_table( df = df.pivot_table(
index="timestamp", index="timestamp",
columns=self.groupby, columns=self.groupby,
values=metrics) values=metrics,)
rolling_periods = request.args.get("rolling_periods") rolling_periods = request.args.get("rolling_periods")
rolling_type = request.args.get("rolling_type") rolling_type = request.args.get("rolling_type")
if rolling_periods and rolling_type: if rolling_periods and rolling_type:
if rolling_type == 'mean': if rolling_type == 'mean':
df = pd.rolling_mean(df, int(rolling_periods)) df = pd.rolling_mean(df, int(rolling_periods))
elif rolling_type == 'std':
df = pd.rolling_std(df, int(rolling_periods))
elif rolling_type == 'sum':
df = pd.rolling_sum(df, int(rolling_periods))
chart = Highchart( chart = Highchart(
df, df,
@ -242,6 +247,7 @@ class TimeSeriesViz(HighchartsViz):
chart_type=self.chart_type, chart_type=self.chart_type,
stacked=self.stacked, stacked=self.stacked,
stockchart=self.stockchart, stockchart=self.stockchart,
sort_legend_y=self.sort_legend_y,
**CHART_ARGS) **CHART_ARGS)
return super(TimeSeriesViz, self).render(chart_js=chart.javascript_cmd) return super(TimeSeriesViz, self).render(chart_js=chart.javascript_cmd)