5

I have the following snippit of code:

data.plot(y='Close', ax = ax)
newdates = exceptthursday.loc[start:end]
for anotate in (newdates.index + BDay()).strftime('%Y-%m-%d'):
    ax.annotate('holliday', xy=(anotate, data['Close'].loc[anotate]),  xycoords='data',
                xytext=(-30, 40), textcoords='offset points',
                size=13, ha='center', va="baseline",
                bbox=dict(boxstyle="round", alpha=0.1),
                arrowprops=dict(arrowstyle="wedge,tail_width=0.5", alpha=0.1)); 

This produces a plot which looks like this: enter image description here

As you can see i have explicitly mentioned the xytext, this makes the "bubbles" messy as at some locations they overlap which makes it hard to read. Is there any way it can be "auto - placed" so that they are not overlapping. Such as some of the "bubbles" are above and below the plot line in such a way that they do not overlap.

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
Slartibartfast
  • 1,058
  • 4
  • 26
  • 60
  • 3
    Have you checked the solutions proposed in these questions? [1](https://stackoverflow.com/q/14938541/8345628), [2](https://stackoverflow.com/q/8850142/8345628), [3](https://stackoverflow.com/q/19073683/8345628) – Lith Jul 05 '20 at 14:54
  • Can you post the sample dataframe? – bigbounty Jul 14 '20 at 05:29
  • @bigbounty I used data from `web.DataReader('fb', 'yahoo')` This is from `from pandas_datareader import data as web` – Slartibartfast Jul 14 '20 at 17:29

2 Answers2

5

Auto-Placing according to me is such that you zoom in zoom out and automatically data and UI is adjusted because you can't avoid overlapping even though you place the bubbles above and below the plot line due to lots of data points which can't be ignored. I have used plotly library as matplotlib has limitations. I have chosen UK holidays. You can change it accordingly.

import plotly.graph_objects as go
import plotly.express as px
from pandas_datareader import data as web
import holidays

data = web.DataReader('fb', 'yahoo')
uk_holidays = holidays.UnitedKingdom()
data["is_holiday"] = [True if i in uk_holidays else False for i in data.index]
data["Date"] = data.index
data.reset_index(drop=True, inplace=True)

fig = px.line(data, x='Date', y='Close')

fig.update_xaxes(
    rangeslider_visible=True,
    rangeselector=dict(
        buttons=list([
            dict(count=1, label="1m", step="month", stepmode="backward"),
            dict(count=6, label="6m", step="month", stepmode="backward"),
            dict(count=1, label="YTD", step="year", stepmode="todate"),
            dict(count=1, label="1y", step="year", stepmode="backward"),
            dict(step="all")
        ])
    )
)


for close, date in data[data["is_holiday"] == True][["Close","Date"]].itertuples(index=False):
    fig.add_annotation(
        x=date.date(),
        y=close,
        xref="x",
        yref="y",
        text="Holiday",
        showarrow=True,
        font=dict(
            family="Courier New, monospace",
            size=16,
            color="#ffffff"
            ),
        align="center",
        arrowhead=2,
        arrowsize=1,
        arrowwidth=2,
        arrowcolor="#636363",
        ax=20,
        ay=-30,
        bordercolor="#c7c7c7",
        borderwidth=2,
        borderpad=4,
        bgcolor="#ff7f0e",
        opacity=0.8
        )
fig.update_layout(title_text='Trend Analysis with Holiday', title_x=0.5,showlegend=False)
fig.show()

Working of the above code:

enter image description here

bigbounty
  • 16,526
  • 5
  • 37
  • 65
2

Because of the small amount of holiday data used, the degree of overlap in the annotations will seem less effective because of the small amount of overlap in the annotations, but the gist of the answer is that the issue could be improved slightly by varying the position of the annotations according to the index.

import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from pandas_datareader import data as web
from pandas.tseries.holiday import USFederalHolidayCalendar as calendar

data = web.DataReader('fb', 'yahoo')
cal = calendar()
holidays = cal.holidays(start=data.index.min(), end=data.index.max())
data['Holiday'] = data.index.isin(holidays)
holiday = data[data['Holiday'] == True]

fig = plt.figure(figsize=(16,6))
ax = fig.add_subplot(111)

ax.plot(data.index, data.Close)

for i,x,y in zip(range(len(holiday)),holiday.index, holiday.Close):
    if i % 2 == 0: 
        ax.annotate('holliday', xy=(x,y),  xycoords='data',
                    xytext=(-30, 40), textcoords='offset points',
                    size=13, ha='center', va="baseline",
                    bbox=dict(boxstyle="round", alpha=0.1),
                    arrowprops=dict(arrowstyle="wedge,tail_width=0.5", alpha=0.1))
    else:
        ax.annotate('holliday', xy=(x,y),  xycoords='data',
                xytext=(30, -40), textcoords='offset points',
                size=13, ha='center', va="baseline",
                bbox=dict(boxstyle="round", alpha=0.1),
                arrowprops=dict(arrowstyle="wedge,tail_width=0.5", alpha=0.1))


ax.xaxis.set_major_locator(mdates.MonthLocator(bymonth=None, interval=3, tz=None))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m-%d"))
ax.tick_params(axis='x', labelrotation=45)

enter image description here

r-beginners
  • 31,170
  • 3
  • 14
  • 32
  • Could you please advise how the x - axis could be changed to dates. Also i am getting `TypeError: float() argument must be a string or a number, not 'Timestamp'` How can i resolve this error? – Slartibartfast Jul 19 '20 at 20:00
  • The x-axis has been modified to show the date, and the code and graph images have been replaced. – r-beginners Jul 20 '20 at 01:32