Skip to content

Commit

Permalink
#150: replace colorscale dropdown with component from dash-colorscales
Browse files Browse the repository at this point in the history
  • Loading branch information
aschonfeld committed Jun 28, 2020
1 parent 0042d84 commit f1c7abb
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 65 deletions.
18 changes: 11 additions & 7 deletions dtale/charts/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,20 @@ def check_all_nan(df, cols=None):
raise Exception('All data for column "{}" is NaN!'.format(col))


DUPES_MSG = (
"{} contains duplicates, please specify group or additional filtering or select 'No Aggregation' from"
" Aggregation drop-down."
)
LIMIT_MSG = "Dataset exceeds {} records, cannot render. Please apply filter..."


def check_exceptions(
df, allow_duplicates, unlimited_data=False, data_limit=15000, limit_msg=LIMIT_MSG
df,
allow_duplicates,
unlimited_data=False,
data_limit=15000,
limit_msg=LIMIT_MSG,
dupes_msg=DUPES_MSG,
):
"""
Checker function to test the output of any chart aggregations to see if it is one of the following:
Expand All @@ -273,12 +282,7 @@ def check_exceptions(
:raises Exception: if any failure condition is met
"""
if not allow_duplicates and any(df.duplicated()):
raise ChartBuildingError(
(
"{} contains duplicates, please specify group or additional filtering or select 'No Aggregation' from"
" Aggregation drop-down."
).format(", ".join(df.columns))
)
raise ChartBuildingError(dupes_msg.format(", ".join(df.columns)))
if not unlimited_data and len(df) > data_limit:
raise ChartBuildingError(limit_msg.format(data_limit))

Expand Down
39 changes: 28 additions & 11 deletions dtale/dash_application/charts.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def chart_url_params(search):
params = dict(get_url_parser()(search.lstrip("?")))
else:
params = search
for gp in ["y", "group", "map_group", "group_val", "yaxis"]:
for gp in ["y", "group", "map_group", "group_val", "yaxis", "colorscale"]:
if gp in params:
params[gp] = json.loads(params[gp])
params["cpg"] = "true" == params.get("cpg")
Expand Down Expand Up @@ -128,16 +128,16 @@ def chart_url_querystring(params, data=None, group_filter=None):
base_props += ["geojson", "featureidkey"]
base_props += ["map_group"]

if chart_type in ["maps", "heatmap"]:
base_props += ["colorscale"]

final_params = {k: params[k] for k in base_props if params.get(k) is not None}
final_params["cpg"] = "true" if params.get("cpg") is True else "false"
if chart_type in ANIMATION_CHARTS:
final_params["animate"] = "true" if params.get("animate") is True else "false"
if chart_type in ANIMATE_BY_CHARTS and params.get("animate_by") is not None:
final_params["animate_by"] = params.get("animate_by")
for gp in ["y", "group", "map_group", "group_val"]:
list_props = ["y", "group", "map_group", "group_val"]
if chart_type in ["maps", "heatmap"]:
list_props += ["colorscale"]
for gp in list_props:
list_param = [val for val in params.get(gp) or [] if val is not None]
if len(list_param):
final_params[gp] = json.dumps(list_param)
Expand All @@ -161,6 +161,12 @@ def chart_url_querystring(params, data=None, group_filter=None):
return url_encode_func()(final_params)


def build_colorscale(colorscale):
if isinstance(colorscale, string_types):
return colorscale
return [[i / (len(colorscale) - 1), rgb] for i, rgb in enumerate(colorscale)]


