Search code examples
pythonaltair

Add formatting, surrounding box to Altair vertical line tooltip label?


I am new to Altair, and am attempting to plot a monthly time-series variable, and have a vertical line tooltip display the date and corresponding y-value.

The code I have (warning, probably a bit ugly) gets me most of the way there:

import altair as alt
import datetime as dt
import numpy as np
import pandas as pd

# create DataFrame
monthly_dates = pd.date_range('1997-09-01', '2022-08-01', freq = 'M')
monthly_data = pd.DataFrame(
    index=['Date', 'y_var'],
    data=[monthly_dates, np.random.normal(size = len(monthly_dates))]
).T

# Create a selection that chooses the nearest point & selects based on x-value
nearest = alt.selection(type='single', nearest=True, on='mouseover',
                        fields=['Date'], empty='none')

# The basic line
line = alt.Chart(monthly_data).mark_line().encode(
    x='Date:T', 
    y=alt.Y('y_var', title='Y variable')
)

# Transparent selectors across the chart. This is what tells us
# the x-value of the cursor
selectors = alt.Chart(monthly_data).mark_point().encode(
    x='Date',
    opacity=alt.value(0),
).add_selection(
    nearest
)

# Draw points on the line, and highlight based on selection
points = line.mark_point().encode(
    opacity=alt.condition(nearest, alt.value(1), alt.value(0))
)

# Draw text labels near the points, and highlight based on selection
text_x = line.mark_text(align='left', dx=5, dy=-10).encode(
    text=alt.condition(nearest, 'Date', alt.value(' '))
)

# Draw text labels near the points, and highlight based on selection
text_y = line.mark_text(align='left', dx=5, dy=5).encode(
    text=alt.condition(nearest, 'y_var', alt.value(' '))
).transform_calculate(label='datum.y_var + "%"')

# Draw a rule at the location of the selection
rules = alt.Chart(monthly_data).mark_rule(color='gray').encode(
    x='Date',
).transform_filter(
    nearest
)


# Put the seven layers into a chart and bind the data
chart = alt.layer(
    line, selectors, points, rules, text_x, text_y
).properties(
    width=600, height=300
).interactive()

chart.show()

yields the following interactive chart:

the current iteration of the chart

There are two things I need to do, though:

  1. Add a box around the tooltip labels (and a plain background to this box), so that they are easy to read.
  2. Format the labels independently: since we have monthly data, it would be great to drop the day and just have Oct 2008 or 2008-10 or something along those lines. For the value, rounding to one or two digits and adding '%' afterwards would be great. I tried using the example found here (as you can see for creating text_y) but to no avail.

Any and all help would be greatly appreciated. Apologies in advance for any dumb mistakes or poor coding practices; again, I am still learning the basics of Altair.


Solution

  • Update: I figured both out.

    The solutions to both 1 and 2 are in the code below.

    For 1: instead of trying to add a box around the text manually, I instead added tooltips to the selectors object and dropped the text_x and text_y entirely.

    For 2: I used transform_calculate to create new fields for x_label and y_label that are exactly what I want to display, then feed these into the tooltip objects. This page has tons of ways to transform data.

    selectors = alt.Chart(monthly_data).mark_point().transform_calculate(
        x_label='timeFormat(datum.Date, "%b %Y")',
        y_label='format(datum.y_var, ".1f") + "%"'
    ).encode(
        x='Date',
        opacity=alt.value(0),
        tooltip=[
            alt.Tooltip('x_label:N', title='Date'),
            alt.Tooltip('y_label:N', title='Pct. Change')
        ]
    ).add_selection(
        nearest
    )
    

    The finished product:

    enter image description here