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 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
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,
|
||||
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)"),
|
||||
|
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;
|
||||
}
|
||||
|
||||
.select2-results .select2-highlighted {
|
||||
background-color: #005c66;
|
||||
}
|
||||
|
||||
.notbtn {
|
||||
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):
|
||||
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
Block a user