Skip to content

Commit

Permalink
1.8.11
Browse files Browse the repository at this point in the history
* #191: improving outlier filter suggestions
* #190: hide "Animate" inputs when "Percentage Sum" or "Percentage Count" aggregations are used
* #189: hide "Barsort" when grouping is being applied
* #187: missing & outlier tooltip descriptions on column headers
* #186: close "Describe" tab after clicking "Update Grid"
  • Loading branch information
aschonfeld committed Apr 26, 2020
1 parent a586f5c commit 7193313
Show file tree
Hide file tree
Showing 12 changed files with 62 additions and 27 deletions.
2 changes: 1 addition & 1 deletion dtale/dash_application/charts.py
Original file line number Diff line number Diff line change
Expand Up @@ -944,7 +944,7 @@ def heatmap_builder(data_id, export=False, **inputs):
code += agg_code
if not len(data):
raise Exception('No data returned for this computation!')
check_exceptions(data[dupe_cols], agg not in ['corr', 'raw'], unlimited_data=True)
check_exceptions(data[dupe_cols], agg in ['corr', 'raw'], unlimited_data=True)
dtypes = {c: classify_type(dtype) for c, dtype in get_dtypes(data).items()}
data_f, _ = chart_formatters(data)
data = data_f.format_df(data)
Expand Down
13 changes: 8 additions & 5 deletions dtale/dash_application/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,17 +407,20 @@ def bar_input_style(**inputs):
"""
Sets display CSS property for bar chart inputs
"""
return dict(display='block' if inputs.get('chart_type') == 'bar' else 'none')
chart_type, group_col = (inputs.get(p) for p in ['chart_type', 'group'])
show_bar = chart_type == 'bar'
show_barsort = show_bar and group_col is None
return dict(display='block' if show_bar else 'none'), dict(display='block' if show_barsort else 'none')


def colorscale_input_style(**inputs):
return dict(display='block' if inputs.get('chart_type') in ['heatmap', 'maps'] else 'none')


def animate_styles(df, **inputs):
chart_type, cpg = (inputs.get(p) for p in ['chart_type', 'cpg'])
chart_type, agg, cpg = (inputs.get(p) for p in ['chart_type', 'agg', 'cpg'])
opts = []
if cpg:
if cpg or agg in ['pctsum', 'pctct']:
return dict(display='none'), dict(display='none'), opts
if chart_type in ANIMATION_CHARTS:
return dict(display='block'), dict(display='none'), opts
Expand Down Expand Up @@ -512,7 +515,7 @@ def charts_layout(df, settings, **inputs):
show_input = show_input_handler(chart_type)
show_cpg = show_chart_per_group(**inputs)
show_yaxis = show_yaxis_ranges(**inputs)
bar_style = bar_input_style(**inputs)
bar_style, barsort_input_style = bar_input_style(**inputs)
animate_style, animate_by_style, animate_opts = animate_styles(df, **inputs)

