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

Merged
merged 23 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f28ca8c
Add original example
huong-li-nguyen Nov 14, 2024
bb0537a
Add lollipop refactored code
huong-li-nguyen Nov 14, 2024
b90d126
Add factory function and add to groups
huong-li-nguyen Nov 14, 2024
7d2f165
Update README.md
huong-li-nguyen Nov 14, 2024
644a9c7
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 14, 2024
fa2f8eb
Declutter some more
huong-li-nguyen Nov 14, 2024
04c6790
Merge branch 'demo/add-lollipop-chart' of https://github.com/mckinsey…
huong-li-nguyen Nov 14, 2024
143a4af
Lint
huong-li-nguyen Nov 14, 2024
3cd607d
Add author
huong-li-nguyen Nov 14, 2024
d2e5190
Merge branch 'main' into demo/add-lollipop-chart
huong-li-nguyen Nov 14, 2024
7f447da
Merge branch 'main' into demo/add-lollipop-chart
huong-li-nguyen Nov 15, 2024
8545946
Fix lollipop
huong-li-nguyen Nov 15, 2024
c58aeb1
Merge branch 'main' into demo/add-lollipop-chart
huong-li-nguyen Nov 15, 2024
eebd889
Dynamic layout updates
huong-li-nguyen Nov 15, 2024
c5994db
Add argument
huong-li-nguyen Nov 15, 2024
1980b53
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 15, 2024
4bcc3ea
Update vizro-core/examples/visual-vocabulary/custom_charts.py
huong-li-nguyen Nov 15, 2024
6ca75dc
Refactor
huong-li-nguyen Nov 15, 2024
5dda70d
Merge branch 'demo/add-lollipop-chart' of https://github.com/mckinsey…
huong-li-nguyen Nov 15, 2024
407421a
Merge branch 'main' into demo/add-lollipop-chart
huong-li-nguyen Nov 19, 2024
5f00159
Tidy README
huong-li-nguyen Nov 19, 2024
74d9bce
Tidy chart
huong-li-nguyen Nov 19, 2024
6dc9368
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 19, 2024
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
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):
huong-li-nguyen marked this conversation as resolved.
Show resolved Hide resolved
"""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
50 changes: 50 additions & 0 deletions vizro-core/examples/visual-vocabulary/custom_charts.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,53 @@ 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.
"""
# Should we allow keyword arguments (kwargs) in this context, given the presence of multiple traces?
# 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 data visualization, while the additional traces merely serve as connecting lines.
# Therefore, should we apply the kwargs solely to the scatter plot, as illustrated below?
fig = px.scatter(data_frame, **kwargs)
x_array = fig.data[0].x
y_array = fig.data[0].y

# Is it necessary to always enable orientation? Unlike bar charts, scatter plots do not have an
# orientation argument. The calculation of the trace below needs to adapt based on the orientation.
# However, since we can't predict the user's choice for x and y axes, I am not sure how we can dynamically update
# the orientation. This chart does not always have a categorical and a numerical column. In can also be
# with both numeric columns. Should we introduce this ourselves? Does this have to be part of this example
# or can we hard-code for the visual-vocabulary?
for i in range(len(data_frame)):
fig.add_trace(
go.Scatter(
x=[0, x_array[i]],
y=[y_array[i], 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)

# These are use-case specific layout updates. Normally not suitable for a chart inside vizro.charts, as these
# depend on the data context.
fig.update_layout(yaxis_title="")
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,35 @@
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):
huong-li-nguyen marked this conversation as resolved.
Show resolved Hide resolved
"""Creates a lollipop chart using Plotly."""
fig = px.scatter(data_frame, **kwargs)
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]],
y=[y_array[i], 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)
fig.update_layout(yaxis_title="")
return fig


fig = lollipop(
data_frame=gapminder.query("year == 2007 and gdpPercap > 36000").sort_values("gdpPercap"),
huong-li-nguyen marked this conversation as resolved.
Show resolved Hide resolved
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]
Loading