mckinsey / vizro

Vizro is a toolkit for creating modular data visualization applications.
https://vizro.readthedocs.io/en/stable/
Apache License 2.0
2.67k stars 139 forks source link

Vizro: passing dynamic inputs to components based on user inputs: vm.AgGrid and vm.Card #589

Closed sumitmehta1025 closed 2 months ago

sumitmehta1025 commented 3 months ago

Question

Hi there! I am creating Vizro dashboard and one of the pages require “custom action”. The action requests values from users and displays card.

question#1: how do I pass a new data frame based on user inputs to vm.AgGrid. When I pass using the code below (in code section as question#1) the data frame does not update. I saw dash documentation it looks like there is a State("grid", "rowData") variable also. Is this why I do not see dataframe being refereshed?

question#2: how do I pass “href” card property when displaying https link? And ignore it when it is text? I am able to get the result now however it comes has https link and open in the dashboard itself instead of another window.

Code/Examples

question#1

@capture("action")
def custom_action_details_df(event_value, date_range):
    """Custom action."""
    details_repulled=df_prep("event_details").tail(10) # creating my data frame in a separate function
    print(details_repulled)
    return details_repulled #passing this to the vm.AgGrid hoping it would replace an existing data_frame parameter.

custom_action_detail_grid = vm.Action(
function=custom_action_details_df(),
inputs=[“details_event_select.value”, “details_datepicker.value”],
outputs=[“event_details.children”] ---is this is correct?
)

question#2

custom_action_select_creative = vm.Action(
function=custom_action_to_select_creative(),
inputs=[“etc.value”],
outputs=[“creative_content.children”]
)
vm.Dropdown(title=‘ver no.’, id=“etc”, options=[“”], multi=False,
actions=[
custom_action_select_creative
]),
vm.Card(id=‘creative_content’,
text=“”"
#### content.
“”“,
href=”"
),

Which package?

vizro

Code of Conduct

petar-qb commented 3 months ago

Hi @sumitmehta1025 👋

question#1:

I'm not quite sure from your question what exactly is the case you want to achieve. So, in terms to successfully find the answer together, could you share more detail with us?

What are the components on the page that should be the inputs of the custom action function (function arguments)? On what user interaction (like changing the dropdown value, or by clicking the value cell in the ag_grid) do you want to trigger the action? Also, what is the desired output of the action (adjusted ag_grid data_frame, or a card text or something else)?

Also, more info on what are details_event_select, details_event_select, event_details, etc, creative_content components and where you want to assign custom_action_detail_grid is welcome. All this info is going to help me to understand what the actual use case it is and how to find the right approach to solve it 😄


However, I've prepared an example for you that might give you an idea on how to find the solution 🙏.

The example probably contains the solution for the question#2. This solution is based on making custom Vizro components.

P.S. Reach out the # TODO docs: links I put in the code section for more information about certain Vizro features. 😃.

"""Example app to show all features of Vizro."""

import vizro.models as vm
import vizro.plotly.express as px
from vizro import Vizro
from vizro.tables import dash_ag_grid
from vizro.managers import data_manager
from typing import Literal

# TODO docs: More about dynamic data loading in Vizro you can find here -> https://vizro.readthedocs.io/en/stable/pages/user-guides/data/
# This 'event_value' could also be added as a vm.Filter in the page.controls.
def load_ag_grid_data(event_value=None):
    """Load data for AgGrid."""
    df = px.data.iris()
    if event_value:
        df = df[df['species'] == event_value]
    return df.tail(10)

data_manager["ag_grid_data"] = load_ag_grid_data

# TODO docs: More about components customising you can find here -> https://vizro.readthedocs.io/en/stable/pages/user-guides/custom-components/
class MyHrefCard(vm.Card):
    type: Literal["my_href_card"] = "my_href_card"

    def build(self):
        card_build_obj = super().build()
        # changing the underlying dbc.NavLink "target" property, responsible for defining where the link will open.
        if self.href:
            # TODO docs: More about link target attribute you can find here -> https://www.w3schools.com/tags/att_a_target.asp
            card_build_obj.children.target = "blank"
        return card_build_obj

