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 visual-vocabulary #787

Closed
wants to merge 3 commits into from

Conversation

yonkmanjl
Copy link

@yonkmanjl yonkmanjl commented Oct 4, 2024

Description

Adds lollipop chart example to visual vocabulary.

Resolves #710 .

Screenshot

image

Notice

  • I acknowledge and agree that, by checking this box and clicking "Submit Pull Request":

    • I submit this contribution under the Apache 2.0 license and represent that I am entitled to do so on behalf of myself, my employer, or relevant third parties, as applicable.
    • I certify that (a) this contribution is my original creation and / or (b) to the extent it is not my original creation, I am authorized to submit this contribution on behalf of the original creator(s) or their licensees.
    • I certify that the use of this contribution as authorized by the Apache 2.0 license does not violate the intellectual property rights of anyone else.
    • I have not referenced individuals, products or companies in any commits, directly or indirectly.
    • I have not added data or restricted code in any commits, directly or indirectly.

@yonkmanjl yonkmanjl changed the title Add lollipop chart to visual vocab [GHC] Add lollipop chart to visual vocab Oct 4, 2024
Copy link
Contributor

@antonymilne antonymilne left a 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)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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.
"""
Copy link
Contributor

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?

Copy link
Contributor

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"),
Copy link
Contributor

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

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.

Copy link
Contributor

@huong-li-nguyen huong-li-nguyen Oct 9, 2024

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)

@huong-li-nguyen huong-li-nguyen changed the title [GHC] Add lollipop chart to visual vocab [Demo] Add lollipop chart to visual-vocabulary Oct 8, 2024
Copy link
Contributor

@huong-li-nguyen huong-li-nguyen left a 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
Copy link
Contributor

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_signal 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()

Copy link
Contributor

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.

Copy link
Contributor

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.
"""
Copy link
Contributor

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.

@huong-li-nguyen
Copy link
Contributor

@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 :)

Screenshot 2024-10-10 at 11 51 13

Comment on lines +2 to +5
import vizro.models as vm
import vizro.plotly.express as px
from vizro import Vizro
from vizro.models.types import capture
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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

Comment on lines +8 to +24
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
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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

Comment on lines +27 to +60
@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
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
@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

Comment on lines +63 to +73
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()
Copy link
Contributor

@huong-li-nguyen huong-li-nguyen Oct 14, 2024

Choose a reason for hiding this comment

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

Suggested change
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)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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)

Comment on lines +135 to +137
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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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.

Comment on lines +127 to +129
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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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.

@huong-li-nguyen huong-li-nguyen self-assigned this Oct 14, 2024
@huong-li-nguyen
Copy link
Contributor

Hello @yonkmanjl :)

For us to be able to merge this PR you would either have to:

  • Solve the merge conflicts and incorporate the suggestions yourself
  • Solve the merge conflicts such that we can commit the suggestion
  • Grant us access to your forked repository

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! :)

@huong-li-nguyen
Copy link
Contributor

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! ⭐

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Contribute Lollipop chart to Vizro visual vocabulary
3 participants