Merge pull request #83 from mistercrunch/js

Big JS refactor
This commit is contained in:
Maxime Beauchemin 2015-12-15 13:59:06 -08:00
commit f1ceac996c
22 changed files with 223 additions and 130 deletions

17
TODO.md
View File

@ -2,18 +2,23 @@
List of TODO items for Panoramix List of TODO items for Panoramix
## Improvments ## Improvments
* [druid] Allow for post aggregations (ratios!)
* [sql] define column based grouping
* [sql] make "Test Connection" test further * [sql] make "Test Connection" test further
* csv export out of table view * csv export
* json export (options)
* in/notin filters autocomplete * in/notin filters autocomplete
* [druid] Allow for post aggregations (ratios!)
## Better Javascript enables ## Better Javascript enables
* Async on Druidify! in exploration page * Async on Druidify! in exploration page
* Async form reload onchange of viz_type dropdown
* Reintroduce query and stopwatch
* Fix resize / refresh
* Configurable widget auto refresh in Dashboard view * Configurable widget auto refresh in Dashboard view
## New Features ## New Features
* Annotations layers * Annotations layers
## Low value
* [sql] define column based grouping
## Community
* Creat a proper doc
* Usage vid
*

View File

@ -11,6 +11,15 @@ form div {
color: white; color: white;
} }
.header span{
margin-left: 3px;
}
#timer {
width: 80px;
text-align: right;
}
.notbtn { .notbtn {
cursor: default; cursor: default;
} }

View File

