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

Allowing not to group by on table view #92

Merged
merged 3 commits into from
Dec 18, 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
3 changes: 0 additions & 3 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@
List of TODO items for Panoramix

## Improvments
* GROUPED boolean for table view
* datasource in explore mode could be a dropdown
* [sql] make "Test Connection" test further
* in/notin filters autocomplete
* [druid] Allow for post aggregations (ratios!)
* in/notin filters autocomplete
* [sql] make "Test Connection" test further

## Better Javascript enables
* Async on Druidify! in exploration page
Expand Down
16 changes: 14 additions & 2 deletions panoramix/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ def __init__(self, viz):
'Columns',
choices=self.choicify(datasource.groupby_column_names),
description="One or many fields to pivot as columns"),
'all_columns': SelectMultipleSortableField(
'Columns',
choices=self.choicify(datasource.column_names),
description="Columns to display"),
'granularity': FreeFormSelectField(
'Time Granularity', default="one day",
choices=self.choicify([
Expand Down Expand Up @@ -322,6 +326,10 @@ def __init__(self, viz):
"Range Filter", default=True,
description=(
"Whether to display the time range interactive selector")),
'include_search': BetterBooleanField(
"Search Box", default=False,
description=(
"Whether to include a client side search box")),
'show_bubbles': BetterBooleanField(
"Show Bubbles", default=False,
description=(
Expand All @@ -335,10 +343,14 @@ def __init__(self, viz):
"Whether to display the min and max values of the axis")),
'rich_tooltip': BetterBooleanField(
"Rich Tooltip", default=True,
description="The rich tooltip shows a list of all series for that point in time"),
description=(
"The rich tooltip shows a list of all series for that"
" point in time")),
'y_axis_zero': BetterBooleanField(
"Y Axis Zero", default=False,
description="Force the Y axis to start at 0 instead of the minimum value"),
description=(
"Force the Y axis to start at 0 instead of the minimum "
"value")),
'y_log_scale': BetterBooleanField(
"Y Log", default=False,
description="Use a log scale for the Y axis"),
Expand Down
19 changes: 14 additions & 5 deletions panoramix/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,8 @@ def query(
is_timeseries=True,
timeseries_limit=15, row_limit=None,
inner_from_dttm=None, inner_to_dttm=None,
extras=None):
extras=None,
columns=None):

