mirror of
https://github.com/apache/superset.git
synced 2024-09-12 00:29:39 -04:00
Merge pull request #88 from mistercrunch/map
World Map viz with bubbles
This commit is contained in:
commit
047e142b43
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),
|
||||
@ -191,6 +203,18 @@ class FormFactory(object):
|
||||
'90 days ago',
|
||||
'1 year ago'])
|
||||
),
|
||||
'max_bubble_size': FreeFormSelectField(
|
||||
'Max Bubble Size', default="25",
|
||||
choices=self.choicify([
|
||||
'5',
|
||||
'10',
|
||||
'15',
|
||||
'25',
|
||||
'50',
|
||||
'75',
|
||||
'100',
|
||||
])
|
||||
),
|
||||
'row_limit':
|
||||
FreeFormSelectField(
|
||||
'Row limit',
|
||||
@ -298,6 +322,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
93
panoramix/static/widgets/viz_world_map.js
Normal file
93
panoramix/static/widgets/viz_world_map.js
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
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.m1});
|
||||
var extRadius = d3.extent(json.data, function(d){return d.m2});
|
||||
var radiusScale = d3.scale.linear()
|
||||
.domain([extRadius[0], extRadius[1]])
|
||||
.range([1, data_attribute.form_data.max_bubble_size]);
|
||||
json.data.forEach(function(d){
|
||||
d.radius = radiusScale(d.m2);
|
||||
})
|
||||
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];
|
||||
country['fillColor'] = colorScale(country.m1);
|
||||
d[country.country] = country;
|
||||
}
|
||||
f = d3.format('.3s');
|
||||
var map = new Datamap({
|
||||
element: document.getElementById(data_attribute.token),
|
||||
data: json.data,
|
||||
fills: {
|
||||
defaultFill: 'grey'
|
||||
},
|
||||
geographyConfig: {
|
||||
popupOnHover: true,
|
||||
highlightOnHover: true,
|
||||
borderWidth: 1,
|
||||
borderColor: 'grey',
|
||||
highlightBorderColor: 'black',
|
||||
highlightFillColor: '#005a63',
|
||||
highlightBorderWidth: 1,
|
||||
popupTemplate: function(geo, data) {
|
||||
return '<div class="hoverinfo"><strong>' + data.name + '</strong><br>'+ f(data.m1) + '</div>';
|
||||
},
|
||||
},
|
||||
bubblesConfig: {
|
||||
borderWidth: 1,
|
||||
borderOpacity: 1,
|
||||
borderColor: '#005a63',
|
||||
popupOnHover: true,
|
||||
radius: null,
|
||||
popupTemplate: function(geo, data) {
|
||||
return '<div class="hoverinfo"><strong>' + data.name + '</strong><br>'+ f(data.m2) + '</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 %}
|
@ -961,6 +961,87 @@ 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',
|
||||
)
|
||||
},
|
||||
{
|
||||
'label': 'Bubbles',
|
||||
'fields': (
|
||||
('show_bubbles', None),
|
||||
'secondary_metric',
|
||||
'max_bubble_size',
|
||||
)
|
||||
})
|
||||
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['m1'] = df[metric]
|
||||
ndf['m2'] = df[metric]
|
||||
else:
|
||||
cols += [metric, secondary_metric]
|
||||
ndf = df[cols]
|
||||
df = ndf
|
||||
df.columns = ['country', 'm1', 'm2']
|
||||
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']
|
||||
row['name'] = country['name']
|
||||
else:
|
||||
row['country'] = "XXX"
|
||||
return dumps(d)
|
||||
|
||||
|
||||
viz_types_list = [
|
||||
TableViz,
|
||||
PivotTableViz,
|
||||
@ -977,6 +1058,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