Skip to content

Commit

Permalink
Bugfixes+ better docs
Browse files Browse the repository at this point in the history
  • Loading branch information
mistercrunch committed Sep 21, 2015
1 parent 2cbe25c commit e1b3c7e
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 79 deletions.
57 changes: 30 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,31 @@ Panoramix
Panoramix is a data exploration platform designed to be visual, intuitive
and interactive.


![img](http://i.imgur.com/aOaH0ty.png)

Panoramix
---------
Panoramix's main goal is to make it easy to slice, dice and visualize data.
It empowers its user to perform **analytics at the speed of thought**.

Panoramix provides:
* A quick way to intuitively visualize datasets
* Create and share simple dashboards
* A rich set of visualizations to analyze your data, as well as a flexible
way to extend the capabilities
* An extensible, high granularity security model allowing intricate rules
on who can access which features, and integration with major
authentication providers (database, OpenID, LDAP, OAuth & REMOTE_USER
through Flask AppBuiler)
* A simple semantic layer, allowing to control how data sources are
displayed in the UI,
by defining which fields should show up in which dropdown and which
aggregation and function (metrics) are made available to the user
* Deep integration with Druid allows for Panoramix to stay blazing fast while
slicing and dicing large, realtime datasets


Buzz Phrases
------------

Expand All @@ -12,17 +37,16 @@ Buzz Phrases
* Realtime analytics when querying [Druid.io](http://druid.io)
* Extentsible to infinity

![img](http://i.imgur.com/aOaH0ty.png)

Database Support
----------------

Panoramix was originally designed on to of Druid.io, but quickly broadened
Panoramix was originally designed on to of Druid.io, but quickly broadened
its scope to support other databases through the use of SqlAlchemy, a Python
ORM that is compatible with
[many external databases](http://docs.sqlalchemy.org/en/rel_1_0/core/engines.html).
ORM that is compatible with
[most common databases](http://docs.sqlalchemy.org/en/rel_1_0/core/engines.html).


What's Druid?
What is Druid?
-------------
From their website at http://druid.io

Expand All @@ -33,27 +57,6 @@ and fast data aggregation. Existing Druid deployments have scaled to
trillions of events and petabytes of data. Druid is best used to
power analytic dashboards and applications.*

Panoramix
---------
Panoramix's main goal is to make it easy to slice, dice and visualize data
out of Druid. It empowers its user to perform **analytics
at the speed of thought**.

Panoramix started as a hackathon project at Airbnb in while running a POC
(proof of concept) on using Druid.

Panoramix provides:
* A way to query intuitively a Druid dataset, allowing for grouping, filtering
limiting and defining a time granularity
* Many charts and visualization to analyze your data, as well as a flexible
way to extend the visualization capabilities
* An extensible, high granularity security model allowing intricate rules
on who can access which features, and integration with major
authentication providers (through Flask AppBuiler)
* A simple semantic layer, allowing to control how Druid datasources are
displayed in the UI,
by defining which fields should show up in which dropdown and which
aggregation and function (metrics) are made available to the user

Installation
------------
Expand Down
8 changes: 4 additions & 4 deletions panoramix/bin/panoramix
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ def load_examples():

with gzip.open(config.basedir + '/data/birth_names.csv.gz') as f:
bb_csv = csv.reader(f)
for i, (state, year, name, num, gender) in enumerate(bb_csv):
if i == 0 or not name or name=="\xc2\xa0":
for i, (state, year, name, gender, num) in enumerate(bb_csv):
if i == 0:
continue
if num == "NA":
num = 0
Expand All @@ -71,8 +71,8 @@ def load_examples():
state=state, year=year,
ds=ds,
name=name, num=num, gender=gender))
if i % 1000 == 0:
print("{} loaded out of 502619 rows".format(i))
if i % 5000 == 0:
print("{} loaded out of 82527 rows".format(i))
session.commit()
session.commit()
print("Done loading table!")
Expand Down
Binary file modified panoramix/data/birth_names.csv.gz
Binary file not shown.
5 changes: 4 additions & 1 deletion panoramix/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@ def datasource_id(self):

@property
def slice_url(self):
d = json.loads(self.params)
try:
d = json.loads(self.params)
except Exception as e:
d = {}
from werkzeug.urls import Href
href = Href(
"/panoramix/datasource/{self.datasource_type}/"
Expand Down
25 changes: 16 additions & 9 deletions panoramix/templates/panoramix/datasource.html
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ <h4>Filters</h4>
<hr style="margin-bottom: 0px;">
<img src="{{ url_for("static", filename="tux_panoramix.png") }}" width=250>
<input type="hidden" id="slice_name" name="slice_name" value="TEST">
<input type="hidden" id="action" name="action" value="">
<input type="hidden" name="datasource_name" value="{{ datasource.name }}">
<input type="hidden" name="datasource_id" value="{{ datasource.id }}">
<input type="hidden" name="datasource_type" value="{{ datasource.type }}">
Expand Down Expand Up @@ -193,14 +194,8 @@ <h4 class="modal-title" id="myModalLabel">Query</h4>
$(this).parent().parent().slideUp("slow", function(){$(this).remove()});
});
}
$("#plus").click(add_filter);
$("#save").click(function () {
var slice_name = prompt("Name your slice!");
$("#slice_name").val(slice_name);
$.get( "/panoramix/save/", $("#query").serialize() );
})
add_filter();
$("#druidify").click(function () {

function druidify(){
var i = 1;

// removing empty filters
Expand All @@ -224,7 +219,19 @@ <h4 class="modal-title" id="myModalLabel">Query</h4>
i++;
});
$("#query").submit();
});
}