# For backward compatibility
if granularity not in self.dttm_cols:
Expand Down Expand Up @@ -427,15 +428,20 @@ def query(
select_exprs.append(outer)
inner_groupby_exprs.append(inner)
inner_select_exprs.append(inner)
elif columns:
for s in columns:
select_exprs.append(s)
metrics_exprs = []

if is_timeseries:
if is_timeseries and groupby:
select_exprs += [timestamp]
groupby_exprs += [timestamp]

select_exprs += metrics_exprs
qry = select(select_exprs)
from_clause = table(self.table_name)
qry = qry.group_by(*groupby_exprs)
if groupby:
qry = qry.group_by(*groupby_exprs)

time_filter = [
timestamp >= from_dttm.isoformat(),
Expand Down Expand Up @@ -466,7 +472,8 @@ def query(
having_clause_and += [text(extras['having'])]
qry = qry.where(and_(*(time_filter + where_clause_and)))
qry = qry.having(and_(*having_clause_and))
qry = qry.order_by(desc(main_metric_expr))
if groupby:
qry = qry.order_by(desc(main_metric_expr))
qry = qry.limit(row_limit)

if timeseries_limit and groupby:
Expand All @@ -492,6 +499,7 @@ def query(
con=engine
)
sql = sqlparse.format(sql, reindent=True)
print(sql)
return QueryResult(
df=df, duration=datetime.now() - qry_start_dttm, query=sql)

Expand Down Expand Up @@ -779,7 +787,8 @@ def query(
timeseries_limit=None,
row_limit=None,
inner_from_dttm=None, inner_to_dttm=None,
extras=None):
extras=None,
select=None):
qry_start_dttm = datetime.now()

inner_from_dttm = inner_from_dttm or from_dttm
Expand Down
6 changes: 6 additions & 0 deletions panoramix/static/panoramix.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
html>body{
margin: 0px; !important
}
.container-fluid {
text-align: left;
}
input[type="checkbox"] {
display: inline-block;
width: 16px;
Expand Down
28 changes: 13 additions & 15 deletions panoramix/static/widgets/viz_pivot_table.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,24 @@ px.registerWidget('pivot_table', function(data_attribute) {
var form_data = data_attribute.form_data;

function refresh(done) {
token.load(data_attribute.json_endpoint, function(response, status, xhr){
if(status=="error"){
$.getJSON(data_attribute.json_endpoint, function(json){
token.html(json.data);
if (form_data.groupby.length == 1){
var table = token.find('table').DataTable({
paging: false,
searching: false,
});
table.column('-1').order( 'desc' ).draw();
}
token.show();
done(json);
}).fail(function(xhr){
var err = '<div class="alert alert-danger">' + xhr.responseText + '</div>';
token.html(err);
token.show();
}
else{
if (form_data.groupby.length == 1){
var table = token.find('table').DataTable({
paging: false,
searching: false,
});
table.column('-1').order( 'desc' ).draw();
}
}
token.show();
done();
done();
});
}

return {
render: refresh,
resize: refresh,
Expand Down
63 changes: 49 additions & 14 deletions panoramix/static/widgets/viz_table.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,63 @@ px.registerWidget('table', function(data_attribute) {
var token = $('#' + token_name);

function refresh(done) {
token.load(data_attribute.json_endpoint, function(response, status, xhr){
if(status=="error"){
var err = '<div class="alert alert-danger">' + xhr.responseText + '</div>';
token.html(err);
token.show();
done();
$.getJSON(data_attribute.json_endpoint, function(json){
var data = json.data;
var metrics = json.form_data.metrics;
function col(c){
arr = [];
for (var i=0; i<data.records.length; i++){
arr.push(json.data.records[i][c]);
}
return arr;
}
else{
var table = token.find('table').DataTable({
paging: false,
searching: false,
});
table.column('-1').order( 'desc' ).draw();
maxes = {};
for (var i=0; i<metrics.length; i++){
maxes[metrics[i]] = d3.max(col(metrics[i]));
}

var table = d3.select('#' + token_name).append('table')
.attr('class', 'dataframe table table-striped table-bordered table-condensed table-hover');
table.append('thead').append('tr')
.selectAll('th')
.data(data.columns).enter()
.append('th')
.text(function(d){return d});
table.append('tbody')
.selectAll('tr')
.data(data.records).enter()
.append('tr')
.selectAll('td')
.data(function(row, i) {
return data.columns.map(function(c) {return [c, row];});
}).enter()
.append('td')
.style('background-image', function(d){
var perc = Math.round((d[1][d[0]] / maxes[d[0]]) * 100);
if (perc !== NaN)
return "linear-gradient(to right, lightgrey, lightgrey " + perc + "%, rgba(0,0,0,0) " + perc + "%"
})
.html(function(d){return d[1][d[0]]});
var datatable = token.find('table').DataTable({
paging: false,
searching: data_attribute.form_data.include_search,
});
// Sorting table by main column
if (data_attribute.form_data.metrics.length > 0) {
var main_metric = data_attribute.form_data.metrics[0];
datatable.column(data.columns.indexOf(main_metric)).order( 'desc' ).draw();
}
done(json);
}).fail(function(xhr){
var err = '<div class="alert alert-danger">' + xhr.responseText + '</div>';
token.html(err);
token.show();
done();
});
}

return {
render: refresh,
resize: refresh,
resize: function(){},
};

});
11 changes: 0 additions & 11 deletions panoramix/templates/panoramix/viz_pivot_table.html
Original file line number Diff line number Diff line change
@@ -1,18 +1,7 @@
{% macro viz_html(viz) %}
{% if viz.request.args.get("async") == "true" %}
{{ viz.get_df().to_html(na_rep='', classes="dataframe table table-striped table-bordered table-condensed")|safe }}
{% else %}
<div id="{{ viz.token }}" style="display: none;overflow: auto; height: 100%;"></div>
{% endif %}
{% endmacro %}

{% macro viz_js(viz) %}
{% if viz.form_data.get("async") != "true" %}
<script>
$( document ).ready(function() {
});
</script>
{% endif %}
{% endmacro %}

{% macro viz_css(viz) %}
Expand Down
2 changes: 1 addition & 1 deletion panoramix/templates/panoramix/viz_table.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{% if viz.request.args.get("async") == "true" %}
{% set df = viz.get_df() %}
<div class="table">
<table class="dataframe table table-striped table-bordered table-condensed">
<table class="dataframe table table-striped table-bordered table-condensed table-hover">
<thead>
<tr>
{% for col in df.columns if not col.endswith('__perc') %}
Expand Down
50 changes: 38 additions & 12 deletions panoramix/viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,25 +242,43 @@ class TableViz(BaseViz):
'fields': (
'granularity',
('since', 'until'),
'metrics', 'groupby',
'row_limit'
'row_limit',
('include_search', None),
)
},
{
'label': "GROUP BY",
'fields': (
'groupby',
'metrics',
)
},
{
'label': "NOT GROUPED BY",
'fields': (
'all_columns',
)
},)
css_files = ['lib/dataTables/dataTables.bootstrap.css']
is_timeseries = False
js_files = [
'lib/d3.min.js',
'lib/dataTables/jquery.dataTables.min.js',
'lib/dataTables/dataTables.bootstrap.js',
'widgets/viz_table.js',
]
css_files = ['widgets/viz_table.css']

@property
def json_endpoint(self):
return self.get_url(async='true', standalone='true', skip_libs='true')

def query_obj(self):
d = super(TableViz, self).query_obj()
fd = self.form_data
if fd.get('all_columns') and (fd.get('groupby') or fd.get('metrics')):
raise Exception(
"Choose either fields to [Group By] and [Metrics] or "
"[Columns], not both")
if fd.get('all_columns'):
d['columns'] = fd.get('all_columns')
d['groupby'] = []
d['is_timeseries'] = False
d['timeseries_limit'] = None
return d
Expand All @@ -271,10 +289,15 @@ def get_df(self):
self.form_data.get("granularity") == "all" and
'timestamp' in df):
del df['timestamp']
for m in self.metrics:
df[m + '__perc'] = np.rint((df[m] / np.max(df[m])) * 100)
return df

def get_json_data(self):
df = self.get_df()
return dumps(dict(
records=df.to_dict(orient="records"),
columns=df.columns,
))


class PivotTableViz(BaseViz):
viz_type = "pivot_table"
Expand All @@ -301,10 +324,6 @@ class PivotTableViz(BaseViz):
)
},)

@property
def json_endpoint(self):
return self.get_url(async='true', standalone='true', skip_libs='true')

def query_obj(self):
d = super(PivotTableViz, self).query_obj()
groupby = self.form_data.get('groupby')
Expand Down Expand Up @@ -343,6 +362,13 @@ def get_df(self):
)
return df

def get_json_data(self):
return dumps(self.get_df().to_html(
na_rep='',
classes=(
"dataframe table table-striped table-bordered "
"table-condensed table-hover")))


class MarkupViz(BaseViz):
viz_type = "markup"
Expand Down