Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Demo] Add lollipop chart to ViViVo #874

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion vizro-core/docs/pages/explanation/authors.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ Natalia Kurakina,
[Rosheen C.](https://github.com/rc678),
[Hilary Ivy](https://github.com/hxe00570),
[Jasmine Wu](https://github.com/jazwu),
[njmcgrat](https://github.com/njmcgrat)
[njmcgrat](https://github.com/njmcgrat),
[Jenelle Yonkman](https://github.com/yonkmanjl)

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):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Old code for reference

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new code is much better 🥇

"""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) |
huong-li-nguyen marked this conversation as resolved.
Show resolved Hide resolved
| 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
42 changes: 42 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,45 @@ 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://community.plotly.com/t/how-to-make-dumbbell-plots-in-plotly-python/47762
huong-li-nguyen marked this conversation as resolved.
Show resolved Hide resolved

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.
"""
# Unlike the column_and_line chart, where all traces hold equal significance, here the traces differ in importance.
# The primary scatter plot is the main trace, while the additional traces merely serve as connecting lines.
# Therefore, should we apply the kwargs solely to the main scatter plot, as illustrated below?
fig = px.scatter(data_frame, **kwargs)
Comment on lines +333 to +336
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One question: In this case, there is one main scatter trace and several subsidiary scatter traces that only add lines. I assume in this case, we only add kwargs to the main trace and we don't enable the the kwargs for the subsidiary scatter traces?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume in this case, we only add kwargs to the main trace and we don't enable the the kwargs for the subsidiary scatter traces?

I think this is totally correct 👍.


# Enable for both orientations
orientation = fig.data[0].orientation
x_array = fig.data[0]["x"]
y_array = fig.data[0]["y"]

for i in range(len(data_frame)):
fig.add_trace(
go.Scatter(
x=[0, x_array[i]] if orientation == "h" else [x_array[i], x_array[i]],
y=[y_array[i], y_array[i]] if orientation == "h" else [0, y_array[i]],
mode="lines",
)
)

fig.update_traces(marker_size=12, line_width=3, line_color=fig.layout.template.layout.colorway[0])
fig.update_layout(showlegend=False, yaxis_title="", yaxis_showgrid=False)
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.

 

#### 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")],
),
]
),
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from vizro.models.types import capture

gapminder = px.data.gapminder()


@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_array = fig.data[0]["x"]
y_array = fig.data[0]["y"]

for i in range(len(data_frame)):
fig.add_trace(
go.Scatter(
x=[0, x_array[i]] if orientation == "h" else [x_array[i], x_array[i]],
y=[y_array[i], y_array[i]] if orientation == "h" else [0, y_array[i]],
mode="lines",
)
)

fig.update_traces(marker_size=12, line_width=3, line_color=fig.layout.template.layout.colorway[0])
fig.update_layout(showlegend=False, yaxis_title="", yaxis_showgrid=False)
return fig


fig = lollipop(
data_frame=gapminder.query("year == 2007 and gdpPercap > 36000").sort_values("gdpPercap"),
y="country",
x="gdpPercap",
)
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]