pythonstreamlitaltair

Altair - Boxplot border to be set black and median line red


I want to set a black border color to the boxplot of a altair graph, i try to add stroke parameter to black on the encoding chanel but this overrided my red median line to black.

This is the code I am trying:

def plot_hourly_boxplot_altair(data, column, session_state=None):
    # Convert 'fecha' column to datetime format
    data['fecha'] = pd.to_datetime(data['fecha'])
    
    # Filter out rows where the specified column has NaN values
    data = data.dropna(subset=[column])

    if session_state and not session_state.zero_values:
        # Erase 0 values from the data
        data = data[data[column] != 0]

    # filter the data to just get the date range selected
    data = range_selector(data, min_date=session_state.min_date, max_date=session_state.max_date)

    # filter the data to just get the days of the week selected
    if session_state.days:
        data = data[data['fecha'].dt.dayofweek.isin(session_state.days)]

    if data.empty:
        print(f"No valid data for column '{column}'.")
        return None

    # Create a boxplot using Altair with x axis as the hour of the day on 24 h format and
    # y axis as the demand that is on the data[column]
    data['fecha'] = data['fecha'].dt.strftime('%Y-%m-%dT%H:%M:%S') 
          
    boxplot = alt.Chart(data).mark_boxplot(size = 23,median={'color': 'red'}).encode(
        x=alt.X('hours(fecha):N', title='Hora', axis=alt.Axis(format='%H'), sort='ascending'),
        y=alt.Y(f'{column}:Q', title='Demanda [kW]'),
        stroke = alt.value('black'),  # Set thke color of the boxplot
        strokeWidth=alt.value(1),  # Set the width of the boxplot
        # color=alt.value('#4C72B0'),  # Set the color of the boxplot
        color=alt.value('#2d667a'),  # Set the color of the bars
        opacity=alt.value(1),  # Set the opacity of the bars           
        tooltip=[alt.Tooltip('hours(fecha):N', title='Hora')]  # Customize the tooltip
    )
    chart = (boxplot).properties(
        width=600,  # Set the width of the chart
        height=600,  # Set the height of the chart
        title=(f'Boxplot de demanda de potencia {column}')  # Remove date from title
    ).configure_axis(
        labelFontSize=12,  # Set the font size of axis labels
        titleFontSize=14,  # Set the font size of axis titles
        grid=True,
        # color of labels of x-axis and y-axis is black
        labelColor='black',
        # x-axis and y-axis titles are bold
        titleFontWeight='bold',
        # color of x-axis and y-axis titles is black
        titleColor='black',
        gridColor='#4C72B0',  # Set the color of grid lines
        gridOpacity=0.2  # Set the opacity of grid lines
    ).configure_view(
        strokeWidth=0,  # Remove the border of the chart
        fill='#FFFFFF'  # Set background color to white
    )

    return chart  # Enable zooming and panning

and this is my result:

boxplot with black border but black median line too

I tryed conditional stroke with this code:

        stroke=alt.condition(
            alt.datum._argmax == 'q3',  # condition for the stroke color (for the box part)
            alt.value('black'),         # color for the stroke
            alt.value('red')            # color for the median line
        ),

but got median and border red as seen here:

border and median line red

how can i achieve my objective? i.e a red median line and black border. I also saw this note on the altair documentation

Note: The stroke encoding has higher precedence than color, thus may override the color encoding if conflicting encodings are specified.

is there any way to achieve this?


Solution

  • You can set the properties of the box components inside mark_boxplot as mentioned here in the docs, rather than via the encoding:

    import altair as alt
    from vega_datasets import data
    
    source = data.cars()
    
    alt.Chart(source).mark_boxplot(
        color='lightblue',
        box={'stroke': 'black'},  # Could have used MarkConfig instead
        median=alt.MarkConfig(stroke='red'),  # Could have used a dict instead
    ).encode(
        alt.X("Miles_per_Gallon:Q").scale(zero=False),
        alt.Y("Origin:N"),
    )
    

    enter image description here

    The advantage of using MarkConfig instead of a dict is that you can view all the available parameter names in the help popup.