World Map with bubbles

This commit is contained in:
Maxime Beauchemin 2015-12-16 11:28:27 -08:00
parent cf0734b8cd
commit 48c1481caa
13 changed files with 26833 additions and 12 deletions

View File

@ -12,7 +12,7 @@ from panoramix import app
from flask.ext.migrate import MigrateCommand
from panoramix import db
from flask.ext.appbuilder import Base
from sqlalchemy import Column, Integer, String, Table, DateTime
from sqlalchemy import Column, Integer, String, Table, DateTime, Float
from panoramix import models, utils
config = app.config
@ -59,6 +59,63 @@ def init():
def load_examples(sample):
"""Loads a set of Slices and Dashboards and a supporting dataset """
print("Loading examples into {}".format(db))
session = db.session()
Country = Table(
"countries", Base.metadata,
Column("id", Integer, primary_key=True),
Column("ds", DateTime, default=datetime.now()),
Column("name", String(255)),
Column("cca2", String(2)),
Column("cca3", String(3)),
Column("cioc", String(3)),
Column("capital", String(255)),
Column("lat", Float),
Column("lng", Float),
Column("area", Float),
)
try:
Country.drop(db.engine)
except:
pass
Country.create(db.engine)
filepath = os.path.join(config.get("BASE_DIR"), 'data/countries.json')
with open(filepath, 'r') as f:
d = {}
for c in json.load(f):
if not c['latlng']:
continue
db.engine.execute(
Country.insert(),
name=c['name']['common'].encode('utf8'),
cca2=c['cca2'],
cca3=c['cca3'],
cioc=c['cioc'],
capital=c['capital'],
lat=c['latlng'][0],
lng=c['latlng'][1],
area=c['area'],
)
d[c['cca3']] = dict(
name=c['name']['common'],
cca2=c['cca2'],
cca3=c['cca3'],
cioc=c['cioc'],
capital=c['capital'],
lat=c['latlng'][0],
lng=c['latlng'][1],
area=c['area'],
)
#print(json.dumps(d, indent=4))
print("Creating database reference")
DB = models.Database
dbobj = session.query(DB).filter_by(database_name='main').first()
if not dbobj:
dbobj = DB(database_name="main")
print(config.get("SQLALCHEMY_DATABASE_URI"))
dbobj.sqlalchemy_uri = config.get("SQLALCHEMY_DATABASE_URI")
session.add(dbobj)
session.commit()
BirthNames = Table(
@ -79,8 +136,8 @@ def load_examples(sample):
pass
BirthNames.create(db.engine)
session = db.session()
filepath = os.path.join(config.get("BASE_DIR"), 'data/birth_names.csv.gz')
'''
with gzip.open(filepath, mode='rt') as f:
bb_csv = csv.reader(f)
for i, (state, year, name, gender, num) in enumerate(bb_csv):
@ -105,16 +162,8 @@ def load_examples(sample):
if sample and i>1000: break
print("Done loading table!")
print("-" * 80)
'''
print("Creating database reference")
DB = models.Database
dbobj = session.query(DB).filter_by(database_name='main').first()
if not dbobj:
dbobj = DB(database_name="main")
print(config.get("SQLALCHEMY_DATABASE_URI"))
dbobj.sqlalchemy_uri = config.get("SQLALCHEMY_DATABASE_URI")
session.add(dbobj)
session.commit()
print("Creating table reference")
TBL = models.SqlaTable

11549
panoramix/data/countries.json Normal file

File diff suppressed because it is too large Load Diff

2490
panoramix/data/countries.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -108,6 +108,18 @@ class FormFactory(object):
'Color Metric', choices=datasource.metrics_combo,
default=default_metric,
description="A metric to use for color"),
'country_fieldtype': SelectField(
'Country Field Type',
default='cca2',
choices=(
('name', 'Full name'),
('cioc', 'code International Olympic Committee (cioc)'),
('cca2', 'code ISO 3166-1 alpha-2 (cca2)'),
('cca3', 'code ISO 3166-1 alpha-3 (cca3)'),
),
description=(
"The country code standard that Panoramix should expect "
"to find in the [country] column")),
'groupby': SelectMultipleSortableField(
'Group by',
choices=self.choicify(datasource.groupby_column_names),
@ -298,6 +310,10 @@ class FormFactory(object):
"Range Filter", default=True,
description=(
"Whether to display the time range interactive selector")),
'show_bubbles': BetterBooleanField(
"Show Bubbles", default=False,
description=(
"Whether to display bubbles on top of countries")),
'show_legend': BetterBooleanField(
"Legend", default=True,
description="Whether to display the legend (toggles)"),

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
panoramix/static/lib/topojson.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -20,6 +20,10 @@ form div {
text-align: right;
}
.select2-results .select2-highlighted {
background-color: #005c66;
}
.notbtn {
cursor: default;
}

View File

