diff --git a/TODO.md b/TODO.md index 47cb75831e..1ac1a3f078 100644 --- a/TODO.md +++ b/TODO.md @@ -2,18 +2,23 @@ List of TODO items for Panoramix ## Improvments -* [druid] Allow for post aggregations (ratios!) -* [sql] define column based grouping * [sql] make "Test Connection" test further -* csv export out of table view +* csv export +* json export (options) * in/notin filters autocomplete +* [druid] Allow for post aggregations (ratios!) ## Better Javascript enables * 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 ## New Features * Annotations layers + +## Low value +* [sql] define column based grouping + +## Community +* Creat a proper doc +* Usage vid +* diff --git a/panoramix/static/panoramix.css b/panoramix/static/panoramix.css index 21366799e9..85ce8bece6 100644 --- a/panoramix/static/panoramix.css +++ b/panoramix/static/panoramix.css @@ -11,6 +11,15 @@ form div { color: white; } +.header span{ + margin-left: 3px; +} + +#timer { + width: 80px; + text-align: right; +} + .notbtn { cursor: default; } diff --git a/panoramix/static/panoramix.js b/panoramix/static/panoramix.js index f6a868f1ca..9f28e83ed6 100644 --- a/panoramix/static/panoramix.js +++ b/panoramix/static/panoramix.js @@ -1,3 +1,4 @@ +var timer; var px = (function() { var visualizations = []; @@ -14,18 +15,43 @@ var px = (function() { } function initializeWidget(data) { + var token = $('#' + data.token); var name = data['viz_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; } -function initializeDatasourceView() { - function getParam(name) { - name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); - var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), - results = regex.exec(location.search); - return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); + function initializeDatasourceView() { + function getParam(name) { + name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); + var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), + results = regex.exec(location.search); + return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); } $(".select2").select2({dropdownAutoWidth : true}); diff --git a/panoramix/static/widgets/viz_bignumber.css b/panoramix/static/widgets/viz_bignumber.css index 83c6c467e7..872a5715a2 100644 --- a/panoramix/static/widgets/viz_bignumber.css +++ b/panoramix/static/widgets/viz_bignumber.css @@ -1,4 +1,4 @@ -.viz_bignumber g.axis text { +.big_number g.axis text { font-size: 10px; font-weight: normal; color: gray; @@ -8,18 +8,18 @@ font-weight: none; } -.viz_bignumber text.big { +.big_number text.big { stroke: black; text-anchor: middle; fill: black; } -.viz_bignumber g.tick line { +.big_number g.tick line { stroke-width: 1px; stroke: grey; } -.viz_bignumber .domain { +.big_number .domain { fill: none; stroke: black; stroke-width: 1; diff --git a/panoramix/static/widgets/viz_bignumber.js b/panoramix/static/widgets/viz_bignumber.js index b59a1cd19e..e8af688d5c 100644 --- a/panoramix/static/widgets/viz_bignumber.js +++ b/panoramix/static/widgets/viz_bignumber.js @@ -4,7 +4,7 @@ px.registerWidget('big_number', function(data_attribute) { var json_callback = data_attribute['json_endpoint']; var div = d3.select('#' + token_name); - function render() { + function render(done) { d3.json(json_callback, function(error, payload){ json = payload.data; div.html(""); @@ -12,6 +12,7 @@ px.registerWidget('big_number', function(data_attribute) { if (error != null){ var err = '
' + error.responseText + '
'; div.html(err); + done(payload); return ''; } 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.axis').transition().duration(500).attr('opacity', 0); }); + done(payload); }); }; diff --git a/panoramix/static/widgets/viz_markup.js b/panoramix/static/widgets/viz_markup.js new file mode 100644 index 0000000000..be6bef7e7b --- /dev/null +++ b/panoramix/static/widgets/viz_markup.js @@ -0,0 +1,11 @@ +px.registerWidget('markup', function(data_attribute) { + + function refresh(done) { + $('#code').attr('rows', '15') + done(); + } + return { + render: refresh, + resize: refresh, + }; +}); diff --git a/panoramix/static/widgets/viz_nvd3.js b/panoramix/static/widgets/viz_nvd3.js index 156f080fe5..3ed37abaab 100644 --- a/panoramix/static/widgets/viz_nvd3.js +++ b/panoramix/static/widgets/viz_nvd3.js @@ -3,6 +3,7 @@ function viz_nvd3(data_attribute) { var token = d3.select('#' + token_name); var json_callback = data_attribute['json_endpoint']; var chart = undefined; + var data = {}; function UTC(dttm){ 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" ]; var jtoken = $('#' + token_name); - var loading = $('#' + token_name).find("img.loading"); var chart_div = $('#' + token_name).find("div.chart"); - var refresh = function() { + var refresh = function(done) { chart_div.hide(); - loading.show(); $.getJSON(json_callback, function(payload) { var data = payload.data; var viz = payload; var viz_type = viz.form_data.viz_type; - $("#query_container").html(data.query); nv.addGraph(function() { if (viz_type === 'line') { if (viz.form_data.show_brush) { @@ -157,10 +155,11 @@ function viz_nvd3(data_attribute) { return chart; }); chart_div.show(); - loading.hide(); - }).fail(function(xhr) { + done(data); + }) + .fail(function(xhr) { var err = '
' + xhr.responseText + '
'; - loading.hide(); + done(data); chart_div.show(); chart_div.html(err); }); diff --git a/panoramix/static/widgets/viz_pivot_table.css b/panoramix/static/widgets/viz_pivot_table.css new file mode 100644 index 0000000000..716da7732f --- /dev/null +++ b/panoramix/static/widgets/viz_pivot_table.css @@ -0,0 +1,3 @@ +li.widget.pivot_table div.token { + overflow: auto; +} diff --git a/panoramix/static/widgets/viz_pivot_table.js b/panoramix/static/widgets/viz_pivot_table.js new file mode 100644 index 0000000000..dfd5649a6b --- /dev/null +++ b/panoramix/static/widgets/viz_pivot_table.js @@ -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 = '
' + xhr.responseText + '
'; + 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, + }; + +}); diff --git a/panoramix/static/widgets/viz_sunburst.js b/panoramix/static/widgets/viz_sunburst.js index 9cd27c0421..9aef8335d5 100644 --- a/panoramix/static/widgets/viz_sunburst.js +++ b/panoramix/static/widgets/viz_sunburst.js @@ -4,7 +4,7 @@ Modified from http://bl.ocks.org/kerryrodden/7090426 function viz_sunburst(data_attribute) { var token = d3.select('#' + data_attribute.token); - var render = function() { + var render = function(done) { // Breadcrumb dimensions: width, height, spacing, width of tip/tail. var b = { w: 100, h: 30, s: 3, t: 10 @@ -48,7 +48,7 @@ function viz_sunburst(data_attribute) { } var tree = buildHierarchy(json.data); createVisualization(tree); - token.select("img.loading").remove(); + done(json); }); // Main function to draw and set up the visualization, once we have the data. diff --git a/panoramix/static/widgets/viz_table.css b/panoramix/static/widgets/viz_table.css new file mode 100644 index 0000000000..57fefabecf --- /dev/null +++ b/panoramix/static/widgets/viz_table.css @@ -0,0 +1,3 @@ +li.widget.table div.token { + overflow: auto; +} diff --git a/panoramix/static/widgets/viz_table.js b/panoramix/static/widgets/viz_table.js new file mode 100644 index 0000000000..2272481b5e --- /dev/null +++ b/panoramix/static/widgets/viz_table.js @@ -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 = '
' + xhr.responseText + '
'; + 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, + }; + +}); diff --git a/panoramix/static/widgets/viz_wordcloud.js b/panoramix/static/widgets/viz_wordcloud.js index afb28a41c9..b2b7d4e7bf 100644 --- a/panoramix/static/widgets/viz_wordcloud.js +++ b/panoramix/static/widgets/viz_wordcloud.js @@ -4,11 +4,12 @@ px.registerWidget('word_cloud', function(data_attribute) { var json_callback = data_attribute['json_endpoint']; var token = d3.select('#' + token_name); - function refresh() { + function refresh(done) { d3.json(json_callback, function(error, json) { if (error != null){ var err = '
' + error.responseText + '
'; token.html(err); + done(); return ''; } var data = json.data; @@ -62,6 +63,7 @@ px.registerWidget('word_cloud', function(data_attribute) { }) .text(function(d) { return d.text; }); } + done(data); }); } diff --git a/panoramix/templates/panoramix/dashboard.html b/panoramix/templates/panoramix/dashboard.html index ee1b81648d..e089d8883d 100644 --- a/panoramix/templates/panoramix/dashboard.html +++ b/panoramix/templates/panoramix/dashboard.html @@ -109,7 +109,10 @@ body { - {{ viz_macros.viz_html(viz) }} +
+ loading + {{ viz_macros.viz_html(viz) }} +
{% endfor %} diff --git a/panoramix/templates/panoramix/explore.html b/panoramix/templates/panoramix/explore.html index 1d842e850f..4a11000aef 100644 --- a/panoramix/templates/panoramix/explore.html +++ b/panoramix/templates/panoramix/explore.html @@ -33,8 +33,9 @@ {{ form.get_field("viz_type")(class_="select2") }} - query + 0 sec
@@ -129,16 +130,18 @@ {% endblock %} {% include 'appbuilder/flash.html' %}
+ loading {% block viz_html %} - {% if viz.error_msg %} -
{{ viz.error_msg }}
- {% endif %} - {% if viz.warning_msg %} -
{{ viz.warning_msg }}
- {% endif %} + {% if viz.error_msg %} +
{{ viz.error_msg }}
+ {% endif %} + {% if viz.warning_msg %} +
{{ viz.warning_msg }}
+ {% endif %} {% endblock %}
diff --git a/panoramix/templates/panoramix/viz_bignumber.html b/panoramix/templates/panoramix/viz_bignumber.html index b3227bff2d..fe549b71db 100644 --- a/panoramix/templates/panoramix/viz_bignumber.html +++ b/panoramix/templates/panoramix/viz_bignumber.html @@ -1,7 +1,5 @@ {% macro viz_html(viz) %} -
- -
+
{% endmacro %} {% macro viz_js(viz) %} diff --git a/panoramix/templates/panoramix/viz_nvd3.html b/panoramix/templates/panoramix/viz_nvd3.html index 2f61e1a31b..d1314e32f5 100644 --- a/panoramix/templates/panoramix/viz_nvd3.html +++ b/panoramix/templates/panoramix/viz_nvd3.html @@ -1,8 +1,5 @@ {% macro viz_html(viz) %} -
-
-
{% endmacro %} {% macro viz_js(viz) %} diff --git a/panoramix/templates/panoramix/viz_pivot_table.html b/panoramix/templates/panoramix/viz_pivot_table.html index 357f1d40fd..9fe1398ea1 100644 --- a/panoramix/templates/panoramix/viz_pivot_table.html +++ b/panoramix/templates/panoramix/viz_pivot_table.html @@ -1,10 +1,8 @@ {% 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 }} - + {{ viz.get_df().to_html(na_rep='', classes="dataframe table table-striped table-bordered table-condensed")|safe }} {% else %} - - + {% endif %} {% endmacro %} @@ -12,26 +10,6 @@ {% if viz.form_data.get("async") != "true" %} {% endif %} diff --git a/panoramix/templates/panoramix/viz_sunburst.html b/panoramix/templates/panoramix/viz_sunburst.html index dd55bea59f..5f4ce4ed8a 100644 --- a/panoramix/templates/panoramix/viz_sunburst.html +++ b/panoramix/templates/panoramix/viz_sunburst.html @@ -1,9 +1,5 @@ {% macro viz_html(viz) %} -
-
- -
-
+
{% endmacro %} {% macro viz_js(viz) %} @@ -11,4 +7,3 @@ {% macro viz_css(viz) %} {% endmacro %} - diff --git a/panoramix/templates/panoramix/viz_table.html b/panoramix/templates/panoramix/viz_table.html index 49cfbcb5dd..ef26a192fa 100644 --- a/panoramix/templates/panoramix/viz_table.html +++ b/panoramix/templates/panoramix/viz_table.html @@ -1,61 +1,36 @@ {% macro viz_html(viz) %} {% if viz.request.args.get("async") == "true" %} {% set df = viz.get_df() %} - - - - {% for col in df.columns if not col.endswith('__perc') %} - +
+
{{ col }}
+ + + {% for col in df.columns if not col.endswith('__perc') %} + + {% endfor %} + + + + {% for row in df.to_dict(orient="records") %} + + {% for col in df.columns if not col.endswith('__perc') %} + {% if col + '__perc' in df.columns %} + + {% else %} + + {% endif %} + {% endfor %} + {% endfor %} - - - - {% for row in df.to_dict(orient="records") %} - - {% for col in df.columns if not col.endswith('__perc') %} - {% if col + '__perc' in df.columns %} - - {% else %} - - {% endif %} - {% endfor %} - - {% endfor %} - -
{{ col }}
+ {{ row[col] }} + {{ row[col] }}
- {{ row[col] }} - {{ row[col] }}
- {% else %} - - + + + {% endif %} {% endmacro %} {% macro viz_js(viz) %} - {% if viz.form_data.get("async") != "true" %} - - {% endif %} {% endmacro %} {% macro viz_css(viz) %} diff --git a/panoramix/templates/panoramix/viz_word_cloud.html b/panoramix/templates/panoramix/viz_word_cloud.html index 48766b29e8..fa8d7a5ff5 100644 --- a/panoramix/templates/panoramix/viz_word_cloud.html +++ b/panoramix/templates/panoramix/viz_word_cloud.html @@ -1,7 +1,5 @@ {% macro viz_html(viz) %} -
- -
+
{% endmacro %} {% macro viz_js(viz) %} diff --git a/panoramix/viz.py b/panoramix/viz.py index 74a2fa56ef..63274b0a64 100644 --- a/panoramix/viz.py +++ b/panoramix/viz.py @@ -65,6 +65,7 @@ class BaseViz(object): if k in form.data} defaults.update(data) self.form_data = defaults + self.query = "" self.form_data['previous_viz_type'] = self.viz_type self.token = self.form_data.get( @@ -127,6 +128,7 @@ class BaseViz(object): self.results = None self.results = self.datasource.query(**query_obj) + self.query = self.results.query df = self.results.df if df is None or df.empty: raise Exception("No data, review your incantations!") @@ -201,6 +203,7 @@ class BaseViz(object): def get_json(self): payload = { 'data': json.loads(self.get_json_data()), + 'query': self.query, 'form_data': self.form_data, } return json.dumps(payload) @@ -208,13 +211,18 @@ class BaseViz(object): def get_json_data(self): return json.dumps([]) + @property + def json_endpoint(self): + return self.get_url(json="true") + def get_data_attribute(self): content = { 'viz_name': self.viz_type, - 'json_endpoint': self.get_url(json="true"), + 'json_endpoint': self.json_endpoint, 'token': self.token, + 'form_data': self.form_data, } - return json.dumps(content) + return dumps(content) class TableViz(BaseViz): viz_type = "table" @@ -234,7 +242,14 @@ class TableViz(BaseViz): is_timeseries = False js_files = [ '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): d = super(TableViz, self).query_obj() @@ -257,11 +272,14 @@ class PivotTableViz(BaseViz): viz_type = "pivot_table" verbose_name = "Pivot Table" 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 js_files = [ 'lib/dataTables/jquery.dataTables.min.js', - 'lib/dataTables/dataTables.bootstrap.js'] + 'lib/dataTables/dataTables.bootstrap.js', + 'widgets/viz_pivot_table.js'] fieldsets = ( { '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): d = super(PivotTableViz, self).query_obj() groupby = self.form_data.get('groupby') @@ -318,6 +340,7 @@ class MarkupViz(BaseViz): viz_type = "markup" verbose_name = "Markup Widget" template = 'panoramix/viz_markup.html' + js_files = ['widgets/viz_markup.js'] fieldsets = ( { 'label': None,