Skip to content

Commit

Permalink
[Demo] Add pycafe link and plotly code to ViViVo (#824)
Browse files Browse the repository at this point in the history
Co-authored-by: Maximilian Schulz <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Lingyi Zhang <[email protected]>
Co-authored-by: Maximilian Schulz <[email protected]>
  • Loading branch information
5 people authored Oct 30, 2024
1 parent 6d06f50 commit 293d79b
Show file tree
Hide file tree
Showing 56 changed files with 755 additions and 1,009 deletions.
1 change: 0 additions & 1 deletion vizro-core/examples/scratch_dev/README.md

This file was deleted.

1 change: 0 additions & 1 deletion vizro-core/examples/scratch_dev/README2.md

This file was deleted.

22 changes: 18 additions & 4 deletions vizro-core/examples/visual-vocabulary/assets/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,36 @@ img[src*="#chart-icon"] {
width: 100%;
}

.open-in-new {
font-size: inherit;
margin-left: 4px;
vertical-align: middle;
}

.code-clipboard {
float: right;
font-size: 20px;
position: absolute;
right: 14px;
top: 12px;
}

.code-clipboard pre {
font-family: monospace;
margin-bottom: 12px;
}

.code-clipboard-container {
background: var(--surfaces-bg-card);
font-family: monospace;
max-height: 500px;
overflow: auto;
padding: 1rem;
position: relative;
}

.code-clipboard-container .pycafe-link,
.code-clipboard-container .pycafe-link:focus {
line-height: unset;
margin-bottom: 12px;
}

.code-clipboard-container::-webkit-scrollbar-thumb {
border-color: var(--surfaces-bg-card);
}
Expand Down
33 changes: 20 additions & 13 deletions vizro-core/examples/visual-vocabulary/custom_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,38 @@
except ImportError: # pragma: no cov
from pydantic import Field

from urllib.parse import quote


class CodeClipboard(vm.VizroBaseModel):
"""Code snippet with a copy to clipboard button."""

type: Literal["code_clipboard"] = "code_clipboard"
code: str
mode: Literal["vizro", "plotly"]
language: str = ""

def build(self):
"""Returns the code clipboard component inside an accordion."""
markdown_code = "\n".join([f"```{self.language}", self.code, "```"])
return dbc.Accordion(

pycafe_link = dbc.Button(
[
"Edit code live on PyCafe",
html.Span("open_in_new", className="material-symbols-outlined open-in-new"),
],
href=f"https://py.cafe/snippet/vizro/v1#code={quote(self.code)}",
target="_blank",
className="pycafe-link",
)

return html.Div(
[
dbc.AccordionItem(
html.Div(
[
dcc.Markdown(markdown_code, id=self.id),
dcc.Clipboard(target_id=self.id, className="code-clipboard"),
],
className="code-clipboard-container",
),
title="SHOW CODE",
)
pycafe_link if self.mode == "vizro" else None,
dcc.Clipboard(target_id=self.id, className="code-clipboard"),
dcc.Markdown(markdown_code, id=self.id),
],
start_collapsed=False,
className="code-clipboard-container",
)


Expand Down Expand Up @@ -68,4 +75,4 @@ def build(self):

vm.Container.add_type("components", FlexContainer)
vm.Container.add_type("components", Markdown)
vm.Page.add_type("components", CodeClipboard)
vm.Container.add_type("components", CodeClipboard)
4 changes: 0 additions & 4 deletions vizro-core/examples/visual-vocabulary/pages/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
# TODO: eventually deduplicate page generation into a function rather than copying and pasting across files?
# TODO: think about the best way to do code examples, e.g.
# - do we want full dashboard example or plot-only example?
# - or both? Could be done using a toggle switch or multiple tabs.
# - a link to PyCafe showing the dashboard code?
75 changes: 51 additions & 24 deletions vizro-core/examples/visual-vocabulary/pages/_factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
"""

import vizro.models as vm
import vizro.plotly.express as px
from custom_charts import butterfly, column_and_line, waterfall

from pages._pages_utils import PAGE_GRID, ages, gapminder, make_code_clipboard_from_py_file, waterfall_data
from pages._pages_utils import PAGE_GRID, make_code_clipboard_from_py_file
from pages.examples import butterfly, column_and_line, connected_scatter, waterfall


def butterfly_factory(group: str):
Expand Down Expand Up @@ -37,10 +36,19 @@ def butterfly_factory(group: str):
categories.
"""
),
vm.Graph(
figure=butterfly(ages, x=["Male", "Female"], y="Age", labels={"value": "Population", "variable": "Sex"})
vm.Graph(figure=butterfly.fig),
vm.Tabs(
tabs=[
vm.Container(
title="Vizro dashboard",
components=[make_code_clipboard_from_py_file("butterfly.py", mode="vizro")],
),
vm.Container(
title="Plotly figure",
components=[make_code_clipboard_from_py_file("butterfly.py", mode="plotly")],
),
]
),
make_code_clipboard_from_py_file("butterfly.py"),
],
)

