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,
|
'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),
|
||||||
@ -191,6 +203,18 @@ class FormFactory(object):
|
|||||||
'90 days ago',
|
'90 days ago',
|
||||||
'1 year 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':
|
'row_limit':
|
||||||
FreeFormSelectField(
|
FreeFormSelectField(
|
||||||
'Row limit',
|
'Row limit',
|
||||||
@ -298,6 +322,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
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')
|
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',
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'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 = [
|
viz_types_list = [
|
||||||
TableViz,
|
TableViz,
|
||||||
PivotTableViz,
|
PivotTableViz,
|
||||||
@ -977,6 +1058,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