@ -1,3 +1,4 @@
var timer;
var px = (function() { var px = (function() {
var visualizations = []; var visualizations = [];
@ -14,13 +15,38 @@ var px = (function() {
} }
function initializeWidget(data) { function initializeWidget(data) {
var token = $('#' + data.token);
var name = data['viz_name']; var name = data['viz_name'];
var initializer = visualizations[name]; var initializer = visualizations[name];
var widget = initializer ? initializer(data) : makeNullWidget(); var user_defined_widget = initializer ? initializer(data) : makeNullWidget();
var dttm = 0;
var timer;
var stopwatch = function () {
dttm += 10;
$('#timer').text(Math.round(dttm/10)/100 + " sec");
}
var done = function (data) {
clearInterval(timer);
token.find("img.loading").hide();
if(data !== undefined)
$("#query_container").html(data.query);
$('#timer').removeClass('btn-warning');
$('span.query').removeClass('disabled');
$('#timer').addClass('btn-success');
}
widget = {
render: function() {
timer = setInterval(stopwatch, 10);
user_defined_widget.render(done);
},
resize: function() {
user_defined_widget.resize();
},
};
return widget; return widget;
} }
function initializeDatasourceView() { function initializeDatasourceView() {
function getParam(name) { function getParam(name) {
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),

View File

@ -1,4 +1,4 @@
.viz_bignumber g.axis text { .big_number g.axis text {
font-size: 10px; font-size: 10px;
font-weight: normal; font-weight: normal;
color: gray; color: gray;
@ -8,18 +8,18 @@
font-weight: none; font-weight: none;
} }
.viz_bignumber text.big { .big_number text.big {
stroke: black; stroke: black;
text-anchor: middle; text-anchor: middle;
fill: black; fill: black;
} }
.viz_bignumber g.tick line { .big_number g.tick line {
stroke-width: 1px; stroke-width: 1px;
stroke: grey; stroke: grey;
} }
.viz_bignumber .domain { .big_number .domain {
fill: none; fill: none;
stroke: black; stroke: black;
stroke-width: 1; stroke-width: 1;

View File

@ -4,7 +4,7 @@ px.registerWidget('big_number', function(data_attribute) {
var json_callback = data_attribute['json_endpoint']; var json_callback = data_attribute['json_endpoint'];
var div = d3.select('#' + token_name); var div = d3.select('#' + token_name);
function render() { function render(done) {
d3.json(json_callback, function(error, payload){ d3.json(json_callback, function(error, payload){
json = payload.data; json = payload.data;
div.html(""); div.html("");
@ -12,6 +12,7 @@ px.registerWidget('big_number', function(data_attribute) {
if (error != null){ if (error != null){
var err = '<div class="alert alert-danger">' + error.responseText + '</div>'; var err = '<div class="alert alert-danger">' + error.responseText + '</div>';
div.html(err); div.html(err);
done(payload);
return ''; return '';
} }
var color_range = [-1, 1]; var color_range = [-1, 1];
@ -136,6 +137,7 @@ px.registerWidget('big_number', function(data_attribute) {
div.select('g.digits').transition().duration(500).attr('opacity', 1); div.select('g.digits').transition().duration(500).attr('opacity', 1);
div.select('g.axis').transition().duration(500).attr('opacity', 0); div.select('g.axis').transition().duration(500).attr('opacity', 0);
}); });
done(payload);
}); });
}; };

View File

@ -0,0 +1,11 @@
px.registerWidget('markup', function(data_attribute) {
function refresh(done) {
$('#code').attr('rows', '15')
done();
}
return {
render: refresh,
resize: refresh,
};
});

View File

@ -3,6 +3,7 @@ function viz_nvd3(data_attribute) {
var token = d3.select('#' + token_name); var token = d3.select('#' + token_name);
var json_callback = data_attribute['json_endpoint']; var json_callback = data_attribute['json_endpoint'];
var chart = undefined; var chart = undefined;
var data = {};
function UTC(dttm){ function UTC(dttm){
return v = new Date(dttm.getUTCFullYear(), dttm.getUTCMonth(), dttm.getUTCDate(), dttm.getUTCHours(), dttm.getUTCMinutes(), dttm.getUTCSeconds()); return v = new Date(dttm.getUTCFullYear(), dttm.getUTCMonth(), dttm.getUTCDate(), dttm.getUTCHours(), dttm.getUTCMinutes(), dttm.getUTCSeconds());
@ -27,17 +28,14 @@ function viz_nvd3(data_attribute) {
"#FFAA91", "#B4A76C", "#9CA299", "#565A5C" "#FFAA91", "#B4A76C", "#9CA299", "#565A5C"
]; ];
var jtoken = $('#' + token_name); var jtoken = $('#' + token_name);
var loading = $('#' + token_name).find("img.loading");
var chart_div = $('#' + token_name).find("div.chart"); var chart_div = $('#' + token_name).find("div.chart");
var refresh = function() { var refresh = function(done) {
chart_div.hide(); chart_div.hide();
loading.show();
$.getJSON(json_callback, function(payload) { $.getJSON(json_callback, function(payload) {
var data = payload.data; var data = payload.data;
var viz = payload; var viz = payload;
var viz_type = viz.form_data.viz_type; var viz_type = viz.form_data.viz_type;
$("#query_container").html(data.query);
nv.addGraph(function() { nv.addGraph(function() {
if (viz_type === 'line') { if (viz_type === 'line') {
if (viz.form_data.show_brush) { if (viz.form_data.show_brush) {
@ -157,10 +155,11 @@ function viz_nvd3(data_attribute) {
return chart; return chart;
}); });
chart_div.show(); chart_div.show();
loading.hide(); done(data);
}).fail(function(xhr) { })
.fail(function(xhr) {
var err = '<div class="alert alert-danger">' + xhr.responseText + '</div>'; var err = '<div class="alert alert-danger">' + xhr.responseText + '</div>';
loading.hide(); done(data);
chart_div.show(); chart_div.show();
chart_div.html(err); chart_div.html(err);
}); });

View File

@ -0,0 +1,3 @@
li.widget.pivot_table div.token {
overflow: auto;
}

View File

@ -0,0 +1,32 @@
px.registerWidget('pivot_table', function(data_attribute) {
var token_name = data_attribute['token'];
var token = $('#' + token_name);
var form_data = data_attribute.form_data;
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();
}
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();
});
}
return {
render: refresh,
resize: refresh,
};
});

View File

@ -4,7 +4,7 @@ Modified from http://bl.ocks.org/kerryrodden/7090426
function viz_sunburst(data_attribute) { function viz_sunburst(data_attribute) {
var token = d3.select('#' + data_attribute.token); var token = d3.select('#' + data_attribute.token);
var render = function() { var render = function(done) {
// Breadcrumb dimensions: width, height, spacing, width of tip/tail. // Breadcrumb dimensions: width, height, spacing, width of tip/tail.
var b = { var b = {
w: 100, h: 30, s: 3, t: 10 w: 100, h: 30, s: 3, t: 10
@ -48,7 +48,7 @@ function viz_sunburst(data_attribute) {
} }
var tree = buildHierarchy(json.data); var tree = buildHierarchy(json.data);
createVisualization(tree); createVisualization(tree);
token.select("img.loading").remove(); done(json);
}); });
// Main function to draw and set up the visualization, once we have the data. // Main function to draw and set up the visualization, once we have the data.

View File

@ -0,0 +1,3 @@
li.widget.table div.token {
overflow: auto;
}

View File

@ -0,0 +1,31 @@
px.registerWidget('table', function(data_attribute) {
var token_name = data_attribute['token'];
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();
}
else{
var table = token.find('table').DataTable({
paging: false,
searching: false,
});
table.column('-1').order( 'desc' ).draw();
}
token.show();
done();
});
}
return {
render: refresh,
resize: refresh,
};
});

View File

@ -4,11 +4,12 @@ px.registerWidget('word_cloud', function(data_attribute) {
var json_callback = data_attribute['json_endpoint']; var json_callback = data_attribute['json_endpoint'];
var token = d3.select('#' + token_name); var token = d3.select('#' + token_name);
function refresh() { function refresh(done) {
d3.json(json_callback, function(error, json) { d3.json(json_callback, function(error, json) {
if (error != null){ if (error != null){
var err = '<div class="alert alert-danger">' + error.responseText + '</div>'; var err = '<div class="alert alert-danger">' + error.responseText + '</div>';
token.html(err); token.html(err);
done();
return ''; return '';
} }
var data = json.data; var data = json.data;
@ -62,6 +63,7 @@ px.registerWidget('word_cloud', function(data_attribute) {
}) })
.text(function(d) { return d.text; }); .text(function(d) { return d.text; });
} }
done(data);
}); });
} }

View File

@ -109,7 +109,10 @@ body {
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div id="{{ viz.token }}" class="token" style="height: 100%;">
<img src="{{ url_for("static", filename="img/loading.gif") }}" class="loading" alt="loading">
{{ viz_macros.viz_html(viz) }} {{ viz_macros.viz_html(viz) }}
</div>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>

View File

@ -33,8 +33,9 @@
</a> </a>
</span> </span>
<span>{{ form.get_field("viz_type")(class_="select2") }}</span> <span>{{ form.get_field("viz_type")(class_="select2") }}</span>
<span class="btn btn-info pull-right" <span class="btn btn-info pull-right disabled query"
data-toggle="modal" data-target="#query_modal">query</span> data-toggle="modal" data-target="#query_modal">query</span>
<span class="btn btn-warning pull-right notbtn" id="timer">0 sec</span>
<hr/> <hr/>
</div> </div>
<div class="row"> <div class="row">
@ -129,9 +130,11 @@
{% endblock %} {% endblock %}
{% include 'appbuilder/flash.html' %} {% include 'appbuilder/flash.html' %}
<div <div
id="{{ viz.token }}"
class="viz widget {{ viz.viz_type }}" class="viz widget {{ viz.viz_type }}"
data-widget="{{ viz.get_data_attribute() }}" data-widget="{{ viz.get_data_attribute() }}"
style="height: 700px;"> style="height: 700px;">
<img src="{{ url_for("static", filename="img/loading.gif") }}" class="loading" alt="loading">
{% block viz_html %} {% block viz_html %}
{% if viz.error_msg %} {% if viz.error_msg %}
<div class="alert alert-danger">{{ viz.error_msg }}</div> <div class="alert alert-danger">{{ viz.error_msg }}</div>

View File

@ -1,7 +1,5 @@
{% macro viz_html(viz) %} {% macro viz_html(viz) %}
<div id="{{ viz.token }}" class="viz_bignumber" style="height: 100%;"> <div id="{{ viz.token }}" class="viz_bignumber" style="height: 100%;"></div>
<img src="{{ url_for("static", filename="img/loading.gif") }}" class="loading">
</div>
{% endmacro %} {% endmacro %}
{% macro viz_js(viz) %} {% macro viz_js(viz) %}

View File

@ -1,8 +1,5 @@
{% macro viz_html(viz) %} {% macro viz_html(viz) %}
<div id="{{ viz.token }}" style="height:100%; width: 100%">
<img src="{{ url_for("static", filename="img/loading.gif") }}" class="loading">
<div class="chart with-3d-shadow with-transitions" style="height:100%; width: 100%"></div> <div class="chart with-3d-shadow with-transitions" style="height:100%; width: 100%"></div>
</div>
{% endmacro %} {% endmacro %}
{% macro viz_js(viz) %} {% macro viz_js(viz) %}

View File

@ -1,10 +1,8 @@
{% macro viz_html(viz) %} {% macro viz_html(viz) %}
{% if viz.request.args.get("async") == "true" %} {% if viz.request.args.get("async") == "true" %}
{{ viz.get_df().to_html(na_rep='', classes="dataframe table table-striped table-bordered table-condensed")|safe }} {{ viz.get_df().to_html(na_rep='', classes="dataframe table table-striped table-bordered table-condensed")|safe }}
{% else %} {% else %}
<div id="{{ viz.token }}" style="display: none;overflow: auto; height: 100%;"></div> <div id="{{ viz.token }}" style="display: none;overflow: auto; height: 100%;"></div>
<img src="{{ url_for("static", filename="img/loading.gif") }}" class="loading">
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}
@ -12,26 +10,6 @@
{% if viz.form_data.get("async") != "true" %} {% if viz.form_data.get("async") != "true" %}
<script> <script>
$( document ).ready(function() { $( document ).ready(function() {
var url = "{{ viz.get_url(async="true", standalone="true", skip_libs="true")|safe }}";
var token = $("#{{ viz.token }}");
token.load(url, function(response, status, xhr){
if(status=="error"){
var err = '<div class="alert alert-danger">' + xhr.responseText + '</div>';
token.html(err);
token.show();
}
else{
{% if viz.form_data.get('groupby') and viz.form_data.get('groupby')|length == 1 %}
var table = token.find('table').DataTable({
paging: false,
searching: false,
});
table.column('-1').order( 'desc' ).draw();
{% endif %}
}
token.show();
token.parent().find("img.loading").hide();
});
}); });
</script> </script>
{% endif %} {% endif %}

View File

@ -1,9 +1,5 @@
{% macro viz_html(viz) %} {% macro viz_html(viz) %}
<div id="{{ viz.token }}" class="token"> <div id="chart"></div>
<div id="chart">
<img src="{{ url_for("static", filename="img/loading.gif") }}" class="loading">
</div>
</div>
{% endmacro %} {% endmacro %}
{% macro viz_js(viz) %} {% macro viz_js(viz) %}
@ -11,4 +7,3 @@
{% macro viz_css(viz) %} {% macro viz_css(viz) %}
{% endmacro %} {% endmacro %}

View File

@ -1,6 +1,7 @@
{% macro viz_html(viz) %} {% macro viz_html(viz) %}
{% if viz.request.args.get("async") == "true" %} {% if viz.request.args.get("async") == "true" %}
{% set df = viz.get_df() %} {% 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">
<thead> <thead>
<tr> <tr>
@ -25,37 +26,11 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% else %} </div>
<div id="{{ viz.token }}" style="display: none;overflow: auto; height: 100%;"></div>
<img src="{{ url_for("static", filename="img/loading.gif") }}" class="loading">
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}
{% macro viz_js(viz) %} {% macro viz_js(viz) %}
{% if viz.form_data.get("async") != "true" %}
<script>
$( document ).ready(function() {
var url = "{{ viz.get_url(async="true", standalone="true", skip_libs="true")|safe }}";
var token = $("#{{ viz.token }}");
token.load(url, function(response, status, xhr){
if(status=="error"){
var err = '<div class="alert alert-danger">' + xhr.responseText + '</div>';
token.html(err);
token.show();
}
else{
var table = token.find('table').DataTable({
paging: false,
searching: false,
});
table.column('-1').order( 'desc' ).draw();
}
token.show();
token.parent().find("img.loading").hide();
});
});
</script>
{% endif %}
{% endmacro %} {% endmacro %}
{% macro viz_css(viz) %} {% macro viz_css(viz) %}

View File

@ -1,7 +1,5 @@
{% macro viz_html(viz) %} {% macro viz_html(viz) %}
<div id="{{ viz.token }}" style="height: 100%;"> <div id="{{ viz.token }}" style="height: 100%;"></div>
<img src="{{ url_for("static", filename="img/loading.gif") }}" class="loading">
</div>
{% endmacro %} {% endmacro %}
{% macro viz_js(viz) %} {% macro viz_js(viz) %}

View File

@ -65,6 +65,7 @@ class BaseViz(object):
if k in form.data} if k in form.data}
defaults.update(data) defaults.update(data)
self.form_data = defaults self.form_data = defaults
self.query = ""
self.form_data['previous_viz_type'] = self.viz_type self.form_data['previous_viz_type'] = self.viz_type
self.token = self.form_data.get( self.token = self.form_data.get(
@ -127,6 +128,7 @@ class BaseViz(object):
self.results = None self.results = None
self.results = self.datasource.query(**query_obj) self.results = self.datasource.query(**query_obj)
self.query = self.results.query
df = self.results.df df = self.results.df
if df is None or df.empty: if df is None or df.empty:
raise Exception("No data, review your incantations!") raise Exception("No data, review your incantations!")
@ -201,6 +203,7 @@ class BaseViz(object):
def get_json(self): def get_json(self):
payload = { payload = {
'data': json.loads(self.get_json_data()), 'data': json.loads(self.get_json_data()),
'query': self.query,
'form_data': self.form_data, 'form_data': self.form_data,
} }
return json.dumps(payload) return json.dumps(payload)
@ -208,13 +211,18 @@ class BaseViz(object):
def get_json_data(self): def get_json_data(self):
return json.dumps([]) return json.dumps([])
@property
def json_endpoint(self):
return self.get_url(json="true")
def get_data_attribute(self): def get_data_attribute(self):
content = { content = {
'viz_name': self.viz_type, 'viz_name': self.viz_type,
'json_endpoint': self.get_url(json="true"), 'json_endpoint': self.json_endpoint,
'token': self.token, 'token': self.token,
'form_data': self.form_data,
} }
return json.dumps(content) return dumps(content)
class TableViz(BaseViz): class TableViz(BaseViz):
viz_type = "table" viz_type = "table"
@ -234,7 +242,14 @@ class TableViz(BaseViz):
is_timeseries = False is_timeseries = False
js_files = [ js_files = [
'lib/dataTables/jquery.dataTables.min.js', 'lib/dataTables/jquery.dataTables.min.js',
'lib/dataTables/dataTables.bootstrap.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): def query_obj(self):
d = super(TableViz, self).query_obj() d = super(TableViz, self).query_obj()
@ -257,11 +272,14 @@ class PivotTableViz(BaseViz):
viz_type = "pivot_table" viz_type = "pivot_table"
verbose_name = "Pivot Table" verbose_name = "Pivot Table"
template = 'panoramix/viz_pivot_table.html' template = 'panoramix/viz_pivot_table.html'
css_files = ['lib/dataTables/dataTables.bootstrap.css'] css_files = [
'lib/dataTables/dataTables.bootstrap.css',
'widgets/viz_pivot_table.css']
is_timeseries = False is_timeseries = False
js_files = [ js_files = [
'lib/dataTables/jquery.dataTables.min.js', 'lib/dataTables/jquery.dataTables.min.js',
'lib/dataTables/dataTables.bootstrap.js'] 'lib/dataTables/dataTables.bootstrap.js',
'widgets/viz_pivot_table.js']
fieldsets = ( fieldsets = (
{ {
'label': None, 'label': None,
@ -275,6 +293,10 @@ class PivotTableViz(BaseViz):
) )
},) },)
@property
def json_endpoint(self):
return self.get_url(async='true', standalone='true', skip_libs='true')
def query_obj(self): def query_obj(self):
d = super(PivotTableViz, self).query_obj() d = super(PivotTableViz, self).query_obj()
groupby = self.form_data.get('groupby') groupby = self.form_data.get('groupby')
@ -318,6 +340,7 @@ class MarkupViz(BaseViz):
viz_type = "markup" viz_type = "markup"
verbose_name = "Markup Widget" verbose_name = "Markup Widget"
template = 'panoramix/viz_markup.html' template = 'panoramix/viz_markup.html'
js_files = ['widgets/viz_markup.js']
fieldsets = ( fieldsets = (
{ {
'label': None, 'label': None,