# Enabling MyHrefCard to live in the vm.Page.components
vm.Page.add_type("components", MyHrefCard)

page = vm.Page(
    title="Page 1",
    layout=vm.Layout(
        grid=[[0, 0, 0, 1, 2, 3], *[[4, 4, 4, 4, 4, 4]] * 3],
        row_min_height="175px",
    ),
    components=[
        vm.Card(
            text="""
        ### What is Vizro?

        Vizro is a toolkit for creating modular data visualization applications.
        """
        ),
        MyHrefCard(
            text="""
                ### Github

                Checkout Vizro's github page.
            """,
            href="https://github.com/mckinsey/vizro",
        ),
        MyHrefCard(
            text="""
                ### Docs

                Visit the documentation for codes examples, tutorials and API reference.
            """,
            href="https://vizro.readthedocs.io/",
        ),
        vm.Card(
            text="""
                ### Nav Link

                Click this for page 2.
            """,
            href="/page-2",
        ),
        vm.AgGrid(id="scatter_grid", figure=dash_ag_grid(data_frame="ag_grid_data")),
    ],
    controls=[
        # This kind of filtering could also be implemented by adding a vm.Filter in the following way.
        # However, here is an example that utilises "parametrise dynamic data" feature.
        # TODO docs: More about this feature you can find here -> https://vizro.readthedocs.io/en/stable/pages/user-guides/data/#parametrize-data-loading
        # vm.Filter(
        #     column="species",
        #     targets=["scatter_grid"],
        #     selector=vm.RadioItems(options=["setosa", "versicolor", "virginica"], value="setosa")),
        vm.Parameter(
            targets=["scatter_grid.data_frame.event_value"],
            selector=vm.RadioItems(options=["setosa", "versicolor", "virginica"], value="setosa"),
        ),
        # TODO docs: More about Vizro filters you can find here -> https://vizro.readthedocs.io/en/stable/pages/user-guides/filters/
        # TODO docs: More about Vizro parameters you can find here -> https://vizro.readthedocs.io/en/stable/pages/user-guides/parameters/
        # You can add a date_range filter if you need. Here is an example: (data_frame is filtered by "date" column)
        # vm.Filter(column="date", targets=["scatter_grid"], selector=vm.DatePicker())
    ],
)

page_2 = vm.Page(
    title="Page 2", components=[vm.Graph(id="hist_chart", figure=px.histogram(px.data.iris(), x="sepal_width", color="species"))]
)

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

if __name__ == "__main__":
    Vizro().build(dashboard).run()
sumitmehta1025 commented 3 months ago

Hi @petar-qb! Thank YOU so much for your reply! This is super helpful!!

Your code helped me answer my question#1 and I plan to code that today and will let you know!

For question #2, my requirement is to pull dynamic links, that change every day, and provide that to "card" to display in a different page. I have modified the code your provided to show case my requirement - please look for in the attached file vizro_example_github_href_management.txt

Search changes using these in comments: 1) added on 7/19 for clarification 2) commented out on 7/19 for clarification

The issue here is the link appear as is on the card and open in the same browser page. Hence my initial question: how do I pass “href” card property when displaying https link? So it displays on a different page.

Thanks again for your time and help! I owe you a lunch if you plan to visit Atlanta 😄

sumitmehta1025 commented 2 months ago

Hi @petar-qb! Did you get a chance to review this and does this question makes sense? Please let me know.

Thanks!

petar-qb commented 2 months ago

Hi @sumitmehta1025! 👋

