Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 11 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,26 @@ python usage.py

The following parameters can be modified:
- `id` *(string; optional)*: The ID used to identify this component in Dash callbacks
- `aggregatorName` *(string; optional)*: Which aggregator is currently selected. E.g. Count, Sum, Average, etc.
- `cols` *(list; optional)*: Which columns are currently in the column area
- `colOrder` *(string; optional)*: The order in which column data is provided to the renderer, must be one
of "key_a_to_z", "value_a_to_z", "value_z_to_a", ordering by value
orders by column total
- `data` *(list; optional)*: The input data
- `hiddenAttributes` *(list; optional)*: contains attribute names to omit from the UI
- `hiddenFromAggregators` *(list; optional)*: contains attribute names to omit from the aggregator arguments dropdowns
- `hiddenFromDragDrop` *(list; optional)*: contains attribute names to omit from the drag'n'drop portion of the UI
- `menuLimit` *(number; optional)*: maximum number of values to list in the double-click menu
- `unusedOrientationCutoff` *(number; optional)*: If the attributes' names' combined length in characters exceeds this
value then the unused attributes area will be shown vertically to the
left of the UI instead of horizontally above it. 0 therefore means
'always vertical', and Infinity means 'always horizontal'.

The following props can be used as an input to callbacks, but can't be modified:
- `cols` *(list; optional)*: Which columns are currently in the column area
- `colOrder` *(string; optional)*: The order in which column data is provided to the renderer, must be one
of "key_a_to_z", "value_a_to_z", "value_z_to_a", ordering by value
orders by column total
- `rendererName` *(string; optional)*: Which renderer is currently selected. E.g. Table, Line Chart, Scatter
- `rows` *(list; optional)*: Which rows is currently inside the row area.
- `rowOrder` *(string; optional)*: The order in which row data is provided to the renderer, must be one
of "key_a_to_z", "value_a_to_z", "value_z_to_a", ordering by value
orders by row total
- `aggregatorName` *(string; optional)*: Which aggregator is currently selected. E.g. Count, Sum, Average, etc.
- `unusedOrientationCutoff` *(number; optional)*: If the attributes' names' combined length in characters exceeds this
value then the unused attributes area will be shown vertically to the
left of the UI instead of horizontally above it. 0 therefore means
'always vertical', and Infinity means 'always horizontal'.
- `vals` *(list; optional)*: Attribute names used as arguments to aggregator (gets passed to aggregator generating function)
- `rendererName` *(string; optional)*: Which renderer is currently selected. E.g. Table, Line Chart, Scatter
- `valueFilter` *(dictionnary; optional)*: Object whose keys are attribute names and values are objects of attribute value-boolean pairs which denote records to include or exclude from computation and rendering; used to prepopulate the filter menus that appear on double-click

Default Values:
Expand All @@ -61,6 +59,7 @@ Default Values:
* `hiddenAttributes`: []
* `hiddenFromAggregators`: []
* `hiddenFromDragDrop`: []
* `valueFilter`: []