Expand Down Expand Up @@ -71,8 +79,19 @@ def connected_scatter_factory(group: str):
avoid misinterpretation.
"""
),
vm.Graph(figure=px.line(gapminder.query("country == 'Australia'"), x="year", y="lifeExp", markers=True)),
make_code_clipboard_from_py_file("connected_scatter.py"),
vm.Graph(figure=connected_scatter.fig),
vm.Tabs(
tabs=[
vm.Container(
title="Vizro dashboard",
components=[make_code_clipboard_from_py_file("connected_scatter.py", mode="vizro")],
),
vm.Container(
title="Plotly figure",
components=[make_code_clipboard_from_py_file("connected_scatter.py", mode="plotly")],
),
]
),
],
)

Expand Down Expand Up @@ -101,15 +120,19 @@ def column_and_line_factory(group: str):
for other types of data comparisons.
"""
),
vm.Graph(
figure=column_and_line(
gapminder.query("country == 'Vietnam'"),
y_column="gdpPercap",
y_line="lifeExp",
x="year",
)
vm.Graph(figure=column_and_line.fig),
vm.Tabs(
tabs=[
vm.Container(
title="Vizro dashboard",
components=[make_code_clipboard_from_py_file("column_and_line.py", mode="vizro")],
),
vm.Container(
title="Plotly figure",
components=[make_code_clipboard_from_py_file("column_and_line.py", mode="plotly")],
),
]
),
make_code_clipboard_from_py_file("column_and_line.py"),
],
)

