Py.Cafe

antonymilne/

vizro-cross-filter-heatmap-and-table

Cross-filter Interactive Heatmap and Table

DocsPricing
  • assets/
  • app.py
  • requirements.txt
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import vizro.actions as va
import vizro.models as vm
import vizro.plotly.express as px
from vizro import Vizro
from vizro.tables import dash_ag_grid
from vizro.actions._abstract_action import _AbstractAction
from typing import Literal, Any

tips = px.data.tips()

# This works already:
page = vm.Page(
    title="Cross-filter from graph",
    components=[
        vm.Graph(
            title="Click on a tile to use that tile's sex and day to filter table",
            figure=px.density_heatmap(tips, x="day", y="sex", category_orders={"day": ["Thur", "Fri", "Sat", "Sun"]}),
            actions=[
                va.set_control(control="day_filter", value="x"),
                va.set_control(control="sex_filter", value="y"),
            ],
        ),
        vm.AgGrid(id="tips_table", figure=dash_ag_grid(tips)),  # (1)!
    ],
    controls=[
        vm.Filter(id="day_filter", column="day", targets=["tips_table"]),
        vm.Filter(id="sex_filter", column="sex", targets=["tips_table"]),
    ],  # (2)!
)

# Doing the same thing from AgGrid does not, but it would be nice if we could enable it.
pivot = tips.pivot_table(
    index='sex',
    columns='day',
    aggfunc='size',
    fill_value=0
).reindex(columns=["Thur", "Fri", "Sat", "Sun"]).reset_index()

# Make new model just to define a different action trigger for now.
class AgGridAlternativeTrigger(vm.AgGrid):
    actions: Any  # Lazy hack

    @property
    def _action_triggers(self):
        return {"__default__": f"{self._inner_component_id}.cellClicked"}

# In reality this wouldn't be a different action, it would be built into set_control.
# We want to somehow detect that we're in pivot table mode here - maybe do by checking "rowSelection": {"enableClickSelection": False}
class set_control_from_pivot_table(_AbstractAction):
    value: Literal["row", "column"]
    control: str

    def function(self, _trigger):
        if self.value == "row":
            return _trigger["rowId"]
        elif self.value == "column":
            return _trigger["colId"]

    @property
    def outputs(self):
        return self.control

page_2 = vm.Page(
    title="Cross-filter from table",
    components=[
        AgGridAlternativeTrigger(
            id="pivot",
            title="Click on a tile to use that tile's sex and day to filter table",
            figure=dash_ag_grid(pivot, dashGridOptions={
                # Need to disable clickSelection or UX is confusing. Also good to have CSS to stop user
                # clicking in sex column - see assets folder.
                # This could be used to signal we want to use set_control with cellClicked rather
                # than rowSelection
                # Only set this when mode="pivot":
                "rowSelection": {
                    "enableClickSelection": False,
                },
                # Might as well always set this, regardless of mode. Use df.columns[0].name for sex.
                "getRowId": "params.data.sex" 
            },
        ),
            actions=[
                # value="column" and "row" are good, like "x"/"y" for graph.
                set_control_from_pivot_table(control="day_filter2", value="column"),
                set_control_from_pivot_table(control="sex_filter2", value="row"),
            ],
        ),
        vm.AgGrid(id="tips_table2", figure=dash_ag_grid(tips)),  # (1)!
    ],
    controls=[
        vm.Filter(id="day_filter2", column="day", targets=["tips_table2"]),
        vm.Filter(id="sex_filter2", column="sex", targets=["tips_table2"]),
    ],  # (2)!
)

dashboard = vm.Dashboard(pages=[page, page_2])
Vizro().build(dashboard).run()