diff --git a/vizro-ai/changelog.d/20241113_200250_nadija_ratkusic_graca_css_fixes.md b/vizro-ai/changelog.d/20241113_200250_nadija_ratkusic_graca_css_fixes.md new file mode 100644 index 000000000..f1f65e73c --- /dev/null +++ b/vizro-ai/changelog.d/20241113_200250_nadija_ratkusic_graca_css_fixes.md @@ -0,0 +1,48 @@ + + + + + + + + + diff --git a/vizro-ai/examples/dashboard_ui/app.py b/vizro-ai/examples/dashboard_ui/app.py index 48dc43d21..aa4f27a2b 100644 --- a/vizro-ai/examples/dashboard_ui/app.py +++ b/vizro-ai/examples/dashboard_ui/app.py @@ -15,6 +15,7 @@ CodeClipboard, CustomDashboard, DropdownMenu, + FlexContainer, HeaderComponent, Icon, Modal, @@ -47,14 +48,10 @@ vm.Container.add_type("components", ToggleSwitch) vm.Container.add_type("components", UserPromptTextArea) vm.Container.add_type("components", DropdownMenu) -vm.Container.add_type("components", HeaderComponent) +vm.Page.add_type("components", HeaderComponent) +vm.Page.add_type("components", FlexContainer) -vm.Page.add_type("components", UserUpload) -vm.Page.add_type("components", MyDropdown) -vm.Page.add_type("components", OffCanvas) -vm.Page.add_type("components", CodeClipboard) -vm.Page.add_type("components", Icon) -vm.Page.add_type("components", Modal) +FlexContainer.add_type("components", DropdownMenu) SUPPORTED_MODELS = { @@ -79,120 +76,102 @@ title="Vizro-AI - create interactive charts with Plotly and Vizro", layout=vm.Layout( grid=[ - [4, 4, 4, 4], - [2, 2, 1, 1], - [2, 2, 1, 1], - [3, 3, 1, 1], - [3, 3, 1, 1], - [3, 3, 1, 1], - *[[0, 0, 1, 1]] * 8, + *[[0, 0, 0, 0]] * 1, + *[[1, 1, 2, 2]] * 11, ] ), components=[ - vm.Container( - title="", - components=[CodeClipboard(id="plot"), ToggleSwitch(id="toggle-id")], - layout=vm.Layout( - grid=[*[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] * 7, [-1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1]], - row_gap="12px", - col_gap="12px", - ), - ), - vm.Container( - title="", - layout=vm.Layout( - grid=[ - *[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] * 10, - [-1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1], - ] - ), - components=[ - vm.Graph(id="graph-id", figure=px.scatter(pd.DataFrame())), - DropdownMenu(id="dropdown-menu"), - ], - ), - vm.Container( - id="upload-data-container", - title="Turn your data into visuals — just upload, describe, and see your chart in action", - layout=vm.Layout( - grid=[ - [1], - [0], - ], - row_gap="0px", - # row_min_height="40px", - ), + HeaderComponent(), + FlexContainer( components=[ - UserUpload( - id="data-upload-id", - actions=[ - vm.Action( - function=data_upload_action(), - inputs=["data-upload-id.contents", "data-upload-id.filename"], - outputs=["data-store-id.data", "modal-table-icon.style", "modal-table-tooltip.style"], - ), - vm.Action( - function=display_filename(), - inputs=["data-store-id.data"], - outputs=["upload-message-id.children"], - ), - vm.Action( - function=update_table(), - inputs=["data-store-id.data"], - outputs=["modal-table.children", "modal-title.children"], + vm.Container( + id="upload-data-container", + title="Turn your data into visuals — just upload, describe, and see your chart in action", + layout=vm.Layout( + grid=[[0], [1]], + row_gap="0px", + ), + components=[ + vm.Figure(id="show-data-component", figure=custom_table(data_frame=pd.DataFrame())), + UserUpload( + id="data-upload-component", + actions=[ + vm.Action( + function=data_upload_action(), + inputs=["data-upload-component.contents", "data-upload-component.filename"], + outputs=["data-store.data", "modal-table-icon.style", "modal-table-tooltip.style"], + ), + vm.Action( + function=display_filename(), + inputs=["data-store.data"], + outputs=["upload-message.children"], + ), + vm.Action( + function=update_table(), + inputs=["data-store.data"], + outputs=["modal-table.children", "modal-title.children"], + ), + ], ), ], ), - vm.Figure(id="show-data-component", figure=custom_table(data_frame=pd.DataFrame())), - ], - ), - vm.Container( - title="", - layout=vm.Layout( - grid=[ - [3, 3, 3, 3, 3, 3, 3, 3, 3], - [3, 3, 3, 3, 3, 3, 3, 3, 3], - [3, 3, 3, 3, 3, 3, 3, 3, 3], - [2, -1, -1, -1, -1, 1, 1, 0, 0], - ], - row_gap="10px", - col_gap="4px", - ), - components=[ - vm.Button( - id="trigger-button-id", - text="Run Vizro-AI", - actions=[ - vm.Action( - function=run_vizro_ai(), - inputs=[ - "text-area-id.value", - "trigger-button-id.n_clicks", - "data-store-id.data", - "model-dropdown-id.value", - "settings-api-key.value", - "settings-api-base.value", - "settings-dropdown.value", + vm.Container( + title="", + layout=vm.Layout( + grid=[ + *[[0, 0, 0, 0, 0, 0, 0, 0, 0]] * 3, + [3, -1, -1, -1, -1, 1, 1, 2, 2], + ], + row_gap="12px", + col_gap="4px", + ), + components=[ + UserPromptTextArea(id="text-area"), + MyDropdown( + options=SUPPORTED_MODELS["OpenAI"], value="gpt-4o-mini", multi=False, id="model-dropdown" + ), + vm.Button( + id="trigger-button", + text="Run Vizro-AI", + actions=[ + vm.Action( + function=run_vizro_ai(), + inputs=[ + "text-area.value", + "trigger-button.n_clicks", + "data-store.data", + "model-dropdown.value", + "settings-api-key.value", + "settings-api-base.value", + "settings-dropdown.value", + ], + outputs=["plot-code-markdown.children", "graph.figure", "code-output-store.data"], + ), ], - outputs=["plot-code-markdown.children", "graph-id.figure", "code-output-store-id.data"], + ), + OffCanvas( + id="settings", + options=["OpenAI", "Anthropic", "Mistral", "xAI"], + value="OpenAI", ), ], ), - MyDropdown( - options=SUPPORTED_MODELS["OpenAI"], value="gpt-4o-mini", multi=False, id="model-dropdown-id" - ), - OffCanvas( - id="settings", - options=["OpenAI", "Anthropic", "Mistral", "xAI"], - value="OpenAI", + vm.Container( + title="", + components=[CodeClipboard(id="plot"), ToggleSwitch()], + layout=vm.Layout( + grid=[*[[0]] * 7, [1]], + row_gap="12px", + col_gap="12px", + ), ), - UserPromptTextArea(id="text-area-id"), - # Modal(id="modal"), - ], + ] ), - vm.Container( - title="", - components=[HeaderComponent()], + FlexContainer( + components=[ + vm.Graph(id="graph", figure=px.scatter(pd.DataFrame())), + DropdownMenu(id="dropdown-menu"), + ], ), ], ) @@ -206,7 +185,7 @@ @callback( Output("settings", "is_open"), - Input("open-settings-id", "n_clicks"), + Input("open-settings", "n_clicks"), [State("settings", "is_open")], ) def open_settings(n_clicks, is_open): @@ -235,7 +214,7 @@ def show_api_base(value): @callback( Output("plot-code-markdown", "children"), Input("toggle-switch", "value"), - [State("code-output-store-id", "data")], + [State("code-output-store", "data")], ) def toggle_code(value, data): """Callback for switching between vizro and plotly code.""" @@ -253,7 +232,7 @@ def toggle_code(value, data): Output("data-modal", "is_open"), Input("modal-table-icon", "n_clicks"), State("data-modal", "is_open"), - State("data-store-id", "data"), + State("data-store", "data"), ) def open_modal(n_clicks, is_open, data): """Callback for opening modal component.""" @@ -267,7 +246,7 @@ def open_modal(n_clicks, is_open, data): @callback( Output("download-file", "data"), [Input("dropdown-menu-html", "n_clicks"), Input("dropdown-menu-json", "n_clicks")], - State("code-output-store-id", "data"), + State("code-output-store", "data"), prevent_initial_call=True, ) def download_fig(n_clicks_html, n_clicks_json, data): @@ -290,9 +269,7 @@ def download_fig(n_clicks_html, n_clicks_json, data): return dcc.send_string(plotly_json, "plotly_fig.json") -@callback( - [Output("model-dropdown-id", "options"), Output("model-dropdown-id", "value")], Input("settings-dropdown", "value") -) +@callback([Output("model-dropdown", "options"), Output("model-dropdown", "value")], Input("settings-dropdown", "value")) def update_model_dropdown(value): """Callback for updating available models.""" available_models = SUPPORTED_MODELS[value] @@ -302,21 +279,13 @@ def update_model_dropdown(value): app = Vizro().build(dashboard) app.dash.layout.children.append( - html.Div( - [ - html.Div( - [ - "Made using ", - html.Img(src=get_asset_url("logo.svg"), id="banner", alt="Vizro logo"), - dbc.NavLink("vizro", href="https://github.com/mckinsey/vizro", target="_blank"), - ], - style={"display": "flex", "flexDirection": "row"}, - ), - ], + dbc.NavLink( + ["Made with ", html.Img(src=get_asset_url("logo.svg"), id="banner", alt="Vizro logo"), "vizro"], + href="https://github.com/mckinsey/vizro", + target="_blank", className="anchor-container", ) ) - server = app.dash.server if __name__ == "__main__": app.run() diff --git a/vizro-ai/examples/dashboard_ui/assets/custom_css.css b/vizro-ai/examples/dashboard_ui/assets/custom_css.css index dea230d73..0eb9a42a0 100644 --- a/vizro-ai/examples/dashboard_ui/assets/custom_css.css +++ b/vizro-ai/examples/dashboard_ui/assets/custom_css.css @@ -22,11 +22,11 @@ outline-width: 0; } -#text-area-id { +#text-area { background-color: inherit; border: 1px solid var(--border-subtle-alpha-01); color: var(--text-primary); - min-height: 13.5vh; + min-height: 14vh; padding: 8px; width: 100%; } @@ -46,7 +46,7 @@ background: var(--surfaces-bg-card); font-family: monospace; height: 100%; - max-height: 470px; + max-height: 42vh; overflow: auto; padding: 1rem; position: relative; @@ -66,7 +66,7 @@ font-size: 12px; } -#model-dropdown-id .Select-menu-outer { +#model-dropdown .Select-menu-outer { font-size: 12px; /* top: 0; */ @@ -74,12 +74,12 @@ /* transform: translateY(3px) translateY(-100%); */ } -#model-dropdow-idn .dash-dropdown { +#model-dropdow .dash-dropdown { background-color: inherit; font-size: 12px; } -#trigger-button-id { +#trigger-button { width: 100%; } @@ -87,7 +87,7 @@ background-color: inherit; } -#model-dropdown-id .Select-clear { +#model-dropdown .Select-clear { display: none; } @@ -95,7 +95,7 @@ width: 50%; } -.card:has(#upload-message-id) { +.card:has(#upload-message) { background-color: inherit; box-shadow: none; font-size: 12px; @@ -121,12 +121,11 @@ width: 20px; } -#data-upload-id { +#data-upload-component { border: 1px dashed var(--border-subtle-alpha-01); - border-radius: 5px; color: var(--text-primary); - height: 46px; - line-height: 46px; + height: 5vh; + line-height: 5vh; text-align: center; } @@ -147,18 +146,6 @@ justify-content: center; } -.anchor-container { - background: #060a17; - bottom: 0; - display: flex; - font-weight: 600; - gap: 2rem; - padding: 4px; - place-content: baseline center; - position: fixed; - width: 100%; -} - .toggle-div { display: flex; flex-direction: row; @@ -191,11 +178,13 @@ .btn-primary:enabled:has(#dropdown-menu-icon) { background-color: inherit; color: var(--text-active); + font-size: 1.5rem; } .btn-primary:disabled:has(#dropdown-menu-icon) { background-color: inherit; color: var(--text-active); + font-size: 1.5rem; } .download-button { @@ -267,14 +256,17 @@ padding-top: 4px; } -#dropdown-menu-id { +#dropdown-menu-div { align-items: center; + align-self: flex-end; border: 0.5px solid gray; border-radius: 8px; display: flex; flex-direction: row; justify-content: center; - width: 100%; + min-width: 130px; + padding-left: 4px; + width: 130px; } #custom-header-div { @@ -303,7 +295,7 @@ color: var(--text-secondary); } -#open-settings-id:hover { +#open-settings:hover { cursor: pointer; } @@ -316,3 +308,59 @@ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); transform: translateY(-2px); } + +.anchor-container { + align-items: center; + background: var(--text-primary); + border-top-left-radius: 8px; + bottom: 0; + color: var(--text-contrast-primary); + display: flex; + font-size: 0.8rem; + font-weight: 500; + height: 24px; + padding: 0 12px; + position: fixed; + right: 0; +} + +.anchor-container:focus, +.anchor-container:hover { + background: var(--text-secondary); + color: var(--text-contrast-primary); +} + +img#banner { + height: 16px; +} + +.dropdown-menu-outer-div { + align-items: end; + display: flex; + justify-content: end; + width: 100%; +} + +.flex-container { + display: flex; + flex-flow: column wrap; + gap: 12px; + justify-content: flex-start; + overflow: none; +} + +.flex-container h4 { + color: var(--text-secondary); + margin: 0; + padding-top: 12px; + text-align: center; +} + +.flex-container .code-clipboard-container { + height: 500px; + width: 100%; +} + +.flex-container #graph { + height: 712px; +} diff --git a/vizro-ai/examples/dashboard_ui/components.py b/vizro-ai/examples/dashboard_ui/components.py index 1e8397b3f..793f27dcc 100644 --- a/vizro-ai/examples/dashboard_ui/components.py +++ b/vizro-ai/examples/dashboard_ui/components.py @@ -89,12 +89,17 @@ def build(self): markdown_code = "\n".join(["```python", code, "```"]) - return html.Div( - [ - dcc.Clipboard(target_id=f"{self.id}-code-markdown", className="code-clipboard"), - dcc.Markdown(markdown_code, id=f"{self.id}-code-markdown"), - ], - className="code-clipboard-container", + return dcc.Loading( + html.Div( + [ + dcc.Clipboard(target_id=f"{self.id}-code-markdown", className="code-clipboard"), + dcc.Markdown(markdown_code, id=f"{self.id}-code-markdown"), + ], + className="code-clipboard-container", + ), + color="grey", + parent_className="loading-container", + overlay_style={"visibility": "visible", "opacity": 0.3}, ) @@ -293,8 +298,8 @@ class CustomDashboard(vm.Dashboard): def build(self): """Returns custom dashboard.""" dashboard_build_obj = super().build() - dashboard_build_obj.children.append(dcc.Store(id="data-store-id", storage_type="session")) - dashboard_build_obj.children.append(dcc.Store(id="code-output-store-id", storage_type="session")) + dashboard_build_obj.children.append(dcc.Store(id="data-store", storage_type="session")) + dashboard_build_obj.children.append(dcc.Store(id="code-output-store", storage_type="session")) return dashboard_build_obj @@ -340,7 +345,7 @@ def custom_table(data_frame): id="modal-table-tooltip", ), html.P( - id="upload-message-id", children=["Upload your data file (csv or excel)"], style={"paddingTop": "10px"} + id="upload-message", children=["Upload your data file (csv or excel)"], style={"paddingTop": "10px"} ), dbc.Modal( id="data-modal", @@ -392,12 +397,11 @@ def build(self): dbc.Tooltip( "Download this plot to your device as a plotly JSON or interactive HTML file " "for easy sharing or future use.", - target="dropdown-menu-icon", + target="dropdown-menu-div", ), ], - id="dropdown-menu-id", + id="dropdown-menu-div", ) - return download_div @@ -415,10 +419,23 @@ def build(self): ) icon = html.Div( children=[ - html.Span("settings", className="material-symbols-outlined", id="open-settings-id"), - dbc.Tooltip("Settings", target="open-settings-id"), + html.Span("settings", className="material-symbols-outlined", id="open-settings"), + dbc.Tooltip("Settings", target="open-settings"), ], className="settings-div", ) return html.Div(children=[header, icon], className="custom_header") + + +class FlexContainer(vm.Container): + """Custom flex `Container`.""" + + type: Literal["flex_container"] = "flex_container" + title: str = None # Title exists in vm.Container but we don't want to use it here. + + def build(self): + """Returns a flex container.""" + return html.Div( + id=self.id, children=[component.build() for component in self.components], className="flex-container" + )