From e755854c29206195a872c7b754486f80ac1391a0 Mon Sep 17 00:00:00 2001 From: Maxime Beauchemin Date: Fri, 11 Sep 2015 21:49:04 -0700 Subject: [PATCH 1/9] Save and embed --- panoramix/models.py | 19 ++++++- panoramix/templates/panoramix/datasource.html | 10 +++- panoramix/templates/panoramix/viz.html | 5 ++ .../templates/panoramix/viz_highcharts.html | 52 +++++++++---------- .../templates/panoramix/viz_standalone.html | 9 ++++ panoramix/templates/panoramix/viz_table.html | 2 +- panoramix/views.py | 31 ++++++++++- panoramix/viz.py | 1 + 8 files changed, 98 insertions(+), 31 deletions(-) create mode 100644 panoramix/templates/panoramix/viz.html create mode 100644 panoramix/templates/panoramix/viz_standalone.html diff --git a/panoramix/models.py b/panoramix/models.py index 18358611c..b3294ae1a 100644 --- a/panoramix/models.py +++ b/panoramix/models.py @@ -32,9 +32,21 @@ class Slice(Model, AuditMixin): """A slice is essentially a report or a view on data""" __tablename__ = 'slices' id = Column(Integer, primary_key=True) - params = Column(JSONEncodedDict) - datasource = Column(String(250)) + slice_name = Column(String(250)) + datasource_id = Column(Integer) + datasource_type = Column(String(200)) + datasource_name = Column(String(2000)) viz_type = Column(String(250)) + params = Column(Text) + + @property + def slice_link(self): + d = json.loads(self.params) + kwargs = "&".join([k + '=' + v for k, v in d.iteritems()]) + url = ( + "/panoramix/{self.datasource_type}/" + "{self.datasource_id}/?{kwargs}").format(**locals()) + return '{self.slice_name}'.format(**locals()) class Queryable(object): @@ -72,6 +84,8 @@ class Database(Model, AuditMixin): class Table(Model, Queryable, AuditMixin): + type = "table" + __tablename__ = 'tables' id = Column(Integer, primary_key=True) table_name = Column(String(255), unique=True) @@ -446,6 +460,7 @@ class Cluster(Model, AuditMixin): class Datasource(Model, AuditMixin, Queryable): + type = "datasource" baselink = "datasourcemodelview" diff --git a/panoramix/templates/panoramix/datasource.html b/panoramix/templates/panoramix/datasource.html index e8d32ae3f..aae04dfdf 100644 --- a/panoramix/templates/panoramix/datasource.html +++ b/panoramix/templates/panoramix/datasource.html @@ -94,6 +94,10 @@ form input.form-control {
+ + + +
@@ -191,7 +195,11 @@ $( document ).ready(function() { }); } $("#plus").click(add_filter); - $("#bookmark").click(function () {alert("Not implemented yet...");}) + $("#bookmark").click(function () { + var slice_name = prompt("Name your slice!"); + $("#slice_name").val(slice_name); + $.get( "/panoramix/save/", $("#query").serialize() ); + }) add_filter(); $("#druidify").click(function () { var i = 1; diff --git a/panoramix/templates/panoramix/viz.html b/panoramix/templates/panoramix/viz.html new file mode 100644 index 000000000..f4803c8f9 --- /dev/null +++ b/panoramix/templates/panoramix/viz.html @@ -0,0 +1,5 @@ +{% if standalone %} + {% extends 'panoramix/viz_standalone.html' %} +{% else %} + {% extends 'panoramix/datasource.html' %} +{% endif %} diff --git a/panoramix/templates/panoramix/viz_highcharts.html b/panoramix/templates/panoramix/viz_highcharts.html index 6af92d2ca..ae10b83e7 100644 --- a/panoramix/templates/panoramix/viz_highcharts.html +++ b/panoramix/templates/panoramix/viz_highcharts.html @@ -1,34 +1,34 @@ -{% extends "panoramix/datasource.html" %} +{% extends "panoramix/viz.html" %} {% block viz %} {{ super() }}
{% endblock %} {% block tail %} -{{ super() }} -{% if viz.stockchart %} - -{% else %} - -{% endif %} - - + {% else %} + {% endif %} -}); - + + {% endblock %} diff --git a/panoramix/templates/panoramix/viz_standalone.html b/panoramix/templates/panoramix/viz_standalone.html new file mode 100644 index 000000000..3e8c32dbe --- /dev/null +++ b/panoramix/templates/panoramix/viz_standalone.html @@ -0,0 +1,9 @@ +{% extends "appbuilder/baselayout.html" %} +{% block body %} + {% block viz %} + {% endblock %} +{% endblock %} +{% block tail %} + {{ super() }} +{% endblock %} + diff --git a/panoramix/templates/panoramix/viz_table.html b/panoramix/templates/panoramix/viz_table.html index 4c5b6e09a..a7a0d64d6 100644 --- a/panoramix/templates/panoramix/viz_table.html +++ b/panoramix/templates/panoramix/viz_table.html @@ -1,4 +1,4 @@ -{% extends "panoramix/datasource.html" %} +{% extends "panoramix/viz.html" %} {% block head_css %} {{super()}} diff --git a/panoramix/views.py b/panoramix/views.py index 982089b38..cd0edd1ea 100644 --- a/panoramix/views.py +++ b/panoramix/views.py @@ -104,6 +104,18 @@ appbuilder.add_view( category_icon='fa-cogs',) +class SliceModelView(ModelView, DeleteMixin): + datamodel = SQLAInterface(models.Slice) + list_columns = ['slice_link', 'viz_type', 'created_by'] + +appbuilder.add_view( + SliceModelView, + "Slices", + icon="fa-bar-chart", + category="", + category_icon='',) + + class DatabaseView(ModelView, DeleteMixin): datamodel = SQLAInterface(models.Database) list_columns = ['database_name'] @@ -176,7 +188,6 @@ class Panoramix(BaseView): @has_access @expose("/table//") def table(self, table_id): - table = ( db.session .query(models.Table) @@ -226,6 +237,24 @@ class Panoramix(BaseView): return obj.check_and_render() + @has_access + @expose("/save/") + def save(self): + session = db.session() + obj = models.Slice( + params=json.dumps(request.args.to_dict()), + viz_type=request.args.get('viz_type'), + datasource_name=request.args.get('datasource_name'), + datasource_id=request.args.get('datasource_id'), + datasource_type=request.args.get('datasource_type'), + slice_name=request.args.get('slice_name', 'junk'), + ) + session.add(obj) + session.commit() + session.close() + + return "super!" + @has_access @expose("/refresh_datasources/") def refresh_datasources(self): diff --git a/panoramix/viz.py b/panoramix/viz.py index 8a4a51bbe..cc8f6760a 100644 --- a/panoramix/viz.py +++ b/panoramix/viz.py @@ -129,6 +129,7 @@ class BaseViz(object): return self.view.render_template( self.template, form=form, viz=self, datasource=self.datasource, results=self.results, + standalone=request.args.get('standalone') == 'true', *args, **kwargs) From 36351918c92504af27603c35a981f25ff31e732f Mon Sep 17 00:00:00 2001 From: Maxime Beauchemin Date: Sat, 12 Sep 2015 23:25:43 -0700 Subject: [PATCH 2/9] Dashboards --- panoramix/static/jquery.gridster.min.css | 2 + .../static/jquery.gridster.with-extras.min.js | 2 + panoramix/static/loading.gif | Bin 0 -> 16671 bytes panoramix/templates/panoramix/dashboard.html | 63 ++++++++++++++++++ panoramix/templates/panoramix/viz_table.html | 4 +- panoramix/views.py | 7 ++ setup.py | 1 + 7 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 panoramix/static/jquery.gridster.min.css create mode 100644 panoramix/static/jquery.gridster.with-extras.min.js create mode 100644 panoramix/static/loading.gif create mode 100644 panoramix/templates/panoramix/dashboard.html diff --git a/panoramix/static/jquery.gridster.min.css b/panoramix/static/jquery.gridster.min.css new file mode 100644 index 000000000..9d9fe8445 --- /dev/null +++ b/panoramix/static/jquery.gridster.min.css @@ -0,0 +1,2 @@ +/*! gridster.js - v0.5.6 - 2014-09-25 - * http://gridster.net/ - Copyright (c) 2014 ducksboard; Licensed MIT */ +.gridster{position:relative}.gridster>*{margin:0 auto;-webkit-transition:height .4s,width .4s;-moz-transition:height .4s,width .4s;-o-transition:height .4s,width .4s;-ms-transition:height .4s,width .4s;transition:height .4s,width .4s}.gridster .gs-w{z-index:2;position:absolute}.ready .gs-w:not(.preview-holder){-webkit-transition:opacity .3s,left .3s,top .3s;-moz-transition:opacity .3s,left .3s,top .3s;-o-transition:opacity .3s,left .3s,top .3s;transition:opacity .3s,left .3s,top .3s}.ready .gs-w:not(.preview-holder),.ready .resize-preview-holder{-webkit-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;-moz-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;-o-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;transition:opacity .3s,left .3s,top .3s,width .3s,height .3s}.gridster .preview-holder{z-index:1;position:absolute;background-color:#fff;border-color:#fff;opacity:.3}.gridster .player-revert{z-index:10!important;-webkit-transition:left .3s,top .3s!important;-moz-transition:left .3s,top .3s!important;-o-transition:left .3s,top .3s!important;transition:left .3s,top .3s!important}.gridster .dragging,.gridster .resizing{z-index:10!important;-webkit-transition:all 0s!important;-moz-transition:all 0s!important;-o-transition:all 0s!important;transition:all 0s!important}.gs-resize-handle{position:absolute;z-index:1}.gs-resize-handle-both{width:20px;height:20px;bottom:-8px;right:-8px;background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg08IS0tIEdlbmVyYXRvcjogQWRvYmUgRmlyZXdvcmtzIENTNiwgRXhwb3J0IFNWRyBFeHRlbnNpb24gYnkgQWFyb24gQmVhbGwgKGh0dHA6Ly9maXJld29ya3MuYWJlYWxsLmNvbSkgLiBWZXJzaW9uOiAwLjYuMSAgLS0+DTwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DTxzdmcgaWQ9IlVudGl0bGVkLVBhZ2UlMjAxIiB2aWV3Qm94PSIwIDAgNiA2IiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjojZmZmZmZmMDAiIHZlcnNpb249IjEuMSINCXhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbDpzcGFjZT0icHJlc2VydmUiDQl4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjZweCIgaGVpZ2h0PSI2cHgiDT4NCTxnIG9wYWNpdHk9IjAuMzAyIj4NCQk8cGF0aCBkPSJNIDYgNiBMIDAgNiBMIDAgNC4yIEwgNCA0LjIgTCA0LjIgNC4yIEwgNC4yIDAgTCA2IDAgTCA2IDYgTCA2IDYgWiIgZmlsbD0iIzAwMDAwMCIvPg0JPC9nPg08L3N2Zz4=);background-position:top left;background-repeat:no-repeat;cursor:se-resize;z-index:20}.gs-resize-handle-x{top:0;bottom:13px;right:-5px;width:10px;cursor:e-resize}.gs-resize-handle-y{left:0;right:13px;bottom:-5px;height:10px;cursor:s-resize}.gs-w:hover .gs-resize-handle,.resizing .gs-resize-handle{opacity:1}.gs-resize-handle,.gs-w.dragging .gs-resize-handle{opacity:0}.gs-resize-disabled .gs-resize-handle{display:none!important}[data-max-sizex="1"] .gs-resize-handle-x,[data-max-sizey="1"] .gs-resize-handle-y,[data-max-sizey="1"][data-max-sizex="1"] .gs-resize-handle{display:none!important} \ No newline at end of file diff --git a/panoramix/static/jquery.gridster.with-extras.min.js b/panoramix/static/jquery.gridster.with-extras.min.js new file mode 100644 index 000000000..1ebb2b251 --- /dev/null +++ b/panoramix/static/jquery.gridster.with-extras.min.js @@ -0,0 +1,2 @@ +/*! gridster.js - v0.5.6 - 2014-09-25 - * http://gridster.net/ - Copyright (c) 2014 ducksboard; Licensed MIT */ (function(t,i){"function"==typeof define&&define.amd?define("gridster-coords",["jquery"],i):t.GridsterCoords=i(t.$||t.jQuery)})(this,function(t){function i(i){return i[0]&&t.isPlainObject(i[0])?this.data=i[0]:this.el=i,this.isCoords=!0,this.coords={},this.init(),this}var e=i.prototype;return e.init=function(){this.set(),this.original_coords=this.get()},e.set=function(t,i){var e=this.el;if(e&&!t&&(this.data=e.offset(),this.data.width=e.width(),this.data.height=e.height()),e&&t&&!i){var s=e.offset();this.data.top=s.top,this.data.left=s.left}var r=this.data;return r.left===void 0&&(r.left=r.x1),r.top===void 0&&(r.top=r.y1),this.coords.x1=r.left,this.coords.y1=r.top,this.coords.x2=r.left+r.width,this.coords.y2=r.top+r.height,this.coords.cx=r.left+r.width/2,this.coords.cy=r.top+r.height/2,this.coords.width=r.width,this.coords.height=r.height,this.coords.el=e||!1,this},e.update=function(i){if(!i&&!this.el)return this;if(i){var e=t.extend({},this.data,i);return this.data=e,this.set(!0,!0)}return this.set(!0),this},e.get=function(){return this.coords},e.destroy=function(){this.el.removeData("coords"),delete this.el},t.fn.coords=function(){if(this.data("coords"))return this.data("coords");var t=new i(this,arguments[0]);return this.data("coords",t),t},i}),function(t,i){"function"==typeof define&&define.amd?define("gridster-collision",["jquery","gridster-coords"],i):t.GridsterCollision=i(t.$||t.jQuery,t.GridsterCoords)}(this,function(t){function i(i,s,r){this.options=t.extend(e,r),this.$element=i,this.last_colliders=[],this.last_colliders_coords=[],this.set_colliders(s),this.init()}var e={colliders_context:document.body,overlapping_region:"C"};i.defaults=e;var s=i.prototype;return s.init=function(){this.find_collisions()},s.overlaps=function(t,i){var e=!1,s=!1;return(i.x1>=t.x1&&i.x1<=t.x2||i.x2>=t.x1&&i.x2<=t.x2||t.x1>=i.x1&&t.x2<=i.x2)&&(e=!0),(i.y1>=t.y1&&i.y1<=t.y2||i.y2>=t.y1&&i.y2<=t.y2||t.y1>=i.y1&&t.y2<=i.y2)&&(s=!0),e&&s},s.detect_overlapping_region=function(t,i){var e="",s="";return t.y1>i.cy&&t.y1i.y1&&t.y2i.cx&&t.x1i.x1&&t.x2o;o++)-1===t.inArray(r[o],i)&&e.call(this,r[o]);for(var n=0,h=i.length;h>n;n++)-1===t.inArray(i[n],r)&&s.call(this,i[n])},s.find_collisions=function(i){for(var e=this,s=this.options.overlapping_region,r=[],o=[],a=this.colliders||this.$colliders,n=a.length,h=e.$element.coords().update(i||!1).get();n--;){var _=e.$colliders?t(a[n]):a[n],d=_.isCoords?_:_.coords(),l=d.get(),c=e.overlaps(h,l);if(c){var p=e.detect_overlapping_region(h,l);if(p===s||"all"===s){var g=e.calculate_overlapped_area_coords(h,l),u=e.calculate_overlapped_area(g),f={area:u,area_coords:g,region:p,coords:l,player_coords:h,el:_};e.options.on_overlap&&e.options.on_overlap.call(this,f),r.push(d),o.push(f)}}}return(e.options.on_overlap_stop||e.options.on_overlap_start)&&this.manage_colliders_start_stop(r,e.options.on_overlap_start,e.options.on_overlap_stop),this.last_colliders_coords=r,o},s.get_closest_colliders=function(t){var i=this.find_collisions(t);return i.sort(function(t,i){return"C"===t.region&&"C"===i.region?t.coords.y1this.player_max_left?o=this.player_max_left:this.player_min_left>o&&(o=this.player_min_left)),{position:{left:o,top:a},pointer:{left:e.left,top:e.top,diff_left:s+(t(window).scrollLeft()-this.win_offset_x),diff_top:r+(t(window).scrollTop()-this.win_offset_y)}}},_.get_drag_data=function(t){var i=this.get_offset(t);return i.$player=this.$player,i.$helper=this.helper?this.$helper:this.$player,i},_.set_limits=function(t){return t||(t=this.$container.width()),this.player_max_left=t-this.player_width+-this.options.offset_left,this.options.container_width=t,this},_.scroll_in=function(i,e){var o,n=r[i],h=50,_=30,d="x"===i,l=d?this.window_width:this.window_height,c=d?t(document).width():t(document).height(),p=d?this.$player.width():this.$player.height(),g=s["scroll"+a(n)](),u=g,f=u+l,w=f-h,m=u+h,y=u+e.pointer[n],v=c-l+p;return y>=w&&(o=g+_,v>o&&(s["scroll"+a(n)](o),this["scroll_offset_"+i]+=_)),m>=y&&(o=g-_,o>0&&(s["scroll"+a(n)](o),this["scroll_offset_"+i]-=_)),this},_.manage_scroll=function(t){this.scroll_in("x",t),this.scroll_in("y",t)},_.calculate_dimensions=function(){this.window_height=s.height(),this.window_width=s.width()},_.drag_handler=function(i){if(i.target.nodeName,!this.disabled&&(1===i.which||o)&&!this.ignore_drag(i)){var e=this,s=!0;return this.$player=t(i.currentTarget),this.el_init_pos=this.get_actual_pos(this.$player),this.mouse_init_pos=this.get_mouse_pos(i),this.offsetY=this.mouse_init_pos.top-this.el_init_pos.top,this.$document.on(this.pointer_events.move,function(t){var i=e.get_mouse_pos(t),r=Math.abs(i.left-e.mouse_init_pos.left),o=Math.abs(i.top-e.mouse_init_pos.top);return r>e.options.distance||o>e.options.distance?s?(s=!1,e.on_dragstart.call(e,t),!1):(e.is_dragging===!0&&e.on_dragmove.call(e,t),!1):!1}),o?void 0:!1}},_.on_dragstart=function(i){if(i.preventDefault(),this.is_dragging)return this;this.drag_start=this.is_dragging=!0;var e=this.$container.offset();return this.baseX=Math.round(e.left),this.baseY=Math.round(e.top),this.initial_container_width=this.options.container_width||this.$container.width(),"clone"===this.options.helper?(this.$helper=this.$player.clone().appendTo(this.$container).addClass("helper"),this.helper=!0):this.helper=!1,this.win_offset_y=t(window).scrollTop(),this.win_offset_x=t(window).scrollLeft(),this.scroll_offset_y=0,this.scroll_offset_x=0,this.el_init_offset=this.$player.offset(),this.player_width=this.$player.width(),this.player_height=this.$player.height(),this.set_limits(this.options.container_width),this.options.start&&this.options.start.call(this.$player,i,this.get_drag_data(i)),!1},_.on_dragmove=function(t){var i=this.get_drag_data(t);this.options.autoscroll&&this.manage_scroll(i),this.options.move_element&&(this.helper?this.$helper:this.$player).css({position:"absolute",left:i.position.left,top:i.position.top});var e=this.last_position||i.position;return i.prev_position=e,this.options.drag&&this.options.drag.call(this.$player,t,i),this.last_position=i.position,!1},_.on_dragstop=function(t){var i=this.get_drag_data(t);return this.drag_start=!1,this.options.stop&&this.options.stop.call(this.$player,t,i),this.helper&&this.options.remove_helper&&this.$helper.remove(),!1},_.on_select_start=function(t){return this.disabled||this.ignore_drag(t)?void 0:!1},_.enable=function(){this.disabled=!1},_.disable=function(){this.disabled=!0},_.destroy=function(){this.disable(),this.$container.off(this.ns),this.$document.off(this.ns),t(window).off(this.ns),t.removeData(this.$container,"drag")},_.ignore_drag=function(i){return this.options.handle?!t(i.target).is(this.options.handle):t.isFunction(this.options.ignore_dragging)?this.options.ignore_dragging(i):t(i.target).is(this.options.ignore_dragging.join(", "))},t.fn.drag=function(t){return new i(this,t)},i}),function(t,i){"function"==typeof define&&define.amd?define(["jquery","gridster-draggable","gridster-collision"],i):t.Gridster=i(t.$||t.jQuery,t.GridsterDraggable,t.GridsterCollision)}(this,function(t,i){function e(i,e){this.options=t.extend(!0,{},s,e),this.$el=t(i),this.$wrapper=this.$el.parent(),this.$widgets=this.$el.children(this.options.widget_selector).addClass("gs-w"),this.widgets=[],this.$changed=t([]),this.wrapper_width=this.$wrapper.width(),this.min_widget_width=2*this.options.widget_margins[0]+this.options.widget_base_dimensions[0],this.min_widget_height=2*this.options.widget_margins[1]+this.options.widget_base_dimensions[1],this.generated_stylesheets=[],this.$style_tags=t([]),this.options.auto_init&&this.init()}var s={namespace:"",widget_selector:"li",widget_margins:[10,10],widget_base_dimensions:[400,225],extra_rows:0,extra_cols:0,min_cols:1,max_cols:1/0,min_rows:15,max_size_x:!1,autogrow_cols:!1,autogenerate_stylesheet:!0,avoid_overlapped_widgets:!0,auto_init:!0,serialize_params:function(t,i){return{col:i.col,row:i.row,size_x:i.size_x,size_y:i.size_y}},collision:{},draggable:{items:".gs-w",distance:4,ignore_dragging:i.defaults.ignore_dragging.slice(0)},resize:{enabled:!1,axes:["both"],handle_append_to:"",handle_class:"gs-resize-handle",max_size:[1/0,1/0],min_size:[1,1]}};e.defaults=s,e.generated_stylesheets=[],e.sort_by_row_asc=function(i){return i=i.sort(function(i,e){return i.row||(i=t(i).coords().grid,e=t(e).coords().grid),i.row>e.row?1:-1})},e.sort_by_row_and_col_asc=function(t){return t=t.sort(function(t,i){return t.row>i.row||t.row===i.row&&t.col>i.col?1:-1})},e.sort_by_col_asc=function(t){return t=t.sort(function(t,i){return t.col>i.col?1:-1})},e.sort_by_row_desc=function(t){return t=t.sort(function(t,i){return t.row+t.size_yn&&this.add_faux_rows(Math.max(e-n,0));var d=o+i-1;d>this.cols&&this.add_faux_cols(d-this.cols);var l={col:_,row:r.row,size_x:i,size_y:e};return this.mutate_widget_in_gridmap(t,r,l),this.set_dom_grid_height(),this.set_dom_grid_width(),s&&s.call(this,l.size_x,l.size_y),t},r.mutate_widget_in_gridmap=function(i,e,s){e.size_x;var r=e.size_y,o=this.get_cells_occupied(e),a=this.get_cells_occupied(s),n=[];t.each(o.cols,function(i,e){-1===t.inArray(e,a.cols)&&n.push(e)});var h=[];t.each(a.cols,function(i,e){-1===t.inArray(e,o.cols)&&h.push(e)});var _=[];t.each(o.rows,function(i,e){-1===t.inArray(e,a.rows)&&_.push(e)});var d=[];if(t.each(a.rows,function(i,e){-1===t.inArray(e,o.rows)&&d.push(e)}),this.remove_from_gridmap(e),h.length){var l=[s.col,s.row,s.size_x,Math.min(r,s.size_y),i];this.empty_cells.apply(this,l)}if(d.length){var c=[s.col,s.row,s.size_x,s.size_y,i];this.empty_cells.apply(this,c)}if(e.col=s.col,e.row=s.row,e.size_x=s.size_x,e.size_y=s.size_y,this.add_to_gridmap(s,i),i.removeClass("player-revert"),i.data("coords").update({width:s.size_x*this.options.widget_base_dimensions[0]+2*(s.size_x-1)*this.options.widget_margins[0],height:s.size_y*this.options.widget_base_dimensions[1]+2*(s.size_y-1)*this.options.widget_margins[1]}),i.attr({"data-col":s.col,"data-row":s.row,"data-sizex":s.size_x,"data-sizey":s.size_y}),n.length){var p=[n[0],s.row,n.length,Math.min(r,s.size_y),i];this.remove_empty_cells.apply(this,p)}if(_.length){var g=[s.col,s.row,s.size_x,s.size_y,i];this.remove_empty_cells.apply(this,g)}return this.move_widget_up(i),this},r.empty_cells=function(i,e,s,r,o){var a=this.widgets_below({col:i,row:e-r,size_x:s,size_y:r});return a.not(o).each(t.proxy(function(i,s){var o=t(s).coords().grid;if(e+r-1>=o.row){var a=e+r-o.row;this.move_widget_down(t(s),a)}},this)),this.set_dom_grid_height(),this},r.remove_empty_cells=function(i,e,s,r,o){var a=this.widgets_below({col:i,row:e,size_x:s,size_y:r});return a.not(o).each(t.proxy(function(i,e){this.move_widget_up(t(e),r)},this)),this.set_dom_grid_height(),this},r.next_position=function(t,i){t||(t=1),i||(i=1);for(var s,r=this.gridmap,o=r.length,a=[],n=1;o>n;n++){s=r[n].length;for(var h=1;s>=h;h++){var _=this.can_move_to({size_x:t,size_y:i},n,h);_&&a.push({col:n,row:h,size_y:i,size_x:t})}}return a.length?e.sort_by_row_and_col_asc(a)[0]:!1},r.remove_widget=function(i,e,s){var r=i instanceof t?i:t(i),o=r.coords().grid;t.isFunction(e)&&(s=e,e=!1),this.cells_occupied_by_placeholder={},this.$widgets=this.$widgets.not(r);var a=this.widgets_below(r);return this.remove_from_gridmap(o),r.fadeOut(t.proxy(function(){r.remove(),e||a.each(t.proxy(function(i,e){this.move_widget_up(t(e),o.size_y)},this)),this.set_dom_grid_height(),s&&s.call(this,i)},this)),this},r.remove_all_widgets=function(i){return this.$widgets.each(t.proxy(function(t,e){this.remove_widget(e,!0,i)},this)),this},r.serialize=function(i){return i||(i=this.$widgets),i.map(t.proxy(function(i,e){var s=t(e);return this.options.serialize_params(s,s.coords().grid)},this)).get()},r.serialize_changed=function(){return this.serialize(this.$changed)},r.dom_to_coords=function(t){return{col:parseInt(t.attr("data-col"),10),row:parseInt(t.attr("data-row"),10),size_x:parseInt(t.attr("data-sizex"),10)||1,size_y:parseInt(t.attr("data-sizey"),10)||1,max_size_x:parseInt(t.attr("data-max-sizex"),10)||!1,max_size_y:parseInt(t.attr("data-max-sizey"),10)||!1,min_size_x:parseInt(t.attr("data-min-sizex"),10)||!1,min_size_y:parseInt(t.attr("data-min-sizey"),10)||!1,el:t}},r.register_widget=function(i){var e=i instanceof jQuery,s=e?this.dom_to_coords(i):i,r=!1;e||(i=s.el);var o=this.can_go_widget_up(s);return o&&(s.row=o,i.attr("data-row",o),this.$el.trigger("gridster:positionchanged",[s]),r=!0),this.options.avoid_overlapped_widgets&&!this.can_move_to({size_x:s.size_x,size_y:s.size_y},s.col,s.row)&&(t.extend(s,this.next_position(s.size_x,s.size_y)),i.attr({"data-col":s.col,"data-row":s.row,"data-sizex":s.size_x,"data-sizey":s.size_y}),r=!0),i.data("coords",i.coords()),i.data("coords").grid=s,this.add_to_gridmap(s,i),this.options.resize.enabled&&this.add_resize_handle(i),r},r.update_widget_position=function(t,i){return this.for_each_cell_occupied(t,function(t,e){return this.gridmap[t]?(this.gridmap[t][e]=i,void 0):this}),this},r.remove_from_gridmap=function(t){return this.update_widget_position(t,!1)},r.add_to_gridmap=function(i,e){if(this.update_widget_position(i,e||i.el),i.el){var s=this.widgets_below(i.el);s.each(t.proxy(function(i,e){this.move_widget_up(t(e))},this))}},r.draggable=function(){var i=this,e=t.extend(!0,{},this.options.draggable,{offset_left:this.options.widget_margins[0],offset_top:this.options.widget_margins[1],container_width:this.cols*this.min_widget_width,limit:!0,start:function(e,s){i.$widgets.filter(".player-revert").removeClass("player-revert"),i.$player=t(this),i.$helper=t(s.$helper),i.helper=!i.$helper.is(i.$player),i.on_start_drag.call(i,e,s),i.$el.trigger("gridster:dragstart")},stop:function(t,e){i.on_stop_drag.call(i,t,e),i.$el.trigger("gridster:dragstop")},drag:throttle(function(t,e){i.on_drag.call(i,t,e),i.$el.trigger("gridster:drag")},60)});return this.drag_api=this.$el.drag(e),this},r.resizable=function(){return this.resize_api=this.$el.drag({items:"."+this.options.resize.handle_class,offset_left:this.options.widget_margins[0],container_width:this.container_width,move_element:!1,resize:!0,limit:this.options.autogrow_cols?!1:!0,start:t.proxy(this.on_start_resize,this),stop:t.proxy(function(i,e){delay(t.proxy(function(){this.on_stop_resize(i,e)},this),120)},this),drag:throttle(t.proxy(this.on_resize,this),60)}),this},r.setup_resize=function(){this.resize_handle_class=this.options.resize.handle_class;var i=this.options.resize.axes,e='';return this.resize_handle_tpl=t.map(i,function(t){return e.replace("{type}",t)}).join(""),t.isArray(this.options.draggable.ignore_dragging)&&this.options.draggable.ignore_dragging.push("."+this.resize_handle_class),this},r.on_start_drag=function(i,e){this.$helper.add(this.$player).add(this.$wrapper).addClass("dragging"),this.highest_col=this.get_highest_occupied_cell().col,this.$player.addClass("player"),this.player_grid_data=this.$player.coords().grid,this.placeholder_grid_data=t.extend({},this.player_grid_data),this.set_dom_grid_height(this.$el.height()+this.player_grid_data.size_y*this.min_widget_height),this.set_dom_grid_width(this.cols);var s=this.player_grid_data.size_x,r=this.cols-this.highest_col;this.options.autogrow_cols&&s>=r&&this.add_faux_cols(Math.min(s-r,1));var o=this.faux_grid,a=this.$player.data("coords").coords;this.cells_occupied_by_player=this.get_cells_occupied(this.player_grid_data),this.cells_occupied_by_placeholder=this.get_cells_occupied(this.placeholder_grid_data),this.last_cols=[],this.last_rows=[],this.collision_api=this.$helper.collision(o,this.options.collision),this.$preview_holder=t("<"+this.$player.get(0).tagName+" />",{"class":"preview-holder","data-row":this.$player.attr("data-row"),"data-col":this.$player.attr("data-col"),css:{width:a.width,height:a.height}}).appendTo(this.$el),this.options.draggable.start&&this.options.draggable.start.call(this,i,e)},r.on_drag=function(t,i){if(null===this.$player)return!1;var e={left:i.position.left+this.baseX,top:i.position.top+this.baseY};if(this.options.autogrow_cols){var s=this.placeholder_grid_data.col+this.placeholder_grid_data.size_x-1;s>=this.cols-1&&this.options.max_cols>=this.cols+1&&(this.add_faux_cols(1),this.set_dom_grid_width(this.cols+1),this.drag_api.set_limits(this.container_width)),this.collision_api.set_colliders(this.faux_grid)}this.colliders_data=this.collision_api.get_closest_colliders(e),this.on_overlapped_column_change(this.on_start_overlapping_column,this.on_stop_overlapping_column),this.on_overlapped_row_change(this.on_start_overlapping_row,this.on_stop_overlapping_row),this.helper&&this.$player&&this.$player.css({left:i.position.left,top:i.position.top}),this.options.draggable.drag&&this.options.draggable.drag.call(this,t,i)},r.on_stop_drag=function(t,i){this.$helper.add(this.$player).add(this.$wrapper).removeClass("dragging"),i.position.left=i.position.left+this.baseX,i.position.top=i.position.top+this.baseY,this.colliders_data=this.collision_api.get_closest_colliders(i.position),this.on_overlapped_column_change(this.on_start_overlapping_column,this.on_stop_overlapping_column),this.on_overlapped_row_change(this.on_start_overlapping_row,this.on_stop_overlapping_row),this.$player.addClass("player-revert").removeClass("player").attr({"data-col":this.placeholder_grid_data.col,"data-row":this.placeholder_grid_data.row}).css({left:"",top:""}),this.$changed=this.$changed.add(this.$player),this.cells_occupied_by_player=this.get_cells_occupied(this.placeholder_grid_data),this.set_cells_player_occupies(this.placeholder_grid_data.col,this.placeholder_grid_data.row),this.$player.coords().grid.row=this.placeholder_grid_data.row,this.$player.coords().grid.col=this.placeholder_grid_data.col,this.options.draggable.stop&&this.options.draggable.stop.call(this,t,i),this.$preview_holder.remove(),this.$player=null,this.$helper=null,this.placeholder_grid_data={},this.player_grid_data={},this.cells_occupied_by_placeholder={},this.cells_occupied_by_player={},this.set_dom_grid_height(),this.set_dom_grid_width(),this.options.autogrow_cols&&this.drag_api.set_limits(this.cols*this.min_widget_width)},r.on_start_resize=function(i,e){this.$resized_widget=e.$player.closest(".gs-w"),this.resize_coords=this.$resized_widget.coords(),this.resize_wgd=this.resize_coords.grid,this.resize_initial_width=this.resize_coords.coords.width,this.resize_initial_height=this.resize_coords.coords.height,this.resize_initial_sizex=this.resize_coords.grid.size_x,this.resize_initial_sizey=this.resize_coords.grid.size_y,this.resize_initial_col=this.resize_coords.grid.col,this.resize_last_sizex=this.resize_initial_sizex,this.resize_last_sizey=this.resize_initial_sizey,this.resize_max_size_x=Math.min(this.resize_wgd.max_size_x||this.options.resize.max_size[0],this.options.max_cols-this.resize_initial_col+1),this.resize_max_size_y=this.resize_wgd.max_size_y||this.options.resize.max_size[1],this.resize_min_size_x=this.resize_wgd.min_size_x||this.options.resize.min_size[0]||1,this.resize_min_size_y=this.resize_wgd.min_size_y||this.options.resize.min_size[1]||1,this.resize_initial_last_col=this.get_highest_occupied_cell().col,this.set_dom_grid_width(this.cols),this.resize_dir={right:e.$player.is("."+this.resize_handle_class+"-x"),bottom:e.$player.is("."+this.resize_handle_class+"-y")},this.$resized_widget.css({"min-width":this.options.widget_base_dimensions[0],"min-height":this.options.widget_base_dimensions[1]});var s=this.$resized_widget.get(0).tagName;this.$resize_preview_holder=t("<"+s+" />",{"class":"preview-holder resize-preview-holder","data-row":this.$resized_widget.attr("data-row"),"data-col":this.$resized_widget.attr("data-col"),css:{width:this.resize_initial_width,height:this.resize_initial_height}}).appendTo(this.$el),this.$resized_widget.addClass("resizing"),this.options.resize.start&&this.options.resize.start.call(this,i,e,this.$resized_widget),this.$el.trigger("gridster:resizestart")},r.on_stop_resize=function(i,e){this.$resized_widget.removeClass("resizing").css({width:"",height:""}),delay(t.proxy(function(){this.$resize_preview_holder.remove().css({"min-width":"","min-height":""}),this.options.resize.stop&&this.options.resize.stop.call(this,i,e,this.$resized_widget),this.$el.trigger("gridster:resizestop")},this),300),this.set_dom_grid_width(),this.options.autogrow_cols&&this.drag_api.set_limits(this.cols*this.min_widget_width)},r.on_resize=function(t,i){var e,s=i.pointer.diff_left,r=i.pointer.diff_top,o=this.options.widget_base_dimensions[0],a=this.options.widget_base_dimensions[1],n=this.options.widget_margins[0],h=this.options.widget_margins[1],_=this.resize_max_size_x,d=this.resize_min_size_x,l=this.resize_max_size_y,c=this.resize_min_size_y,p=this.options.autogrow_cols,g=1/0,u=1/0,f=Math.ceil(s/(o+2*n)-.2),w=Math.ceil(r/(a+2*h)-.2),m=Math.max(1,this.resize_initial_sizex+f),y=Math.max(1,this.resize_initial_sizey+w),v=this.container_width/this.min_widget_width-this.resize_initial_col+1,z=v*this.min_widget_width-2*n;if(m=Math.max(Math.min(m,_),d),m=Math.min(v,m),e=_*o+2*(m-1)*n,g=Math.min(e,z),min_width=d*o+2*(m-1)*n,y=Math.max(Math.min(y,l),c),u=l*a+2*(y-1)*h,min_height=c*a+2*(y-1)*h,this.resize_dir.right?y=this.resize_initial_sizey:this.resize_dir.bottom&&(m=this.resize_initial_sizex),p){var x=this.resize_initial_col+m-1;p&&x>=this.resize_initial_last_col&&(this.set_dom_grid_width(Math.max(x+1,this.cols)),x>this.cols&&this.add_faux_cols(x-this.cols))}var $={};!this.resize_dir.bottom&&($.width=Math.max(Math.min(this.resize_initial_width+s,g),min_width)),!this.resize_dir.right&&($.height=Math.max(Math.min(this.resize_initial_height+r,u),min_height)),this.$resized_widget.css($),(m!==this.resize_last_sizex||y!==this.resize_last_sizey)&&(this.resize_widget(this.$resized_widget,m,y),this.set_dom_grid_width(this.cols),this.$resize_preview_holder.css({width:"",height:""}).attr({"data-row":this.$resized_widget.attr("data-row"),"data-sizex":m,"data-sizey":y})),this.options.resize.resize&&this.options.resize.resize.call(this,t,i,this.$resized_widget),this.$el.trigger("gridster:resize"),this.resize_last_sizex=m,this.resize_last_sizey=y},r.on_overlapped_column_change=function(i,e){if(!this.colliders_data.length)return this;var s,r=this.get_targeted_columns(this.colliders_data[0].el.data.col),o=this.last_cols.length,a=r.length;for(s=0;a>s;s++)-1===t.inArray(r[s],this.last_cols)&&(i||t.noop).call(this,r[s]);for(s=0;o>s;s++)-1===t.inArray(this.last_cols[s],r)&&(e||t.noop).call(this,this.last_cols[s]);return this.last_cols=r,this},r.on_overlapped_row_change=function(i,e){if(!this.colliders_data.length)return this;var s,r=this.get_targeted_rows(this.colliders_data[0].el.data.row),o=this.last_rows.length,a=r.length;for(s=0;a>s;s++)-1===t.inArray(r[s],this.last_rows)&&(i||t.noop).call(this,r[s]);for(s=0;o>s;s++)-1===t.inArray(this.last_rows[s],r)&&(e||t.noop).call(this,this.last_rows[s]);this.last_rows=r},r.set_player=function(t,i,e){var s=this;e||this.empty_cells_player_occupies();var r=e?{col:t}:s.colliders_data[0].el.data,o=r.col,a=i||r.row;this.player_grid_data={col:o,row:a,size_y:this.player_grid_data.size_y,size_x:this.player_grid_data.size_x},this.cells_occupied_by_player=this.get_cells_occupied(this.player_grid_data);var n=this.get_widgets_overlapped(this.player_grid_data),h=this.widgets_constraints(n);if(this.manage_movements(h.can_go_up,o,a),this.manage_movements(h.can_not_go_up,o,a),!n.length){var _=this.can_go_player_up(this.player_grid_data);_!==!1&&(a=_),this.set_placeholder(o,a)}return{col:o,row:a}},r.widgets_constraints=function(i){var s,r=t([]),o=[],a=[];return i.each(t.proxy(function(i,e){var s=t(e),n=s.coords().grid;this.can_go_widget_up(n)?(r=r.add(s),o.push(n)):a.push(n)},this)),s=i.not(r),{can_go_up:e.sort_by_row_asc(o),can_not_go_up:e.sort_by_row_desc(a)}},r.manage_movements=function(i,e,s){return t.each(i,t.proxy(function(t,i){var r=i,o=r.el,a=this.can_go_widget_up(r);if(a)this.move_widget_to(o,a),this.set_placeholder(e,a+r.size_y);else{var n=this.can_go_player_up(this.player_grid_data);if(!n){var h=s+this.player_grid_data.size_y-r.row;this.move_widget_down(o,h),this.set_placeholder(e,s)}}},this)),this},r.is_player=function(t,i){if(i&&!this.gridmap[t])return!1;var e=i?this.gridmap[t][i]:t;return e&&(e.is(this.$player)||e.is(this.$helper))},r.is_player_in=function(i,e){var s=this.cells_occupied_by_player||{};return t.inArray(i,s.cols)>=0&&t.inArray(e,s.rows)>=0},r.is_placeholder_in=function(i,e){var s=this.cells_occupied_by_placeholder||{};return this.is_placeholder_in_col(i)&&t.inArray(e,s.rows)>=0},r.is_placeholder_in_col=function(i){var e=this.cells_occupied_by_placeholder||[];return t.inArray(i,e.cols)>=0},r.is_empty=function(t,i){return this.gridmap[t]!==void 0?this.gridmap[t][i]!==void 0&&this.gridmap[t][i]===!1?!0:!1:!0},r.is_occupied=function(t,i){return this.gridmap[t]?this.gridmap[t][i]?!0:!1:!1},r.is_widget=function(t,i){var e=this.gridmap[t];return e?(e=e[i],e?e:!1):!1},r.is_widget_under_player=function(t,i){return this.is_widget(t,i)?this.is_player_in(t,i):!1},r.get_widgets_under_player=function(i){i||(i=this.cells_occupied_by_player||{cols:[],rows:[]});var e=t([]);return t.each(i.cols,t.proxy(function(s,r){t.each(i.rows,t.proxy(function(t,i){this.is_widget(r,i)&&(e=e.add(this.gridmap[r][i]))},this))},this)),e},r.set_placeholder=function(i,e){var s=t.extend({},this.placeholder_grid_data),r=this.widgets_below({col:s.col,row:s.row,size_y:s.size_y,size_x:s.size_x}),o=i+s.size_x-1;o>this.cols&&(i-=o-i);var a=e>this.placeholder_grid_data.row,n=this.placeholder_grid_data.col!==i;this.placeholder_grid_data.col=i,this.placeholder_grid_data.row=e,this.cells_occupied_by_placeholder=this.get_cells_occupied(this.placeholder_grid_data),this.$preview_holder.attr({"data-row":e,"data-col":i}),(a||n)&&r.each(t.proxy(function(e,r){this.move_widget_up(t(r),this.placeholder_grid_data.col-i+s.size_y)},this));var h=this.get_widgets_under_player(this.cells_occupied_by_placeholder);h.length&&h.each(t.proxy(function(i,r){var o=t(r);this.move_widget_down(o,e+s.size_y-o.data("coords").grid.row)},this))},r.can_go_player_up=function(t){var i=t.row+t.size_y-1,e=!0,s=[],r=1e4,o=this.get_widgets_under_player();return this.for_each_column_occupied(t,function(t){var a=this.gridmap[t],n=i+1;for(s[t]=[];--n>0&&(this.is_empty(t,n)||this.is_player(t,n)||this.is_widget(t,n)&&a[n].is(o));)s[t].push(n),r=r>n?n:r;return 0===s[t].length?(e=!1,!0):(s[t].sort(function(t,i){return t-i +}),void 0)}),e?this.get_valid_rows(t,s,r):!1},r.can_go_widget_up=function(t){var i=t.row+t.size_y-1,e=!0,s=[],r=1e4;return this.for_each_column_occupied(t,function(o){var a=this.gridmap[o];s[o]=[];for(var n=i+1;--n>0&&(!this.is_widget(o,n)||this.is_player_in(o,n)||a[n].is(t.el));)this.is_player(o,n)||this.is_placeholder_in(o,n)||this.is_player_in(o,n)||s[o].push(n),r>n&&(r=n);return 0===s[o].length?(e=!1,!0):(s[o].sort(function(t,i){return t-i}),void 0)}),e?this.get_valid_rows(t,s,r):!1},r.get_valid_rows=function(i,e,s){for(var r=i.row,o=i.row+i.size_y-1,a=i.size_y,n=s-1,h=[];o>=++n;){var _=!0;if(t.each(e,function(i,e){t.isArray(e)&&-1===t.inArray(n,e)&&(_=!1)}),_===!0&&(h.push(n),h.length===a))break}var d=!1;return 1===a?h[0]!==r&&(d=h[0]||!1):h[0]!==r&&(d=this.get_consecutive_numbers_index(h,a)),d},r.get_consecutive_numbers_index=function(t,i){for(var e=t.length,s=[],r=!0,o=-1,a=0;e>a;a++){if(r||t[a]===o+1){if(s.push(a),s.length===i)break;r=!1}else s=[],r=!0;o=t[a]}return s.length>=i?t[s[0]]:!1},r.get_widgets_overlapped=function(){var i=t([]),e=[],s=this.cells_occupied_by_player.rows.slice(0);return s.reverse(),t.each(this.cells_occupied_by_player.cols,t.proxy(function(r,o){t.each(s,t.proxy(function(s,r){if(!this.gridmap[o])return!0;var a=this.gridmap[o][r];this.is_occupied(o,r)&&!this.is_player(a)&&-1===t.inArray(a,e)&&(i=i.add(a),e.push(a))},this))},this)),i},r.on_start_overlapping_column=function(t){this.set_player(t,!1)},r.on_start_overlapping_row=function(t){this.set_player(!1,t)},r.on_stop_overlapping_column=function(t){this.set_player(t,!1);var i=this;this.for_each_widget_below(t,this.cells_occupied_by_player.rows[0],function(){i.move_widget_up(this,i.player_grid_data.size_y)})},r.on_stop_overlapping_row=function(t){this.set_player(!1,t);for(var i=this,e=this.cells_occupied_by_player.cols,s=0,r=e.length;r>s;s++)this.for_each_widget_below(e[s],t,function(){i.move_widget_up(this,i.player_grid_data.size_y)})},r.move_widget_to=function(i,e){var s=this,r=i.coords().grid;e-r.row;var o=this.widgets_below(i),a=this.can_move_to(r,r.col,e,i);return a===!1?!1:(this.remove_from_gridmap(r),r.row=e,this.add_to_gridmap(r),i.attr("data-row",e),this.$changed=this.$changed.add(i),o.each(function(i,e){var r=t(e),o=r.coords().grid,a=s.can_go_widget_up(o);a&&a!==o.row&&s.move_widget_to(r,a)}),this)},r.move_widget_up=function(i,e){var s=i.coords().grid,r=s.row,o=[];return e||(e=1),this.can_go_up(i)?(this.for_each_column_occupied(s,function(s){if(-1===t.inArray(i,o)){var a=i.coords().grid,n=r-e;if(n=this.can_go_up_to_row(a,s,n),!n)return!0;var h=this.widgets_below(i);this.remove_from_gridmap(a),a.row=n,this.add_to_gridmap(a),i.attr("data-row",a.row),this.$changed=this.$changed.add(i),o.push(i),h.each(t.proxy(function(i,s){this.move_widget_up(t(s),e)},this))}}),void 0):!1},r.move_widget_down=function(i,e){var s,r,o,a;if(0>=e)return!1;if(s=i.coords().grid,r=s.row,o=[],a=e,!i)return!1;if(-1===t.inArray(i,o)){var n=i.coords().grid,h=r+e,_=this.widgets_below(i);this.remove_from_gridmap(n),_.each(t.proxy(function(i,e){var s=t(e),r=s.coords().grid,o=this.displacement_diff(r,n,a);o>0&&this.move_widget_down(s,o)},this)),n.row=h,this.update_widget_position(n,i),i.attr("data-row",n.row),this.$changed=this.$changed.add(i),o.push(i)}},r.can_go_up_to_row=function(i,e,s){var r,o=this.gridmap,a=!0,n=[],h=i.row;if(this.for_each_column_occupied(i,function(t){for(o[t],n[t]=[],r=h;r--&&this.is_empty(t,r)&&!this.is_placeholder_in(t,r);)n[t].push(r);return n[t].length?void 0:(a=!1,!0)}),!a)return!1;for(r=s,r=1;h>r;r++){for(var _=!0,d=0,l=n.length;l>d;d++)n[d]&&-1===t.inArray(r,n[d])&&(_=!1);if(_===!0){a=r;break}}return a},r.displacement_diff=function(t,i,e){var s=t.row,r=[],o=i.row+i.size_y;this.for_each_column_occupied(t,function(t){for(var i=0,e=o;s>e;e++)this.is_empty(t,e)&&(i+=1);r.push(i)});var a=Math.max.apply(Math,r);return e-=a,e>0?e:0},r.widgets_below=function(i){var s=t.isPlainObject(i)?i:i.coords().grid,r=this;this.gridmap;var o=s.row+s.size_y-1,a=t([]);return this.for_each_column_occupied(s,function(i){r.for_each_widget_below(i,o,function(){return r.is_player(this)||-1!==t.inArray(this,a)?void 0:(a=a.add(this),!0)})}),e.sort_by_row_asc(a)},r.set_cells_player_occupies=function(t,i){return this.remove_from_gridmap(this.placeholder_grid_data),this.placeholder_grid_data.col=t,this.placeholder_grid_data.row=i,this.add_to_gridmap(this.placeholder_grid_data,this.$player),this},r.empty_cells_player_occupies=function(){return this.remove_from_gridmap(this.placeholder_grid_data),this},r.can_go_up=function(t){var i=t.coords().grid,e=i.row,s=e-1;this.gridmap;var r=!0;return 1===e?!1:(this.for_each_column_occupied(i,function(t){return this.is_widget(t,s),this.is_occupied(t,s)||this.is_player(t,s)||this.is_placeholder_in(t,s)||this.is_player_in(t,s)?(r=!1,!0):void 0}),r)},r.can_move_to=function(t,i,e,s){this.gridmap;var r=t.el,o={size_y:t.size_y,size_x:t.size_x,col:i,row:e},a=!0,n=i+t.size_x-1;return n>this.cols?!1:s&&e+t.size_y-1>s?!1:(this.for_each_cell_occupied(o,function(i,e){var s=this.is_widget(i,e);!s||t.el&&!s.is(r)||(a=!1)}),a)},r.get_targeted_columns=function(t){for(var i=(t||this.player_grid_data.col)+(this.player_grid_data.size_x-1),e=[],s=t;i>=s;s++)e.push(s);return e},r.get_targeted_rows=function(t){for(var i=(t||this.player_grid_data.row)+(this.player_grid_data.size_y-1),e=[],s=t;i>=s;s++)e.push(s);return e},r.get_cells_occupied=function(i){var e,s={cols:[],rows:[]};for(arguments[1]instanceof t&&(i=arguments[1].coords().grid),e=0;i.size_x>e;e++){var r=i.col+e;s.cols.push(r)}for(e=0;i.size_y>e;e++){var o=i.row+e;s.rows.push(o)}return s},r.for_each_cell_occupied=function(t,i){return this.for_each_column_occupied(t,function(e){this.for_each_row_occupied(t,function(t){i.call(this,e,t)})}),this},r.for_each_column_occupied=function(t,i){for(var e=0;t.size_x>e;e++){var s=t.col+e;i.call(this,s,t)}},r.for_each_row_occupied=function(t,i){for(var e=0;t.size_y>e;e++){var s=t.row+e;i.call(this,s,t)}},r._traversing_widgets=function(i,e,s,r,o){var a=this.gridmap;if(a[s]){var n,h,_=i+"/"+e;if(arguments[2]instanceof t){var d=arguments[2].coords().grid;s=d.col,r=d.row,o=arguments[3]}var l=[],c=r,p={"for_each/above":function(){for(;c--&&!(c>0&&this.is_widget(s,c)&&-1===t.inArray(a[s][c],l)&&(n=o.call(a[s][c],s,c),l.push(a[s][c]),n)););},"for_each/below":function(){for(c=r+1,h=a[s].length;h>c&&(!this.is_widget(s,c)||-1!==t.inArray(a[s][c],l)||(n=o.call(a[s][c],s,c),l.push(a[s][c]),!n));c++);}};p[_]&&p[_].call(this)}},r.for_each_widget_above=function(t,i,e){return this._traversing_widgets("for_each","above",t,i,e),this},r.for_each_widget_below=function(t,i,e){return this._traversing_widgets("for_each","below",t,i,e),this},r.get_highest_occupied_cell=function(){for(var t,i=this.gridmap,e=i[1].length,s=[],r=[],o=i.length-1;o>=1;o--)for(t=e-1;t>=1;t--)if(this.is_widget(o,t)){s.push(t),r.push(o);break}return{col:Math.max.apply(Math,r),row:Math.max.apply(Math,s)}},r.get_widgets_from=function(i,e){this.gridmap;var s=t();return i&&(s=s.add(this.$widgets.filter(function(){var e=t(this).attr("data-col");return e===i||e>i}))),e&&(s=s.add(this.$widgets.filter(function(){var i=t(this).attr("data-row");return i===e||i>e}))),s},r.set_dom_grid_height=function(t){if(t===void 0){var i=this.get_highest_occupied_cell().row;t=i*this.min_widget_height}return this.container_height=t,this.$el.css("height",this.container_height),this},r.set_dom_grid_width=function(t){t===void 0&&(t=this.get_highest_occupied_cell().col);var i=this.options.autogrow_cols?this.options.max_cols:this.cols;return t=Math.min(i,Math.max(t,this.options.min_cols)),this.container_width=t*this.min_widget_width,this.$el.css("width",this.container_width),this},r.generate_stylesheet=function(i){var s,r="",o=this.options.max_size_x||this.cols;i||(i={}),i.cols||(i.cols=this.cols),i.rows||(i.rows=this.rows),i.namespace||(i.namespace=this.options.namespace),i.widget_base_dimensions||(i.widget_base_dimensions=this.options.widget_base_dimensions),i.widget_margins||(i.widget_margins=this.options.widget_margins),i.min_widget_width=2*i.widget_margins[0]+i.widget_base_dimensions[0],i.min_widget_height=2*i.widget_margins[1]+i.widget_base_dimensions[1];var a=t.param(i);if(t.inArray(a,e.generated_stylesheets)>=0)return!1;for(this.generated_stylesheets.push(a),e.generated_stylesheets.push(a),s=i.cols;s>=0;s--)r+=i.namespace+' [data-col="'+(s+1)+'"] { left:'+(s*i.widget_base_dimensions[0]+s*i.widget_margins[0]+(s+1)*i.widget_margins[0])+"px; }\n";for(s=i.rows;s>=0;s--)r+=i.namespace+' [data-row="'+(s+1)+'"] { top:'+(s*i.widget_base_dimensions[1]+s*i.widget_margins[1]+(s+1)*i.widget_margins[1])+"px; }\n";for(var n=1;i.rows>=n;n++)r+=i.namespace+' [data-sizey="'+n+'"] { height:'+(n*i.widget_base_dimensions[1]+(n-1)*2*i.widget_margins[1])+"px; }\n";for(var h=1;o>=h;h++)r+=i.namespace+' [data-sizex="'+h+'"] { width:'+(h*i.widget_base_dimensions[0]+(h-1)*2*i.widget_margins[0])+"px; }\n";return this.remove_style_tags(),this.add_style_tag(r)},r.add_style_tag=function(t){var i=document,e=i.createElement("style");return i.getElementsByTagName("head")[0].appendChild(e),e.setAttribute("type","text/css"),e.styleSheet?e.styleSheet.cssText=t:e.appendChild(document.createTextNode(t)),this.$style_tags=this.$style_tags.add(e),this},r.remove_style_tags=function(){var i=e.generated_stylesheets,s=this.generated_stylesheets;this.$style_tags.remove(),e.generated_stylesheets=t.map(i,function(i){return-1===t.inArray(i,s)?i:void 0})},r.generate_faux_grid=function(t,i){this.faux_grid=[],this.gridmap=[];var e,s;for(e=i;e>0;e--)for(this.gridmap[e]=[],s=t;s>0;s--)this.add_faux_cell(s,e);return this},r.add_faux_cell=function(i,e){var s=t({left:this.baseX+(e-1)*this.min_widget_width,top:this.baseY+(i-1)*this.min_widget_height,width:this.min_widget_width,height:this.min_widget_height,col:e,row:i,original_col:e,original_row:i}).coords();return t.isArray(this.gridmap[e])||(this.gridmap[e]=[]),this.gridmap[e][i]=!1,this.faux_grid.push(s),this},r.add_faux_rows=function(t){for(var i=this.rows,e=i+(t||1),s=e;s>i;s--)for(var r=this.cols;r>=1;r--)this.add_faux_cell(s,r);return this.rows=e,this.options.autogenerate_stylesheet&&this.generate_stylesheet(),this},r.add_faux_cols=function(t){var i=this.cols,e=i+(t||1);e=Math.min(e,this.options.max_cols);for(var s=i+1;e>=s;s++)for(var r=this.rows;r>=1;r--)this.add_faux_cell(r,s);return this.cols=e,this.options.autogenerate_stylesheet&&this.generate_stylesheet(),this},r.recalculate_faux_grid=function(){var i=this.$wrapper.width();return this.baseX=(t(window).width()-i)/2,this.baseY=this.$wrapper.offset().top,t.each(this.faux_grid,t.proxy(function(t,i){this.faux_grid[t]=i.update({left:this.baseX+(i.data.col-1)*this.min_widget_width,top:this.baseY+(i.data.row-1)*this.min_widget_height})},this)),this},r.get_widgets_from_DOM=function(){var i=this.$widgets.map(t.proxy(function(i,e){var s=t(e);return this.dom_to_coords(s)},this));i=e.sort_by_row_and_col_asc(i);var s=t(i).map(t.proxy(function(t,i){return this.register_widget(i)||null},this));return s.length&&this.$el.trigger("gridster:positionschanged"),this},r.generate_grid_and_stylesheet=function(){var i=this.$wrapper.width(),e=this.options.max_cols,s=Math.floor(i/this.min_widget_width)+this.options.extra_cols,r=this.$widgets.map(function(){return t(this).attr("data-col")}).get();r.length||(r=[0]);var o=Math.max.apply(Math,r);this.cols=Math.max(o,s,this.options.min_cols),1/0!==e&&e>=o&&this.cols>e&&(this.cols=e);var a=this.options.extra_rows;return this.$widgets.each(function(i,e){a+=+t(e).attr("data-sizey")}),this.rows=Math.max(a,this.options.min_rows),this.baseX=(t(window).width()-i)/2,this.baseY=this.$wrapper.offset().top,this.options.autogenerate_stylesheet&&this.generate_stylesheet(),this.generate_faux_grid(this.rows,this.cols)},r.destroy=function(i){return this.$el.removeData("gridster"),t(window).unbind(".gridster"),this.drag_api&&this.drag_api.destroy(),this.remove_style_tags(),i&&this.$el.remove(),this},t.fn.gridster=function(i){return this.each(function(){t(this).data("gridster")||t(this).data("gridster",new e(this,i))})},e}),function(t,i){"function"==typeof define&&define.amd?define(["jquery","gridster"],i):t.Gridster=i(t.$||t.jQuery,t.Gridster)}(this,function(t,i){var e=i.prototype;return e.widgets_in_col=function(t){if(!this.gridmap[t])return!1;for(var i=this.gridmap[t].length-1;i>=0;i--)if(this.is_widget(t,i)!==!1)return!0;return!1},e.widgets_in_row=function(t){for(var i=this.gridmap.length;i>=1;i--)if(this.is_widget(i,t)!==!1)return!0;return!1},e.widgets_in_range=function(i,e,s,r){var o,a,n,h,_=t([]);for(o=s;o>=i;o--)for(a=r;a>=e;a--)n=this.is_widget(o,a),n!==!1&&(h=n.data("coords").grid,h.col>=i&&s>=h.col&&h.row>=e&&r>=h.row&&(_=_.add(n)));return _},e.get_bottom_most_occupied_cell=function(){var t=0,i=0;return this.for_each_cell(function(e,s,r){e&&r>t&&(t=r,i=s)}),{col:i,row:t}},e.get_right_most_occupied_cell=function(){var t=0,i=0;return this.for_each_cell(function(e,s,r){return e?(t=r,i=s,!1):void 0}),{col:i,row:t}},e.for_each_cell=function(t,i){i||(i=this.gridmap);var e=i.length,s=i[1].length;t:for(var r=e-1;r>=1;r--)for(var o=s-1;o>=1;o--){var a=i[r]&&i[r][o];if(t){if(t.call(this,a,r,o)===!1)break t}else;}},e.next_position_in_range=function(t,e,s){t||(t=1),e||(e=1);for(var r,o=this.gridmap,a=o.length,n=[],h=1;a>h;h++){r=s||o[h].length;for(var _=1;r>=_;_++){var d=this.can_move_to({size_x:t,size_y:e},h,_,s);d&&n.push({col:h,row:_,size_y:e,size_x:t})}}return n.length>=1?i.sort_by_col_asc(n)[0]:!1},e.closest_to_right=function(t,i){if(!this.gridmap[t])return!1;for(var e=this.gridmap.length-1,s=t;e>=s;s++)if(this.gridmap[s][i])return{col:s,row:i};return!1},e.closest_to_left=function(t,i){if(this.gridmap.length-1,!this.gridmap[t])return!1;for(var e=t;e>=1;e--)if(this.gridmap[e][i])return{col:e,row:i};return!1},i}); \ No newline at end of file diff --git a/panoramix/static/loading.gif b/panoramix/static/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..01ae3939c49bfd7c8c1086c69775c098dca2485e GIT binary patch literal 16671 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 literal 0 HcmV?d00001 diff --git a/panoramix/templates/panoramix/dashboard.html b/panoramix/templates/panoramix/dashboard.html new file mode 100644 index 000000000..c65aff43b --- /dev/null +++ b/panoramix/templates/panoramix/dashboard.html @@ -0,0 +1,63 @@ +{% extends "panoramix/base.html" %} + +{% block head_css %} + {{super()}} + + +{% endblock %} + +{% block content_fluid %} +
+
    +
  • +
  • +
  • +
  • + +
  • +
  • + +
  • +
  • +
  • + +
  • +
  • + +
  • +
  • +
  • +
  • +
+
+{% endblock %} + +{% block tail %} + {{ super() }} + + + +{% endblock %} + diff --git a/panoramix/templates/panoramix/viz_table.html b/panoramix/templates/panoramix/viz_table.html index a7a0d64d6..a68b63033 100644 --- a/panoramix/templates/panoramix/viz_table.html +++ b/panoramix/templates/panoramix/viz_table.html @@ -21,7 +21,9 @@ {% for col in df.columns if not col.endswith('__perc') %} {% if col + '__perc' in df.columns %} - {{ row[col] }} + + {{ row[col] }} + {% else %} {{ row[col] }} {% endif %} diff --git a/panoramix/views.py b/panoramix/views.py index cd0edd1ea..b3a3f1815 100644 --- a/panoramix/views.py +++ b/panoramix/views.py @@ -194,6 +194,8 @@ class Panoramix(BaseView): .filter_by(id=table_id) .first() ) + if not table: + flash("The table seem to have been deleted", "alert") viz_type = request.args.get("viz_type") if not viz_type and table.default_endpoint: return redirect(table.default_endpoint) @@ -255,6 +257,11 @@ class Panoramix(BaseView): return "super!" + @has_access + @expose("/dashboard/") + def dashboard(self): + return self.render_template("panoramix/dashboard.html") + @has_access @expose("/refresh_datasources/") def refresh_datasources(self): diff --git a/setup.py b/setup.py index 1f6627d93..da5f69c6d 100644 --- a/setup.py +++ b/setup.py @@ -16,6 +16,7 @@ setup( install_requires=[ 'flask-appbuilder>=1.4.5', 'flask-migrate>=1.5.1', + 'flask-login==0.2.11', 'gunicorn>=19.3.0', 'pandas>=0.16.2', 'pydruid>=0.2.2', From a5b896414d1ae73783c9a26bd74342bd4c05d75f Mon Sep 17 00:00:00 2001 From: Maxime Beauchemin Date: Sun, 13 Sep 2015 19:07:54 -0700 Subject: [PATCH 3/9] Dashboards --- TODO.md | 2 - panoramix/models.py | 65 ++++++++++++++++++- panoramix/templates/panoramix/dashboard.html | 47 ++++++++------ panoramix/templates/panoramix/viz.html | 15 +++++ .../templates/panoramix/viz_highcharts.html | 6 -- panoramix/templates/panoramix/viz_table.html | 26 +++----- panoramix/views.py | 31 +++++++-- panoramix/viz.py | 7 ++ 8 files changed, 147 insertions(+), 52 deletions(-) diff --git a/TODO.md b/TODO.md index 40c646ac8..e15ee80c4 100644 --- a/TODO.md +++ b/TODO.md @@ -3,8 +3,6 @@ * DRUID: Allow for post aggregations (ratios!) * compare time ranges * csv export out of table view -* Save / bookmark / url shortener * SQL: Find a way to manage granularity * Create ~/.panoramix/ to host DB and config, generate default config there * Add a per-datasource permission - diff --git a/panoramix/models.py b/panoramix/models.py index b3294ae1a..4eb47a5a9 100644 --- a/panoramix/models.py +++ b/panoramix/models.py @@ -10,7 +10,7 @@ from sqlalchemy import ( Column, Integer, String, ForeignKey, Text, Boolean, DateTime) from panoramix.utils import JSONEncodedDict from sqlalchemy import Table as sqlaTable -from sqlalchemy import create_engine, MetaData, desc, select, and_ +from sqlalchemy import create_engine, MetaData, desc, select, and_, Table from sqlalchemy.orm import relationship from sqlalchemy.sql import table, literal_column, text @@ -33,12 +33,23 @@ class Slice(Model, AuditMixin): __tablename__ = 'slices' id = Column(Integer, primary_key=True) slice_name = Column(String(250)) - datasource_id = Column(Integer) + datasource_id = Column(Integer, ForeignKey('datasources.id')) + table_id = Column(Integer, ForeignKey('tables.id')) datasource_type = Column(String(200)) datasource_name = Column(String(2000)) viz_type = Column(String(250)) params = Column(Text) + table = relationship('Table', backref='slices') + druid_datasource = relationship('Datasource', backref='slices') + + def __repr__(self): + return self.slice_name + + @property + def datasource(self): + return self.table or self.druid_datasource + @property def slice_link(self): d = json.loads(self.params) @@ -48,6 +59,53 @@ class Slice(Model, AuditMixin): "{self.datasource_id}/?{kwargs}").format(**locals()) return '{self.slice_name}'.format(**locals()) + @property + def js_files(self): + from panoramix.viz import viz_types + return viz_types[self.viz_type].js_files + + @property + def css_files(self): + from panoramix.viz import viz_types + return viz_types[self.viz_type].css_files + + +dashboard_slices = Table('dashboard_slices', Model.metadata, + Column('id', Integer, primary_key=True), + Column('dashboard_id', Integer, ForeignKey('dashboards.id')), + Column('slice_id', Integer, ForeignKey('slices.id')), +) + + +class Dashboard(Model, AuditMixin): + """A dash to slash""" + __tablename__ = 'dashboards' + id = Column(Integer, primary_key=True) + dashboard_title = Column(String(500)) + slices = relationship( + 'Slice', secondary=dashboard_slices, backref='dashboards') + + def __repr__(self): + return self.dashboard_title + + def dashboard_link(self): + url = "/panoramix/dashboard/{}/".format(self.id) + return '{self.dashboard_title}'.format(**locals()) + + @property + def js_files(self): + l = [] + for o in self.slices: + l += o.js_files + return list(set(l)) + + @property + def css_files(self): + l = [] + for o in self.slices: + l += o.css_files + return list(set(l)) + class Queryable(object): @property @@ -99,6 +157,9 @@ class Table(Model, Queryable, AuditMixin): baselink = "tableview" + def __repr__(self): + return self.table_name + @property def name(self): return self.table_name diff --git a/panoramix/templates/panoramix/dashboard.html b/panoramix/templates/panoramix/dashboard.html index c65aff43b..ad8dfa0e8 100644 --- a/panoramix/templates/panoramix/dashboard.html +++ b/panoramix/templates/panoramix/dashboard.html @@ -1,7 +1,7 @@ {% extends "panoramix/base.html" %} {% block head_css %} - {{super()}} + {{ super() }} {% endblock %} {% block content_fluid %} +

{{ dashboard.dashboard_title }}

    -
  • -
  • -
  • -
  • - -
  • -
  • - -
  • -
  • -
  • - -
  • -
  • - -
  • -
  • -
  • + {% for slice in dashboard.slices %} +
  • +
    +
    {{ slice.slice_name }}
    +
  • + {% endfor %}
{% endblock %} @@ -48,15 +52,16 @@ {{ super() }} {% endblock %} diff --git a/panoramix/templates/panoramix/viz.html b/panoramix/templates/panoramix/viz.html index f4803c8f9..eec9f330d 100644 --- a/panoramix/templates/panoramix/viz.html +++ b/panoramix/templates/panoramix/viz.html @@ -3,3 +3,18 @@ {% else %} {% extends 'panoramix/datasource.html' %} {% endif %} + +{% block head %} + {{super()}} + {% for css in viz.css_files %} + + {% endfor %} +{% endblock %} + + +{% block tail %} + {{super()}} + {% for js in viz.js_files %} + + {% endfor %} +{% endblock %} diff --git a/panoramix/templates/panoramix/viz_highcharts.html b/panoramix/templates/panoramix/viz_highcharts.html index ae10b83e7..5d815d464 100644 --- a/panoramix/templates/panoramix/viz_highcharts.html +++ b/panoramix/templates/panoramix/viz_highcharts.html @@ -6,12 +6,6 @@ {% block tail %} {{ super() }} - {% if viz.stockchart %} - - {% else %} - - {% endif %} - - - - + {% endblock %} diff --git a/panoramix/views.py b/panoramix/views.py index b3a3f1815..1be93d7a7 100644 --- a/panoramix/views.py +++ b/panoramix/views.py @@ -106,7 +106,7 @@ appbuilder.add_view( class SliceModelView(ModelView, DeleteMixin): datamodel = SQLAInterface(models.Slice) - list_columns = ['slice_link', 'viz_type', 'created_by'] + list_columns = ['slice_link', 'viz_type', 'datasource', 'created_by'] appbuilder.add_view( SliceModelView, @@ -116,6 +116,21 @@ appbuilder.add_view( category_icon='',) +class DashboardModelView(ModelView, DeleteMixin): + datamodel = SQLAInterface(models.Dashboard) + list_columns = ['dashboard_link', 'created_by'] + edit_columns = ['dashboard_title', 'slices',] + add_columns = edit_columns + + +appbuilder.add_view( + DashboardModelView, + "Dashboards", + icon="fa-dashboard", + category="", + category_icon='',) + + class DatabaseView(ModelView, DeleteMixin): datamodel = SQLAInterface(models.Database) list_columns = ['database_name'] @@ -258,9 +273,17 @@ class Panoramix(BaseView): return "super!" @has_access - @expose("/dashboard/") - def dashboard(self): - return self.render_template("panoramix/dashboard.html") + @expose("/dashboard//") + def dashboard(self, id_): + session = db.session() + dashboard = ( + session + .query(models.Dashboard) + .filter(models.Dashboard.id == id_) + .first() + ) + return self.render_template( + "panoramix/dashboard.html", dashboard=dashboard) @has_access @expose("/refresh_datasources/") diff --git a/panoramix/viz.py b/panoramix/viz.py index cc8f6760a..4a38b8f79 100644 --- a/panoramix/viz.py +++ b/panoramix/viz.py @@ -24,6 +24,8 @@ class BaseViz(object): form_fields = [ 'viz_type', 'metrics', 'groupby', 'granularity', ('since', 'until')] + js_files = [] + css_files = [] def __init__(self, datasource, form_data, view): self.datasource = datasource @@ -137,6 +139,8 @@ class TableViz(BaseViz): verbose_name = "Table View" template = 'panoramix/viz_table.html' form_fields = BaseViz.form_fields + ['row_limit'] + css_files = ['dataTables.bootstrap.css'] + js_files = ['jquery.dataTables.min.js', 'dataTables.bootstrap.js'] def query_obj(self): d = super(TableViz, self).query_obj() @@ -165,6 +169,7 @@ class HighchartsViz(BaseViz): stacked = False chart_type = 'not_stock' compare = False + js_files = ['highcharts.js'] class BubbleViz(HighchartsViz): @@ -174,6 +179,7 @@ class BubbleViz(HighchartsViz): form_fields = [ 'viz_type', 'since', 'until', 'series', 'entity', 'x', 'y', 'size', 'limit'] + js_files = ['highcharts.js', 'highcharts-more.js'] def query_obj(self): d = super(BubbleViz, self).query_obj() @@ -212,6 +218,7 @@ class TimeSeriesViz(HighchartsViz): chart_type = "spline" stockchart = True sort_legend_y = True + js_files = ['highstock.js', 'highcharts-more.js'] form_fields = [ 'viz_type', 'granularity', ('since', 'until'), From 6daf92e3c1fdbd76079bbeb4dd6f8790f6551871 Mon Sep 17 00:00:00 2001 From: Maxime Beauchemin Date: Mon, 14 Sep 2015 08:04:32 -0700 Subject: [PATCH 4/9] About to start ajaxifying --- panoramix/models.py | 4 +- panoramix/templates/panoramix/dashboard.html | 39 ++++++++++++++++--- panoramix/templates/panoramix/datasource.html | 4 +- panoramix/templates/panoramix/viz.html | 20 +++++++--- .../templates/panoramix/viz_highcharts.html | 2 +- .../templates/panoramix/viz_standalone.html | 19 +++++---- panoramix/viz.py | 7 ++-- 7 files changed, 69 insertions(+), 26 deletions(-) diff --git a/panoramix/models.py b/panoramix/models.py index 4eb47a5a9..575764dc7 100644 --- a/panoramix/models.py +++ b/panoramix/models.py @@ -96,8 +96,8 @@ class Dashboard(Model, AuditMixin): def js_files(self): l = [] for o in self.slices: - l += o.js_files - return list(set(l)) + l += [f for f in o.js_files if f not in l] + return l @property def css_files(self): diff --git a/panoramix/templates/panoramix/dashboard.html b/panoramix/templates/panoramix/dashboard.html index ad8dfa0e8..d56ea6f8c 100644 --- a/panoramix/templates/panoramix/dashboard.html +++ b/panoramix/templates/panoramix/dashboard.html @@ -2,12 +2,14 @@ {% block head_css %} {{ super() }} + {% for css in dashboard.css_files %} + + {% endfor %} {% endblock %} @@ -42,6 +48,7 @@
{{ slice.slice_name }}
+
{% endfor %} @@ -50,18 +57,40 @@ {% block tail %} {{ super() }} + {% for js in dashboard.js_files %} + + {% endfor %} + + {% endblock %} diff --git a/panoramix/templates/panoramix/datasource.html b/panoramix/templates/panoramix/datasource.html index aae04dfdf..257dd77ef 100644 --- a/panoramix/templates/panoramix/datasource.html +++ b/panoramix/templates/panoramix/datasource.html @@ -112,6 +112,7 @@ form input.form-control { {% endif %}
+
{% block viz %} {% if viz.error_msg %}
{{ viz.error_msg }}
@@ -120,6 +121,7 @@ form input.form-control {
{{ viz.warning_msg }}
{% endif %} {% endblock %} +
{% if debug %}

Results

@@ -154,10 +156,8 @@ form input.form-control { {% block tail_js %} {{ super() }} - - {% endfor %} + + + {% if not skip_libs %} + {% for js in viz.js_files %} + + {% endfor %} + {% endif %} {% endblock %} diff --git a/panoramix/templates/panoramix/viz_highcharts.html b/panoramix/templates/panoramix/viz_highcharts.html index 5d815d464..6ec6f8fed 100644 --- a/panoramix/templates/panoramix/viz_highcharts.html +++ b/panoramix/templates/panoramix/viz_highcharts.html @@ -1,7 +1,7 @@ {% extends "panoramix/viz.html" %} {% block viz %} {{ super() }} -
+
{% endblock %} {% block tail %} diff --git a/panoramix/templates/panoramix/viz_standalone.html b/panoramix/templates/panoramix/viz_standalone.html index 3e8c32dbe..64adf199f 100644 --- a/panoramix/templates/panoramix/viz_standalone.html +++ b/panoramix/templates/panoramix/viz_standalone.html @@ -1,9 +1,14 @@ -{% extends "appbuilder/baselayout.html" %} -{% block body %} + + + {% if not skip_libs %} + {% block head %} + + {% endblock %} + {% endif %} + {% block tail %}{% endblock %} + + {% block viz %} {% endblock %} -{% endblock %} -{% block tail %} - {{ super() }} -{% endblock %} - + + diff --git a/panoramix/viz.py b/panoramix/viz.py index 4a38b8f79..08cb114d9 100644 --- a/panoramix/viz.py +++ b/panoramix/viz.py @@ -11,7 +11,7 @@ from panoramix.highchart import Highchart, HighchartBubble from panoramix.forms import form_factory CHART_ARGS = { - 'height': 700, + #'height': 700, 'title': None, 'target_div': 'chart', } @@ -132,6 +132,7 @@ class BaseViz(object): self.template, form=form, viz=self, datasource=self.datasource, results=self.results, standalone=request.args.get('standalone') == 'true', + skip_libs=request.args.get('skip_libs') == 'true', *args, **kwargs) @@ -169,7 +170,7 @@ class HighchartsViz(BaseViz): stacked = False chart_type = 'not_stock' compare = False - js_files = ['highcharts.js'] + js_files = ['highstock.js'] class BubbleViz(HighchartsViz): @@ -179,7 +180,7 @@ class BubbleViz(HighchartsViz): form_fields = [ 'viz_type', 'since', 'until', 'series', 'entity', 'x', 'y', 'size', 'limit'] - js_files = ['highcharts.js', 'highcharts-more.js'] + js_files = ['highstock.js', 'highcharts-more.js'] def query_obj(self): d = super(BubbleViz, self).query_obj() From 0bc2e71ac6dbb74e651cd428f8180a42a24a64db Mon Sep 17 00:00:00 2001 From: Maxime Beauchemin Date: Mon, 14 Sep 2015 08:50:01 -0700 Subject: [PATCH 5/9] Changing the way viz templates are defined using macros instead --- panoramix/templates/panoramix/datasource.html | 2 +- panoramix/templates/panoramix/viz.html | 9 +++ .../templates/panoramix/viz_highcharts.html | 18 ++--- .../templates/panoramix/viz_standalone.html | 2 +- panoramix/templates/panoramix/viz_table.html | 79 +++++++++---------- panoramix/viz.py | 16 ++-- 6 files changed, 67 insertions(+), 59 deletions(-) diff --git a/panoramix/templates/panoramix/datasource.html b/panoramix/templates/panoramix/datasource.html index 257dd77ef..f10eb699d 100644 --- a/panoramix/templates/panoramix/datasource.html +++ b/panoramix/templates/panoramix/datasource.html @@ -113,7 +113,7 @@ form input.form-control {
- {% block viz %} + {% block viz_html %} {% if viz.error_msg %}
{{ viz.error_msg }}
{% endif %} diff --git a/panoramix/templates/panoramix/viz.html b/panoramix/templates/panoramix/viz.html index 47cd18292..23aeca85e 100644 --- a/panoramix/templates/panoramix/viz.html +++ b/panoramix/templates/panoramix/viz.html @@ -1,9 +1,16 @@ +{% import viz.template as viz_macros %} + {% if standalone %} {% extends 'panoramix/viz_standalone.html' %} {% else %} {% extends 'panoramix/datasource.html' %} {% endif %} + +{% block viz_html %} + {{ viz_macros.viz_html(viz) }} +{% endblock %} + {% block head %} {{super()}} {% if not skip_libs %} @@ -11,6 +18,7 @@ {% endfor %} {% endif %} + {{ viz_macros.viz_css(viz) }} {% endblock %} @@ -25,4 +33,5 @@ {% endfor %} {% endif %} + {{ viz_macros.viz_js(viz) }} {% endblock %} diff --git a/panoramix/templates/panoramix/viz_highcharts.html b/panoramix/templates/panoramix/viz_highcharts.html index 6ec6f8fed..b47607ecc 100644 --- a/panoramix/templates/panoramix/viz_highcharts.html +++ b/panoramix/templates/panoramix/viz_highcharts.html @@ -1,11 +1,8 @@ -{% extends "panoramix/viz.html" %} -{% block viz %} - {{ super() }} +{% macro viz_html(viz) %}
-{% endblock %} +{% endmacro %} -{% block tail %} - {{ super() }} +{% macro viz_js(viz) %} -{% endblock %} +{% endmacro %} + +{% macro viz_css(viz) %} +{% endmacro %} diff --git a/panoramix/templates/panoramix/viz_standalone.html b/panoramix/templates/panoramix/viz_standalone.html index 64adf199f..cc52fb8fb 100644 --- a/panoramix/templates/panoramix/viz_standalone.html +++ b/panoramix/templates/panoramix/viz_standalone.html @@ -8,7 +8,7 @@ {% block tail %}{% endblock %} - {% block viz %} + {% block viz_html %} {% endblock %} diff --git a/panoramix/templates/panoramix/viz_table.html b/panoramix/templates/panoramix/viz_table.html index a10b984a9..ae9a63398 100644 --- a/panoramix/templates/panoramix/viz_table.html +++ b/panoramix/templates/panoramix/viz_table.html @@ -1,45 +1,42 @@ -{% extends "panoramix/viz.html" %} +{% macro viz_html(viz) %} + {% set df = viz.df %} + + + + {% 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 %} + +
{{ col }}
+ {{ row[col] }} + {{ row[col] }}
+{% endmacro %} -{% block viz %} - {{ super() }} - {% if not error_msg %} - - - - {% 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 %} - -
{{ col }}
- {{ row[col] }} - {{ row[col] }}
- {% endif %} -{% endblock %} - -{% block tail %} - {{ super() }} - -{% endblock %} + +{% endmacro %} +{% macro viz_css(viz) %} +{% endmacro %} diff --git a/panoramix/viz.py b/panoramix/viz.py index 08cb114d9..b51f5b6c4 100644 --- a/panoramix/viz.py +++ b/panoramix/viz.py @@ -129,7 +129,7 @@ class BaseViz(object): def render(self, *args, **kwargs): form = self.form_class(self.form_data) return self.view.render_template( - self.template, form=form, viz=self, datasource=self.datasource, + "panoramix/viz.html", form=form, viz=self, datasource=self.datasource, results=self.results, standalone=request.args.get('standalone') == 'true', skip_libs=request.args.get('skip_libs') == 'true', @@ -211,7 +211,8 @@ class BubbleViz(HighchartsViz): df['name'] = df[[self.entity]] df['group'] = df[[self.series]] chart = HighchartBubble(df) - return super(BubbleViz, self).render(chart_js=chart.javascript_cmd) + self.chart_js = chart.javascript_cmd + return super(BubbleViz, self).render() class TimeSeriesViz(HighchartsViz): @@ -259,7 +260,8 @@ class TimeSeriesViz(HighchartsViz): stockchart=self.stockchart, sort_legend_y=self.sort_legend_y, **CHART_ARGS) - return super(TimeSeriesViz, self).render(chart_js=chart.javascript_cmd) + self.chart_js = chart.javascript_cmd + return super(TimeSeriesViz, self).render() def bake_query(self): """ @@ -314,8 +316,8 @@ class DistributionBarViz(HighchartsViz): df = df.sort(self.metrics[0], ascending=False) chart = Highchart( df, chart_type=self.chart_type, **CHART_ARGS) - return super(DistributionBarViz, self).render( - chart_js=chart.javascript_cmd) + self.chart_js = chart.javascript_cmd + return super(DistributionBarViz, self).render() class DistributionPieViz(HighchartsViz): @@ -337,8 +339,8 @@ class DistributionPieViz(HighchartsViz): df = df.sort(self.metrics[0], ascending=False) chart = Highchart( df, chart_type=self.chart_type, **CHART_ARGS) - return super(DistributionPieViz, self).render( - chart_js=chart.javascript_cmd) + self.chart_js = chart.javascript_cmd + return super(DistributionPieViz, self).render() viz_types = OrderedDict([ ['table', TableViz], From 359a81eee3ff04cf92dd5db0569c3fdd58b14379 Mon Sep 17 00:00:00 2001 From: Maxime Beauchemin Date: Tue, 15 Sep 2015 09:17:59 -0700 Subject: [PATCH 6/9] Getting back into a working state --- panoramix/highchart.py | 11 +- panoramix/templates/panoramix/datasource.html | 299 +++++++++--------- panoramix/templates/panoramix/viz.html | 68 ++-- .../templates/panoramix/viz_highcharts.html | 47 +-- .../templates/panoramix/viz_standalone.html | 21 +- panoramix/templates/panoramix/viz_table.html | 63 ++-- panoramix/views.py | 9 +- panoramix/viz.py | 138 +++----- 8 files changed, 319 insertions(+), 337 deletions(-) diff --git a/panoramix/highchart.py b/panoramix/highchart.py index 709cc2a59..295d80537 100644 --- a/panoramix/highchart.py +++ b/panoramix/highchart.py @@ -9,12 +9,17 @@ class BaseHighchart(object): target_div = 'chart' @property - def javascript_cmd(self): + def json(self): js = dumps(self.chart) - js = ( + return ( js.replace('"{{TOOLTIP_FORMATTER}}"', self.tooltip_formatter) .replace("\n", " ") ) + + + @property + def javascript_cmd(self): + js = self.json if self.stockchart: return "new Highcharts.StockChart(%s);" % js return "new Highcharts.Chart(%s);" % js @@ -83,7 +88,7 @@ class Highchart(BaseHighchart): if sort_legend_y: if 'tooltip' not in chart: chart['tooltip'] = { - 'formatter': "{{TOOLTIP_FORMATTER}}" + #'formatter': "{{TOOLTIP_FORMATTER}}" } if self.zoom: chart["zoomType"] = self.zoom diff --git a/panoramix/templates/panoramix/datasource.html b/panoramix/templates/panoramix/datasource.html index f10eb699d..641d6ee40 100644 --- a/panoramix/templates/panoramix/datasource.html +++ b/panoramix/templates/panoramix/datasource.html @@ -1,36 +1,40 @@ {% extends "panoramix/base.html" %} {% block head_css %} {{super()}} -{% set datasource = viz.datasource %} {% endblock %} {% block content_fluid %} +{% set datasource = viz.datasource %} +{% set form = viz.form %}

{{ datasource.name }} {% if datasource.description %} - + {% endif %} @@ -40,51 +44,51 @@ form input.form-control {