mirror of
https://github.com/apache/superset.git
synced 2024-09-12 00:29:39 -04:00
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 flask.ext.migrate import MigrateCommand
|
||||||
from panoramix import db
|
from panoramix import db
|
||||||
from flask.ext.appbuilder import Base
|
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
|
from panoramix import models, utils
|
||||||
|
|
||||||
config = app.config
|
config = app.config
|
||||||
@ -59,6 +59,63 @@ def init():
|
|||||||
def load_examples(sample):
|
def load_examples(sample):
|
||||||
"""Loads a set of Slices and Dashboards and a supporting dataset """
|
"""Loads a set of Slices and Dashboards and a supporting dataset """
|
||||||
print("Loading examples into {}".format(db))
|
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(
|
BirthNames = Table(
|
||||||
@ -79,8 +136,8 @@ def load_examples(sample):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
BirthNames.create(db.engine)
|
BirthNames.create(db.engine)
|
||||||
session = db.session()
|
|
||||||
filepath = os.path.join(config.get("BASE_DIR"), 'data/birth_names.csv.gz')
|
filepath = os.path.join(config.get("BASE_DIR"), 'data/birth_names.csv.gz')
|
||||||
|
'''
|
||||||
with gzip.open(filepath, mode='rt') as f:
|
with gzip.open(filepath, mode='rt') as f:
|
||||||
bb_csv = csv.reader(f)
|
bb_csv = csv.reader(f)
|
||||||
for i, (state, year, name, gender, num) in enumerate(bb_csv):
|
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
|
if sample and i>1000: break
|
||||||
print("Done loading table!")
|
print("Done loading table!")
|
||||||
print("-" * 80)
|
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")
|
print("Creating table reference")
|
||||||
TBL = models.SqlaTable
|
TBL = models.SqlaTable
|
||||||
|
11549
panoramix/data/countries.json
Normal file
11549
panoramix/data/countries.json
Normal file
File diff suppressed because it is too large
Load Diff
2490
panoramix/data/countries.py
Normal file
2490
panoramix/data/countries.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -108,6 +108,18 @@ class FormFactory(object):
|
|||||||
'Color Metric', choices=datasource.metrics_combo,
|
'Color Metric', choices=datasource.metrics_combo,
|
||||||
default=default_metric,
|
default=default_metric,
|
||||||
description="A metric to use for color"),
|
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(
|
'groupby': SelectMultipleSortableField(
|
||||||
'Group by',
|
'Group by',
|
||||||
choices=self.choicify(datasource.groupby_column_names),
|
choices=self.choicify(datasource.groupby_column_names),
|
||||||
@ -298,6 +310,10 @@ class FormFactory(object):
|
|||||||
"Range Filter", default=True,
|
"Range Filter", default=True,
|
||||||
description=(
|
description=(
|
||||||
"Whether to display the time range interactive selector")),
|
"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(
|
'show_legend': BetterBooleanField(
|
||||||
"Legend", default=True,
|
"Legend", default=True,
|
||||||
description="Whether to display the legend (toggles)"),
|
description="Whether to display the legend (toggles)"),
|
||||||
|
12532
panoramix/static/lib/datamaps.all.js
Normal file
12532
panoramix/static/lib/datamaps.all.js
Normal file
File diff suppressed because one or more lines are too long
2
panoramix/static/lib/datamaps.world.min.js
vendored
Normal file
2
panoramix/static/lib/datamaps.world.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
panoramix/static/lib/topojson.min.js
vendored
Normal file
1
panoramix/static/lib/topojson.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -20,6 +20,10 @@ form div {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.select2-results .select2-highlighted {
|
||||||
|
background-color: #005c66;
|
||||||
|
}
|
||||||
|
|
||||||
.notbtn {
|
.notbtn {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
0
panoramix/static/widgets/viz_world_map.css
Normal file
0
panoramix/static/widgets/viz_world_map.css
Normal file
87
panoramix/static/widgets/viz_world_map.js
Normal file
87
panoramix/static/widgets/viz_world_map.js
Normal 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);
|
8
panoramix/templates/panoramix/viz_world_map.html
Normal file
8
panoramix/templates/panoramix/viz_world_map.html
Normal file
@ -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):
|
class DatasourceModelView(PanoramixModelView, DeleteMixin):
|
||||||
datamodel = SQLAInterface(models.Datasource)
|
datamodel = SQLAInterface(models.Datasource)
|
||||||
list_columns = [
|
list_columns = [
|
||||||
'datasource_link', 'cluster', 'owner', 'is_featured', 'is_hidden',
|
'datasource_link', 'cluster', 'owner',
|
||||||
|
'created_by', 'created_on',
|
||||||
|
'changed_by', 'changed_on',
|
||||||
'offset']
|
'offset']
|
||||||
related_views = [ColumnInlineView, MetricInlineView]
|
related_views = [ColumnInlineView, MetricInlineView]
|
||||||
edit_columns = [
|
edit_columns = [
|
||||||
|
@ -961,6 +961,86 @@ class DirectedForceViz(BaseViz):
|
|||||||
d = df.to_dict(orient='records')
|
d = df.to_dict(orient='records')
|
||||||
return dumps(d)
|
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 = [
|
viz_types_list = [
|
||||||
TableViz,
|
TableViz,
|
||||||
PivotTableViz,
|
PivotTableViz,
|
||||||
@ -977,6 +1057,7 @@ viz_types_list = [
|
|||||||
SunburstViz,
|
SunburstViz,
|
||||||
DirectedForceViz,
|
DirectedForceViz,
|
||||||
SankeyViz,
|
SankeyViz,
|
||||||
|
WorldMapViz,
|
||||||
]
|
]
|
||||||
# This dict is used to
|
# This dict is used to
|
||||||
viz_types = OrderedDict([(v.viz_type, v) for v in viz_types_list])
|
viz_types = OrderedDict([(v.viz_type, v) for v in viz_types_list])
|
||||||
|
Loading…
Reference in New Issue
Block a user