Skip to content

Commit

Permalink
1.8.14
Browse files Browse the repository at this point in the history
* #168, updated ddefault colorscale for heatmaps to be Jet
* #152, added scattermapbox as a valid map type
  • Loading branch information
aschonfeld committed May 23, 2020
1 parent 8cf15ce commit 76bfa6c
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 25 deletions.
15 changes: 14 additions & 1 deletion dtale/charts/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@
YAXIS_CHARTS = ['line', 'bar', 'scatter']
ZAXIS_CHARTS = ['heatmap', '3d_scatter', 'surface']
MAX_GROUPS = 30
MAPBOX_TOKEN = None


def get_mapbox_token():
global MAPBOX_TOKEN

return MAPBOX_TOKEN


def set_mapbox_token(token):
global MAPBOX_TOKEN

MAPBOX_TOKEN = token


def valid_chart(chart_type=None, x=None, y=None, z=None, **inputs):
Expand All @@ -33,7 +46,7 @@ def valid_chart(chart_type=None, x=None, y=None, z=None, **inputs):
map_type = inputs.get('map_type')
if map_type == 'choropleth' and all(inputs.get(p) is not None for p in ['loc_mode', 'loc', 'map_val']):
return True
elif map_type == 'scattergeo' and all(inputs.get(p) is not None for p in ['lat', 'lon']):
elif map_type in ['scattergeo', 'mapbox'] and all(inputs.get(p) is not None for p in ['lat', 'lon']):
return True
return False

Expand Down
50 changes: 47 additions & 3 deletions dtale/dash_application/charts.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,11 @@ def chart_url_querystring(params, data=None, group_filter=None):
if chart_type == 'bar':
base_props += ['barmode', 'barsort']
elif chart_type == 'maps':
if params.get('map_type') == 'scattergeo':
map_type = params.get('map_type')
if map_type == 'scattergeo':
base_props += ['map_type', 'lat', 'lon', 'map_val', 'scope', 'proj']
elif map_type == 'mapbox':
base_props += ['map_type', 'lat', 'lon', 'map_val', 'mapbox_style']
else:
base_props += ['map_type', 'loc_mode', 'loc', 'map_val']
base_props += ['map_group']
Expand Down Expand Up @@ -1013,8 +1016,9 @@ def map_builder(data_id, export=False, **inputs):
try:
if not valid_chart(**inputs):
return None, None
props = ['map_type', 'loc_mode', 'loc', 'lat', 'lon', 'map_val', 'scope', 'proj', 'agg', 'animate_by']
map_type, loc_mode, loc, lat, lon, map_val, scope, proj, agg, animate_by = (inputs.get(p) for p in props)
props = ['map_type', 'loc_mode', 'loc', 'lat', 'lon', 'map_val', 'scope', 'proj', 'mapbox_style', 'agg',
'animate_by']
map_type, loc_mode, loc, lat, lon, map_val, scope, proj, style, agg, animate_by = (inputs.get(p) for p in props)
map_group, group_val = (inputs.get(p) for p in ['map_group', 'group_val'])
raw_data = run_query(
global_state.get_data(data_id),
Expand Down Expand Up @@ -1069,6 +1073,46 @@ def build_frame(df):
config=dict(topojsonURL='/maps/'),
figure=figure_cfg
)
elif map_type == 'mapbox':
from dtale.charts.utils import get_mapbox_token
data, code = retrieve_chart_data(raw_data, lat, lon, map_val, animate_by, map_group, group_val=group_val)
if agg is not None:
data, agg_code = build_agg_data(raw_data, lat, lon, {}, agg, z=map_val, animate_by=animate_by)
code += agg_code

mapbox_layout = {'style': style}
mapbox_token = get_mapbox_token()
if mapbox_token is not None:
mapbox_layout['accesstoken'] = mapbox_token
# if test_plotly_version('4.5.0') and animate_by is None:
# geo_layout['fitbounds'] = 'locations'
if len(mapbox_layout):
layout['mapbox'] = mapbox_layout