Expand Down Expand Up @@ -141,14 +164,18 @@ def waterfall_factory(group: str):
colors for positive and negative values, and arrange categories logically to tell a coherent story.
"""
),
vm.Graph(
figure=waterfall(
waterfall_data,
x="x",
y="y",
measure="measure",
)
vm.Graph(figure=waterfall.fig),
vm.Tabs(
tabs=[
vm.Container(
title="Vizro dashboard",
components=[make_code_clipboard_from_py_file("waterfall.py", mode="vizro")],
),
vm.Container(
title="Plotly figure",
components=[make_code_clipboard_from_py_file("waterfall.py", mode="plotly")],
),
]
),
make_code_clipboard_from_py_file("waterfall.py"),
],
)
123 changes: 39 additions & 84 deletions vizro-core/examples/visual-vocabulary/pages/_pages_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,102 +3,57 @@
import logging
from pathlib import Path

import autoflake
import black
import pandas as pd
import vizro.plotly.express as px
import isort
from custom_components import CodeClipboard

# To disable logging info messages caused by black.format_str: https://github.com/psf/black/issues/2058
logging.getLogger("blib2to3").setLevel(logging.ERROR)

VIZRO_CODE_TEMPLATE = """
import vizro.models as vm
from vizro import Vizro
{example_code}
page = vm.Page(title="My page", components=[vm.Graph(figure=fig)])
dashboard = vm.Dashboard(pages=[page])
Vizro().build(dashboard).run()
"""


def _format_and_lint(code_string: str, line_length: int) -> str:
"""Inspired by vizro.models._base._format_and_lint. The only difference is that this does isort too."""
# Tracking https://github.com/astral-sh/ruff/issues/659 for proper Python API
# Good example: https://github.com/astral-sh/ruff/issues/8401#issuecomment-1788806462
# While we wait for the API, we can use autoflake and black to process code strings
# Isort is needed since otherwise example code looks quite strange sometimes. Autoflake is needed since isort can't
# remove imports by itself: https://github.com/PyCQA/isort/issues/1105.

removed_imports = autoflake.fix_code(code_string, remove_all_unused_imports=True)
sorted_imports = isort.code(removed_imports)
# Black doesn't yet have a Python API, so format_str might not work at some point in the future.
# https://black.readthedocs.io/en/stable/faq.html#does-black-have-an-api
formatted = black.format_str(sorted_imports, mode=black.Mode(line_length=line_length))
return formatted

def make_code_clipboard_from_py_file(filepath: str):

def make_code_clipboard_from_py_file(filepath: str, mode="vizro"):
# Black doesn't yet have a Python API, so format_str might not work at some point in the future.
# https://black.readthedocs.io/en/stable/faq.html#does-black-have-an-api
filepath = Path(__file__).parents[1] / "pages/examples" / filepath
example_code = (Path(__file__).parents[1] / "pages/examples" / filepath).read_text()

if mode == "vizro":
example_code = VIZRO_CODE_TEMPLATE.format(example_code=example_code)
else:
replacements = {"import vizro.plotly.express as px": "import plotly.express as px", '@capture("graph")': ""}
for old_code, new_code in replacements.items():
example_code = example_code.replace(old_code, new_code)

return CodeClipboard(
code=black.format_str(filepath.read_text(encoding="utf-8"), mode=black.Mode(line_length=80)),
code=_format_and_lint(example_code, line_length=80),
mode=mode,
language="python",
)


PAGE_GRID = [[0, 0, 0, 0, 0, 0, 0]] * 2 + [[1, 1, 1, 1, 2, 2, 2]] * 5

# DATA --------------------------------------------------------------
gapminder = px.data.gapminder()
iris = px.data.iris()
stocks = px.data.stocks()
tips = px.data.tips()
wind = px.data.wind()
ages = pd.DataFrame(
{
"Age": ["0-19", "20-29", "30-39", "40-49", "50-59", ">=60"],
"Male": [800, 2000, 4200, 5000, 2100, 800],
"Female": [1000, 3000, 3500, 3800, 3600, 700],
}
)
sankey_data = pd.DataFrame(
{
"Origin": [0, 1, 0, 2, 3, 3],
"Destination": [2, 3, 3, 4, 4, 5],
"Value": [8, 4, 2, 8, 4, 2],
}
)

funnel_data = pd.DataFrame(
{"Stage": ["Leads", "Sales calls", "Follow-up", "Conversion", "Sales"], "Value": [10, 7, 4, 2, 1]}
)

stepped_line_data = pd.DataFrame(
{
"year": [1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003],
"rate": [0.10, 0.12, 0.15, 0.13, 0.14, 0.13, 0.14, 0.16, 0.15],
}
)


carshare = px.data.carshare()

tasks = pd.DataFrame(
[
{"Task": "Job A", "Start": "2009-01-01", "Finish": "2009-02-28"},
{"Task": "Job B", "Start": "2009-03-05", "Finish": "2009-04-15"},
{"Task": "Job C", "Start": "2009-02-20", "Finish": "2009-05-30"},
]
)

waterfall_data = pd.DataFrame(
{
"x": ["Sales", "Consulting", "Net revenue", "Purchases", "Other expenses", "Profit before tax"],
"y": [60, 80, 0, -40, -20, 0],
"measure": ["relative", "relative", "total", "relative", "relative", "total"],
}
)

pastries = pd.DataFrame(
{
"pastry": [
"Scones",
"Bagels",
"Muffins",
"Cakes",
"Donuts",
"Cookies",
"Croissants",
"Eclairs",
],
"Profit Ratio": [-0.10, -0.05, 0.10, 0.05, 0.15, -0.08, 0.08, -0.12],
"Strongly Disagree": [20, 30, 10, 5, 15, 5, 10, 25],
"Disagree": [30, 25, 20, 10, 20, 10, 15, 30],
"Agree": [30, 25, 40, 40, 45, 40, 40, 25],
"Strongly Agree": [20, 20, 30, 45, 20, 45, 35, 20],
}
)

salaries = pd.DataFrame(
{
"Job": ["Developer", "Analyst", "Manager", "Specialist"],
"Min": [60000, 55000, 70000, 50000],
"Max": [130000, 110000, 96400, 80000],
}
)
Loading

0 comments on commit 293d79b

Please sign in to comment.