Skip to content

Commit

Permalink
[Demo] Add lollipop chart to ViViVo (#874)
Browse files Browse the repository at this point in the history
Co-authored-by: Petar Pejovic <[email protected]>
  • Loading branch information
huong-li-nguyen and petar-qb authored Nov 19, 2024
1 parent ed15ddc commit 2b656bf
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 38 deletions.
1 change: 1 addition & 0 deletions vizro-core/docs/pages/explanation/authors.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Natalia Kurakina,
[Hilary Ivy](https://github.com/hxe00570),
[Jasmine Wu](https://github.com/jazwu),
[njmcgrat](https://github.com/njmcgrat),
[Jenelle Yonkman](https://github.com/yonkmanjl),
[ataraexia](https://github.com/ataraexia)

with thanks to Sam Bourton and Kevin Staight for sponsorship, inspiration and guidance,
Expand Down
78 changes: 46 additions & 32 deletions vizro-core/examples/scratch_dev/app.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,59 @@
"""Dev app to try things out."""

import pandas as pd
import plotly.graph_objects as go
import vizro.models as vm
import vizro.plotly.express as px
from vizro import Vizro
from vizro._themes._color_values import COLORS

pastry = pd.DataFrame(
{
"pastry": [
"Scones",
"Bagels",
"Muffins",
"Cakes",
"Donuts",
"Cookies",
"Croissants",
"Eclairs",
"Brownies",
"Tarts",
"Macarons",
"Pies",
],
"Profit Ratio": [-0.10, -0.15, -0.05, 0.10, 0.05, 0.20, 0.15, -0.08, 0.08, -0.12, 0.02, -0.07],
}
)
from vizro.models.types import capture


@capture("graph")
def lollipop(data_frame: pd.DataFrame, x: str, y: str):
"""Creates a lollipop chart using Plotly.
This function generates a scatter chart and then draws lines extending from each point to the x-axis.
Args:
data_frame (pd.DataFrame): The data source for the chart.
x (str): The column name to be used for the x-axis.
y (str): The column name to be used for the y-axis.
Returns:
go.Figure: : A Plotly Figure object representing the lollipop chart.
"""
fig = go.Figure()

# Draw points
fig.add_trace(
go.Scatter(
x=data_frame[x],
y=data_frame[y],
mode="markers",
marker=dict(color="#00b4ff", size=12),
)
)

for i in range(len(data_frame)):
fig.add_trace(
go.Scatter(
x=[0, data_frame[x].iloc[i]],
y=[data_frame[y].iloc[i], data_frame[y].iloc[i]],
mode="lines",
line=dict(color="#00b4ff", width=3),
)
)
fig.update_layout(showlegend=False)
return fig


gapminder = px.data.gapminder()


page = vm.Page(
title="Charts UI",
title="Lollipop",
components=[
vm.Graph(
figure=px.bar(
pastry.sort_values("Profit Ratio"),
orientation="h",
x="Profit Ratio",
y="pastry",
color="Profit Ratio",
color_continuous_scale=COLORS["DIVERGING_RED_CYAN"],
),
),
vm.Graph(figure=lollipop(gapminder.query("year == 2007 and gdpPercap > 36000"), y="country", x="gdpPercap"))
],
)

Expand Down
2 changes: 1 addition & 1 deletion vizro-core/examples/visual-vocabulary/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ The dashboard is still in development. Below is an overview of the chart types f
| Correlation matrix || Correlation | | |
| Histogram || Distribution | [Histograms with px](https://plotly.com/python/histograms/) | [px.histogram](https://plotly.github.io/plotly.py-docs/generated/plotly.express.histogram) |
| Line || Time | [Line plot with px](https://plotly.com/python/line-charts/) | [px.line](https://plotly.com/python-api-reference/generated/plotly.express.line) |
| Lollipop | | Ranking, Magnitude | | |
| Lollipop | | Ranking, Magnitude | [Lollipop & Dumbbell Charts with Plotly](https://towardsdatascience.com/lollipop-dumbbell-charts-with-plotly-696039d5f85) | [px.scatter](https://plotly.com/python-api-reference/generated/plotly.express.scatter) |
| Marimekko || Magnitude, Part-to-whole | | |
| Network || Flow | | |
| Ordered bar || Ranking | [Bar chart with px](https://plotly.com/python/bar-charts/) | [px.bar](https://plotly.com/python-api-reference/generated/plotly.express.bar.html) |
Expand Down
2 changes: 0 additions & 2 deletions vizro-core/examples/visual-vocabulary/chart_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ class ChartGroup:
incomplete_pages=[
IncompletePage("Ordered bubble"),
IncompletePage("Slope"),
IncompletePage("Lollipop"),
IncompletePage("Bump"),
],
icon="Stacked Bar Chart",
Expand Down Expand Up @@ -117,7 +116,6 @@ class ChartGroup:
pages=pages.magnitude.pages,
incomplete_pages=[
IncompletePage("Marimekko"),
IncompletePage("Lollipop"),
IncompletePage("Pictogram"),
IncompletePage("Bullet"),
IncompletePage("Radial"),
Expand Down
48 changes: 48 additions & 0 deletions vizro-core/examples/visual-vocabulary/custom_charts.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,3 +310,51 @@ def diverging_stacked_bar(data_frame: pd.DataFrame, **kwargs) -> go.Figure:
fig.add_hline(y=0, line_width=2, line_color="grey")

return fig


@capture("graph")
def lollipop(data_frame: pd.DataFrame, **kwargs):
"""Creates a lollipop based on px.scatter.
A lollipop chart is a variation of a bar chart where each data point is represented by a line and a dot at the end
to mark the value.
Inspired by: https://towardsdatascience.com/lollipop-dumbbell-charts-with-plotly-696039d5f85
Args:
data_frame: DataFrame for the chart. Can be long form or wide form.
See https://plotly.com/python/wide-form/.
**kwargs: Keyword arguments to pass into px.scatter (e.g. x, y, labels).
See https://plotly.com/python-api-reference/generated/plotly.scatter.html.
Returns:
go.Figure: Lollipop chart.
"""
# Plots the dots of the lollipop chart
fig = px.scatter(data_frame, **kwargs)

# Enables the orientation of the chart to be either horizontal or vertical
orientation = fig.data[0].orientation
x_or_y = "x" if orientation == "h" else "y"
y_or_x = "y" if orientation == "h" else "x"

# Plots the lines of the lollipop chart
for x_or_y_value, y_or_x_value in zip(fig.data[0][x_or_y], fig.data[0][y_or_x]):
fig.add_trace(go.Scatter({x_or_y: [0, x_or_y_value], y_or_x: [y_or_x_value, y_or_x_value], "mode": "lines"}))

# Styles the lollipop chart and makes it uni-colored
fig.update_traces(
marker_size=12,
line_width=3,
line_color=fig.layout.template.layout.colorway[0],
)

fig.update_layout(
{
"showlegend": False,
f"{x_or_y}axis_showgrid": True,
f"{y_or_x}axis_showgrid": False,
f"{x_or_y}axis_rangemode": "tozero",
},
)
return fig
47 changes: 46 additions & 1 deletion vizro-core/examples/visual-vocabulary/pages/_factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import vizro.models as vm

from pages._pages_utils import PAGE_GRID, make_code_clipboard_from_py_file
from pages.examples import butterfly, column_and_line, connected_scatter, waterfall
from pages.examples import butterfly, column_and_line, connected_scatter, lollipop, waterfall


def butterfly_factory(group: str):
Expand Down Expand Up @@ -179,3 +179,48 @@ def waterfall_factory(group: str):
),
],
)


def lollipop_factory(group: str):
"""Reusable function to create the page content for the lollipop chart with a unique ID."""
return vm.Page(
id=f"{group}-lollipop",
path=f"{group}/lollipop",
title="Lollipop",
layout=vm.Layout(grid=PAGE_GRID),
components=[
vm.Card(
text="""
#### What is a lollipop chart?
A lollipop chart is a variation of a bar chart where each data point is represented by a line and a
dot at the end to mark the value. It functions like a bar chart but offers a cleaner visual,
especially useful when dealing with a large number of high values, to avoid the clutter of tall columns.
However, it can be less precise due to the difficulty in judging the exact center of the circle.
&nbsp;
#### When should I use it?
Use a lollipop chart to compare values across categories, especially when dealing with many high values.
It highlights differences and trends clearly without the visual bulk of a bar chart. Ensure clarity by
limiting categories, using consistent scales, and clearly labeling axes. Consider alternatives if
precise value representation is crucial.
"""
),
vm.Graph(figure=lollipop.fig),
vm.Tabs(
tabs=[
vm.Container(
title="Vizro dashboard",
components=[make_code_clipboard_from_py_file("lollipop.py", mode="vizro")],
),
vm.Container(
title="Plotly figure",
components=[make_code_clipboard_from_py_file("lollipop.py", mode="plotly")],
),
]
),
],
)
42 changes: 42 additions & 0 deletions vizro-core/examples/visual-vocabulary/pages/examples/lollipop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from vizro.models.types import capture


@capture("graph")
def lollipop(data_frame: pd.DataFrame, **kwargs):
"""Creates a lollipop chart using Plotly."""
fig = px.scatter(data_frame, **kwargs)

orientation = fig.data[0].orientation
x_or_y = "x" if orientation == "h" else "y"
y_or_x = "y" if orientation == "h" else "x"

for x_or_y_value, y_or_x_value in zip(fig.data[0][x_or_y], fig.data[0][y_or_x]):
fig.add_trace(go.Scatter({x_or_y: [0, x_or_y_value], y_or_x: [y_or_x_value, y_or_x_value], "mode": "lines"}))

fig.update_traces(
marker_size=12,
line_width=3,
line_color=fig.layout.template.layout.colorway[0],
)

fig.update_layout(
{
"showlegend": False,
f"{x_or_y}axis_showgrid": True,
f"{y_or_x}axis_showgrid": False,
f"{x_or_y}axis_rangemode": "tozero",
},
)
return fig


gapminder = (
px.data.gapminder()
.query("year == 2007 and country.isin(['United States', 'Pakistan', 'India', 'China', 'Indonesia'])")
.sort_values("pop")
)

fig = lollipop(gapminder, y="country", x="pop")
12 changes: 11 additions & 1 deletion vizro-core/examples/visual-vocabulary/pages/magnitude.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import vizro.models as vm

from pages._factories import lollipop_factory
from pages._pages_utils import PAGE_GRID, make_code_clipboard_from_py_file
from pages.examples import bar, magnitude_column, paired_bar, paired_column, parallel_coordinates, radar

Expand Down Expand Up @@ -238,4 +239,13 @@
],
)

pages = [bar_page, column_page, paired_bar_page, paired_column_page, parallel_coordinates_page, radar_page]
lollipop_page = lollipop_factory("magnitude")
pages = [
bar_page,
column_page,
paired_bar_page,
paired_column_page,
parallel_coordinates_page,
radar_page,
lollipop_page,
]
5 changes: 4 additions & 1 deletion vizro-core/examples/visual-vocabulary/pages/ranking.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import vizro.models as vm

from pages._factories import lollipop_factory
from pages._pages_utils import PAGE_GRID, make_code_clipboard_from_py_file
from pages.examples import ordered_bar, ordered_column

Expand Down Expand Up @@ -85,4 +86,6 @@
)


pages = [ordered_bar_page, ordered_column_page]
lollipop_page = lollipop_factory("deviation")

pages = [ordered_bar_page, ordered_column_page, lollipop_page]

0 comments on commit 2b656bf

Please sign in to comment.