## FAQ
Expand Down
2 changes: 1 addition & 1 deletion dash_pivottable/PivotTable.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class PivotTable(Component):
orders by row total
- aggregatorName (string; optional): Which aggregator is currently selected. E.g. Count, Sum, Average, etc.
- vals (list; optional): Vals for the aggregator.
- valueFilter (dict; optional): Value filter for each attibute name.
- valueFilter (dict; optional): Value filter for each attribute name.
- rendererName (string; optional): Which renderer is currently selected. E.g. Table, Line Chart, Scatter
Chart, etc."""
@_explicitize_args
Expand Down
12 changes: 10 additions & 2 deletions dash_pivottable/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,22 @@
"name": "array"
},
"required": false,
"description": "Vals for the aggregator."
"description": "Vals for the aggregator.",
"defaultValue": {
"value": "[]",
"computed": false
}
},
"valueFilter": {
"type": {
"name": "object"
},
"required": false,
"description": "Value filter for each attibute name."
"description": "Value filter for each attribute name.",
"defaultValue": {
"value": "[]",
"computed": false
}
},
"rendererName": {
"type": {
Expand Down
23 changes: 16 additions & 7 deletions src/lib/components/PivotTable.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ const PlotlyRenderers = createPlotlyRenderers(Plot);
export default class PivotTable extends Component {
constructor(props) {
super(props);
this.state = props;
this.handleChange = this.handleChange.bind(this);
}

Expand All @@ -46,7 +45,9 @@ export default class PivotTable extends Component {
rows,
rowOrder,
aggregatorName,
rendererName
rendererName,
valueFilter,
vals,
} = state;

if (typeof this.props.setProps === 'function') {
Expand All @@ -56,7 +57,9 @@ export default class PivotTable extends Component {
rows,
rowOrder,
aggregatorName,
rendererName
rendererName,
valueFilter,
vals,
});
}

Expand All @@ -70,7 +73,9 @@ export default class PivotTable extends Component {
hiddenFromAggregators,
hiddenFromDragDrop,
menuLimit,
unusedOrientationCutoff
unusedOrientationCutoff,
valueFilter,
vals,
} = this.props;

return (
Expand All @@ -83,7 +88,9 @@ export default class PivotTable extends Component {
hiddenFromDragDrop={hiddenFromDragDrop}
menuLimit={menuLimit}
unusedOrientationCutoff={unusedOrientationCutoff}
{...this.state}
valueFilter={valueFilter}
vals={vals}
{...this.props}
/>
);
}
Expand All @@ -94,7 +101,9 @@ PivotTable.defaultProps = {
unusedOrientationCutoff: 85,
hiddenAttributes: [],
hiddenFromAggregators: [],
hiddenFromDragDrop: []
hiddenFromDragDrop: [],
valueFilter: [],
vals: [],
};

PivotTable.propTypes = {
Expand Down Expand Up @@ -181,7 +190,7 @@ PivotTable.propTypes = {
vals: PropTypes.array,

/**
* Value filter for each attibute name.
* Value filter for each attribute name.
*/
valueFilter: PropTypes.object,

Expand Down
3 changes: 1 addition & 2 deletions tests/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@

dash[dev,testing]
flake8
pylint
astroid<=2.5,>=2.4.0
pylint
10 changes: 6 additions & 4 deletions tests/test_callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@ def test_callbacks(dash_duo):
'col_order': 'key_a_to_z',
'aggregator': 'Average',
'renderer': 'Grouped Column Chart',
'data_length': 'Data length: 245',
'value_filter': "{'Day of Week': {'Thursday': False}}",
}

app = import_app('usage')
dash_duo.start_server(app)

for prop in expected:
for prop, expected_value in expected.items():
try:
dash_duo.wait_for_text_to_equal(
f'#{prop}', expected[prop], timeout=4
f'#{prop}', expected_value, timeout=4
)
except TimeoutException:
raise Exception(f"Attribute '{prop}' failed to render correctly.")
except TimeoutException as exc:
raise Exception(f"Attribute '{prop}' failed to render correctly.") from exc
79 changes: 75 additions & 4 deletions usage.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import dash
from dash.dependencies import Input, Output
from dash.dependencies import Input, Output, State
import dash_html_components as html
import dash_pivottable

Expand All @@ -8,6 +8,9 @@
app = dash.Dash(__name__)
app.title = 'My Dash example'

VALUE_FILTER_DEFAULT = {'Day of Week': {'Thursday': False}}
VALUE_FILTER_ALT = {'Day of Week': {'Friday': False}}

app.layout = html.Div([
dash_pivottable.PivotTable(
id='table',
Expand All @@ -19,8 +22,13 @@
rendererName="Grouped Column Chart",
aggregatorName="Average",
vals=["Total Bill"],
valueFilter={'Day of Week': {'Thursday': False}}
valueFilter=VALUE_FILTER_DEFAULT,
),
html.Button('Toggle data', id='toggle-data-button', n_clicks=0),
html.Button('Toggle filter', id='toggle-filter-button', n_clicks=0),
html.Button('Toggle rows and cols', id='toggle-rows-cols-button', n_clicks=0),
html.Button('Toggle order', id='toggle-order-button', n_clicks=0),
html.Button('Toggle analysis', id='toggle-analysis-button', n_clicks=0),
html.Div(
id='output'
)
Expand All @@ -33,17 +41,80 @@
Input('table', 'rowOrder'),
Input('table', 'colOrder'),
Input('table', 'aggregatorName'),
Input('table', 'rendererName')])
def display_props(cols, rows, row_order, col_order, aggregator, renderer):
Input('table', 'rendererName'),
Input('table', 'data'),
Input('table', 'valueFilter')])
def display_props(cols, rows, row_order, col_order, aggregator, renderer, data_input, value_filter):
return [
html.P(str(cols), id='columns'),
html.P(str(rows), id='rows'),
html.P(str(row_order), id='row_order'),
html.P(str(col_order), id='col_order'),
html.P(str(aggregator), id='aggregator'),
html.P(str(renderer), id='renderer'),
html.P(str(value_filter), id='value_filter'),
html.P(f'Data length: {len(data_input)}', id='data_length'),
]


@app.callback(Output('table', 'data'),
[Input('toggle-data-button', 'n_clicks')],
prevent_initial_call=True)
def toggle_data(n_clicks):
if n_clicks % 2 == 0:
return data
return data[:len(data)//2]


@app.callback(Output('table', 'valueFilter'),
[Input('toggle-filter-button', 'n_clicks')],
prevent_initial_call=True)
def toggle_filter(n_clicks):
if n_clicks % 2 == 0:
return VALUE_FILTER_DEFAULT
return VALUE_FILTER_ALT


@app.callback([Output('table', 'rows'),
Output('table', 'cols')],
[Input('toggle-rows-cols-button', 'n_clicks')],
[State('table', 'rows'),
State('table', 'cols')],
prevent_initial_call=True)
def toggle_rows_cols(_n_clicks, rows, cols):
return cols, rows


@app.callback([Output('table', 'rowOrder'),
Output('table', 'colOrder')],
[Input('toggle-order-button', 'n_clicks')],
[State('table', 'rowOrder'),
State('table', 'colOrder')],
prevent_initial_call=True)
def toggle_order(_n_clicks, row_order, col_order):

def switch_order(order):
if order == 'key_a_to_z':
return 'value_z_to_a'
return 'key_a_to_z'

return switch_order(row_order), switch_order(col_order)


@app.callback([Output('table', 'rendererName'),
Output('table', 'aggregatorName'),
Output('table', 'vals')],
[Input('toggle-analysis-button', 'n_clicks')],
[State('table', 'rendererName'),
State('table', 'aggregatorName'),
State('table', 'vals')],
prevent_initial_call=True)
def toggle_analysis(_n_clicks, renderer_name, aggregator_name, vals):
renderer_name = 'Grouped Column Chart' if renderer_name == 'Line Chart' else 'Line Chart'
aggregator_name = 'Average' if aggregator_name == 'Sum' else 'Sum'
vals = ['Total Bill'] if vals == ['Party Size'] else ['Party Size']
return renderer_name, aggregator_name, vals


if __name__ == '__main__':
app.run_server(debug=True)