plotly / plotly_express

Plotly Express - Simple syntax for complex charts. Now integrated into plotly.py!
https://plot.ly/python/plotly-express/
MIT License
4 stars 0 forks source link

Is there a way to add a horizontal line or vertical line similar to matplotlib? #143

Closed helloitsjulian closed 4 years ago

helloitsjulian commented 5 years ago

Something similar to this https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.axes.Axes.axhline.html

Is there any intention for this to be added or should we go through accessing layout?

indiana-nikel commented 5 years ago

Also wondering if this is in the works! Maybe something along the lines of:

.update_layout(hline=5)
.update_layout(vline=-1)
nicolaskruchten commented 5 years ago

You can indeed add horizontal and vertical lines to plots in general, but we don't have a "facet-aware" way of doing this yet unfortunately, so this only really works well/easily with a single plot:

import plotly.express as px

df = px.data.tips()
fig = px.scatter(df, x="total_bill", y="tip")
fig.update_layout(shapes=[
    dict(
      type= 'line',
      yref= 'paper', y0= 0, y1= 1,
      xref= 'x', x0= 5, x1= 5
    )
])

This adds a vertical line at x=5

image

indiana-nikel commented 5 years ago

I'm finding that if you add fig.show(config={"showLink":False}), you receive duplicate charts:

import plotly.express as px

df = px.data.tips()
fig = px.scatter(df, x="total_bill", y="tip")
fig.update_layout(shapes=[
    dict(
      type= 'line',
      yref= 'paper', y0= 0, y1= 1,
      xref= 'x', x0= 5, x1= 5
    )
])
fig.show(config={"showLink":False})

Is this behavior intended/built in to .update_layout(...)?

nicolaskruchten commented 5 years ago

I'm not sure what you mean by "duplicate charts" here...? Also note that the "showLink: False" is redundant in Plotly.py v4, as this is the default :)

nicolaskruchten commented 5 years ago

Ah, if you mean that fig.update_layout() returns fig then yes, this is intentional, so if you have a notebook cell that ends with this call, then fig.show() is optional. But in a single notebook cell, your code above should output only a single chart.

vcmorini commented 4 years ago

Nice @nicolaskruchten ! Is there a way to add more than one xref or yref? Because my express px.scatter have multiple facets.

Answer: just add another dict in the shape list:

        fig.update_layout(shapes=[         # Line Diagonal
            dict(
                type="line",
                yref='y1',
                y0=200,
                y1=200,
                xref='x1',
                x0=min_x*0.8,
                x1=max_x*1.2,
                line=dict(
                    color="Red",
                    width=4,
                    dash="dashdot",
                )
        ),
            dict(
                type="line",
                yref='y2',
                y0=200,
                y1=200,
                xref='x2',
                x0=min_x*0.8,
                x1=max_x*1.2,
                line=dict(
                    color="Red",
                    width=4,
                    dash="dashdot",
                )
        )
        ])
jvschoen commented 4 years ago

we don't have a "facet-aware" way of doing this yet unfortunately

@nicolaskruchten Is there any plans of implementing this? It's very useful when trying to display thresholds values or summary statistics (mean, median) across facets (Either constant value across all facets, or generated upon grouping for facets). yintercept or xintercept values could be used and just utilize the trend line functionality to just display a constant trend.

nicolaskruchten commented 4 years ago

Yes, we will implement a nice .add_hlines() and .add_vlines() in an upcoming version, as the current way is pretty clunky.

nicolaskruchten commented 4 years ago

You can follow the development here: https://github.com/plotly/plotly.py/issues/2141

jvschoen commented 4 years ago
        fig.update_layout(shapes=[         # Line Diagonal
            dict(
                type="line",
                yref='y1',
                y0=200,
                y1=200,
                xref='x1',
                x0=min_x*0.8,
                x1=max_x*1.2,
                line=dict(
                    color="Red",
                    width=4,
                    dash="dashdot",
                )
        ),
            dict(
                type="line",
                yref='y2',
                y0=200,
                y1=200,
                xref='x2',
                x0=min_x*0.8,
                x1=max_x*1.2,
                line=dict(
                    color="Red",
                    width=4,
                    dash="dashdot",
                )
        )
        ])

@vcmorini Thanks for the idea. A more generalized solution based off this:

fig.layout.update(
    shapes=[{'type': 'line',
                    'y0':y_intercept,' y1': y_intercept,
                    'x0':str(df.x_value.min()), 'x1':str(df.x_value.max()),
                    'xref':'x' + str(i + 1), 'yref':'y' + str(i + 1),
                    'line': {'color': 'black', 'width': 1, 'dash': 'dot'}} 
               for i, member in enumerate(pd.unique(df.x_value))])
liweipace commented 4 years ago

I was using go.Scatter to generate a plot. One solution, which is also clunky, for go is adding another plot with

trace2 = go.Scatter(
       x = [df.Date.min(),df.Date.max()],
       y = [df.Close.mean(),df.Close.mean()]
       mode = "lines",
       name = "Close price",
       marker = dict(color = 'rgba(80, 26, 80, 0.8)')
)

.add_hlines() is just finding the x_min and x_max with two fixed y .add_vlines() vice versa. BTW, looking forward to upcoming versions!

