Adding standalone mode

This commit is contained in:
Maxime Beauchemin 2015-12-21 07:58:20 -08:00
parent fffb0a7a80
commit 6a28ad3f4e
11 changed files with 246 additions and 223 deletions

View File

@ -1,6 +1,8 @@
# TODO # TODO
List of TODO items for Panoramix List of TODO items for Panoramix
* fix directed
## Improvments ## Improvments
* dashboard controller + filters * dashboard controller + filters
* Color hash in JS * Color hash in JS

View File

@ -136,7 +136,7 @@ legend {
.datasource .tooltip-inner { .datasource .tooltip-inner {
max-width: 350px; max-width: 350px;
} }
.datasource img.loading { img.loading {
width: 30px; width: 30px;
} }

View File

@ -1,6 +1,7 @@
var px = (function() { var px = (function() {
var visualizations = []; var visualizations = [];
var dashboard = undefined;
var Slice = function(data, dashboard){ var Slice = function(data, dashboard){
var timer; var timer;
@ -73,10 +74,14 @@ var px = (function() {
container.hide(); container.hide();
container.html(''); container.html('');
timer = setInterval(stopwatch, 10); timer = setInterval(stopwatch, 10);
viz.render(this); viz.render();
}, },
resize: function() { resize: function() {
viz.resize(this); token.find("img.loading").show();
container.hide();
container.html('');
viz.render();
viz.resize();
}, },
addFilter: function(col, vals) { addFilter: function(col, vals) {
if(dashboard !== undefined) if(dashboard !== undefined)
@ -92,10 +97,11 @@ var px = (function() {
return slice; return slice;
} }
var Dashboard = function(){ var Dashboard = function(id){
var dash = { var dash = {
slices: [], slices: [],
filters: {}, filters: {},
id: id,
addFilter: function(slice_id, field, values) { addFilter: function(slice_id, field, values) {
this.filters[slice_id] = [field, values]; this.filters[slice_id] = [field, values];
this.refreshExcept(slice_id); this.refreshExcept(slice_id);
@ -111,6 +117,13 @@ var px = (function() {
delete this.filters[slice_id]; delete this.filters[slice_id];
this.refreshExcept(slice_id); this.refreshExcept(slice_id);
}, },
getSlice: function(slice_id) {
for(var i=0; i<this.slices.length; i++){
console.log([this.slices[i].data.slice_id, slice_id]);
if (this.slices[i].data.slice_id == slice_id)
return this.slices[i];
}
}
} }
$('.dashboard li.widget').each(function() { $('.dashboard li.widget').each(function() {
var data = $(this).data('slice'); var data = $(this).data('slice');
@ -121,6 +134,7 @@ var px = (function() {
dash.slices.push(slice); dash.slices.push(slice);
slice.render(); slice.render();
}); });
dashboard = dash;
return dash; return dash;
} }
@ -134,185 +148,184 @@ var px = (function() {
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search); results = regex.exec(location.search);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
$(".select2").select2({dropdownAutoWidth : true});
$(".select2Sortable").select2();
$(".select2Sortable").select2Sortable();
$("form").show();
$('[data-toggle="tooltip"]').tooltip({container: 'body'});
function set_filters(){
for (var i = 1; i < 10; i++){
var eq = getParam("flt_eq_" + i);
if (eq != ''){
add_filter(i);
}
} }
}
set_filters();
function add_filter(i) { $(".select2").select2({dropdownAutoWidth : true});
cp = $("#flt0").clone(); $(".select2Sortable").select2();
$(cp).appendTo("#filters"); $(".select2Sortable").select2Sortable();
$(cp).show(); $("form").show();
if (i != undefined){ $('[data-toggle="tooltip"]').tooltip({container: 'body'});
$(cp).find("#flt_eq_0").val(getParam("flt_eq_" + i));
$(cp).find("#flt_op_0").val(getParam("flt_op_" + i));
$(cp).find("#flt_col_0").val(getParam("flt_col_" + i));
}
$(cp).find('select').select2();
$(cp).find('.remove').click(function() {
$(this).parent().parent().remove();
});
}
function druidify(){ function set_filters(){
var i = 1; for (var i = 1; i < 10; i++){
// Assigning the right id to form elements in filters var eq = getParam("flt_eq_" + i);
$("#filters > div").each(function() { if (eq != ''){
$(this).attr("id", function() {return "flt_" + i;}) add_filter(i);
$(this).find("#flt_col_0")
.attr("id", function() {return "flt_col_" + i;})
.attr("name", function() {return "flt_col_" + i;});
$(this).find("#flt_op_0")
.attr("id", function() {return "flt_op_" + i;})
.attr("name", function() {return "flt_op_" + i;});
$(this).find("#flt_eq_0")
.attr("id", function() {return "flt_eq_" + i;})
.attr("name", function() {return "flt_eq_" + i;});
i++;
});
$("#query").submit();
}
$("#plus").click(add_filter);
$("#btn_save").click(function () {
var slice_name = prompt("Name your slice!");
if (slice_name != "" && slice_name != null) {
$("#slice_name").val(slice_name);
$("#action").val("save");
druidify();
}
});
$("#btn_overwrite").click(function () {
var flag = confirm("Overwrite slice [" + $("#slice_name").val() + "] !?");
if (flag) {
$("#action").val("overwrite");
druidify();
}
});
add_filter();
$(".druidify").click(druidify);
function create_choices(term, data) {
var filtered = $(data).filter(function() {
return this.text.localeCompare(term) === 0;
});
if (filtered.length === 0) {
return {id: term, text: term};
}
}
function initSelectionToValue(element, callback) {
callback({id: element.val(), text: element.val()});
}
function list_data(arr) {
var obj = [];
for (var i=0; i<arr.length; i++){
obj.push({id: arr[i], text: arr[i]});
}
return obj;
}
$(".select2_freeform").each(function(){
parent = $(this).parent();
var name = $(this).attr('name');
var l = [];
var selected = '';
for(var i=0; i<this.options.length; i++) {
l.push({id: this.options[i].value, text: this.options[i].text});
if(this.options[i].selected){
selected = this.options[i].value;
} }
}
obj = parent.append(
'<input class="' + $(this).attr('class') + '" name="'+ name +'" type="text" value="' + selected + '">');
$("input[name='" + name +"']")
.select2({
createSearchChoice: create_choices,
initSelection: initSelectionToValue,
multiple: false,
data: l,
});
$(this).remove();
});
}
function initDashboardView(dashboard_id) {
var gridster = $(".gridster ul").gridster({
widget_margins: [5, 5],
widget_base_dimensions: [100, 100],
draggable: {
handle: '.drag',
},
resize: {
enabled: true,
stop: function(e, ui, element) {
var slice = $(element).data('slice');
slice.resize();
} }
}, }
serialize_params: function(_w, wgd) { set_filters();
return {
slice_id: $(_w).attr('slice_id'), function add_filter(i) {
col: wgd.col, cp = $("#flt0").clone();
row: wgd.row, $(cp).appendTo("#filters");
size_x: wgd.size_x, $(cp).show();
size_y: wgd.size_y if (i != undefined){
}; $(cp).find("#flt_eq_0").val(getParam("flt_eq_" + i));
}, $(cp).find("#flt_op_0").val(getParam("flt_op_" + i));
}).data('gridster'); $(cp).find("#flt_col_0").val(getParam("flt_col_" + i));
$("div.gridster").css('visibility', 'visible'); }
$("#savedash").click(function() { $(cp).find('select').select2();
var data = { $(cp).find('.remove').click(function() {
positions: gridster.serialize(), $(this).parent().parent().remove();
css: $("#dash_css").val() });
}; }
$.ajax({
type: "POST", function druidify(){
url: '/panoramix/save_dash/' + dashboard_id + '/', var i = 1;
data: {'data': JSON.stringify(data)}, // Assigning the right id to form elements in filters
success: function() {alert("Saved!")}, $("#filters > div").each(function() {
error: function() {alert("Error :(")}, $(this).attr("id", function() {return "flt_" + i;})
$(this).find("#flt_col_0")
.attr("id", function() {return "flt_col_" + i;})
.attr("name", function() {return "flt_col_" + i;});
$(this).find("#flt_op_0")
.attr("id", function() {return "flt_op_" + i;})
.attr("name", function() {return "flt_op_" + i;});
$(this).find("#flt_eq_0")
.attr("id", function() {return "flt_eq_" + i;})
.attr("name", function() {return "flt_eq_" + i;});
i++;
});
$("#query").submit();
}
$("#plus").click(add_filter);
$("#btn_save").click(function () {
var slice_name = prompt("Name your slice!");
if (slice_name != "" && slice_name != null) {
$("#slice_name").val(slice_name);
$("#action").val("save");
druidify();
}
}); });
}); $("#btn_overwrite").click(function () {
$("a.closeslice").click(function() { var flag = confirm("Overwrite slice [" + $("#slice_name").val() + "] !?");
var li = $(this).parents("li"); if (flag) {
gridster.remove_widget(li); $("#action").val("overwrite");
}); druidify();
$("table.slice_header").mouseover(function() { }
$(this).find("td.icons nobr").show(); });
}); add_filter();
$("table.slice_header").mouseout(function() { $(".druidify").click(druidify);
$(this).find("td.icons nobr").hide();
});
$("#dash_css").on("keyup", function(){
css = $(this).val();
$("#user_style").html(css);
});
// this sets the z-index for left side boxes higher function create_choices(term, data) {
$('li.slice').each(function() { var filtered = $(data).filter(function() {
current_row = $(this).attr('data-col'); return this.text.localeCompare(term) === 0;
$( this ).css('z-index', 100 - current_row); });
}); if (filtered.length === 0) {
return {id: term, text: term};
}
}
function initSelectionToValue(element, callback) {
callback({id: element.val(), text: element.val()});
}
function list_data(arr) {
var obj = [];
for (var i=0; i<arr.length; i++){
obj.push({id: arr[i], text: arr[i]});
}
return obj;
}
$(".select2_freeform").each(function(){
parent = $(this).parent();
var name = $(this).attr('name');
var l = [];
var selected = '';
for(var i=0; i<this.options.length; i++) {
l.push({id: this.options[i].value, text: this.options[i].text});
if(this.options[i].selected){
selected = this.options[i].value;
}
}
obj = parent.append(
'<input class="' + $(this).attr('class') + '" name="'+ name +'" type="text" value="' + selected + '">');
$("input[name='" + name +"']")
.select2({
createSearchChoice: create_choices,
initSelection: initSelectionToValue,
multiple: false,
data: l,
});
$(this).remove();
});
}
// this makes the whole chart fit within the dashboard div function initDashboardView() {
$("div.chart").each(function() { var gridster = $(".gridster ul").gridster({
$(this).css('height', '95%'); widget_margins: [5, 5],
}); widget_base_dimensions: [100, 100],
draggable: {
handle: '.drag',
},
resize: {
enabled: true,
stop: function(e, ui, element) {
var slice_data = $(element).data('slice');
dashboard.getSlice(slice_data.slice_id).resize();
}
},
serialize_params: function(_w, wgd) {
return {
slice_id: $(_w).attr('slice_id'),
col: wgd.col,
row: wgd.row,
size_x: wgd.size_x,
size_y: wgd.size_y
};
},
}).data('gridster');
console.log(gridster);
$("div.gridster").css('visibility', 'visible');
$("#savedash").click(function() {
var data = {
positions: gridster.serialize(),
css: $("#dash_css").val()
};
$.ajax({
type: "POST",
url: '/panoramix/save_dash/' + dashboard.id + '/',
data: {'data': JSON.stringify(data)},
success: function() {alert("Saved!")},
error: function() {alert("Error :(")},
});
});
$("a.closeslice").click(function() {
var li = $(this).parents("li");
gridster.remove_widget(li);
});
$("table.slice_header").mouseover(function() {
$(this).find("td.icons nobr").show();
});
$("table.slice_header").mouseout(function() {
$(this).find("td.icons nobr").hide();
});
$("#dash_css").on("keyup", function(){
css = $(this).val();
$("#user_style").html(css);
});
// this sets the z-index for left side boxes higher
$('li.slice').each(function() {
current_row = $(this).attr('data-col');
$( this ).css('z-index', 100 - current_row);
});
} // this makes the whole chart fit within the dashboard div
$("div.chart").each(function() {
$(this).css('height', '95%');
});
}
// Export public functions // Export public functions
return { return {
@ -323,4 +336,3 @@ function initDashboardView(dashboard_id) {
initDashboardView: initDashboardView, initDashboardView: initDashboardView,
} }
})(); })();

View File

@ -3,9 +3,6 @@
stroke: #000; stroke: #000;
stroke-width: 1.5px; stroke-width: 1.5px;
} }
.directed_force #chart {
height: 100%;
}
.directed_force circle { .directed_force circle {
fill: #ccc; fill: #ccc;

View File

@ -3,11 +3,10 @@ Modified from http://bl.ocks.org/d3noob/5141278
*/ */
function viz_directed_force(slice) { function viz_directed_force(slice) {
var div = d3.select(slice.selector);
var width = slice.container.width(); var width = slice.container.width();
var height = slice.container.height() - 25; var height = slice.container.height() - 25;
var radius = Math.min(width, height) / 2;
var link_length = slice.data.form_data['link_length']; var link_length = slice.data.form_data['link_length'];
var div = d3.select(slice.selector);
if (link_length === undefined){ if (link_length === undefined){
link_length = 200; link_length = 200;
} }

View File

@ -122,8 +122,8 @@ body {
<script src="/static/lib/gridster/jquery.gridster.with-extras.min.js"></script> <script src="/static/lib/gridster/jquery.gridster.with-extras.min.js"></script>
<script> <script>
$(document).ready(function() { $(document).ready(function() {
px.initDashboardView({{ dashboard.id }}) px.initDashboardView();
var dashboard = px.Dashboard(); var dashboard = px.Dashboard({{ dashboard.id }});
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -36,13 +36,16 @@
<span class="btn btn-info pull-right disabled query" <span class="btn btn-info pull-right disabled query"
data-toggle="modal" data-target="#query_modal">query</span> data-toggle="modal" data-target="#query_modal">query</span>
<span class="btn btn-warning pull-right notbtn" id="timer">0 sec</span> <span class="btn btn-warning pull-right notbtn" id="timer">0 sec</span>
<div class="btn-group pull-right" role="group"> <span class="btn btn-default pull-right" id="csv" title="Export to .csv format" data-toggle="tooltip">
<span class="btn btn-default disabled"> <i class="fa fa-file-text-o"></i>.csv
<i class="fa fa-file-text"></i> </span>
</span> <span class="btn btn-default pull-right" id="json" title="Export to .json" data-toggle="tooltip">
<span class="btn btn-default" id="csv">.csv</span> <i class="fa fa-file-code-o"></i>
<span class="btn btn-default" id="json">.json</span> .json
</div> </span>
<span class="btn btn-default pull-right" id="standalone" title="Standalone version, use to embed anywhere" data-toggle="tooltip">
<i class="fa fa-code"></i>
</span>
<hr/> <hr/>
</div> </div>
<div class="row"> <div class="row">
@ -133,23 +136,14 @@
</div> </div>
<div class="col-md-9"> <div class="col-md-9">
{% block messages %} {% block messages %}{% endblock %}
{% endblock %} {% include 'appbuilder/flash.html' %}
{% include 'appbuilder/flash.html' %}
<div <div
id="{{ viz.token }}" id="{{ viz.token }}"
class="viz slice {{ viz.viz_type }}" class="viz slice {{ viz.viz_type }}"
data-slice="{{ viz.json_data }}" data-slice="{{ viz.json_data }}"
style="height: 700px;"> style="height: 700px;">
<img src="{{ url_for("static", filename="img/loading.gif") }}" class="loading" alt="loading"> <img src="{{ url_for("static", filename="img/loading.gif") }}" class="loading" alt="loading">
{% block viz_html %}
{% if viz.error_msg %}
<div class="alert alert-danger">{{ viz.error_msg }}</div>
{% endif %}
{% if viz.warning_msg %}
<div class="alert alert-warning">{{ viz.warning_msg }}</div>
{% endif %}
{% endblock %}
<div id="{{ viz.token }}_con" class="slice_container" style="height: 100%; width: 100%"></div> <div id="{{ viz.token }}_con" class="slice_container" style="height: 100%; width: 100%"></div>
</div> </div>
</div> </div>
@ -243,6 +237,9 @@
$('#csv').click(function () { $('#csv').click(function () {
window.location = '{{ viz.csv_endpoint | safe }}'; window.location = '{{ viz.csv_endpoint | safe }}';
}); });
$('#standalone').click(function () {
window.location = '{{ viz.standalone_endpoint | safe }}';
});
$("#viz_type").change(function() {$("#query").submit();}); $("#viz_type").change(function() {$("#query").submit();});
collapsed_fieldsets = get_collapsed_fieldsets(); collapsed_fieldsets = get_collapsed_fieldsets();
for(var i=0; i < collapsed_fieldsets.length; i++){ for(var i=0; i < collapsed_fieldsets.length; i++){

View File

@ -0,0 +1,29 @@
<html>
<head>
<script src="/static/panoramix.js"></script>
{% block head_css %}
<script src="{{url_for('appbuilder.static',filename='js/jquery-latest.js')}}"></script>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='panoramix.css') }}">
{% endblock %}
</head>
<body>
<div
id="{{ viz.token }}"
class="viz slice {{ viz.viz_type }}"
data-slice="{{ viz.json_data }}"
style="height: 700px;">
<img src="{{ url_for("static", filename="img/loading.gif") }}" class="loading" alt="loading">
<div id="{{ viz.token }}_con" class="slice_container" style="height: 100%; width: 100%"></div>
</div>
<script>
$(document).ready(function() {
var data = $('.slice').data('slice');
var slice = px.Slice(data);
slice.render();
});
</script>
{% block tail %}{% endblock %}
</body>
</html>

View File

@ -2,7 +2,7 @@
{{ viz.get_json() }} {{ viz.get_json() }}
{% else %} {% else %}
{% if viz.request.args.get("standalone") == "true" %} {% if viz.request.args.get("standalone") == "true" %}
{% extends 'panoramix/viz_standalone.html' %} {% extends 'panoramix/standalone.html' %}
{% else %} {% else %}
{% extends 'panoramix/explore.html' %} {% extends 'panoramix/explore.html' %}
{% endif %} {% endif %}
@ -10,20 +10,16 @@
{% block head_css %} {% block head_css %}
{{super()}} {{super()}}
{% if viz.request.args.get("skip_libs") != "true" %} {% for css in viz.css_files %}
{% for css in viz.css_files %} <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename=css) }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename=css) }}"> {% endfor %}
{% endfor %}
{% endif %}
{% endblock %} {% endblock %}
{% block tail %} {% block tail %}
{{super()}} {{super()}}
{% if viz.request.args.get("skip_libs") != "true" %} {% for js in viz.js_files %}
{% for js in viz.js_files %} <script src="{{ url_for('static', filename=js) }}"></script>
<script src="{{ url_for('static', filename=js) }}"></script> {% endfor %}
{% endfor %}
{% endif %}
{% endblock %} {% endblock %}
{% endif %} {% endif %}

View File

@ -1,13 +0,0 @@
<html>
<head>
{% if viz.request.args.get("skip_libs") != "true" %}
{% block head %}
<script src="{{url_for('appbuilder.static',filename='js/jquery-latest.js')}}"></script>
{% endblock %}
{% endif %}
{% block tail %}{% endblock %}
</head>
<body>
{% block viz_html %}{% endblock %}
</body>
</html>

View File

@ -222,6 +222,10 @@ class BaseViz(object):
def csv_endpoint(self): def csv_endpoint(self):
return self.get_url(csv="true") return self.get_url(csv="true")
@property
def standalone_endpoint(self):
return self.get_url(standalone="true")
@property @property
def data(self): def data(self):
content = { content = {