From 07df0f109f78aa8a1ff28107872c2bb04cc17e87 Mon Sep 17 00:00:00 2001 From: Maxime Beauchemin Date: Sat, 26 Dec 2015 11:54:04 -0800 Subject: [PATCH] Adding a filter box widget --- panoramix/static/panoramix.css | 4 ++ panoramix/static/panoramix.js | 6 +-- panoramix/static/widgets/viz_filter_box.js | 59 ++++++++++++++++++++++ panoramix/static/widgets/viz_table.js | 2 +- panoramix/viz.py | 57 ++++++++++++++++++++- 5 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 panoramix/static/widgets/viz_filter_box.js diff --git a/panoramix/static/panoramix.css b/panoramix/static/panoramix.css index a4968729dda09..ac926808dc2fb 100644 --- a/panoramix/static/panoramix.css +++ b/panoramix/static/panoramix.css @@ -2,6 +2,10 @@ html>body{ margin: 0px; !important } +.padded{ + padding: 10px; +} + .slice_container { height: 100%; } diff --git a/panoramix/static/panoramix.js b/panoramix/static/panoramix.js index 29e521a85a63d..f663b501f9030 100644 --- a/panoramix/static/panoramix.js +++ b/panoramix/static/panoramix.js @@ -68,7 +68,6 @@ var px = (function() { $('#timer').removeClass('btn-danger btn-success'); $('#timer').addClass('btn-warning'); viz.render(); - console.log(slice); $('#json').click(function(){window.location=slice.jsonEndpoint()}); $('#standalone').click(function(){window.location=slice.data.standalone_endpoint}); $('#csv').click(function(){window.location=slice.data.csv_endpoint}); @@ -99,9 +98,10 @@ var px = (function() { slices: [], filters: {}, id: id, - addFilter: function(slice_id, field, values) { - this.filters[slice_id] = [field, values]; + addFilter: function(slice_id, filters) { + this.filters[slice_id] = filters; this.refreshExcept(slice_id); + console.log(this.filters); }, refreshExcept: function(slice_id) { this.slices.forEach(function(slice){ diff --git a/panoramix/static/widgets/viz_filter_box.js b/panoramix/static/widgets/viz_filter_box.js new file mode 100644 index 0000000000000..bbcd6b7caed07 --- /dev/null +++ b/panoramix/static/widgets/viz_filter_box.js @@ -0,0 +1,59 @@ +px.registerViz('filter_box', function(slice) { + var slice = slice; + d3token = d3.select(slice.selector); + + var fltChanged = function() { + filters = [] + d3token.selectAll('select.select2_box_filter').each(function(){ + val = $(this).val(); + name = $(this).attr('name'); + if (val !== null && val !== undefined){ + if (typeof val === 'string') + val = [val]; + filters.push([name, val]); + } + }); + slice.addFilter(filters); + } + + var refresh = function() { + $('#code').attr('rows', '15') + var container = d3token + .append('div') + .classed('padded', true); + $.getJSON(slice.jsonEndpoint(), function(payload) { + for (filter in payload.data){ + data = payload.data[filter]; + var id = 'fltbox__' + filter; + + var div = container.append('div'); + div.append("label").text(filter); + var sel = div + .append('select') + .attr('name', filter) + .attr('multiple', '') + .attr('id', id); + + sel.classed('select2_box_filter form-control', true); + sel.selectAll('option').data(data).enter() + .append('option') + .attr('value', function(d){return d[0];}) + .text(function(d){return d[0];}); + $('#' + id).select2({ + //allowClear: true, + placeholder: "Select [" + filter + ']', + dropdownAutoWidth : true, + }) + .on('change', fltChanged); + } + slice.done(); + }) + .fail(function(xhr) { + slice.error(xhr.responseText); + }); + }; + return { + render: refresh, + resize: refresh, + }; +}); diff --git a/panoramix/static/widgets/viz_table.js b/panoramix/static/widgets/viz_table.js index 7c83d906b5a46..5baaa67d94b25 100644 --- a/panoramix/static/widgets/viz_table.js +++ b/panoramix/static/widgets/viz_table.js @@ -55,7 +55,7 @@ px.registerViz('table', function(slice) { } else { table.selectAll('.filtered').classed('filtered', false); d3.select(this).classed('filtered', true); - slice.addFilter(d.col, [d.val]); + slice.addFilter([[d.col, [d.val]]]); } } }) diff --git a/panoramix/viz.py b/panoramix/viz.py index 1fed212c5faa9..4fa5fe9e217c4 100644 --- a/panoramix/viz.py +++ b/panoramix/viz.py @@ -161,8 +161,11 @@ def query_filters(self): extra_filters = form_data.get('extra_filters', []) if extra_filters: extra_filters = json.loads(extra_filters) - for slice_id, (col, vals) in extra_filters.items(): - filters += [(col, 'in', ",".join(vals))] + for slice_id, slice_filters in extra_filters.items(): + if slice_filters: + for col, vals in slice_filters: + if col and vals: + filters += [(col, 'in', ",".join(vals))] return filters @@ -1105,6 +1108,55 @@ def get_json_data(self): return dumps(d) +class FilterBoxViz(BaseViz): + viz_type = "filter_box" + verbose_name = "Filters" + is_timeseries = False + js_files = [ + 'lib/d3.min.js', + 'widgets/viz_filter_box.js'] + css_files = [] + fieldsets = ( + { + 'label': None, + 'fields': ( + 'granularity', + ('since', 'until'), + 'groupby', + 'metric', + ) + },) + form_overrides = { + 'groupby': { + 'label': 'Filter fields', + 'description': "The fields you want to filter on", + }, + } + def query_obj(self): + qry = super(FilterBoxViz, self).query_obj() + groupby = self.form_data['groupby'] + if len(groupby) < 1: + raise Exception("Pick at least one filter field") + qry['metrics'] = [ + self.form_data['metric']] + return qry + + def get_df(self): + qry = self.query_obj() + + filters = [g for g in qry['groupby']] + d = {} + for flt in filters: + qry['groupby'] = [flt] + df = super(FilterBoxViz, self).get_df(qry) + d[flt] = [row for row in df.itertuples(index=False)] + return d + + def get_json_data(self): + d = self.get_df() + return dumps(d) + + viz_types_list = [ TableViz, PivotTableViz, @@ -1122,6 +1174,7 @@ def get_json_data(self): DirectedForceViz, SankeyViz, WorldMapViz, + FilterBoxViz, ] # This dict is used to viz_types = OrderedDict([(v.viz_type, v) for v in viz_types_list])