chart_kwargs = dict(lon=data[lon], lat=data[lat], mode='markers', marker=dict(color='darkblue'))
if map_val is not None:
chart_kwargs['text'] = data[map_val]
chart_kwargs['marker'] = dict(
color=data[map_val], cmin=data[map_val].min(), cmax=data[map_val].max(),
colorscale=inputs.get('colorscale') or 'Jet',
colorbar_title=map_val
)
figure_cfg = dict(data=[go.Scattermapbox(**chart_kwargs)], layout=layout)
if animate_by is not None:
def build_frame(df):
frame = dict(lon=df[lon], lat=df[lat], mode='markers')
if map_val is not None:
frame['text'] = df[map_val]
frame['marker'] = dict(color=df[map_val])
return frame

update_cfg_w_frames(figure_cfg, *build_map_frames(data, animate_by, build_frame))
chart = graph_wrapper(
id='scattermapbox-graph',
style={'margin-right': 'auto', 'margin-left': 'auto', 'height': '95%'},
config=dict(topojsonURL='/maps/'),
figure=figure_cfg
)
else:
data, code = retrieve_chart_data(raw_data, loc, map_val, map_group, animate_by, group_val=group_val)
if agg is not None:
Expand Down
95 changes: 83 additions & 12 deletions dtale/dash_application/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,22 @@ def base_layout(github_fork, app_root, **kwargs):
<a href="https://github.com/man-group/dtale">Fork me on GitHub</a>
</span>
'''
favicon_path = '../../images/favicon.png'
if is_app_root_defined(app_root):
webroot_html = '''
<script type="text/javascript">
window.resourceBaseUrl = '{app_root}';
</script>
'''.format(app_root=app_root)
favicon_path = '{}/images/favicon.png'.format(app_root)
return '''
<!DOCTYPE html>
<html>
<head>
{webroot_html}
{metas}
<title>D-Tale Charts</title>
<link rel="shortcut icon" href="{app_root}/images/favicon.png">
<link rel="shortcut icon" href="{favicon_path}">
{css}
</head>
<body>
Expand Down Expand Up @@ -97,7 +99,8 @@ def base_layout(github_fork, app_root, **kwargs):
back_to_data_padding=back_to_data_padding,
webroot_html=webroot_html,
github_fork_html=github_fork_html,
app_root=app_root or ''
app_root=app_root if is_app_root_defined(app_root) else '',
favicon_path=favicon_path
)


Expand Down Expand Up @@ -189,7 +192,11 @@ def build_option(value, label=None):
FREQS = ['H', 'H2', 'WD', 'D', 'W', 'M', 'Q', 'Y']
FREQ_LABELS = dict(H='Hourly', H2='Hour', WD='Weekday', W='Weekly', M='Monthly', Q='Quarterly', Y='Yearly')

MAP_TYPES = [dict(value='choropleth'), dict(value='scattergeo', label='ScatterGeo')]
MAP_TYPES = [
dict(value='choropleth', image=True),
dict(value='scattergeo', label='ScatterGeo', image=True),
dict(value='mapbox')
]
SCOPES = ['world', 'usa', 'europe', 'asia', 'africa', 'north america', 'south america']
PROJECTIONS = ['equirectangular', 'mercator', 'orthographic', 'natural earth', 'kavrayskiy7', 'miller', 'robinson',
'eckert4', 'azimuthal equal area', 'azimuthal equidistant', 'conic equal area', 'conic conformal',
Expand Down Expand Up @@ -229,6 +236,40 @@ def build_proj_hover(proj):
)


def build_mapbox_token_children():
from dtale.charts.utils import get_mapbox_token
msg = 'To access additional styles enter a token here...'
if get_mapbox_token() is None:
msg = 'Change your token here...'
return [
html.I(className='ico-help-outline', style=dict(color='white')),
html.Div(
[
html.Span('Mapbox Access Token:'),
dcc.Input(id='mapbox-token-input', type='text', placeholder=msg, className='form-control',
value='', style={'lineHeight': 'inherit'})
],
className='hoverable__content',
style=dict(width='20em', right='-1.45em')
)
]


def build_mapbox_token_hover():
return html.Span(
[
'Style',
html.Div(
build_mapbox_token_children(),
className='ml-3 hoverable',
style=dict(borderBottom='none'),
id='token-hover'
)
],
className='input-group-addon'
)