def graph_wrapper(**kwargs):
curr_style = kwargs.pop("style", None) or {}
return dcc.Graph(style=dict_merge({"height": "100%"}, curr_style), **kwargs)
Expand Down Expand Up @@ -1166,7 +1172,7 @@ def heatmap_builder(data_id, export=False, **inputs):
)
wrapper = chart_wrapper(data_id, raw_data, inputs)
hm_kwargs = dict(
colorscale=inputs.get("colorscale") or "Greens",
colorscale=build_colorscale(inputs.get("colorscale") or "Greens"),
showscale=True,
hoverinfo="x+y+z",
)
Expand Down Expand Up @@ -1381,7 +1387,7 @@ def map_builder(data_id, export=False, **inputs):
color=data[map_val],
cmin=data[map_val].min(),
cmax=data[map_val].max(),
colorscale=inputs.get("colorscale") or "Reds",
colorscale=build_colorscale(inputs.get("colorscale") or "Reds"),
colorbar_title=map_val,
)
figure_cfg = dict(data=[go.Scattergeo(**chart_kwargs)], layout=layout)
Expand Down Expand Up @@ -1419,8 +1425,6 @@ def build_frame(df):
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

Expand All @@ -1436,7 +1440,7 @@ def build_frame(df):
color=data[map_val],
cmin=data[map_val].min(),
cmax=data[map_val].max(),
colorscale=inputs.get("colorscale") or "Jet",
colorscale=build_colorscale(inputs.get("colorscale") or "Jet"),
colorbar_title=map_val,
)
figure_cfg = dict(data=[go.Scattermapbox(**chart_kwargs)], layout=layout)
Expand Down Expand Up @@ -1468,6 +1472,19 @@ def build_frame(df):
data, loc, map_val, {}, agg, animate_by=animate_by
)
code += agg_code
if not len(data):
raise Exception("No data returned for this computation!")
dupe_cols = [loc]
if animate_by is not None:
dupe_cols = [animate_by, loc]
kwargs = {}
if agg == "raw":
kwargs["dupes_msg"] = (
"'No Aggregation' is not a valid aggregation for a choropleth map! {} contains duplicates, please "
"select a different aggregation or additional filtering."
)
check_exceptions(data[dupe_cols], False, unlimited_data=True, **kwargs)

if loc_mode == "USA-states":
layout["geo"] = dict(scope="usa")
elif loc_mode == "geojson-id":
Expand All @@ -1488,7 +1505,7 @@ def build_frame(df):
locations=data[loc],
locationmode=loc_mode,
z=data[map_val],
colorscale=inputs.get("colorscale") or "Reds",
colorscale=build_colorscale(inputs.get("colorscale") or "Reds"),
colorbar_title=map_val,
zmin=data[map_val].min(),
zmax=data[map_val].max(),
Expand Down
53 changes: 19 additions & 34 deletions dtale/dash_application/layout/layout.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json

import dash_colorscales as dcs
import dash_core_components as dcc
import dash_daq as daq
import dash_html_components as html
Expand Down Expand Up @@ -431,26 +432,8 @@ def build_loc_mode_hover(loc_mode):
)


COLORSCALES = [
"Blackbody",
"Bluered",
"Blues",
"Earth",
"Electric",
"Greens",
"Greys",
"Hot",
"Jet",
"Picnic",
"Portland",
"Rainbow",
"RdBu",
"Reds",
"Viridis",
"YlGnBu",
"YlOrRd",
]

JET = ["#000083", "#003CAA", "#05FFFF", "#FFFF00", "#FA0000", "#800000"]
REDS = ["#fff5f0", "#fdcab4", "#fc8a6a", "#f24632", "#bc141a", "#67000d"]
ANIMATION_CHARTS = ["line"]
ANIMATE_BY_CHARTS = ["bar", "3d_scatter", "maps"]

Expand All @@ -465,6 +448,15 @@ def _show_input(input_id, input_type="single"):
return _show_input


def show_group_input(inputs, group_cols=None):
chart_type = inputs.get("chart_type")
if show_input_handler(chart_type)("group"):
return len(group_cols or make_list(inputs.get("group")))
elif show_input_handler(chart_type)("map_group"):
return len(group_cols or make_list(inputs.get("map_group")))
return False