I noticed two behaviours of your card with a link:

  1. By clicking on the text link in the card, it opens the correct link (the one that's selected in the dropdown) in the same browser tab.
  2. By clicking on the card body (below the text link), it opens incorrect link (the static one hardcoded in the code) in the new browser tab.

To enable that the correct link is opened in a new browser tab we can:

  1. Add the id to the MyHrefCard component to be able to target it from the custom action or
  2. Replace the card component with the html.A component.

Here is the code example that solves the problem in both ways:

"""Example app to show all features of Vizro."""
from dash import html
from typing import Literal

import vizro.models as vm
import vizro.plotly.express as px
from vizro import Vizro
from vizro.models.types import capture

# GLOBALS --------------------------------------------------------------------->
DROPDOWN_LINK_OPTIONS = [
    {
        "label": "Google",
        "value": "https://google.com",
    },
    {
        "label": "Vizro GitHub",
        "value": "https://github.com/mckinsey/vizro",
    },
    {
        "label": "Vizro Docs",
        "value": "https://vizro.readthedocs.io/",
    },
]

# CUSTOM COMPONENTS ---------------------------------------------------------->

class MyTextLink(vm.VizroBaseModel):
    type: Literal["my_text_link"] = "my_text_link"

    text: str
    href: str

    def build(self):
        return html.A(
            id=self.id,
            children=self.text,
            href=self.href,
            # setting target to _blank to open the link in a new tab
            target="_blank"
        )

class MyHrefCard(vm.Card):
    type: Literal["my_href_card"] = "my_href_card"

    def build(self):
        card_build_obj = super().build()
        if self.href:
            # Setting id to the nav link to be able to reference it in the custom action
            card_build_obj.children.id = f"{self.id}_nav_link"
            # Setting target to _blank to open the link in a new tab
            card_build_obj.children.target = "_blank"
        return card_build_obj

vm.Page.add_type("components", MyTextLink)
vm.Page.add_type("components", MyHrefCard)
vm.Page.add_type("components", vm.Dropdown)

# CUSTOM ACTION ------------------------------------------------------------->
@capture("action")
def set_text_and_href_action(dropdown_selected_label):
    """Custom action."""

    text = ""
    href = dropdown_selected_label

    for option in DROPDOWN_LINK_OPTIONS:
        if option["value"] == dropdown_selected_label:
            text = option["label"]
            break

    return text, href, text, href

# PAGE ----------------------------------------------------------------------->
page = vm.Page(
    title="Page Title",
    layout=vm.Layout(grid=[
       [0, 1, 2]
    ]),
    components=[
        vm.Dropdown(
            id="dropdown_link_selection",
            title='Select a link',
            options=DROPDOWN_LINK_OPTIONS,
            multi=False,
            value='https://google.com',
            actions=[
                vm.Action(
                    function=set_text_and_href_action(),
                    inputs=[
                        "dropdown_link_selection.value"
                    ],
                    outputs=[
                        "href_link_id.children",
                        "href_link_id_nav_link.href",
                        "my_text_link_id.children",
                        "my_text_link_id.href",
                    ]
                ),
            ]),
        MyHrefCard(
            id='href_link_id',
            text="Google",
            href="https://google.com",
        ),
        MyTextLink(
            id="my_text_link_id",
            text="Google",
            href="https://google.com",
        ),
    ],
)

dashboard = vm.Dashboard(pages=[page])

if __name__ == "__main__":
    Vizro().build(dashboard).run()
petar-qb commented 2 months ago

@sumitmehta1025 I also noticed your question in the comments section if this youtube video. If you're still looking for the answer (and I'm sure there are other people interested in the same/similar things too), it would be nice if you could create a github issue for it, so we can answer it 😄

sumitmehta1025 commented 2 months ago

Thank you @petar-qb! This was a great help! I struggled at the beginning mainly because in MyHrefCard, I had href="" (empty string) and the function just does not work, because of the "if self.href" condition in "class MyHrefCard(vm.Card):". After I fixed that worked like a charm. Thanks again. I am going to close this request.

sumitmehta1025 commented 2 months ago

@sumitmehta1025 I also noticed your question in the comments section if this youtube video. If you're still looking for the answer (and I'm sure there are other people interested in the same/similar things too), it would be nice if you could create a github issue for it, so we can answer it 😄

Yes, I will create a new one. I also will add a topic after my learnings on my Vizro dashboard AWS prod deployment (took one day to figure out dependencies and silly mistakes 😄).