-
Notifications
You must be signed in to change notification settings - Fork 142
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 visual-vocabulary #787
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you very much for your contribution @yonkmanjl!! It's hugely appreciated. Love this plot and it's a perfect example dataset to illustrate the plot 🙌
A few minor comments and ideas left in comments - some are very easy to deal with, others will take ae bit more thought. Do let me know if you're able to continue working on this or whether you'd like someone else to take over.
@huong-li-nguyen would like your thoughts on some of the comments too when you are back 🙏
go.Figure: : A Plotly Figure object representing the lollipop chart. | ||
|
||
""" | ||
data = px.scatter(data_frame, x=x, y=y) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
data = px.scatter(data_frame, x=x, y=y) | |
fig = px.scatter(data_frame, x=x, y=y) |
This is the convention we normally follow 🙂 Since px.scatter
returns a figure, rather than data.
"line": {"color": "grey", "width": 2}, | ||
} | ||
) | ||
fig = data.update_layout(shapes=shapes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fig = data.update_layout(shapes=shapes) | |
fig = fig.update_layout(shapes=shapes) |
Use a lollipop chart when you want to highlight individual data points and make comparisons across | ||
categories. It is particularly useful for displaying ranking or distribution data, and it can be more | ||
visually appealing and easier to read than traditional bar charts. | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should add more information from https://datavizproject.com/data-type/lollipop-chart/ here. In particular, I think the "dealing with large number of high values" and potential downsides are interesting points.
@huong-li-nguyen wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I like that - maybe let's use below:
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.
@@ -84,7 +84,6 @@ class ChartGroup: | |||
incomplete_pages=[ | |||
IncompletePage("Ordered bubble"), | |||
IncompletePage("Slope"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change should be applied to magnitude_chart_group
also 🙂
"line": {"color": "grey", "width": 2}, | ||
} | ||
) | ||
fig = data.update_layout(shapes=shapes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From a style point of view I would probably make the blobs a bit bigger and maybe keep the same blue colour but I'm not sure - I'll leave it to our style guru @huong-li-nguyen to comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree with @antonymilne 👍 I'll propose something to start with: #787 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Congratulation on your first PR in this repository! 👏 This already looked great.
I've some code suggestions and iterated on your recent version. Hopefully that helps! The rest looked great to me, so after implementing the changes, this is ready to merge for me :)
@@ -0,0 +1,73 @@ | |||
import pandas as pd |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This already looked great - great job figuring this out! 👏
I've iterated on your version and simplified the code. This will just be a starter code for now, as some things are hard-coded (design and direction of the chart). I'll refactor this properly later on, so don't worry about this now.
- I used the
go.Scatter
for both traces such that we have better control for the styling of the marks 👍 - I've removed the
offset_signa
l function as it's not required given that the dot and line will have the same color according to our design
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.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="Lollipop",
components=[
vm.Graph(figure=lollipop(gapminder.query("year == 2007 and gdpPercap > 36000"), y="country", x="gdpPercap"))
],
)
dashboard = vm.Dashboard(pages=[page])
Vizro().build(dashboard).run()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@antonymilne - the direction and the design are currently hard-coded. I would leave it as is for this PR and we can refactor this later on to make it more flexible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. I would still suggest doing the loop a bit differently (completely untested):
for x, y in zip(data_frame[x], data_frame[y])):
fig.add_trace(
go.Scatter(
x=[0, x],
y=[y, y],
mode="lines",
line=dict(color="#00b4ff", width=3),
)
)
Use a lollipop chart when you want to highlight individual data points and make comparisons across | ||
categories. It is particularly useful for displaying ranking or distribution data, and it can be more | ||
visually appealing and easier to read than traditional bar charts. | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I like that - maybe let's use below:
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.
@yonkmanjl - do let me know if you prefer to finish this PR off on your own or if you want me to help you finish this off :) If you want me to finish this off, you would have to provide me write access to your forked repository: https://github.com/yonkmanjl/vizro For this you need to go to your forked repository -> settings -> collaborators and teams --> add people Then grant me write access :) |
import vizro.models as vm | ||
import vizro.plotly.express as px | ||
from vizro import Vizro | ||
from vizro.models.types import capture |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import vizro.models as vm | |
import vizro.plotly.express as px | |
from vizro import Vizro | |
from vizro.models.types import capture | |
import plotly.graph_objects as go | |
import vizro.models as vm | |
import vizro.plotly.express as px | |
from vizro import Vizro | |
from vizro.models.types import capture |
def offset_signal(signal: float, marker_offset: float): | ||
"""Offsets a signal value by marker_offset. | ||
|
||
Reduces for positive signal values and increasing for negative values. Used to reduce the length of | ||
lines on lollipop charts to end just before the dot. | ||
|
||
Args: | ||
signal (float): the value to be updated. | ||
marker_offset (float): the offset to be added/subtracted. | ||
|
||
Returns: | ||
float: the updated value. | ||
|
||
""" | ||
if abs(signal) <= marker_offset: | ||
return 0 | ||
return signal - marker_offset if signal > 0 else signal + marker_offset |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
def offset_signal(signal: float, marker_offset: float): | |
"""Offsets a signal value by marker_offset. | |
Reduces for positive signal values and increasing for negative values. Used to reduce the length of | |
lines on lollipop charts to end just before the dot. | |
Args: | |
signal (float): the value to be updated. | |
marker_offset (float): the offset to be added/subtracted. | |
Returns: | |
float: the updated value. | |
""" | |
if abs(signal) <= marker_offset: | |
return 0 | |
return signal - marker_offset if signal > 0 else signal + marker_offset |
@capture("graph") | ||
def lollipop(data_frame: pd.DataFrame, x: str, y: str, y_offset: float): | ||
"""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. | ||
y_offset (float): The amount to offset the end of each line from each scatter point. | ||
|
||
Returns: | ||
go.Figure: : A Plotly Figure object representing the lollipop chart. | ||
|
||
""" | ||
data = px.scatter(data_frame, x=x, y=y) | ||
|
||
shapes = [] | ||
for i, row in data_frame.iterrows(): | ||
shapes.append( | ||
{ | ||
"type": "line", | ||
"xref": "x", | ||
"yref": "y", | ||
"x0": row[x], | ||
"y0": 0, | ||
"x1": row[x], | ||
"y1": offset_signal(row[y], y_offset), | ||
"line": {"color": "grey", "width": 2}, | ||
} | ||
) | ||
fig = data.update_layout(shapes=shapes) | ||
return fig |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@capture("graph") | |
def lollipop(data_frame: pd.DataFrame, x: str, y: str, y_offset: float): | |
"""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. | |
y_offset (float): The amount to offset the end of each line from each scatter point. | |
Returns: | |
go.Figure: : A Plotly Figure object representing the lollipop chart. | |
""" | |
data = px.scatter(data_frame, x=x, y=y) | |
shapes = [] | |
for i, row in data_frame.iterrows(): | |
shapes.append( | |
{ | |
"type": "line", | |
"xref": "x", | |
"yref": "y", | |
"x0": row[x], | |
"y0": 0, | |
"x1": row[x], | |
"y1": offset_signal(row[y], y_offset), | |
"line": {"color": "grey", "width": 2}, | |
} | |
) | |
fig = data.update_layout(shapes=shapes) | |
return fig | |
@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() | ||
df = gapminder.query("year == 2007 and gdpPercap > 36000") | ||
marker_offset = 5.0 | ||
|
||
page = vm.Page( | ||
title="Lollipop", | ||
components=[vm.Graph(figure=lollipop(df, "country", "gdpPercap", marker_offset))], | ||
) | ||
|
||
dashboard = vm.Dashboard(pages=[page]) | ||
Vizro().build(dashboard).run() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gapminder = px.data.gapminder() | |
df = gapminder.query("year == 2007 and gdpPercap > 36000") | |
marker_offset = 5.0 | |
page = vm.Page( | |
title="Lollipop", | |
components=[vm.Graph(figure=lollipop(df, "country", "gdpPercap", marker_offset))], | |
) | |
dashboard = vm.Dashboard(pages=[page]) | |
Vizro().build(dashboard).run() | |
gapminder = px.data.gapminder() | |
page = vm.Page( | |
title="Lollipop", | |
components=[ | |
vm.Graph(figure=lollipop(gapminder.query("year == 2007 and gdpPercap > 36000") | |
.sort("gdpPercap"), y="country", x="gdpPercap")) | |
], | |
) | |
dashboard = vm.Dashboard(pages=[page]) | |
Vizro().build(dashboard).run() |
""" | ||
), | ||
vm.Graph( | ||
figure=lollipop(gapminder.query("year == 2007 and gdpPercap > 36000"), "country", "gdpPercap", 5.0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
figure=lollipop(gapminder.query("year == 2007 and gdpPercap > 36000"), "country", "gdpPercap", 5.0) | |
figure=lollipop(gapminder.query("year == 2007 and gdpPercap > 36000") | |
.sort("gdpPercap"), "country", "gdpPercap", 5.0) |
Use a lollipop chart when you want to highlight individual data points and make comparisons across | ||
categories. It is particularly useful for displaying ranking or distribution data, and it can be more | ||
visually appealing and easier to read than traditional bar charts. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use a lollipop chart when you want to highlight individual data points and make comparisons across | |
categories. It is particularly useful for displaying ranking or distribution data, and it can be more | |
visually appealing and easier to read than traditional bar charts. | |
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. |
A lollipop chart is a type of chart that combines elementsof a bar chart and a scatter plot. | ||
A line extends from the x-axis to a dot, which marks the value for that category. This makes | ||
it easy to compare different categories visually. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A lollipop chart is a type of chart that combines elementsof a bar chart and a scatter plot. | |
A line extends from the x-axis to a dot, which marks the value for that category. This makes | |
it easy to compare different categories visually. | |
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. |
Hello @yonkmanjl :) For us to be able to merge this PR you would either have to:
At the moment we can't continue on your branch because of remaining merge conflicts. Let us know if you want to continue working on this, no problems if not! :) |
Hello @yonkmanjl, Thank you so much for your contribution! Unfortunately, we're unable to continue with this branch due to some merge conflicts and our lack of write access to your fork. To keep the momentum going, we'll create a new branch that includes the change requests above. However, I'll ensure you are credited as a contributor in our documentation. If you have any questions on the code suggestions above, please feel free to reach out 👍 We look forward to more of your contributions in the future! ⭐ |
Description
Adds lollipop chart example to visual vocabulary.
Resolves #710 .
Screenshot
Notice
I acknowledge and agree that, by checking this box and clicking "Submit Pull Request":