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 19 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
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):
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
45 changes: 45 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,48 @@ 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.
"""
# 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)
huong-li-nguyen marked this conversation as resolved.
Show resolved Hide resolved

# Enable for both orientations
is_horizontal = fig.data[0].orientation == "h"
x_coords = [[0, x] if is_horizontal else [x, x] for x in fig.data[0]["x"]]
y_coords = [[y, y] if is_horizontal else [0, y] for y in fig.data[0]["y"]]
for x, y in zip(x_coords, y_coords):
fig.add_trace(go.Scatter(x=x, y=y, mode="lines"))

xaxis_showgrid = is_horizontal
yaxis_showgrid = not is_horizontal

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

fig.update_layout(
showlegend=False, yaxis_showgrid=yaxis_showgrid, xaxis_showgrid=xaxis_showgrid, yaxis_rangemode="tozero"
huong-li-nguyen marked this conversation as resolved.
Show resolved Hide resolved
)
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,40 @@
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)

# Enable for both orientations
is_horizontal = fig.data[0].orientation == "h"
x_coords = [[0, x] if is_horizontal else [x, x] for x in fig.data[0]["x"]]
y_coords = [[y, y] if is_horizontal else [0, y] for y in fig.data[0]["y"]]
for x, y in zip(x_coords, y_coords):
fig.add_trace(go.Scatter(x=x, y=y, mode="lines"))

xaxis_showgrid = is_horizontal
yaxis_showgrid = not is_horizontal

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

fig.update_layout(
showlegend=False, yaxis_showgrid=yaxis_showgrid, xaxis_showgrid=xaxis_showgrid, yaxis_rangemode="tozero"
huong-li-nguyen marked this conversation as resolved.
Show resolved Hide resolved
)
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