def update_label_for_freq(val):
"""
Formats sub-values contained within 'val' to display date frequencies if included.
Expand Down Expand Up @@ -755,13 +747,9 @@ def _build_hoverable():


def main_inputs_and_group_val_display(inputs):
chart_type = inputs.get("chart_type")
show_group = show_input_handler(inputs.get("chart_type", "line"))("group")
if chart_type == "maps" and not len(make_list(inputs.get("map_group"))):
return dict(display="none"), "col-md-12"
elif show_group and not len(make_list(inputs.get("group"))):
return dict(display="none"), "col-md-12"
return dict(display="block"), "col-md-8"
if show_group_input(inputs):
return dict(display="block"), "col-md-8"
return dict(display="none"), "col-md-12"


def charts_layout(df, settings, **inputs):
Expand Down Expand Up @@ -828,7 +816,7 @@ def charts_layout(df, settings, **inputs):
df, type=map_type, loc=loc, lat=lat, lon=lon, map_val=map_val
)
cscale_style = colorscale_input_style(**inputs)
default_cscale = "Jet" 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 @@ -1356,12 +1344,9 @@ def show_map_style(show):
),
build_input(
"Colorscale",
dcc.Dropdown(
id="colorscale-dropdown",
options=[
build_option(o) for o in COLORSCALES
],
value=inputs.get("colorscale")
dcs.DashColorscales(
id="colorscale-picker",
colorscale=inputs.get("colorscale")
or default_cscale,
),
className="col-auto addon-min-width",
Expand Down
11 changes: 4 additions & 7 deletions dtale/dash_application/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
get_yaxis_type_tabs,
main_inputs_and_group_val_display,
show_chart_per_group,
show_group_input,
show_input_handler,
show_yaxis_ranges,
)
Expand Down Expand Up @@ -447,7 +448,7 @@ def input_toggles(_ts, inputs, pathname):
Input("cpg-toggle", "on"),
Input("barmode-dropdown", "value"),
Input("barsort-dropdown", "value"),
Input("colorscale-dropdown", "value"),
Input("colorscale-picker", "colorscale"),
Input("animate-toggle", "on"),
Input("animate-by-dropdown", "value"),
Input("trendline-dropdown", "value"),
Expand Down Expand Up @@ -683,13 +684,9 @@ def main_input_class(ts_, ts2_, inputs, map_inputs):
def group_values(
chart_type, group_cols, map_group_cols, pathname, inputs, prev_group_vals
):
group_cols = make_list(group_cols)
if show_input_handler(chart_type or "line")("group") and not len(group_cols):
group_cols = make_list(map_group_cols if chart_type == "maps" else group_cols)
if not show_group_input(inputs, group_cols):
return [], None
elif chart_type == "maps": # all maps have a group input
group_cols = make_list(map_group_cols)
if not len(group_cols):
return [], None
data_id = get_data_id(pathname)
group_vals = run_query(
global_state.get_data(data_id),
Expand Down
14 changes: 14 additions & 0 deletions dtale/static/css/dash.css
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,20 @@ div.tab-container > div.tab:last-child {
#trendline-dropdown .Select-menu-outer{
z-index: 7;
}
#colorscale-picker {
border: 1px solid #a7b3b7;
border-radius: 0 .25rem .25rem 0;
height: 36px;
}
.colorscale-block {
margin: 5px 10px !important;
}
.colorscaleButton {
color: #fff !important;
}
.colorscalePickerContainer input[type='checkbox'] {
vertical-align: text-top !important;
}
#yaxis-type-div {
min-width: fit-content;
border: 1px solid #a7b3b7;
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def run_tests(self):
"lz4; python_version > '3.0'",
"dash>=1.5.0",
"dash-bootstrap-components",
"dash-colorscales",
"dash_daq",
"Flask>=1.0",
"Flask-Compress",
Expand Down
Loading

0 comments on commit f1c7abb

Please sign in to comment.