vcmorini commented 4 years ago

It somehow bugs with Plotly Express with multiple facets:

Untitled

                    {
                        "type": "line",
                        "yref": "paper",
                        "y0": 0,
                        "y1": 50,
                        "xref": "x{}".format(i + 1),
                        "x0": -200,
                        "x1": -200,
                        "line": {
                            "color":"Red",
                            "width":1,
                        }
                    }

Workaround:

                counts, _ = np.histogram(df["data"], bins=bins)
                max_bin_count = counts.max()
....
                    {
                        "type": "line",
                        "yref": "y{}".format(i + 1),
                        "y0": 0,
                        "y1": max_bin_count,
                        "xref": "x{}".format(i + 1),
                        "x0": -200,
                        "x1": -200,
                        "line": {
                            "color":"Red",
                            "width":1,
                        }
                    }

Ref: https://plotly.com/python/histograms/#accessing-the-counts-yaxis-values

awheels-ds commented 4 years ago

Is there a way to add a label on the x-axis for each vertical line?

mariliarosa4 commented 4 years ago

I would like to know too.

SpyderRivera commented 4 years ago

In R, there is an easy way that could be adapted to python.

vline <- function(x = 0, color = "red") {
  list(
    type = "line", 
    y0 = 0, 
    y1 = 1, 
    yref = "paper",
    x0 = x, 
    x1 = x, 
    line = list(color = color)
  )
}

hline <- function(y = 0, color = "blue") {
  list(
    type = "line", 
    x0 = 0, 
    x1 = 1, 
    xref = "paper",
    y0 = y, 
    y1 = y, 
    line = list(color = color)
  )
}

plot_ly() %>%
  layout(shapes = list(vline(4), hline(5)))

image

nicolaskruchten commented 4 years ago

@SpyderRivera thanks!

We've got a PR almost ready to merge that introduces a fig.add_hline(y=...) API which will make this much easier... should be out in Plotly.py 4.11 by the end of September. Stay tuned! :)

nickmuchi87 commented 4 years ago

added a horizontal line using fig.add_shape(), however, the line was disabled once i specified the 'seaborn' template, is there a way around this? @nicolaskruchten, thanks in advance

victorsalles commented 4 years ago

@SpyderRivera thanks!

We've got a PR almost ready to merge that introduces a fig.add_hline(y=...) API which will make this much easier... should be out in Plotly.py 4.11 by the end of September. Stay tuned! :)

great to hear that! I'm looking forward for 4.11 release! are there any dates already?

nicolaskruchten commented 4 years ago

This feature has been moved to 4.12, but I estimate that within 2 weeks we should have it out :)

nicolaskruchten commented 3 years ago

OK took longer than two weeks, sorry folks :) But it's out now in 4.12! https://community.plotly.com/t/announcing-plotly-py-4-12-horizontal-and-vertical-lines-and-rectangles/46783

HugoGit39 commented 3 years ago

@nicolaskruchten @SpyderRivera so the hline method works in a single line plot. However I have 2 lines with 2 differnt scales..of which I want to add an hline according to the 2nd rrighter scale...though this doesnt work:

plot %>% layout(yaxis2 = list(shapes = list(hline(10))))

Any help?

plot%>% layout(shapes = list(hline(10))) does work but only for the left 1st scale

Okroshiashvili commented 3 years ago

OK took longer than two weeks, sorry folks :) But it's out now in 4.12! https://community.plotly.com/t/announcing-plotly-py-4-12-horizontal-and-vertical-lines-and-rectangles/46783

Guys, you all rock. Thanks for your dedication and commitment :tada:

martppa commented 3 years ago

Hi! I have found add_hline doesn't work when ploting it with dash :(

nicolaskruchten commented 3 years ago

This will work with recent versions of Dash: I recommend upgrading to the latest, which is 1.20 :)

martppa commented 3 years ago

Hi @nicolaskruchten , thanks for the support. Well, I do actually have the latest version of dash, I also noted I am not using plotly express, I am using graphic objects. But still can't get the add_hline or add_vline working.

@self.app.callback(
    Output(_id, 'figure'),
    [Input(_id, 'id'),
     Input('update_interval', 'n_intervals')]
)
def callback(component_id, _):
    graph = next(component for component in components if component.id == component_id)
    figure = go.Figure()
    lines = graph.get_lines()
    for line in lines:
        if isinstance(line, VectorLine):
            figure.add_trace(go.Scatter(
                x=[point[0] for point in line.vector],
                y=[point[1] for point in line.vector],
                name=line.title,
                mode='lines',
            ))
        if isinstance(line, HLine):
            figure.add_hline(line.y,
                             annotation_text=line.title,
                             annotation_position="bottom right")
        if isinstance(line, VLine):
            figure.add_vline(line.x)

    figure.layout = go.Layout(title=graph.title, height=700)
    return figure

The thing is, if I do a figure.show() I get the horizontal line.

martppa commented 3 years ago

Solved, The layout must be set before hlines and vlines are added.

nicolaskruchten commented 3 years ago

To be precise: setting the layout to some value will overwrite what add_*line added to the pre-existing layout, yes :)