LOC_MODE_INFO = {
'ISO-3': dict(
url=html.A('ISO-3', href='https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3', target='_blank'),
Expand Down Expand Up @@ -412,6 +453,17 @@ def build_map_options(df, type='choropleth', loc=None, lat=None, lon=None, map_v
return loc_options, lat_options, lon_options, val_options


def build_mapbox_style_options():
from dtale.charts.utils import get_mapbox_token
free_styles = ['open-street-map', 'carto-positron', 'carto-darkmatter', 'stamen-terrain', 'stamen-toner',
'stamen-watercolor']
token_styles = ['basic', 'streets', 'outdoors', 'light', 'dark', 'satellite', 'satellite-streets']
styles = free_styles
if get_mapbox_token() is not None:
styles += token_styles
return [build_option(v) for v in styles]


def bar_input_style(**inputs):
"""
Sets display CSS property for bar chart inputs
Expand Down Expand Up @@ -478,10 +530,11 @@ def build_group_val_options(df, group_cols):
def build_map_type_tabs(map_type):
def _build_hoverable():
for t in MAP_TYPES:
yield html.Div([
html.Span(t.get('label', t['value'].capitalize())),
html.Img(src=build_img_src(t['value'], img_type='map_type'))
], className='col-md-6')
if t.get('image', False):
yield html.Div([
html.Span(t.get('label', t['value'].capitalize())),
html.Img(src=build_img_src(t['value'], img_type='map_type'))
], className='col-md-6')

return html.Div(
[dcc.Tabs(
Expand All @@ -493,7 +546,7 @@ def _build_hoverable():
html.Div(list(_build_hoverable()), className='row'),
className='hoverable__content map-types'
)],
style=dict(paddingLeft=15, borderBottom='none'), className="pr-5 hoverable"
style=dict(paddingLeft=15, borderBottom='none', width='20em'), className="hoverable"
)


Expand Down Expand Up @@ -544,11 +597,11 @@ def charts_layout(df, settings, **inputs):
show_map = chart_type == 'maps'
map_props = ['map_type', 'loc_mode', 'loc', 'lat', 'lon', 'map_val']
map_type, loc_mode, loc, lat, lon, map_val = (inputs.get(p) for p in map_props)
map_scope, proj = (inputs.get(p) for p in ['scope', 'proj'])
map_scope, proj, mapbox_style = (inputs.get(p) for p in ['scope', 'proj', 'mapbox_style'])
loc_options, lat_options, lon_options, map_val_options = build_map_options(df, type=map_type, loc=loc, lat=lat,
lon=lon, map_val=map_val)
cscale_style = colorscale_input_style(**inputs)
default_cscale = 'Greens' if chart_type == 'heatmap' else 'Reds'
default_cscale = 'Jet' if chart_type == 'heatmap' else 'Reds'

group_val_style, main_input_class = main_inputs_and_group_val_display(inputs)
group_val = [json.dumps(gv) for gv in inputs.get('group_val') or []]
Expand Down Expand Up @@ -698,7 +751,7 @@ def show_map_style(show):
),
id='map-lat-input',
label_class='input-group-addon d-block pt-1 pb-0',
style=show_map_style(map_type == 'scattergeo')
style=show_map_style(map_type in ['scattergeo', 'mapbox'])
),
build_input(
[html.Div('Lon'), html.Small('(Agg By)')],
Expand All @@ -711,14 +764,32 @@ def show_map_style(show):
),
id='map-lon-input',
label_class='input-group-addon d-block pt-1 pb-0',
style=show_map_style(map_type == 'scattergeo')
style=show_map_style(map_type in ['scattergeo', 'mapbox'])
),
build_input('Scope', dcc.Dropdown(
id='map-scope-dropdown',
options=[build_option(v) for v in SCOPES],
style=dict(width='inherit'),
value=map_scope or 'world'
), id='map-scope-input', style=show_map_style(map_type == 'scattergeo')),
html.Div(
[
html.Div(
[
build_mapbox_token_hover(),
dcc.Dropdown(
id='map-mapbox-style-dropdown',
options=build_mapbox_style_options(),
style=dict(width='inherit'),
value=mapbox_style or 'open-street-map'
)
],
className='input-group mr-3',
)
],
id='map-mapbox-style-input', className='col-auto',
style=show_map_style(map_type == 'mapbox')
),
html.Div(
[
html.Div(
Expand Down
23 changes: 19 additions & 4 deletions dtale/dash_application/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
build_input_options,
build_loc_mode_hover_children,
build_map_options,
build_mapbox_style_options,
build_proj_hover_children,
charts_layout,
colorscale_input_style,
Expand Down Expand Up @@ -197,6 +198,7 @@ def input_data(_ts, chart_type, x, y_multi, y_single, z, group, group_val, agg,
Output('map-lat-input', 'style'),
Output('map-lon-input', 'style'),
Output('map-scope-input', 'style'),
Output('map-mapbox-style-input', 'style'),
Output('map-proj-input', 'style'),
Output('proj-hover', 'style'),
Output('proj-hover', 'children'),
Expand All @@ -211,16 +213,19 @@ def input_data(_ts, chart_type, x, y_multi, y_single, z, group, group_val, agg,
Input('map-lon-dropdown', 'value'),
Input('map-val-dropdown', 'value'),
Input('map-scope-dropdown', 'value'),
Input('map-mapbox-style-dropdown', 'value'),
Input('map-proj-dropdown', 'value'),
Input('map-group-dropdown', 'value')
],
[State('url', 'pathname')]
)
def map_data(map_type, loc_mode, loc, lat, lon, map_val, scope, proj, group, pathname):
def map_data(map_type, loc_mode, loc, lat, lon, map_val, scope, style, proj, group, pathname):
data_id = get_data_id(pathname)
map_type = map_type or 'choropleth'
if map_type == 'choropleth':
map_data = dict(map_type=map_type, loc_mode=loc_mode, loc=loc, map_val=map_val)
elif map_type == 'mapbox':
map_data = dict(map_type=map_type, lat=lat, lon=lon, map_val=map_val, mapbox_style=style)
else:
map_data = dict(map_type=map_type, lat=lat, lon=lon, map_val=map_val, scope=scope, proj=proj)

Expand All @@ -230,17 +235,27 @@ def map_data(map_type, loc_mode, loc, lat, lon, map_val, scope, proj, group, pat
loc_options, lat_options, lon_options, map_val_options = build_map_options(df, type=map_type, loc=loc,
lat=lat, lon=lon, map_val=map_val)
choro_style = {} if map_type == 'choropleth' else {'display': 'none'}
coord_style = {} if map_type in ['scattergeo', 'mapbox'] else {'display': 'none'}
scatt_style = {} if map_type == 'scattergeo' else {'display': 'none'}
mapbox_style = {} if map_type == 'mapbox' else {'display': 'none'}
proj_hover_style = {'display': 'none'} if proj is None else dict(borderBottom='none')
proj_hopver_children = build_proj_hover_children(proj)
loc_mode_hover_style = {'display': 'none'} if loc_mode is None else dict(borderBottom='none')
loc_mode_children = build_loc_mode_hover_children(loc_mode)
return (
map_data, loc_options, lat_options, lon_options, map_val_options, choro_style, choro_style, scatt_style,
scatt_style, scatt_style, scatt_style, proj_hover_style, proj_hopver_children, loc_mode_hover_style,
loc_mode_children
map_data, loc_options, lat_options, lon_options, map_val_options, choro_style, choro_style, coord_style,
coord_style, scatt_style, mapbox_style, scatt_style, proj_hover_style, proj_hopver_children,
loc_mode_hover_style, loc_mode_children
)

@dash_app.callback(Output('map-mapbox-style-dropdown', 'options'), [Input('mapbox-token-input', 'value')])
def update_mapbox_token(token):
from dtale.charts.utils import set_mapbox_token

if token:
set_mapbox_token(token)
return build_mapbox_style_options()

@dash_app.callback(
[
Output('y-multi-input', 'style'),
Expand Down
10 changes: 8 additions & 2 deletions dtale/static/css/dash.css
Original file line number Diff line number Diff line change
Expand Up @@ -526,8 +526,8 @@ div.modebar > div.modebar-group:first-child /* hide plotly "export to png" */
min-width: 10em;
}

#map-inputs > div.col-auto + div.col-auto {
padding-left: 0;
#map-inputs > div.col-auto {
padding-right: 0;
}

div#non-map-inputs > div,
Expand All @@ -536,3 +536,9 @@ div#chart-inputs > div {
padding-bottom: 0.25rem !important;
}

#mapbox-token-input {
width: 100%;
border-top-left-radius: .25rem;
border-bottom-left-radius: .25rem;
}

Loading

0 comments on commit 76bfa6c

Please sign in to comment.