mirror of https://github.com/apache/superset.git
World Map with bubbles
This commit is contained in:
parent
cf0734b8cd
commit
48c1481caa
|
@ -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
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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
File diff suppressed because one or more lines are too long
|
@ -20,6 +20,10 @@ form div {
|
|||
text-align: right;
|
||||
}
|
||||
|
||||
.select2-results .select2-highlighted {
|
||||
background-color: #005c66;
|
||||
}
|
||||
|
||||
.notbtn {
|
||||
cursor: default;
|
||||
}
|
||||
|
|
|
@ -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);
|
|
@ -0,0 +1,8 @@
|
|||
{% macro viz_html(viz) %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro viz_js(viz) %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro viz_css(viz) %}
|
||||
{% endmacro %}
|
|
@ -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 = [
|
||||
|
|
|
@ -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])
|
||||
|
|
Loading…
Reference in New Issue