options = build_input_options(df, **inputs)
Expand Down Expand Up @@ -803,7 +806,7 @@ def show_map_style(show):
), className='col-auto addon-min-width', style=bar_style, id='barmode-input'),
build_input('Barsort', dcc.Dropdown(
id='barsort-dropdown', options=barsort_options, value=inputs.get('barsort')
), className='col-auto addon-min-width', style=bar_style, id='barsort-input'),
), className='col-auto addon-min-width', style=barsort_input_style, id='barsort-input'),
html.Div(
html.Div(
[
Expand Down
4 changes: 2 additions & 2 deletions dtale/dash_application/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,15 +262,15 @@ def input_toggles(_ts, inputs, pathname):
group_style = {'display': 'block' if show_input('group') else 'none'}
rolling_style = {'display': 'inherit' if agg == 'rolling' else 'none'}
cpg_style = {'display': 'block' if show_chart_per_group(**inputs) else 'none'}
bar_style = bar_input_style(**inputs)
bar_style, barsort_style = bar_input_style(**inputs)
yaxis_style = {'display': 'block' if show_yaxis_ranges(**inputs) else 'none'}

data_id = get_data_id(pathname)
df = global_state.get_data(data_id)
animate_style, animate_by_style, animate_opts = animate_styles(df, **inputs)

return (
y_multi_style, y_single_style, z_style, group_style, rolling_style, cpg_style, bar_style, bar_style,
y_multi_style, y_single_style, z_style, group_style, rolling_style, cpg_style, bar_style, barsort_style,
yaxis_style, animate_style, animate_by_style, animate_opts
)

Expand Down
13 changes: 11 additions & 2 deletions dtale/static/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -10196,6 +10196,12 @@ li.hoverable:hover {
top: unset;
bottom: 110%;
}
.hoverable__content.col-menu-desc {
padding: .5em .5em;
text-align: center;
top: unset;
bottom: 110%;
}

div.hoverable.label {
border-bottom: none;
Expand Down Expand Up @@ -10229,8 +10235,10 @@ div.hoverable.label > div.hoverable__content {
right: unset;
left: 2em;
}
.hoverable__content.copy-tt-top::before {
.hoverable__content.copy-tt-top::before,
.hoverable__content.col-menu-desc::before {
bottom: unset;
border-bottom: none;
top: 95%;
-moz-transform: rotate(180deg);
-webkit-transform: rotate(180deg);
Expand Down Expand Up @@ -10263,7 +10271,8 @@ div.hoverable.label > div.hoverable__content {
right: unset;
left: 2em;
}
.hoverable__content.copy-tt-top::after {
.hoverable__content.copy-tt-top::after,
.hoverable__content.col-menu-desc::after {
bottom: unset;
top: calc(92% + .1em);
-moz-transform: rotate(180deg);
Expand Down
17 changes: 12 additions & 5 deletions dtale/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ def _formatter(col_index, col):
visible = prev_dtypes[col].get('visible', True)
s = data[col]
dtype_data = dict(name=col, dtype=dtype, index=col_index, visible=visible,
hasMissing=bool(s.isnull().any()), hasOutliers=False)
hasMissing=int(s.isnull().sum()), hasOutliers=0)
classification = classify_type(dtype)
if classification in ['F', 'I'] and not data[col].isnull().all() and col in data_ranges: # floats/ints
col_ranges = data_ranges[col]
Expand All @@ -419,11 +419,11 @@ def _formatter(col_index, col):
# load outlier information
o_s, o_e = calc_outlier_range(s)
if not any((np.isnan(v) or np.isinf(v) for v in [o_s, o_e])):
dtype_data['hasOutliers'] = bool(((s < o_s) | (s > o_e)).any())
dtype_data['hasOutliers'] += int(((s < o_s) | (s > o_e)).sum())
dtype_data['outlierRange'] = dict(lower=o_s, upper=o_e)

if classification == 'S' and not dtype_data['hasMissing']:
dtype_data['hasMissing'] = bool((s.str.strip() == '').any())
dtype_data['hasMissing'] += int((s.str.strip() == '').sum())
return dtype_data
return _formatter

Expand Down Expand Up @@ -1069,10 +1069,17 @@ def outliers(data_id, column):
iqr_lower, iqr_upper = calc_outlier_range(s)
formatter = find_dtype_formatter(find_dtype(df[column]))
outliers = s[(s < iqr_lower) | (s > iqr_upper)].unique()
if not len(outliers):
return jsonify(outliers=[])
top = len(outliers) > 100
outliers = [formatter(v) for v in outliers[:100]]
query = '(({column} < {lower}) or ({column} > {upper}))'.format(column=column, lower=json_float(iqr_lower),
upper=json_float(iqr_upper))
queries = []
if iqr_lower > s.min():
queries.append('{column} < {lower}'.format(column=column, lower=json_float(iqr_lower)))
if iqr_upper < s.max():
queries.append('{column} > {upper}'.format(column=column, upper=json_float(iqr_upper)))
query = '(({}))'.format(') or ('.join(queries)) if len(queries) > 1 else queries[0]

code = (
"s = df['{column}']\n"
"q1 = s.quantile(0.25)\n"
Expand Down
5 changes: 4 additions & 1 deletion static/__tests__/dtale/DataViewer-describe-test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const originalInnerHeight = Object.getOwnPropertyDescriptor(HTMLElement.prototyp

describe("DataViewer tests", () => {
const { post } = $;
const { opener } = window;
const { close, opener } = window;

beforeAll(() => {
Object.defineProperty(HTMLElement.prototype, "offsetHeight", {
Expand All @@ -34,7 +34,9 @@ describe("DataViewer tests", () => {
});

delete window.opener;
delete window.close;
window.opener = { location: { reload: jest.fn() } };
window.close = jest.fn();

const mockBuildLibs = withGlobalJquery(() =>
mockPopsicle.mock(url => {
Expand Down Expand Up @@ -66,6 +68,7 @@ describe("DataViewer tests", () => {
Object.defineProperty(window, "innerHeight", originalInnerHeight);
$.post = post;
window.opener = opener;
window.close = close;
});

test("DataViewer: describe", done => {
Expand Down
8 changes: 4 additions & 4 deletions static/__tests__/redux-test-utils.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ const DTYPES = {
min: 2,
max: 5,
visible: true,
hasMissing: true,
hasOutliers: false,
hasMissing: 1,
hasOutliers: 0,
},
{
name: "col2",
Expand All @@ -32,8 +32,8 @@ const DTYPES = {
min: 2.5,
max: 5.5,
visible: true,
hasMissing: false,
hasOutliers: false,
hasMissing: 0,
hasOutliers: 0,
outlierRange: { lower: 3.5, upper: 4.5 },
},
{ name: "col3", index: 2, dtype: "object", visible: true },
Expand Down
7 changes: 5 additions & 2 deletions static/dtale/DataViewerInfo.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,11 @@ class ReactDataViewerInfo extends React.Component {
const hidden = _.map(_.filter(columns, { visible: false }), "name");
const clearHidden = () => {
const visibility = _.reduce(columns, (ret, { name }) => _.assignIn(ret, { [name]: true }), {});
const updatedColumns = _.map(columns, c => _.assignIn({}, c, { visible: true }));
serverState.updateVisibility(dataId, visibility, () => propagateState({ columns: updatedColumns }));
const updatedState = {
columns: _.map(columns, c => _.assignIn({}, c, { visible: true })),
triggerResize: true,
};
serverState.updateVisibility(dataId, visibility, () => propagateState(updatedState));
};
const clearAll = (
<i key={2} className="ico-cancel pl-3 pointer" style={{ marginTop: "-0.1em" }} onClick={clearHidden} />
Expand Down
4 changes: 2 additions & 2 deletions static/dtale/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ class ReactHeader extends React.Component {
colNameMarkup = <div title={`DType: ${colCfg.dtype}`}>{colName}</div>;
}
if (this.props.backgroundMode === "missing" && colCfg.hasMissing) {
colNameMarkup = `${bu.missingIcon}${colName}`;
colNameMarkup = <div title={`Missing Values: ${colCfg.hasMissing}`}>{`${bu.missingIcon}${colName}`}</div>;
}
if (this.props.backgroundMode === "outliers" && colCfg.hasOutliers) {
colNameMarkup = `${bu.outlierIcon} ${colName}`;
colNameMarkup = <div title={`Outliers: ${colCfg.hasOutliers}`}>{`${bu.outlierIcon} ${colName}`}</div>;
}
return (
<div className={`headerCell ${toggleId}`} style={headerStyle} onClick={menuHandler}>
Expand Down
3 changes: 3 additions & 0 deletions static/dtale/iframe/column-menu-descriptions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"filter": "For more complex filters use \"Custom Filter\" popup on main menu."
}
7 changes: 5 additions & 2 deletions static/filters/ColumnFilter.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { components } from "react-select";

import { buildURLString, saveColFilterUrl } from "../actions/url-utils";
import { exports as gu } from "../dtale/gridUtils";
import Descriptions from "../dtale/iframe/column-menu-descriptions.json";
import { fetchJson } from "../fetcher";
import { DateFilter } from "./DateFilter";
import { NumericFilter } from "./NumericFilter";
Expand Down Expand Up @@ -105,7 +106,7 @@ class ColumnFilter extends React.Component {
render() {
if (this.state.loadingState) {
return (
<li>
<li className="hoverable">
<span className="toggler-action">
<i className="fa fa-filter" />
</span>
Expand All @@ -114,6 +115,7 @@ class ColumnFilter extends React.Component {
<components.LoadingIndicator getStyles={getStyles} cx={() => ""} />
</div>
</div>
<div className="hoverable__content col-menu-desc">{Descriptions.filter}</div>
</li>
);
}
Expand Down Expand Up @@ -146,13 +148,14 @@ class ColumnFilter extends React.Component {
missingToggle = this.renderMissingToggle(true);
} else {
markup = (
<li key={0}>
<li key={0} className="hoverable">
<span className="toggler-action">
<i className="fa fa-filter" />
</span>
<div className="m-auto">
<div className="column-filter m-2">{markup}</div>
</div>
<div className="hoverable__content col-menu-desc">{Descriptions.filter}</div>
</li>
);
missingToggle = this.renderMissingToggle(false);
Expand Down
6 changes: 5 additions & 1 deletion static/popups/Describe.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ class Describe extends React.Component {
}
const save = () => {
const visibility = _.reduce(this._grid.state.dtypes, (ret, d) => _.assignIn(ret, { [d.name]: d.visible }), {});
serverState.updateVisibility(this.props.dataId, visibility, () => window.opener.location.reload());
const callback = () => {
window.opener.location.reload();
window.close();
};
serverState.updateVisibility(this.props.dataId, visibility, callback);
};
const propagateState = state => this.setState(state);
return [
Expand Down

0 comments on commit 7193313

Please sign in to comment.