$("#plus").click(add_filter);
$("#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();
}
})
add_filter();
$("#druidify").click(druidify);

function create_choices (term, data) {
if ($(data).filter(function() {
Expand Down
2 changes: 0 additions & 2 deletions panoramix/templates/panoramix/viz_bignumber.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,12 @@
data = example_data();
data = json.data;
var compare_suffix = ' ' + json.compare_suffix;
console.log(data);
var v_compare = null;
var v = data[data.length -1][1];
if (json.compare_lag >0){
pos = data.length - (json.compare_lag+1);
if(pos >=0)
v_compare = 1-(v / data[pos][1]);
console.log(v_compare)
}
var date_ext = d3.extent(data, function(d){return d[0]});
var value_ext = d3.extent(data, function(d){return d[1]});
Expand Down
4 changes: 3 additions & 1 deletion panoramix/templates/panoramix/viz_table.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
{% macro viz_js(viz) %}
{% if viz.args.get("async") != "true" %}
<script>
$( document ).ready(function() {
var url = "{{ viz.get_url(async="true", standalone="true", skip_libs="true")|safe }}";
console.log(url);
var token = $("#{{ viz.token }}");
Expand All @@ -44,7 +45,7 @@
token.show();
}
else{
var table = $('table').DataTable({
var table = token.find('table').DataTable({
paging: false,
searching: false,
});
Expand All @@ -53,6 +54,7 @@
token.show();
token.parent().find("img.loading").hide();
});
});
</script>
{% endif %}
{% endmacro %}
Expand Down
72 changes: 38 additions & 34 deletions panoramix/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ class ClusterModelView(ModelView, DeleteMixin):

class SliceModelView(ModelView, DeleteMixin):
datamodel = SQLAInterface(models.Slice)
can_add = False
list_columns = ['slice_link', 'viz_type', 'datasource', 'created_by']
edit_columns = [
'slice_name', 'viz_type', 'druid_datasource', 'table',
Expand Down Expand Up @@ -211,6 +212,41 @@ class Panoramix(BaseView):
@has_access
@expose("/datasource/<datasource_type>/<datasource_id>/")
def datasource(self, datasource_type, datasource_id):
action = request.args.get('action')
if action == 'save':
session = db.session()
d = request.args.to_dict(flat=False)
del d['action']
as_list = ('metrics', 'groupby')
for k in d:
v = d.get(k)
if k in as_list and not isinstance(v, list):
d[k] = [v] if v else []
if k not in as_list and isinstance(v, list):
d[k] = v[0]

table_id = druid_datasource_id = None
datasource_type = request.args.get('datasource_type')
if datasource_type == 'druid':
druid_datasource_id = request.args.get('datasource_id')
else:
table_id = request.args.get('datasource_id')
slice_name = request.args.get('slice_name')

obj = models.Slice(
params=json.dumps(d, indent=4, sort_keys=True),
viz_type=request.args.get('viz_type'),
datasource_name=request.args.get('datasource_name'),
druid_datasource_id=druid_datasource_id,
table_id=table_id,
datasource_type=datasource_type,
slice_name=slice_name,
)
session.add(obj)
session.commit()
flash("Slice <{}> has been added to the pie".format(slice_name), "info")
redirect(obj.slice_url)

if datasource_type == "table":
datasource = (
db.session
Expand Down Expand Up @@ -249,15 +285,13 @@ def datasource(self, datasource_type, datasource_id):
status=status,
mimetype="application/json")
else:
#try:
resp = self.render_template("panoramix/viz.html", viz=obj)
'''
try:
resp = self.render_template("panoramix/viz.html", viz=obj)
except Exception as e:
return Response(
str(e),
status=500,
mimetype="application/json")
'''
return resp

@has_access
Expand All @@ -279,36 +313,6 @@ def save_dash(self, dashboard_id):

@has_access
@expose("/save/")
def save(self):
session = db.session()
d = request.args.to_dict(flat=False)
as_list = ('metrics', 'groupby')
for m in as_list:
v = d.get(m)
if v and not isinstance(d[m], list):
d[m] = [d[m]]

table_id = druid_datasource_id = None
datasource_type = request.args.get('datasource_type')
if datasource_type == 'druid':
druid_datasource_id = request.args.get('datasource_id')
else:
table_id = request.args.get('datasource_id')

obj = models.Slice(
params=json.dumps(d, indent=4),
viz_type=request.args.get('viz_type'),
datasource_name=request.args.get('datasource_name'),
druid_datasource_id=druid_datasource_id,
table_id=table_id,
datasource_type=datasource_type,
slice_name=request.args.get('slice_name', 'junk'),
)
session.add(obj)
session.commit()
session.close()

return "super!"

@has_access
@expose("/dashboard/<id_>/")
Expand Down
6 changes: 5 additions & 1 deletion panoramix/viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ def __init__(self, datasource, form_data):

def get_url(self, **kwargs):
d = self.args.copy()
if 'action' in d:
del d['action']
d.update(kwargs)
href = Href(
'/panoramix/datasource/{self.datasource.type}/'
Expand Down Expand Up @@ -335,7 +337,9 @@ class DistributionPieViz(HighchartsViz):
verbose_name = "Distribution - Pie Chart"
chart_type = "pie"
js_files = ['highstock.js']
form_fields = BaseViz.form_fields + ['limit']
form_fields = [
'viz_type', 'metrics', 'groupby',
('since', 'until'), 'limit']

def query_obj(self):
d = super(DistributionPieViz, self).query_obj()
Expand Down

0 comments on commit e1b3c7e

Please sign in to comment.