@ -0,0 +1,87 @@
/*
Using the awesome lib at http://datamaps.github.io/
*/
function viz_world_map(data_attribute) {
var token = d3.select('#' + data_attribute.token);
var render = function(done) {
// Breadcrumb dimensions: width, height, spacing, width of tip/tail.
var div = token;
var xy = div.node().getBoundingClientRect();
var width = xy.width;
var height = xy.height - 25;
d3.json(data_attribute.json_endpoint, function(error, json){
if (error != null){
var err = '<div class="alert alert-danger">' + error.responseText + '</div>';
token.html(err);
return '';
done();
}
var ext = d3.extent(json.data, function(d){return d.metric});
var extRadius = d3.extent(json.data, function(d){return d.radius});
var radiusScale = d3.scale.linear()
.domain([extRadius[0], extRadius[1]])
.range([1, 40]);
json.data.forEach(function(d){
d.radius = radiusScale(d.radius);
})
var colorScale = d3.scale.linear()
.domain([ext[0], ext[1]])
.range(["#FFF", "black"]);
var d = {};
for (var i=0; i<json.data.length; i++){
var country = json.data[i];
d[country.country] = colorScale(country.metric);
}
var map = new Datamap({
element: document.getElementById(data_attribute.token),
fills: {
defaultFill: 'grey'
},
geographyConfig: {
popupOnHover: true,
highlightOnHover: true,
borderWidth: 1,
borderColor: 'grey',
highlightBorderColor: 'black',
highlightFillColor: '#005a63',
highlightBorderWidth: 1,
},
bubblesConfig: {
borderWidth: 1,
borderOpacity: 1,
borderColor: '#005a63',
popupOnHover: true,
radius: null,
popupTemplate: function(geography, data) {
return '<div class="hoverinfo"><strong>' + data.country + '</strong></div>';
},
fillOpacity: 0.5,
animate: true,
highlightOnHover: true,
highlightFillColor: '#005a63',
highlightBorderColor: 'black',
highlightBorderWidth: 2,
highlightBorderOpacity: 1,
highlightFillOpacity: 0.85,
exitDelay: 100,
key: JSON.stringify
},
});
map.updateChoropleth(d);
if(data_attribute.form_data.show_bubbles){
map.bubbles(json.data);
token.selectAll("circle.datamaps-bubble").style('fill', '#005a63');
}
done(json);
});
}
return {
render: render,
resize: render,
};
}
px.registerWidget('world_map', viz_world_map);

View File

@ -0,0 +1,8 @@
{% macro viz_html(viz) %}
{% endmacro %}
{% macro viz_js(viz) %}
{% endmacro %}
{% macro viz_css(viz) %}
{% endmacro %}

View File

@ -262,7 +262,9 @@ appbuilder.add_view(
class DatasourceModelView(PanoramixModelView, DeleteMixin):
datamodel = SQLAInterface(models.Datasource)
list_columns = [
'datasource_link', 'cluster', 'owner', 'is_featured', 'is_hidden',
'datasource_link', 'cluster', 'owner',
'created_by', 'created_on',
'changed_by', 'changed_on',
'offset']
related_views = [ColumnInlineView, MetricInlineView]
edit_columns = [

View File

@ -961,6 +961,86 @@ class DirectedForceViz(BaseViz):
d = df.to_dict(orient='records')
return dumps(d)
class WorldMapViz(BaseViz):
viz_type = "world_map"
verbose_name = "World Map"
is_timeseries = False
template = 'panoramix/viz_world_map.html'
js_files = [
'lib/d3.min.js',
'lib/topojson.min.js',
'lib/datamaps.all.js',
'widgets/viz_world_map.js']
css_files = ['widgets/viz_world_map.css']
fieldsets = (
{
'label': None,
'fields': (
'granularity',
('since', 'until'),
'entity',
'country_fieldtype',
'metric',
'secondary_metric',
)
},
{
'label': 'Bubbles',
'fields': (
('show_bubbles', None),
'secondary_metric',
)
})
form_overrides = {
'entity': {
'label': 'Country Field',
'description': "3 letter code of the country",
},
'metric': {
'label': 'Metric for color',
'description': ("Metric that defines the color of the country"),
},
'secondary_metric': {
'label': 'Bubble size',
'description': ("Metric that defines the size of the bubble"),
},
}
def query_obj(self):
qry = super(WorldMapViz, self).query_obj()
qry['metrics'] = [
self.form_data['metric'], self.form_data['secondary_metric']]
qry['groupby'] = [self.form_data['entity']]
return qry
def get_json_data(self):
from panoramix.data import countries
df = self.get_df()
cols = [self.form_data.get('entity')]
metric = self.form_data.get('metric')
secondary_metric = self.form_data.get('secondary_metric')
if metric == secondary_metric:
ndf = df[cols]
ndf['metric'] = df[metric]
ndf['radius'] = df[metric]
else:
cols += [metric, secondary_metric]
ndf = df[cols]
df = ndf
df.columns = ['country', 'metric', 'radius']
d = df.to_dict(orient='records')
for row in d:
country = countries.get(
self.form_data.get('country_fieldtype'), row['country'])
if country:
row['country'] = country['cca3']
row['latitude'] = country['lat']
row['longitude'] = country['lng']
else:
row['country'] = "XXX"
return dumps(d)
viz_types_list = [
TableViz,
PivotTableViz,
@ -977,6 +1057,7 @@ viz_types_list = [
SunburstViz,
DirectedForceViz,
SankeyViz,
WorldMapViz,
]
# This dict is used to
viz_types = OrderedDict([(v.viz_type, v) for v in viz_types_list])